Repository: mojisdev/mojis
Pull request #30: refactor: improve adapters
Author: login: luxass
Labels: name: pkg: internal-utils
description: changes related to the internal-utils pkg
name: pkg: adapters
description: changes related to the adapters pkg
name: pkg: cli
description: changes related to the cli pkg
name: pkg: parsers
description: changes related to the parsers pkg
Created: 2025-03-05T20:00:43Z | Merged: 2025-03-08T11:39:49Z

This PR fixes #21 

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced new handlers for metadata, sequences, and variations to enhance emoji processing.
  - Streamlined the CLI to focus solely on metadata generation.
  - Added new type definitions and interfaces to improve flexibility in adapter handling.
  - Introduced a new function for running adapter handlers with enhanced context management.

- **Refactor**
  - Overhauled the adapter management and type definitions for improved type safety, error handling, and version alignment logic.
  - Removed deprecated functions and types to simplify the codebase.
  - Enhanced the logic and error handling for version comparison functions.

- **Tests**
  - Expanded test coverage for new and existing functionalities, including unit tests for type predicates and handler methods.
  - Added unit tests for new generic types and their expected outputs.

- **Chores**
  - Added a new dependency to support advanced emoji parsing capabilities.
  - Updated test configurations to include type checking and broadened file matching patterns.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #21: rewrite adapters approach
The adapter module should be rewritten again, since what i was trying to achieve in #16 didn't work that great. There is too many type casts, and the return types of the adapters is any because of the `resolveAdapter`. This can be fixed, and it should be fixed.

At the same time, it would be cool to make the adapter's more generic and modular.

So by introducing an `defineAdapterHandler` helper, we could probably make type support a little happier.

# Adapter Handler

```ts
const metadataHandler = defineAdapterHandler({
  urls: string | string[] | UrlBuilder,
  // this will run for each of the urls
  transform: TransformFn,
  fetchOptions: RequestInit,
  cacheOptions: {},

  // if aggregate is defined, it will receive all the transformed results
  // as parameters, which it can then aggregate the results into a final output.
  aggregate: AggregateFn
});
```

If the `aggregate` isn't defined it will return the transformed results directly either as an array or a single element.

Linked issue comments: user: login: luxass
created_at: 2025-03-05T05:13:48Z
body: After some more consideration, i think we will get the best typescript experience if we define each handler in a list or object of some sort, and then let typescript infer based on that.
Otherwise we will really have to do some magic to get a very good typescript experience.

I am also thinking about doing a vite-styled plugin pattern.

```ts
interface AdapterContext {
  // context about adapters
  version: string

  // some kind of hook system similar to astro's.
  hook: any
}

type UrlBuilder = (ctx: AdapterContext) => Promise<string | string[]> | string | string[];

type AdapterHandler = {
  /**
   * The type of the handler, this is used to identify the handler.
   */
  type: "emojis" | "shortcodes",
  /**
   * The urls that will be fetched.
   */
  urls: string | string[] | UrlBuilder,

  /**
   * Options that will be passed to the fetch function.
   */
  fetchOptions: RequestInit,

  /**
   * Options that will be passed to the cache function.
   */
  cacheOptions: CacheOptions

  /**
   * A semver range that this handler is compatible with.
   */
  range?: string

  /**
   * Whether or not that this handler should handle the request.
   */
  shouldHandle?: (ctx: AdapterContext) => Promise<boolean> | boolean,

  /**
   * A list of other "handlers types", that is required to be run before this handler.
   */
  dependencies?: string[],

  /**
   * A transform function that will run for each of the urls.
   * It will pass the returned type downwards to either the aggregate function or the output.
   */
  transform: TransformFn,

  /**
   * An aggregate function that will run if defined.
   * It will receive all the transformed results as parameters, which it can then aggregate the results into a final output.
   */
  aggregate?: AggregateFn

  /**
   * Output function which will receive the transformed result from the transform function, or from the aggregate function.
   * It will then output the result.
   */
  output: OutputFn
}
```

If the `shouldHandle` returns false, it will then just go through to the next defined handler, so maybe our `range` isn't needed since we can do the checks inside the `shouldHandle`
author_association: MEMBER

Diff:
diff --git a/.changeset/lucky-pears-wonder.md b/.changeset/lucky-pears-wonder.md
new file mode 100644
index 00000000..6e8d22fc
--- /dev/null
+++ b/.changeset/lucky-pears-wonder.md
@@ -0,0 +1,8 @@
+---
+"@mojis/internal-utils": patch
+"@mojis/adapters": patch
+"@mojis/parsers": patch
+"@mojis/cli": patch
+---
+
+refactor: rewrite our adapter code to improve our type dx
diff --git a/packages/adapters/package.json b/packages/adapters/package.json
index 00d74815..325038a0 100644
--- a/packages/adapters/package.json
+++ b/packages/adapters/package.json
@@ -37,6 +37,7 @@
   },
   "dependencies": {
     "@mojis/internal-utils": "workspace:*",
+    "@mojis/parsers": "workspace:*",
     "defu": "catalog:",
     "semver": "catalog:"
   },
diff --git a/packages/adapters/src/_handlers.ts b/packages/adapters/src/_handlers.ts
new file mode 100644
index 00000000..d51fe70b
--- /dev/null
+++ b/packages/adapters/src/_handlers.ts
@@ -0,0 +1,23 @@
+import { modernMetadataHandler } from "./handlers/modern/metadata";
+import { modernSequenceHandler } from "./handlers/modern/sequence";
+import { modernVariationHandler } from "./handlers/modern/variation";
+import { preAlignmentMetadataHandler } from "./handlers/pre-alignment/metadata";
+
+export const METADATA_HANDLERS = [
+  modernMetadataHandler,
+  preAlignmentMetadataHandler,
+];
+
+export const SEQUENCE_HANDLERS = [
+  modernSequenceHandler,
+];
+
+export const VARIATION_HANDLERS = [
+  modernVariationHandler,
+];
+
+export const ALL_HANDLERS = {
+  metadata: METADATA_HANDLERS,
+  sequence: SEQUENCE_HANDLERS,
+  variation: VARIATION_HANDLERS,
+};
diff --git a/packages/adapters/src/adapter.ts b/packages/adapters/src/adapter.ts
deleted file mode 100644
index 23c223fd..00000000
--- a/packages/adapters/src/adapter.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import type { EmojiSpecRecord } from "@mojis/internal-utils";
-import type {
-  AdapterContext,
-  AdapterHandler,
-  AdapterHandlers,
-  CacheableUrlRequestReturnType,
-  ExtractDataTypeFromUrls,
-  MojiAdapter,
-} from "./types";
-import { createCacheKeyFromUrl, fetchCache } from "@mojis/internal-utils";
-import { defu } from "defu";
-import semver from "semver";
-import { baseAdapter } from "./adapters/base/adapter";
-import { modernAdapter } from "./adapters/modern/adapter";
-import { preAlignmentAdapter } from "./adapters/pre-alignment/adapter";
-
-interface AdapterRegistry {
-  [key: string]: MojiAdapter<
-    any, // metadata
-    any, // sequence
-    any
-  >;
-}
-
-const ADAPTERS: AdapterRegistry = {
-  base: baseAdapter,
-  modern: modernAdapter,
-  preAlignment: preAlignmentAdapter,
-};
-
-type ExtractAdapterType<T> = T extends MojiAdapter<
-  infer Metadata,
-  infer Sequences,
-  infer Variations
-> ? MojiAdapter<
-    Metadata,
-    Sequences,
-    Variations
-  > : never;
-
-export function resolveAdapter<T extends EmojiSpecRecord>(
-  emojiVersion: T,
-): ExtractAdapterType<typeof baseAdapter> | ExtractAdapterType<typeof modernAdapter> | null {
-  const version = semver.coerce(emojiVersion.emoji_version);
-
-  if (version == null) {
-    throw new Error(`invalid emoji version ${emojiVersion.emoji_version}`);
-  }
-
-  const matchingAdapters = Object.values(ADAPTERS).filter((adapter) =>
-    semver.satisfies(version, adapter.range),
-  );
-
-  if (matchingAdapters.length === 0) {
-    throw new Error(`no adapter found for version ${version}`);
-  }
-
-  if (matchingAdapters.length > 1) {
-    return matchingAdapters.reduce((selected, current) => {
-      const selectedLower = semver.minVersion(selected.range);
-      const currentLower = semver.minVersion(current.range);
-
-      if (!selectedLower || !currentLower) {
-        return extendAdapter(selected);
-      }
-
-      const adapter = semver.gt(currentLower, selectedLower) ? current : selected;
-
-      return extendAdapter(adapter);
-    });
-  }
-
-  if (matchingAdapters.length === 1 && matchingAdapters[0] != null) {
-    return extendAdapter(matchingAdapters[0]);
-  }
-
-  return null;
-}
-
-function extendAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
->(
-  adapter: MojiAdapter<
-    TMetadataUrlReturn,
-    TSequencesUrlReturn,
-    TVariationsUrlReturn
-  >,
-): MojiAdapter<
-    TMetadataUrlReturn,
-    TSequencesUrlReturn,
-    TVariationsUrlReturn
-  > {
-  if (adapter.extend == null) {
-    return adapter;
-  }
-
-  const parent = ADAPTERS[adapter.extend];
-
-  if (parent == null) {
-    throw new Error(
-      `adapter ${adapter.name} extends ${adapter.extend}, but ${adapter.extend} is not registered`,
-    );
-  }
-
-  return Object.assign({}, parent, adapter) as MojiAdapter<
-    TMetadataUrlReturn,
-    TSequencesUrlReturn,
-    TVariationsUrlReturn
-  >;
-}
-
-export async function runAdapterHandler<
-  TAdapter extends MojiAdapter<any, any, any>,
-  THandler extends AdapterHandlers<TAdapter>,
-  THandlerFn extends NonNullable<TAdapter[THandler]>,
-  TUrlReturn extends CacheableUrlRequestReturnType = THandlerFn extends AdapterHandler<infer U, any, any> ? U : never,
-  TExtraContext extends Record<string, unknown> = THandlerFn extends AdapterHandler<any, infer E, any> ? E : never,
-  TTransformOutput = THandlerFn extends AdapterHandler<any, any, infer V, any> ? V : never,
-  TOutput = THandlerFn extends AdapterHandler<any, any, any, infer V> ? V : never,
->(
-  adapter: TAdapter,
-  handlerName: THandler,
-  ctx: AdapterContext,
-): Promise<TOutput> {
-  // we know this is an AdapterHandler because of the constraint on THandler
-  const handler = adapter[handlerName] as unknown as AdapterHandler<TUrlReturn, AdapterContext & TExtraContext, TTransformOutput, TOutput>;
-
-  if (!handler) {
-    throw new Error(`Handler ${String(handlerName)} not found in adapter ${adapter.name}`);
-  }
-
-  // check if urls method exists
-  if (!handler.urls) {
-    throw new Error(`Handler ${String(handlerName)} in adapter ${adapter.name} does not have a urls function`);
-  }
-
-  const urlsResult = await handler.urls(ctx);
-
-  // early return for undefined
-  if (urlsResult === undefined) {
-    // @ts-expect-error - we're returning undefined but TypeScript doesn't know this matches TOutput
-    return undefined;
-  }
-
-  if (!Array.isArray(urlsResult)) {
-    const cacheOptions = defu(urlsResult.cacheOptions || {}, handler.cacheOptions || {});
-    const fetchOptions = defu(urlsResult.fetchOptions || {}, handler.fetchOptions || {});
-    const key = urlsResult.key ?? createCacheKeyFromUrl(urlsResult.url);
-
-    const newCtx = {
-      ...urlsResult.extraContext,
-      key,
-      emoji_version: ctx.emoji_version,
-      force: ctx.force,
-      unicode_version: ctx.unicode_version,
-    } as unknown as AdapterContext & TExtraContext;
-
-    const data = await fetchCache(urlsResult.url, {
-      cacheKey: urlsResult.cacheKey ?? `v${ctx.emoji_version}/${createCacheKeyFromUrl(urlsResult.url)}`,
-      parser: (data) => data,
-      options: fetchOptions,
-      cacheFolder: cacheOptions.cacheFolder,
-      encoding: cacheOptions.encoding,
-      ttl: cacheOptions.ttl,
-      bypassCache: ctx.force,
-    });
-
-    return handler.transform(newCtx, data as unknown as ExtractDataTypeFromUrls<TUrlReturn>) as unknown as TOutput;
-  }
-
-  const promises = urlsResult.map(async (item) => {
-    const key = item.key ?? createCacheKeyFromUrl(item.url);
-    const cacheOptions = defu(item.cacheOptions || {}, handler.cacheOptions || {});
-    const fetchOptions = defu(item.fetchOptions || {}, handler.fetchOptions || {});
-
-    const data = await fetchCache(item.url, {
-      cacheKey: item.cacheKey ?? `v${ctx.emoji_version}/${createCacheKeyFromUrl(item.url)}`,
-      parser: (data) => data,
-      options: fetchOptions,
-      cacheFolder: cacheOptions.cacheFolder,
-      encoding: cacheOptions.encoding,
-      ttl: cacheOptions.ttl,
-      bypassCache: ctx.force,
-    });
-
-    const newCtx = {
-      ...item.extraContext,
-      key,
-      emoji_version: ctx.emoji_version,
-      force: ctx.force,
-      unicode_version: ctx.unicode_version,
-    } as unknown as AdapterContext & TExtraContext;
-
-    return handler.transform(newCtx, data as ExtractDataTypeFromUrls<TUrlReturn>);
-  });
-
-  const results = await Promise.all(promises);
-
-  if (handler.aggregate == null) {
-    throw new Error(`Handler ${String(handlerName)} in adapter ${adapter.name} does not have an aggregate function`);
-  }
-
-  if (results.length === 0) {
-    throw new Error(`Handler ${String(handlerName)} in adapter ${adapter.name} returned an empty array.`);
-  }
-
-  return handler.aggregate(ctx, [results[0]!, ...results.slice(1)]);
-}
diff --git a/packages/adapters/src/adapters/base/adapter.ts b/packages/adapters/src/adapters/base/adapter.ts
deleted file mode 100644
index ed2269bd..00000000
--- a/packages/adapters/src/adapters/base/adapter.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import type { EmojiGroup, EmojiMetadata } from "@mojis/internal-utils";
-import {
-
-  extractEmojiVersion,
-  extractUnicodeVersion,
-} from "@mojis/internal-utils";
-import { defineMojiAdapter } from "../../define";
-
-function slugify(val: string): string {
-  return val.normalize("NFD")
-    .toLowerCase()
-    .trim()
-    .replace(/[\u0300-\u036F]/g, "")
-    .replace(/\(.+\)/g, "")
-    .replace("&", "")
-    .replace(/[\W_]+/g, "-")
-    .replace(/^-+|-+$/g, "");
-}
-
-// These emoji versions doesn't seem to have a emoji-test,
-// where we can extract the metadata from.
-const DISALLOWED_EMOJI_VERSIONS = ["1.0", "2.0", "3.0"];
-
-export const baseAdapter = defineMojiAdapter({
-  name: "base",
-  description: "base adapter",
-  range: "*",
-  metadata: {
-    urls(ctx) {
-      if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
-        return undefined;
-      }
-
-      return {
-        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
-        cacheKey: `v${ctx.emoji_version}/metadata`,
-      };
-    },
-
-    transform(ctx, data) {
-      if (data == null) {
-        return {
-          emojis: {},
-          groups: [],
-        };
-      }
-
-      const lines = data.split("\n");
-      let currentGroup: EmojiGroup | undefined;
-
-      const groups: EmojiGroup[] = [];
-
-      // [group-subgroup][hexcode] = metadata
-      const emojis: Record<string, Record<string, EmojiMetadata>> = {};
-
-      for (const line of lines) {
-        if (line.trim() === "") {
-          continue;
-        }
-
-        if (line.startsWith("# group:")) {
-          const groupName = line.slice(8).trim();
-
-          const group: EmojiGroup = {
-            name: groupName,
-            slug: slugify(groupName),
-            subgroups: [],
-          };
-
-          currentGroup = group;
-
-          groups.push(group);
-
-          continue;
-        } else if (line.startsWith("# subgroup:")) {
-          const subgroupName = line.slice(11).trim();
-
-          if (currentGroup == null) {
-            throw new Error(`subgroup ${subgroupName} without group`);
-          }
-
-          currentGroup.subgroups.push(slugify(subgroupName));
-
-          continue;
-        } else if (line.startsWith("#")) {
-          continue;
-        }
-
-        const [baseHexcode, trailingLine] = line.split(";");
-
-        if (baseHexcode == null || trailingLine == null) {
-          throw new Error(`invalid line: ${line}`);
-        }
-
-        const [baseQualifier, comment] = trailingLine.split("#");
-
-        if (baseQualifier == null || comment == null) {
-          throw new Error(`invalid line: ${line}`);
-        }
-
-        const hexcode = baseHexcode.trim().replace(/\s+/g, "-");
-        const qualifier = baseQualifier.trim();
-
-        const emojiVersion = extractEmojiVersion(comment.trim());
-        const [emoji, trimmedComment] = comment.trim().split(` E${emojiVersion} `);
-
-        const groupName = currentGroup?.slug ?? "unknown";
-        const subgroupName = currentGroup?.subgroups[currentGroup.subgroups.length - 1] ?? "unknown";
-
-        const metadataGroup = `${groupName}-${subgroupName}`;
-
-        if (emojis[metadataGroup] == null) {
-          emojis[metadataGroup] = {};
-        }
-
-        emojis[metadataGroup][hexcode] = {
-          group: groupName,
-          subgroup: subgroupName,
-          qualifier,
-          emojiVersion: emojiVersion || null,
-          unicodeVersion: extractUnicodeVersion(emojiVersion, ctx.unicode_version),
-          description: trimmedComment || "",
-          emoji: emoji || null,
-          hexcodes: hexcode.split("-"),
-        };
-      }
-
-      return {
-        emojis,
-        groups,
-      };
-    },
-  },
-});
diff --git a/packages/adapters/src/adapters/modern/adapter.ts b/packages/adapters/src/adapters/modern/adapter.ts
deleted file mode 100644
index 370fbcb9..00000000
--- a/packages/adapters/src/adapters/modern/adapter.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-import type { EmojiSequence, EmojiVariation } from "@mojis/internal-utils";
-import {
-
-  expandHexRange,
-  FEMALE_SIGN,
-  MALE_SIGN,
-} from "@mojis/internal-utils";
-import semver from "semver";
-import { defineMojiAdapter } from "../../define";
-
-export const modernAdapter = defineMojiAdapter({
-  name: "modern",
-  description: "adapter for the modern emoji versions",
-  range: ">=11.0.0",
-  extend: "base",
-  sequences: {
-    urls(ctx) {
-      return [
-        {
-          key: "sequences",
-          url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-sequences.txt`,
-          cacheKey: `v${ctx.emoji_version}/sequences`,
-        },
-        {
-          key: "zwj",
-          url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-zwj-sequences.txt`,
-          cacheKey: `v${ctx.emoji_version}/zwj-sequences`,
-        },
-      ];
-    },
-    transform(_, data) {
-      if (data == null) {
-        return [];
-      }
-
-      const sequences: EmojiSequence[] = [];
-
-      const lines = data.split("\n");
-
-      for (let line of lines) {
-        // skip empty line & comments
-        if (line.trim() === "" || line.startsWith("#")) {
-          continue;
-        }
-
-        // remove line comment
-        const commentIndex = line.indexOf("#");
-        if (commentIndex !== -1) {
-          line = line.slice(0, commentIndex).trim();
-        }
-
-        const [hex, property, description] = line.split(";").map((col) => col.trim()).slice(0, 4);
-
-        if (hex == null || property == null || description == null) {
-          throw new Error(`invalid line: ${line}`);
-        }
-
-        const expandedHex = expandHexRange(hex);
-
-        for (const hex of expandedHex) {
-          sequences.push({
-            hex: hex.replace(/\s+/g, "-"),
-            property,
-            description,
-            gender: hex.includes(FEMALE_SIGN) ? "female" : hex.includes(MALE_SIGN) ? "male" : null,
-          });
-        }
-      }
-
-      return sequences;
-    },
-    aggregate(_, data) {
-      return {
-        sequences: data[0],
-        zwj: data[1],
-      };
-    },
-  },
-  variations: {
-    urls(ctx) {
-      if (semver.lte(`${ctx.unicode_version}.0`, "12.1.0")) {
-        return {
-          url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-variation-sequences.txt`,
-          cacheKey: `v${ctx.emoji_version}/variations`,
-        };
-      }
-      return {
-        url: `https://unicode.org/Public/${ctx.unicode_version}.0/ucd/emoji/emoji-variation-sequences.txt`,
-        cacheKey: `v${ctx.emoji_version}/variations`,
-      };
-    },
-    transform(ctx, data) {
-      if (data == null) {
-        return [];
-      }
-
-      const lines = data.split("\n");
-
-      const variations: EmojiVariation[] = [];
-
-      for (let line of lines) {
-        // skip empty line & comments
-        if (line.trim() === "" || line.startsWith("#")) {
-          continue;
-        }
-
-        // remove line comment
-        const commentIndex = line.indexOf("#");
-        if (commentIndex !== -1) {
-          line = line.slice(0, commentIndex).trim();
-        }
-
-        const [hex, style] = line.split(";").map((col) => col.trim()).slice(0, 4);
-
-        if (hex == null || style == null) {
-          throw new Error(`invalid line: ${line}`);
-        }
-
-        const hexcode = hex.replace(/\s+/g, "-");
-
-        const type = style.replace("style", "").trim();
-
-        if (type !== "text" && type !== "emoji") {
-          throw new Error(`invalid style: ${style}`);
-        }
-
-        variations.push({
-          emoji: type === "emoji" ? hexcode : null,
-          text: type === "text" ? hexcode : null,
-          property: ["Emoji"],
-        });
-      }
-
-      return variations;
-    },
-  },
-});
diff --git a/packages/adapters/src/adapters/pre-alignment/adapter.ts b/packages/adapters/src/adapters/pre-alignment/adapter.ts
deleted file mode 100644
index b08840a5..00000000
--- a/packages/adapters/src/adapters/pre-alignment/adapter.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { defineMojiAdapter } from "../../define";
-
-export const preAlignmentAdapter = defineMojiAdapter({
-  name: "pre-alignment",
-  description: "adapter for before the alignment with unicode versions",
-  range: "1.x>= <6.x.x",
-  extend: "base",
-});
diff --git a/packages/adapters/src/define.ts b/packages/adapters/src/define.ts
index c0fd813d..444e1860 100644
--- a/packages/adapters/src/define.ts
+++ b/packages/adapters/src/define.ts
@@ -1,72 +1,35 @@
-import type { CacheableUrlRequestReturnType, MojiAdapter } from "./types";
-import semver from "semver";
-
-/**
- * Validates and defines a Moji adapter configuration.
- *
- * @param {MojiAdapter} adapter - The adapter configuration object to validate
- * @throws {Error} If adapter.name is missing
- * @throws {Error} If adapter.description is missing
- * @throws {Error} If adapter.range is missing
- * @throws {Error} If adapter.range is not a valid semver range
- * @throws {Error} If required functions are missing when adapter.extend is null
- * @returns {MojiAdapter} The validated adapter configuration
- *
- * @example
- * ```ts
- * const adapter = defineMojiAdapter({
- *   name: 'my-adapter',
- *   description: 'My adapter',
- *   range: '>=1.0.0',
- * });
- * ```
- */
-export function defineMojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
->(adapter: MojiAdapter<
-  TMetadataUrlReturn,
-  TSequencesUrlReturn,
-  TVariationsUrlReturn
->): MojiAdapter<
-    TMetadataUrlReturn,
-    TSequencesUrlReturn,
-    TVariationsUrlReturn
+import type {
+  AdapterContext,
+  AdapterHandler,
+  AdapterHandlerType,
+  InferParseOutput,
+  ParserFn,
+} from "./types";
+
+export function defineAdapterHandler<
+  TAdapterHandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TParser extends (string | ParserFn<TContext, unknown>),
+  TTransformOutput,
+  TParseOutput = InferParseOutput<TContext, TParser>,
+  TAggregateOutput = TTransformOutput,
+  TOutput = TTransformOutput | TAggregateOutput,
+>(handler: AdapterHandler<
+  TAdapterHandlerType,
+  TContext,
+  TParser,
+  TTransformOutput,
+  TParseOutput,
+  TAggregateOutput,
+  TOutput
+>): AdapterHandler<
+    TAdapterHandlerType,
+    TContext,
+    TParser,
+    TTransformOutput,
+    TParseOutput,
+    TAggregateOutput,
+    TOutput
   > {
-  // validate the adapter has name, description, range.
-  if (!adapter.name) {
-    throw new Error(`adapter.name is required`);
-  }
-
-  if (!adapter.description) {
-    throw new Error(`adapter.description is required`);
-  }
-
-  if (!adapter.range) {
-    throw new Error(`adapter.range is required`);
-  }
-
-  // verify the adapter.range is a valid semver range.
-  if (semver.validRange(adapter.range) === null) {
-    throw new Error(`adapter.range is not a valid semver range ${adapter.range}`);
-  }
-
-  if (adapter.extend == null) {
-    // verify the adapter has the required functions.
-
-    // TODO: figure out how we can make it throw type error if the adapter is missing a required function
-    // const REQUIRED_FUNCTIONS = [
-    //   "sequences",
-    //   "metadata",
-    //   "variations",
-    // ] satisfies NonNullable<MojiAdapterFunctionNames<MojiAdapter>>[];
-
-    // const missingFunctions = REQUIRED_FUNCTIONS.filter((fn) => adapter[fn] == null);
-    // if (missingFunctions.length > 0) {
-    //   throw new Error(`adapter ${adapter.name} is missing required functions: ${missingFunctions.join(", ")}`);
-    // }
-  }
-
-  return adapter;
+  return handler;
 }
diff --git a/packages/adapters/src/handler.ts b/packages/adapters/src/handler.ts
new file mode 100644
index 00000000..09a8c987
--- /dev/null
+++ b/packages/adapters/src/handler.ts
@@ -0,0 +1,106 @@
+import type { AdapterContext, AdapterHandlerType, InferOutputFromAdapterHandlerType } from "./types";
+import { fetchCache } from "@mojis/internal-utils";
+import { genericParse } from "@mojis/parsers";
+import { ALL_HANDLERS } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser } from "./utils";
+
+export async function runAdapterHandler<
+  TAdapterHandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: TAdapterHandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<InferOutputFromAdapterHandlerType<TAdapterHandlerType>> {
+  const handlerGroup = ALL_HANDLERS[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // figure out if we should execute this handler
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    // generate a list of all urls, that should be handled
+    const urls = await getHandlerUrls(handler.urls, ctx);
+
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser === "generic") {
+              return genericParse(data, {
+                separator: handler.parserOptions?.separator ?? ";",
+                commentPrefix: handler.parserOptions?.commentPrefix ?? "#",
+                defaultProperty: handler.parserOptions?.defaultProperty ?? "",
+                propertyMap: handler.parserOptions?.propertyMap ?? {},
+              });
+            }
+
+            throw new Error(`Parser "${handler.parser}" is not implemented.`);
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // Narrow the type of data based on the parser type
+      const transformedData = handler.transform(buildContext(ctx, {
+        key,
+      }), data as any);
+
+      transformedDataList.push(transformedData);
+    }
+
+    // only run aggregate if defined, but still call output
+    if (!handler.aggregate) {
+      const data = transformedDataList.length === 1 ? transformedDataList[0] : transformedDataList;
+      // @ts-expect-error i'm not smart enough to fix these
+      return handler.output(buildContext(ctx, {}), data);
+    }
+
+    const aggregatedData = handler.aggregate(buildContext(ctx, {
+      data: transformedDataList,
+      // @ts-expect-error i'm not smart enough to fix these
+    }), transformedDataList);
+
+    // run output
+    const output = handler.output(buildContext(ctx, {
+      data: aggregatedData,
+      // @ts-expect-error i'm not smart enough to fix these
+    }), aggregatedData);
+
+    // @ts-expect-error i'm not smart enough to fix these
+    return output;
+  }
+
+  throw new Error(`No handler found for type: ${type}`);
+}
+
+function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
+  ctx: TContext,
+  extraContext: TExtraContext,
+): TContext & TExtraContext {
+  return Object.assign({}, ctx, extraContext);
+}
diff --git a/packages/adapters/src/handlers/modern/metadata.ts b/packages/adapters/src/handlers/modern/metadata.ts
new file mode 100644
index 00000000..c22c704c
--- /dev/null
+++ b/packages/adapters/src/handlers/modern/metadata.ts
@@ -0,0 +1,126 @@
+import type { EmojiGroup, EmojiMetadata } from "@mojis/internal-utils";
+import { extractEmojiVersion, extractUnicodeVersion } from "@mojis/internal-utils";
+import { defineAdapterHandler } from "../../define";
+
+function slugify(val: string): string {
+  return val.normalize("NFD")
+    .toLowerCase()
+    .trim()
+    .replace(/[\u0300-\u036F]/g, "")
+    .replace(/\(.+\)/g, "")
+    .replace("&", "")
+    .replace(/[\W_]+/g, "-")
+    .replace(/^-+|-+$/g, "");
+}
+
+// These emoji versions doesn't seem to have a emoji-test,
+// where we can extract the metadata from.
+const DISALLOWED_EMOJI_VERSIONS = ["1.0", "2.0", "3.0"];
+
+export const modernMetadataHandler = defineAdapterHandler({
+  type: "metadata",
+  shouldExecute: (ctx) => {
+    return !DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version);
+  },
+  urls: (ctx) => {
+    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
+      return undefined;
+    }
+
+    return {
+      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
+      cacheKey: `v${ctx.emoji_version}/metadata`,
+    };
+  },
+  parser: (_, data) => {
+    return data.split("\n");
+  },
+  transform(ctx, lines) {
+    let currentGroup: EmojiGroup | undefined;
+
+    const groups: EmojiGroup[] = [];
+
+    // [group-subgroup][hexcode] = metadata
+    const emojis: Record<string, Record<string, EmojiMetadata>> = {};
+
+    for (const line of lines) {
+      if (line.trim() === "") {
+        continue;
+      }
+
+      if (line.startsWith("# group:")) {
+        const groupName = line.slice(8).trim();
+
+        const group: EmojiGroup = {
+          name: groupName,
+          slug: slugify(groupName),
+          subgroups: [],
+        };
+
+        currentGroup = group;
+
+        groups.push(group);
+
+        continue;
+      } else if (line.startsWith("# subgroup:")) {
+        const subgroupName = line.slice(11).trim();
+
+        if (currentGroup == null) {
+          throw new Error(`subgroup ${subgroupName} without group`);
+        }
+
+        currentGroup.subgroups.push(slugify(subgroupName));
+
+        continue;
+      } else if (line.startsWith("#")) {
+        continue;
+      }
+
+      const [baseHexcode, trailingLine] = line.split(";");
+
+      if (baseHexcode == null || trailingLine == null) {
+        throw new Error(`invalid line: ${line}`);
+      }
+
+      const [baseQualifier, comment] = trailingLine.split("#");
+
+      if (baseQualifier == null || comment == null) {
+        throw new Error(`invalid line: ${line}`);
+      }
+
+      const hexcode = baseHexcode.trim().replace(/\s+/g, "-");
+      const qualifier = baseQualifier.trim();
+
+      const emojiVersion = extractEmojiVersion(comment.trim());
+      const [emoji, trimmedComment] = comment.trim().split(` E${emojiVersion} `);
+
+      const groupName = currentGroup?.slug ?? "unknown";
+      const subgroupName = currentGroup?.subgroups[currentGroup.subgroups.length - 1] ?? "unknown";
+
+      const metadataGroup = `${groupName}-${subgroupName}`;
+
+      if (emojis[metadataGroup] == null) {
+        emojis[metadataGroup] = {};
+      }
+
+      emojis[metadataGroup][hexcode] = {
+        group: groupName,
+        subgroup: subgroupName,
+        qualifier,
+        emojiVersion: emojiVersion || null,
+        unicodeVersion: extractUnicodeVersion(emojiVersion, ctx.unicode_version),
+        description: trimmedComment || "",
+        emoji: emoji || null,
+        hexcodes: hexcode.split("-"),
+      };
+    }
+
+    return {
+      groups,
+      emojis,
+    };
+  },
+  output(_ctx, transformed) {
+    return transformed;
+  },
+});
diff --git a/packages/adapters/src/handlers/modern/sequence.ts b/packages/adapters/src/handlers/modern/sequence.ts
new file mode 100644
index 00000000..33e85dd7
--- /dev/null
+++ b/packages/adapters/src/handlers/modern/sequence.ts
@@ -0,0 +1,86 @@
+import type { EmojiSequence } from "@mojis/internal-utils";
+import { expandHexRange, FEMALE_SIGN, MALE_SIGN } from "@mojis/internal-utils";
+import semver from "semver";
+import { defineAdapterHandler } from "../../define";
+
+export const modernSequenceHandler = defineAdapterHandler({
+  type: "sequence",
+  urls: (ctx) => {
+    return [
+      {
+        key: "sequences",
+        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-sequences.txt`,
+        cacheKey: `v${ctx.emoji_version}/sequences`,
+      },
+      {
+        key: "zwj",
+        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-zwj-sequences.txt`,
+        cacheKey: `v${ctx.emoji_version}/zwj-sequences`,
+      },
+    ];
+  },
+  parser: "generic",
+  parserOptions: {
+    defaultProperty: "Emoji",
+    propertyMap: {
+      "# Combining sequences": "Emoji_Combining_Sequence",
+      "# Emoji Combining Sequence": "Emoji_Combining_Sequence",
+      "# Emoji Flag Sequence": "Emoji_Flag_Sequence",
+      "# Emoji Keycap Sequence": "Emoji_Keycap_Sequence",
+      "# Emoji Modifier Sequence": "Emoji_Modifier_Sequence",
+      "# Emoji Tag Sequence": "Emoji_Tag_Sequence",
+      "# Emoji ZWJ Sequence": "Emoji_ZWJ_Sequence",
+      "# Flag sequences": "Emoji_Flag_Sequence",
+      "# Modifier sequences": "Emoji_Modifier_Sequence",
+      "# ZWJ sequences": "Emoji_ZWJ_Sequence",
+      // 12.0+
+      "# Basic_Emoji": "Basic_Emoji",
+      "# Emoji_Keycap_Sequence": "Emoji_Keycap_Sequence",
+      "# Emoji_Flag_Sequence": "Emoji_Flag_Sequence",
+      "# Emoji_Tag_Sequence": "Emoji_Tag_Sequence",
+      "# Emoji_Modifier_Sequence": "Emoji_Modifier_Sequence",
+      "# Emoji_ZWJ_Sequence": "Emoji_ZWJ_Sequence",
+      // 13.0+
+      "# RGI_Emoji_Flag_Sequence": "RGI_Emoji_Flag_Sequence",
+      "# RGI_Emoji_Modifier_Sequence": "RGI_Emoji_Modifier_Sequence",
+      "# RGI_Emoji_Tag_Sequence": "RGI_Emoji_Tag_Sequence",
+      "# RGI_Emoji_ZWJ_Sequence": "RGI_Emoji_ZWJ_Sequence",
+    },
+  },
+  shouldExecute: (ctx) => {
+    return semver.gte(`${ctx.emoji_version}.0`, "16.0.0");
+  },
+  transform(ctx, data) {
+    const sequences: EmojiSequence[] = [];
+
+    for (const line of data.lines) {
+      const [hex, property, description] = line.fields;
+
+      if (hex == null || property == null || description == null) {
+        throw new Error(`invalid line: ${line}`);
+      }
+
+      const expandedHex = expandHexRange(hex);
+
+      for (const hex of expandedHex) {
+        sequences.push({
+          hex: hex.replace(/\s+/g, "-"),
+          property,
+          description,
+          gender: hex.includes(FEMALE_SIGN) ? "female" : hex.includes(MALE_SIGN) ? "male" : null,
+        });
+      }
+    }
+
+    return sequences;
+  },
+  aggregate(_, data) {
+    return {
+      sequences: data[0],
+      zwj: data[1],
+    };
+  },
+  output(_ctx, transformed) {
+    return transformed;
+  },
+});
diff --git a/packages/adapters/src/handlers/modern/variation.ts b/packages/adapters/src/handlers/modern/variation.ts
new file mode 100644
index 00000000..db57ef1e
--- /dev/null
+++ b/packages/adapters/src/handlers/modern/variation.ts
@@ -0,0 +1,33 @@
+import semver from "semver";
+import { defineAdapterHandler } from "../../define";
+
+export const modernVariationHandler = defineAdapterHandler({
+  type: "variation",
+  urls: (ctx) => {
+    if (semver.lte(`${ctx.unicode_version}.0`, "12.1.0")) {
+      return {
+        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-variation-sequences.txt`,
+        cacheKey: `v${ctx.emoji_version}/variations`,
+      };
+    }
+
+    return {
+      url: `https://unicode.org/Public/${ctx.unicode_version}.0/ucd/emoji/emoji-variation-sequences.txt`,
+      cacheKey: `v${ctx.emoji_version}/variations`,
+    };
+  },
+  parser: "generic",
+  shouldExecute: (ctx) => {
+    return semver.gte(`${ctx.unicode_version}.0`, "11.0.0");
+  },
+  transform(ctx, data) {
+    console.warn("key", ctx.key);
+    return data;
+  },
+  aggregate(ctx, data) {
+    return data.flat();
+  },
+  output(_ctx, transformed) {
+    return transformed;
+  },
+});
diff --git a/packages/adapters/src/handlers/pre-alignment/metadata.ts b/packages/adapters/src/handlers/pre-alignment/metadata.ts
new file mode 100644
index 00000000..74678ff5
--- /dev/null
+++ b/packages/adapters/src/handlers/pre-alignment/metadata.ts
@@ -0,0 +1,22 @@
+import { isBeforeAlignment } from "@mojis/internal-utils";
+import { defineAdapterHandler } from "../../define";
+
+export const preAlignmentMetadataHandler = defineAdapterHandler({
+  type: "metadata",
+  shouldExecute: (ctx) => {
+    return isBeforeAlignment(ctx.emoji_version);
+  },
+  urls: () => {
+    return undefined;
+  },
+  parser: "generic",
+  transform() {
+    return undefined;
+  },
+  output() {
+    return {
+      groups: [],
+      emojis: {},
+    };
+  },
+});
diff --git a/packages/adapters/src/index.ts b/packages/adapters/src/index.ts
index 2dd57c1c..0fb6d849 100644
--- a/packages/adapters/src/index.ts
+++ b/packages/adapters/src/index.ts
@@ -1,3 +1,3 @@
-export * from "./adapter";
+export * from "./handler";
 
 export type * from "./types";
diff --git a/packages/adapters/src/types.ts b/packages/adapters/src/types.ts
index 233d2302..57cd6431 100644
--- a/packages/adapters/src/types.ts
+++ b/packages/adapters/src/types.ts
@@ -1,149 +1,178 @@
 import type {
   EmojiGroup,
   EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { GenericParseOptions, GenericParseResult } from "@mojis/parsers";
+import type { METADATA_HANDLERS, SEQUENCE_HANDLERS, VARIATION_HANDLERS } from "./_handlers";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
-  /**
-   * The name of the adapter.
-   */
-  name: string;
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
 
+export interface AdapterContext {
   /**
-   * A description of the adapter.
+   * The emoji version.
    */
-  description: string;
+  emoji_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  range: string;
+  unicode_version: string;
 
   /**
-   * The name of the adapter to extend from.
+   * Whether or not the force mode was enabled.
    */
-  extend?: string;
+  force: boolean;
+}
 
+export type AdapterHandlerType = "metadata" | "sequence" | "variation";
+
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The metadata handler for the adapter.
+   * The url to fetch the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  url: string;
 
   /**
-   * The sequences handler for the adapter.
+   * The cache key for the data.
    */
-  sequences?: AdapterHandler<
-    TSequencesUrlReturn,
-    ExtraContext<TSequencesUrlReturn>,
-    EmojiSequence[],
-    any
-  >;
+  cacheKey: string;
 
   /**
-   * The variations handler for the adapter.
+   * The key to identify the data.
+   *
+   * NOTE:
+   * If not set it will be generated from the cache key.
    */
-  variations?: AdapterHandler<
-    TVariationsUrlReturn,
-    ExtraContext<TVariationsUrlReturn>,
-    EmojiVariation[],
-    any
-  >;
+  key?: string;
 }
 
-type ExtraContext<T> = {
-  key: string;
-} & (T extends CacheableUrlRequest
-  ? T["extraContext"] extends Record<string, unknown>
-    ? T["extraContext"]
-    : Record<string, never>
-  : Record<string, never>);
+export type TransformFn<
+  TContext extends AdapterContext,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
+
+export type OutputFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TOutput,
+> = (ctx: TContext, data: TTransformOutput) => TOutput;
+
+export type BuiltinParser = typeof BUILTIN_PARSERS[number];
+
+export type ParserFn<
+  TContext extends AdapterContext,
+  TOutput,
+> = (ctx: TContext, data: string) => TOutput;
 
-export interface CacheableUrlRequest {
+export type GetParseOutputFromBuiltInParser<TParser extends string> =
+  TParser extends "generic" ? GenericParseResult :
+    never;
+
+export type GetParseOptionsFromParser<TParser extends string> =
+  TParser extends "generic" ? GenericParseOptions :
+    never;
+
+export interface AdapterHandler<
+  TType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TParser extends string | ParserFn<TContext, any>,
+  TTransformOutput,
+  TParseOutput = InferParseOutput<TContext, TParser>,
+  TAggregateOutput = TTransformOutput,
+  TOutput = TTransformOutput | TAggregateOutput,
+> {
   /**
-   * The key for the data.
-   * If not provided, a key will be generated based on the url.
+   * The type of the handler, this is used to identify the handler.
    */
-  key?: string;
+  type: TType;
 
   /**
-   * The url to fetch the data
-   * @example "https://example.com/data.json"
+   * The urls that will be fetched.
    */
-  url: string;
+  urls: Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder;
 
   /**
-   * The cache key for the data.
-   * If not provided, a key will be generated based on the url.
+   * Options that will be passed to the fetch function.
    */
-  cacheKey?: string;
+  fetchOptions?: RequestInit;
 
   /**
-   * Extra data to be passed to the handler.
-   * @default {}
+   * Options that will be passed to the cache function.
    */
-  extraContext?: Record<string, unknown>;
+  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
 
   /**
-   * The fetch options for the request.
-   * @default {}
-   *
-   * NOTE:
-   * This will be merged together with the `fetchOptions` of the handler.
+   * Whether or not that this handler should handle the request.
    */
-  fetchOptions?: RequestInit;
+  shouldExecute: ShouldExecute<TContext>;
 
   /**
-   * The cache options for the data.
-   * @default {}
+   * A parse function or a reference to a builtin parser.
    *
-   * NOTE:
-   * This will be merged together with the `cacheOptions` of the handler.
    */
-  cacheOptions?: Omit<WriteCacheOptions<any>, "transform">;
-}
+  parser: TParser;
 
-export type CacheableUrlRequestReturnType = CacheableUrlRequest | CacheableUrlRequest[] | undefined;
-
-export type ExtractDataTypeFromUrls<T extends CacheableUrlRequestReturnType> =
-T extends undefined ? undefined :
-  T extends CacheableUrlRequest ? string :
-    T extends CacheableUrlRequest[] ? string :
-      never;
+  /**
+   * Options that will be passed to the parser.
+   * This will only be used if the parser is a builtin parser.
+   */
+  parserOptions?: TParser extends BuiltinParser ? GetParseOptionsFromParser<TParser> : never;
 
-export interface AdapterHandler<
-  TUrlsReturn extends CacheableUrlRequestReturnType,
-  TExtraContext extends Record<string, unknown>,
-  TTransformOutput,
-  TOutput = TTransformOutput,
-> {
-  urls: (ctx: AdapterContext) => Promise<TUrlsReturn> | TUrlsReturn;
-  fetchOptions?: RequestInit;
-  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
-  transform: (ctx: AdapterContext & TExtraContext, data: ExtractDataTypeFromUrls<TUrlsReturn>) => TTransformOutput;
-  aggregate?: (ctx: AdapterContext, data: [TTransformOutput, ...TTransformOutput[]]) => TOutput;
-}
+  /**
+   * A transform function that will run for each of the urls.
+   * It will pass the returned type downwards to either the aggregate function or the output.
+   */
+  transform: TransformFn<TContext & {
+    key: string;
+  }, TParseOutput, TTransformOutput>;
 
-export type AdapterHandlers<TAdapter = MojiAdapter<any, any, any>> = {
-  [K in keyof TAdapter]: NonNullable<TAdapter[K]> extends AdapterHandler<any, any, any, any> ? K : never;
-}[keyof TAdapter];
+  /**
+   * An aggregate function that will run if defined.
+   * It will receive all the transformed results as parameters, which it can then aggregate the results into a final output.
+   */
+  aggregate?: AggregateFn<TContext, TTransformOutput, TAggregateOutput>;
 
-export interface AdapterContext {
-  force: boolean;
-  emoji_version: string;
-  unicode_version: string;
+  /**
+   * Output function which will receive the transformed result from the transform function, or from the aggregate function.
+   * It will then output the result.
+   */
+  output: OutputFn<TContext, TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput, TOutput>;
 }
+
+export type MetadataAdapterHandler = AdapterHandler<
+  "metadata", // type
+  AdapterContext, // context
+  () => { lines: string[] }, // parser
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }
+>;
+
+export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> =
+   THandlerType extends "metadata" ? ReturnType<typeof METADATA_HANDLERS[number]["output"]> :
+     THandlerType extends "sequence" ? ReturnType<typeof SEQUENCE_HANDLERS[number]["output"]> :
+       THandlerType extends "variation" ? ReturnType<typeof VARIATION_HANDLERS[number]["output"]> :
+         unknown;
+
+export type InferParseOutput<TContext extends AdapterContext, TParser extends string | ParserFn<TContext, any>> =
+  TParser extends ParserFn<AdapterContext, infer TOutput> ? TOutput :
+    TParser extends BuiltinParser ? GetParseOutputFromBuiltInParser<TParser> :
+      never;
diff --git a/packages/adapters/src/utils.ts b/packages/adapters/src/utils.ts
new file mode 100644
index 00000000..79969836
--- /dev/null
+++ b/packages/adapters/src/utils.ts
@@ -0,0 +1,104 @@
+import type { AdapterContext, Arrayable, BuiltinParser, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl } from "@mojis/internal-utils";
+
+/**
+ * Type predicate function that checks if the given value is a UrlBuilder.
+ *
+ * A UrlBuilder is expected to be a callable function.
+ *
+ * @param {unknown} fn - The value to check
+ * @returns {boolean} True if the value is a function (and thus potentially a UrlBuilder), false otherwise
+ */
+export function isUrlBuilder(fn: unknown): fn is UrlBuilder {
+  return typeof fn === "function";
+}
+
+export const BUILTIN_PARSERS = [
+  "generic",
+] as const;
+
+/**
+ * Checks if the given parser is a builtin parser.
+ *
+ * A builtin parser is a string that corresponds to a key in the BUILTIN_PARSERS object.
+ *
+ * @param {unknown} parser - The parser to check
+ * @returns {boolean} True if the parser is a builtin parser, false otherwise
+ */
+export function isBuiltinParser(parser: unknown): parser is BuiltinParser {
+  return typeof parser === "string" && BUILTIN_PARSERS.includes(parser as BuiltinParser);
+}
+
+function createUrlWithCache(url: string): UrlWithCache {
+  const cacheKey = createCacheKeyFromUrl(url);
+  return { url, cacheKey, key: cacheKey };
+}
+
+/**
+ * Processes various URL formats into a standardized array of URL objects with cache keys.
+ *
+ * This utility function handles multiple input formats:
+ * - Single string URL
+ * - Array of string URLs
+ * - URL object with cache information
+ * - Array of URL objects
+ * - URL builder function that returns URLs based on context
+ * - Null/undefined values (returns empty array)
+ *
+ * @param {Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder} urls - The URL(s) to process, which can be:
+ *               - A single string URL
+ *               - An array of string URLs
+ *               - A single {@link UrlWithCache} object
+ *               - An array of {@link UrlWithCache} objects
+ *               - A {@link UrlBuilder} function that generates URLs from context
+ *               - null or undefined (returns empty array)
+ * @param {TContext} ctx - The adapter context object used when resolving URL builder functions
+ * @returns A promise that resolves to an array of {@link UrlWithCache} objects with normalized structure
+ *
+ * @template TContext - The type of context passed to URL builder functions
+ */
+export async function getHandlerUrls<TContext extends AdapterContext>(
+  urls: Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder,
+  ctx: TContext,
+): Promise<UrlWithCache[]> {
+  const result: UrlWithCache[] = [];
+
+  if (urls == null) {
+    return result;
+  }
+
+  if (isUrlBuilder(urls)) {
+    const urlsResults = await urls(ctx);
+
+    if (urlsResults == null) {
+      return result;
+    }
+
+    if (!Array.isArray(urlsResults)) {
+      if (typeof urlsResults === "string") {
+        return [createUrlWithCache(urlsResults)];
+      }
+
+      return [urlsResults];
+    }
+
+    return urlsResults
+      .filter((url) => url != null)
+      .map((url) => typeof url === "string" ? createUrlWithCache(url) : url);
+  }
+
+  if (Array.isArray(urls)) {
+    for (const url of urls) {
+      if (url != null) {
+        result.push(typeof url === "string" ? createUrlWithCache(url) : url);
+      }
+    }
+    return result;
+  }
+
+  if (typeof urls === "string") {
+    return [createUrlWithCache(urls)];
+  }
+
+  return [urls];
+}
diff --git a/packages/adapters/test/types.test-d.ts b/packages/adapters/test/types.test-d.ts
new file mode 100644
index 00000000..ee2f4493
--- /dev/null
+++ b/packages/adapters/test/types.test-d.ts
@@ -0,0 +1,27 @@
+import type { GenericParseOptions, GenericParseResult } from "@mojis/parsers";
+import type { GetParseOptionsFromParser, GetParseOutputFromBuiltInParser } from "../src/types";
+import { describe, expectTypeOf, it } from "vitest";
+
+describe("GetParseOutputFromBuiltInParser", () => {
+  it("generic should return ParseResult", () => {
+    type Result = GetParseOutputFromBuiltInParser<"generic">;
+    expectTypeOf<Result>().toEqualTypeOf<GenericParseResult>();
+  });
+
+  it("unknown parser should return never", () => {
+    type Result = GetParseOutputFromBuiltInParser<"unknown">;
+    expectTypeOf<Result>().toEqualTypeOf<never>();
+  });
+});
+
+describe("GetParseOptionsFromParser", () => {
+  it("should return options if using valid parser", () => {
+    type Result = GetParseOptionsFromParser<"generic">;
+    expectTypeOf<Result>().toEqualTypeOf<GenericParseOptions>();
+  });
+
+  it("invalid parser should return never", () => {
+    type Result = GetParseOptionsFromParser<"unknown">;
+    expectTypeOf<Result>().toEqualTypeOf<never>();
+  });
+});
diff --git a/packages/adapters/test/utils.test.ts b/packages/adapters/test/utils.test.ts
new file mode 100644
index 00000000..7f701306
--- /dev/null
+++ b/packages/adapters/test/utils.test.ts
@@ -0,0 +1,148 @@
+import type { AdapterContext, Arrayable } from "../src/types";
+import { describe, expect, it, vi } from "vitest";
+import { BUILTIN_PARSERS, getHandlerUrls, isBuiltinParser, isUrlBuilder } from "../src/utils";
+
+describe("is url builder", () => {
+  it.each([
+    [
+      () => "https://example.com",
+      true,
+    ],
+    [
+      "https://example.com",
+      false,
+    ],
+  ])("should return %p for %p", (value, expected) => {
+    expect(isUrlBuilder(value)).toBe(expected);
+  });
+});
+
+describe("is builtin parser", () => {
+  it.each([
+    ...(BUILTIN_PARSERS.map((parser) => [parser, true] as [string, boolean])),
+
+    // not valid parser
+    [
+      "not a builtin parser",
+      false,
+    ],
+    [
+      () => "generic",
+      false,
+    ],
+    [
+      null,
+      false,
+    ],
+    [
+      undefined,
+      false,
+    ],
+    [
+      0,
+      false,
+    ],
+  ])("should return %p for %p", (value, expected) => {
+    expect(isBuiltinParser(value)).toBe(expected);
+  });
+});
+
+describe("getHandlerUrls", () => {
+  const ctx = {} as AdapterContext;
+
+  it("should handle null or undefined inputs", async () => {
+    expect(await getHandlerUrls(
+      // @ts-expect-error null is not a valid input type
+      null,
+      ctx,
+    )).toEqual([]);
+    expect(await getHandlerUrls(undefined, ctx)).toEqual([]);
+  });
+
+  it("should handle single string URL", async () => {
+    const result = await getHandlerUrls("https://example.com", ctx);
+    expect(result).toEqual([
+      { url: "https://example.com", cacheKey: "example_com", key: "example_com" },
+    ]);
+  });
+
+  it("should handle array of string URLs", async () => {
+    const result = await getHandlerUrls(["https://example.com", "https://test.com"], ctx);
+    expect(result).toEqual([
+      { url: "https://example.com", cacheKey: "example_com", key: "example_com" },
+      { url: "https://test.com", cacheKey: "test_com", key: "test_com" },
+    ]);
+  });
+
+  it("should handle array with null values", async () => {
+    const result = await getHandlerUrls(
+      [
+        "https://example.com",
+        null,
+        "https://test.com",
+      ] as Arrayable<string>,
+      ctx,
+    );
+    expect(result).toEqual([
+      { url: "https://example.com", cacheKey: "example_com", key: "example_com" },
+      { url: "https://test.com", cacheKey: "test_com", key: "test_com" },
+    ]);
+  });
+
+  it("should handle UrlWithCache object", async () => {
+    const urlObj = { url: "https://example.com", cacheKey: "custom-cache-key", key: "custom-cache-key" };
+    const result = await getHandlerUrls(urlObj, ctx);
+    expect(result).toEqual([urlObj]);
+  });
+
+  it("should handle array of UrlWithCache objects", async () => {
+    const urlObj1 = { url: "https://example.com", cacheKey: "key-1", key: "key-1" };
+    const urlObj2 = { url: "https://test.com", cacheKey: "key-2", key: "key-2" };
+    const result = await getHandlerUrls([urlObj1, urlObj2], ctx);
+    expect(result).toEqual([urlObj1, urlObj2]);
+  });
+
+  it("should handle UrlBuilder returning string", async () => {
+    const urlBuilder = vi.fn().mockResolvedValue("https://example.com");
+    const result = await getHandlerUrls(urlBuilder, ctx);
+    expect(urlBuilder).toHaveBeenCalledWith(ctx);
+    expect(result).toEqual([
+      { url: "https://example.com", cacheKey: "example_com", key: "example_com" },
+    ]);
+  });
+
+  it("should handle UrlBuilder returning null/undefined", async () => {
+    const urlBuilder = vi.fn().mockResolvedValue(null);
+    const result = await getHandlerUrls(urlBuilder, ctx);
+    expect(urlBuilder).toHaveBeenCalledWith(ctx);
+    expect(result).toEqual([]);
+  });
+
+  it("should handle UrlBuilder returning UrlWithCache", async () => {
+    const urlObj = { url: "https://example.com", cacheKey: "builder-key", key: "builder-key" };
+    const urlBuilder = vi.fn().mockResolvedValue(urlObj);
+    const result = await getHandlerUrls(urlBuilder, ctx);
+    expect(urlBuilder).toHaveBeenCalledWith(ctx);
+    expect(result).toEqual([urlObj]);
+  });
+
+  it("should handle UrlBuilder returning array of URLs", async () => {
+    const urlBuilder = vi.fn().mockResolvedValue(["https://example.com", "https://test.com"]);
+    const result = await getHandlerUrls(urlBuilder, ctx);
+    expect(urlBuilder).toHaveBeenCalledWith(ctx);
+    expect(result).toEqual([
+      { url: "https://example.com", cacheKey: "example_com", key: "example_com" },
+      { url: "https://test.com", cacheKey: "test_com", key: "test_com" },
+    ]);
+  });
+
+  it("should handle UrlBuilder returning array with null values", async () => {
+    const urlBuilder = vi.fn().mockResolvedValue(["https://example.com", null, "https://test.com"]);
+    const result = await getHandlerUrls(urlBuilder, ctx);
+    expect(urlBuilder).toHaveBeenCalledWith(ctx);
+    expect(result).toEqual([
+      { url: "https://example.com", cacheKey: "example_com", key: "example_com" },
+      { url: "https://test.com", cacheKey: "test_com", key: "test_com" },
+    ]);
+  });
+});
diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts
index eba21791..d38f0641 100644
--- a/packages/cli/src/cli.ts
+++ b/packages/cli/src/cli.ts
@@ -2,7 +2,7 @@ import type { EmojiSpecRecord, ShortcodeProvider } from "@mojis/internal-utils";
 import type { Argv } from "yargs";
 import { join } from "node:path";
 import process from "node:process";
-import { resolveAdapter, runAdapterHandler } from "@mojis/adapters";
+import { runAdapterHandler } from "@mojis/adapters";
 import {
 
   getAllEmojiVersions,
@@ -134,22 +134,12 @@ cli.command(
     console.info(`using the following generators ${args.generators.map((g) => yellow(g)).join(", ")}`);
 
     const promises = versions.map(async (version) => {
-      const adapter = resolveAdapter(version);
-
-      if (adapter == null) {
-        throw new Error(`no adapter found for version ${version.emoji_version}`);
-      }
-
       const baseDir = `./data/v${version.emoji_version}`;
 
       await fs.ensureDir(baseDir);
 
       if (isGeneratorEnabled("metadata")) {
-        if (adapter.metadata == null) {
-          throw new MojisNotImplemented("metadata");
-        }
-
-        const { groups, emojis } = await runAdapterHandler(adapter, "metadata", {
+        const { groups, emojis } = await runAdapterHandler("metadata", {
           force,
           emoji_version: version.emoji_version,
           unicode_version: version.unicode_version,
@@ -171,11 +161,7 @@ cli.command(
       }
 
       if (isGeneratorEnabled("sequences")) {
-        if (adapter.sequences == null) {
-          throw new MojisNotImplemented("sequences");
-        }
-
-        const { sequences, zwj } = await runAdapterHandler(adapter, "sequences", {
+        const { sequences, zwj } = await runAdapterHandler("sequence", {
           force,
           emoji_version: version.emoji_version,
           unicode_version: version.unicode_version,
@@ -195,19 +181,15 @@ cli.command(
       }
 
       if (isGeneratorEnabled("variations")) {
-        if (adapter.variations == null) {
-          throw new MojisNotImplemented("variations");
-        }
-
-        const variations = await runAdapterHandler(adapter, "variations", {
+        const variations = await runAdapterHandler("variation", {
           force,
           emoji_version: version.emoji_version,
           unicode_version: version.unicode_version,
         });
 
-        await fs.ensureDir(`./data/v${version.emoji_version}`);
+        await fs.ensureDir(join(baseDir, "variations"));
         await fs.writeFile(
-          `./data/v${version.emoji_version}/variations.json`,
+          join(baseDir, "variations.json"),
           JSON.stringify(variations, null, 2),
           "utf-8",
         );
diff --git a/packages/internal-utils/src/cache.ts b/packages/internal-utils/src/cache.ts
index f08fc0cc..18b05429 100644
--- a/packages/internal-utils/src/cache.ts
+++ b/packages/internal-utils/src/cache.ts
@@ -184,7 +184,7 @@ export async function fetchCache<TData>(url: string, options: FetchCacheOptions<
   if (!response.ok) {
     throw new Error(`failed to fetch: url=(${url}) status=(${response.status})`);
   }
-
+  // TODO: don't use .text if encoding is binary or something like that
   const data = await response.text();
   const parsedData = parser(data);
 
diff --git a/packages/internal-utils/src/predicates.ts b/packages/internal-utils/src/predicates.ts
index efc1c042..cd3c6192 100644
--- a/packages/internal-utils/src/predicates.ts
+++ b/packages/internal-utils/src/predicates.ts
@@ -1,9 +1,31 @@
+import semver from "semver";
+
 /**
- * Checks if a version is less than "13.0.0".
+ * Checks if a version is less than "11.0.0".
  *
  * @param {string} version - The version string to check
  * @returns {boolean} `true` if the version is less than "11.0.0", `false` otherwise
  */
 export function isBeforeAlignment(version: string): boolean {
-  return version < "11.0.0";
+  if (version == null) {
+    return false;
+  }
+
+  const coerced = semver.coerce(version);
+  if (coerced == null) {
+    return false;
+  }
+
+  return coerced.major < 11;
+}
+
+/**
+ * Checks if the given version is after the alignment.
+ * It is the inverse of `isBeforeAlignment`.
+ *
+ * @param {string} version - The version string to check
+ * @returns {boolean} `true` if the version is after alignment, `false` otherwise
+ */
+export function isAfterAlignment(version: string): boolean {
+  return !isBeforeAlignment(version);
 }
diff --git a/packages/internal-utils/test/predicates.test.ts b/packages/internal-utils/test/predicates.test.ts
new file mode 100644
index 00000000..1e4a683e
--- /dev/null
+++ b/packages/internal-utils/test/predicates.test.ts
@@ -0,0 +1,61 @@
+import { describe, expect, it } from "vitest";
+import { isAfterAlignment, isBeforeAlignment } from "../src/predicates";
+
+describe("alignment predicates", () => {
+  describe("before", () => {
+    it.each([
+      { version: "10.0.0", expected: true },
+      { version: "9.5.2", expected: true },
+      { version: "11.0.0", expected: false },
+      { version: "12.1.0", expected: false },
+      { version: "11.0.1", expected: false },
+      { version: "10.99.99", expected: true },
+      { version: "11.0.0-alpha.1", expected: false },
+      { version: "10.0.0-beta.5", expected: true },
+      { version: "10.0.0+build.123", expected: true },
+      { version: "11.0.0+build.123", expected: false },
+      { version: "10", expected: true },
+      { version: "11", expected: false },
+      { version: "12", expected: false },
+      { version: "10.1", expected: true },
+      { version: "11.1", expected: false },
+      { version: "12.1", expected: false },
+      { version: "10.1.2", expected: true },
+      { version: "11.1.2", expected: false },
+      { version: "12.1.2", expected: false },
+      { version: null, expected: false },
+      { version: undefined, expected: false },
+      { version: "", expected: false },
+      { version: "abc", expected: false },
+      { version: "1.2.3-rc.1+build.123", expected: true },
+      { version: "11.0.0-rc.1+build.123", expected: false },
+    ])("should return $expected for version $version", ({ version, expected }) => {
+      expect(isBeforeAlignment(version as string)).toBe(expected);
+    });
+  });
+
+  describe("after", () => {
+    it.each([
+      { version: "10.0.0", expected: false },
+      { version: "11.0.0", expected: true },
+      { version: "12.1.0", expected: true },
+      { version: null, expected: true },
+      { version: undefined, expected: true },
+      { version: "", expected: true },
+      { version: "abc", expected: true },
+      { version: "10", expected: false },
+      { version: "11", expected: true },
+      { version: "12", expected: true },
+      { version: "10.1", expected: false },
+      { version: "11.1", expected: true },
+      { version: "12.1", expected: true },
+      { version: "10.1.2", expected: false },
+      { version: "11.1.2", expected: true },
+      { version: "12.1.2", expected: true },
+      { version: "1.2.3-rc.1+build.123", expected: false },
+      { version: "11.0.0-rc.1+build.123", expected: true },
+    ])("should return $expected for version $version", ({ version, expected }) => {
+      expect(isAfterAlignment(version as string)).toBe(expected);
+    });
+  });
+});
diff --git a/packages/internal-utils/test/versions.test.ts b/packages/internal-utils/test/versions.test.ts
index e72a5a4b..cc7951a5 100644
--- a/packages/internal-utils/test/versions.test.ts
+++ b/packages/internal-utils/test/versions.test.ts
@@ -1,7 +1,14 @@
 import type { EmojiSpecRecord } from "../src/types";
 import fs from "node:fs";
 import { describe, expect, it } from "vitest";
-import { extractEmojiVersion, extractVersionFromReadme, getCurrentDraftVersion, getLatestEmojiVersion, mapEmojiVersionToUnicodeVersion, toSemverCompatible } from "../src/versions";
+import {
+  extractEmojiVersion,
+  extractVersionFromReadme,
+  getCurrentDraftVersion,
+  getLatestEmojiVersion,
+  mapEmojiVersionToUnicodeVersion,
+  toSemverCompatible,
+} from "../src/versions";
 
 describe("get draft version", () => {
   it("returns draft versions when fetches succeed and versions match", async () => {
@@ -63,7 +70,6 @@ describe("extract version", () => {
 
     { name: "draft readme", path: "root/README-valid.txt", version: "17.0.0" },
     { name: "invalid draft readme", path: "root/README-invalid.txt", version: null },
-
   ])("extract version from $name", ({ path, version }) => {
     it("should extract version from file path", () => {
       const content = fs.readFileSync(`./test/fixtures/extract-version/${path}`, "utf-8");
diff --git a/packages/parsers/src/generic.ts b/packages/parsers/src/generic.ts
index ee4bd2f5..102d6a4a 100644
--- a/packages/parsers/src/generic.ts
+++ b/packages/parsers/src/generic.ts
@@ -4,14 +4,14 @@ export interface ParsedLine {
   property?: string;
 }
 
-export interface ParseResult {
+export interface GenericParseResult {
   totalLines: number;
   lines: ParsedLine[];
   properties?: Record<string, string>;
   totals?: Record<string, number>;
 }
 
-export interface ParseOptions {
+export interface GenericParseOptions {
   commentPrefix?: string;
   separator?: string;
   propertyMap?: Record<string, string>;
@@ -22,9 +22,9 @@ export interface ParseOptions {
  * Parses a string content into a structured format based on provided options.
  *
  * @param {string} content - The string content to parse
- * @param {ParseOptions} options - Configuration options for parsing
+ * @param {GenericParseOptions} options - Configuration options for parsing
  *
- * @returns  {ParseResult} A ParseResult object containing:
+ * @returns  {GenericParseResult} A ParseResult object containing:
  *   - totalLines: The number of non-empty, non-comment lines parsed
  *   - lines: Array of ParsedLine objects with fields, comments, and property values
  *   - properties: (Optional) Record of property mappings that were found in the content
@@ -38,10 +38,10 @@ export interface ParseOptions {
  * });
  * ```
  */
-export function parse(
+export function genericParse(
   content: string,
-  options: ParseOptions = {},
-): ParseResult {
+  options: GenericParseOptions = {},
+): GenericParseResult {
   const {
     commentPrefix = "#",
     separator = ";",
@@ -120,7 +120,7 @@ export function parse(
   }
 
   // only include optional properties and totals if they have content
-  const result: ParseResult = {
+  const result: GenericParseResult = {
     totalLines,
     lines,
   };
diff --git a/packages/parsers/src/index.ts b/packages/parsers/src/index.ts
index 90d66314..e478d28c 100644
--- a/packages/parsers/src/index.ts
+++ b/packages/parsers/src/index.ts
@@ -1 +1,6 @@
-export { parse, type ParsedLine, type ParseOptions, type ParseResult } from "./generic";
+export {
+  genericParse,
+  type GenericParseOptions,
+  type GenericParseResult,
+  type ParsedLine,
+} from "./generic";
diff --git a/packages/parsers/test/generic.test.ts b/packages/parsers/test/generic.test.ts
index 414ed83b..2c36b668 100644
--- a/packages/parsers/test/generic.test.ts
+++ b/packages/parsers/test/generic.test.ts
@@ -1,15 +1,15 @@
 import { describe, expect, it } from "vitest";
-import { parse } from "../src/generic";
+import { genericParse } from "../src/generic";
 
 describe("parse function", () => {
   it("should handle empty content", () => {
-    const result = parse("");
+    const result = genericParse("");
     expect(result).toEqual({ totalLines: 0, lines: [] });
   });
 
   it("should parse simple lines with default options", () => {
     const content = "field1;field2;field3\nfield4;field5;field6";
-    const result = parse(content);
+    const result = genericParse(content);
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -21,7 +21,7 @@ describe("parse function", () => {
 
   it("should handle custom separator", () => {
     const content = "field1,field2,field3\nfield4,field5,field6";
-    const result = parse(content, { separator: "," });
+    const result = genericParse(content, { separator: "," });
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -33,7 +33,7 @@ describe("parse function", () => {
 
   it("should handle comments", () => {
     const content = "field1;field2;field3 # comment1\nfield4;field5;field6 # comment2";
-    const result = parse(content);
+    const result = genericParse(content);
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -45,7 +45,7 @@ describe("parse function", () => {
 
   it("should handle custom comment prefix", () => {
     const content = "field1;field2;field3 // comment1\nfield4;field5;field6 // comment2";
-    const result = parse(content, { commentPrefix: "//" });
+    const result = genericParse(content, { commentPrefix: "//" });
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -57,7 +57,7 @@ describe("parse function", () => {
 
   it("should handle property mapping", () => {
     const content = "# SECTION1\nfield1;field2\n# SECTION2\nfield3;field4";
-    const result = parse(content, { propertyMap: { "# SECTION1": "section1", "# SECTION2": "section2" } });
+    const result = genericParse(content, { propertyMap: { "# SECTION1": "section1", "# SECTION2": "section2" } });
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -70,7 +70,7 @@ describe("parse function", () => {
 
   it("should handle default property", () => {
     const content = "field1;field2\n# SECTION1\nfield3;field4";
-    const result = parse(content, { propertyMap: { "# SECTION1": "section1" }, defaultProperty: "default" });
+    const result = genericParse(content, { propertyMap: { "# SECTION1": "section1" }, defaultProperty: "default" });
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -83,7 +83,7 @@ describe("parse function", () => {
 
   it("should handle totals in comments", () => {
     const content = "# SECTION1 Total: 10\nfield1;field2\n# SECTION2 Total: 20\nfield3;field4";
-    const result = parse(content, { propertyMap: { "# SECTION1": "section1", "# SECTION2": "section2" } });
+    const result = genericParse(content, { propertyMap: { "# SECTION1": "section1", "# SECTION2": "section2" } });
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -97,7 +97,7 @@ describe("parse function", () => {
 
   it("should skip empty lines", () => {
     const content = "field1;field2\n\nfield3;field4";
-    const result = parse(content);
+    const result = genericParse(content);
     expect(result).toEqual({
       totalLines: 2,
       lines: [
@@ -109,13 +109,13 @@ describe("parse function", () => {
 
   it("should handle a line with only a comment", () => {
     const content = "# This is a comment only line";
-    const result = parse(content, {});
+    const result = genericParse(content, {});
     expect(result).toEqual({ totalLines: 0, lines: [] });
   });
 
   it("should handle a line with a comment at the very beginning of the string", () => {
     const content = "#comment\nfield1;field2";
-    const result = parse(content);
+    const result = genericParse(content);
     expect(result).toEqual({
       totalLines: 1,
       lines: [
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 173f2cac..3d38126d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -113,6 +113,9 @@ importers:
       '@mojis/internal-utils':
         specifier: workspace:*
         version: link:../internal-utils
+      '@mojis/parsers':
+        specifier: workspace:*
+        version: link:../parsers
       defu:
         specifier: 'catalog:'
         version: 6.1.4
diff --git a/vitest.config.ts b/vitest.config.ts
index 0462625d..b2ff1cdb 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,5 +1,5 @@
-import { existsSync, readdirSync } from 'node:fs';
-import { defineConfig } from 'vitest/config'
+import { existsSync, readdirSync } from "node:fs";
+import { defineConfig } from "vitest/config";
 
 const pkgRoot = (pkg: string) =>
   new URL(`./packages/${pkg}`, import.meta.url).pathname;
@@ -28,10 +28,15 @@ export default defineConfig({
       {
         extends: true,
         test: {
-          include: ["./packages/internal-utils/**/*.test.{ts,js}"],
+          include: ["./packages/internal-utils/**/*.{test,spec}.?(c|m)[jt]s?(x)"],
           name: "internal-utils",
           environment: "node",
           mockReset: true,
+          typecheck: {
+            enabled: true,
+            include: ["./packages/internal-utils/**/*.{test,spec}-d.?(c|m)[jt]s?(x)"],
+            tsconfig: "./packages/internal-utils/tsconfig.json"
+          }
         },
         esbuild: { target: "es2020" },
         resolve: { alias: aliases },
@@ -39,10 +44,16 @@ export default defineConfig({
       {
         extends: true,
         test: {
-          include: ["./packages/adapters/**/*.test.{ts,js}"],
+          include: [
+            "./packages/adapters/**/*.{test,spec}.?(c|m)[jt]s?(x)"],
           name: "adapters",
           environment: "node",
           mockReset: true,
+          typecheck: {
+            enabled: true,
+            include: ["./packages/adapters/**/*.{test,spec}-d.?(c|m)[jt]s?(x)"],
+            tsconfig: "./packages/adapters/tsconfig.json"
+          }
         },
         esbuild: { target: "es2020" },
         resolve: { alias: aliases },
@@ -50,10 +61,15 @@ export default defineConfig({
       {
         extends: true,
         test: {
-          include: ["./packages/cli/**/*.test.{ts,js}"],
+          include: ["./packages/cli/**/*.{test,spec}.?(c|m)[jt]s?(x)"],
           name: "cli",
           environment: "node",
           mockReset: true,
+          typecheck: {
+            enabled: true,
+            include: ["./packages/cli/**/*.{test,spec}-d.?(c|m)[jt]s?(x)"],
+            tsconfig: "./packages/cli/tsconfig.json"
+          }
         },
         esbuild: { target: "es2020" },
         resolve: { alias: aliases },
@@ -61,10 +77,15 @@ export default defineConfig({
       {
         extends: true,
         test: {
-          include: ["./packages/parsers/**/*.test.{ts,js}"],
+          include: ["./packages/parsers/**/*.{test,spec}.?(c|m)[jt]s?(x)"],
           name: "parsers",
           environment: "node",
           mockReset: true,
+          typecheck: {
+            enabled: true,
+            include: ["./packages/parsers/**/*.{test,spec}-d.?(c|m)[jt]s?(x)"],
+            tsconfig: "./packages/parsers/tsconfig.json"
+          }
         },
         esbuild: { target: "es2020" },
         resolve: { alias: aliases },


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (6)</summary><blockquote>

<details>
<summary>packages/adapters/src/v2_adapters.ts (1)</summary><blockquote>

`4-6`: **Consider using more specific types instead of `any`.**

Using `any` for all generic parameters in the type assertion bypasses type safety. Consider defining more specific types for your adapter handler if possible.

```diff
-] satisfies v2_AdapterHandler<"metadata", any, any, any, any>[];
+] satisfies v2_AdapterHandler<"metadata", MetadataInput, MetadataOutput, MetadataTransformed, MetadataAggregated>[];
```

Where you would define these types based on the actual data structures used in your metadata handlers.

</blockquote></details>
<details>
<summary>packages/adapters/src/utils.ts (1)</summary><blockquote>

`3-13`: **Consider enhancing the type predicate for more robust validation.**

The current implementation only checks if the value is a function, but doesn't verify if it conforms to the expected `UrlBuilder` contract beyond being callable. While this is often the best you can do at runtime in TypeScript, consider if there are any additional runtime properties or behaviors that could be checked.


For example, if `UrlBuilder` functions are expected to have specific properties or methods:

```diff
export function isUrlBuilder(fn: unknown): fn is UrlBuilder {
-  return typeof fn === "function";
+  return typeof fn === "function" && (
+    // Add additional checks if applicable, e.g.:
+    // 'expectedProperty' in fn ||
+    // typeof (fn as any).someMethod === 'function'
+    true
+  );
}
```

If no additional checks are applicable, the current implementation is appropriate.

</blockquote></details>
<details>
<summary>packages/adapters/test/utils.test.ts (1)</summary><blockquote>

`6-17`: **Consider adding more test cases and improving error message.**

The current tests are a good start, but consider:

1. Testing more edge cases like `null`, `undefined`, object with a callable `toString()`, etc.
2. The error message format `"should return %p for %p"` shows the expected result first and the input second, which could be confusing. Consider reversing for clarity.

```diff
-    it.each([
-      [
-        () => "https://example.com",
-        true,
-      ],
-      [
-        "https://example.com",
-        false,
-      ],
-    ])("should return %p for %p", (value, expected) => {
+    it.each([
+      [() => "https://example.com", true],
+      ["https://example.com", false],
+      [null, false],
+      [undefined, false],
+      [{}, false],
+      [{ toString: () => "https://example.com" }, false],
+      [{ [Symbol.toPrimitive]: () => "https://example.com" }, false],
+    ])("should return %s when input is %p", (value, expected) => {
       expect(isUrlBuilder(value)).toBe(expected);
     });
```

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (3)</summary><blockquote>

`209-210`: **Consider a more descriptive name for the “splitter parser”.**  
The TODO note indicates the name might evolve. For clarity and discoverability, you could rename it to something like `"delimiterParser"`.

---

`298-325`: **Type-safety for aggregator + output scenario.**  
The first overload ensures that the handler is explicitly configured with both an `aggregate` and an `output`. Consider avoiding `any` for the return type in line 316, if feasible.

---

`327-346`: **Type-safety for single transform + output scenario.**  
The second overload covers the case where only an `output` is specified. Similarly, try to avoid `any` in line 337 to tighten type guarantees.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f4f351c0055b969488860d82f20c94890b35c414 and 958cd5e7646729e1857ef53a4bbafca1764fcc2a.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `packages/adapters/src/handlers/modern.ts` (1 hunks)
* `packages/adapters/src/types.ts` (3 hunks)
* `packages/adapters/src/utils.ts` (1 hunks)
* `packages/adapters/src/v2_adapters.ts` (1 hunks)
* `packages/adapters/test/utils.test.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Check: build</summary>

<details>
<summary>packages/adapters/src/v2_adapters.ts</summary>

[failure] 1-1: 
'v2_AdapterHandlerType' is defined but never used

</details>
<details>
<summary>packages/adapters/src/handlers/modern.ts</summary>

[failure] 1-1: 
'v2_AdapterHandler' is defined but never used

</details>

</details>
<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handlers/modern.ts</summary>

[error] 1-1: 'v2_AdapterHandler' is defined but never used  unused-imports/no-unused-imports

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (17)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern.ts (1)</summary>

`9-18`: **Approved URL building logic.**

The URL construction logic correctly handles disallowed emoji versions and builds appropriate URLs and cache keys for allowed versions.

</details>
<details>
<summary>packages/adapters/src/types.ts (16)</summary>

`9-9`: **Good use of a generic type alias for promise compatibility.**  
Defining `Promisable<T>` is a succinct and clear way to handle both synchronous and asynchronous return types.

---

`125-128`: **Verify that returning a single string for multiple request arrays meets your needs.**  
When `T` is a `CacheableUrlRequest[]`, this type mapping resolves to `string`. Ensure you actually intend to handle multiple URLs’ data as a single string instead of separate strings, as this might cause unexpected downstream behavior if multiple resources return different data.

---

`147-163`: **Appreciate manageable property grouping in `AdapterContext`.**  
Adding `emoji_version`, `unicode_version`, and `force` clarifies the adapter’s context usage. Make sure to document how `force` interacts with caching or other logic so that future contributors see its effect immediately.

---

`165-165`: **Useful abstraction for URL generation.**  
`UrlBuilder` elegantly captures both synchronous and asynchronous URL resolution scenarios. This keeps the rest of the adapter logic simpler.

---

`167-167`: **Expanded adapter handler types.**  
Adding `"metadata"` to the union broadens the possible uses and keeps the type system aligned with new adapter features.

---

`169-172`: **Helpful for conditional adapter execution.**  
`v2_ShouldExecute` allows runtime decisions on whether a handler should run, facilitating flexible pipelines. Great pattern.

---

`174-174`: **Compact approach for single-or-array logic.**  
`Arrayable<T>` is a common pattern for flexible parameter types; it improves developer experience when working with multiple or single items interchangeably.

---

`176-186`: **Straightforward cache metadata structure.**  
`v2_UrlWithCache` is well-defined, and having a mandatory `cacheKey` helps ensure consistent caching.

---

`188-193`: **Clean function signature.**  
`v2_TransformFn` concisely documents the transformation step for adapter pipelines, pairing well with built-in or custom parser outputs.

---

`195-200`: **Flexible aggregator design.**  
`v2_AggregateFn` effectively captures the pattern of combining multiple transformed chunks into a single output, which decouples the transform logic from the final consolidation.

---

`202-207`: **Lucid output definition.**  
`v2_OutputFn` clarifies the final-handling responsibilities, leaving intermediate steps to transform or aggregate functions. This separation of concerns is beneficial.

---

`213-217`: **Flexible custom parsing.**  
`v2_ParserFn` provides a place to parse downloaded data into typed structures, complementing your aggregator and output functions.

---

`219-221`: **Simple built-in parser outputs.**  
`v2_GetParseOutputFromBuiltInParser` is minimal, but meets the “splitter” use case precisely. If you expand built-in parsers, this design is straightforward to extend.

---

`223-225`: **Parser options design is consistent.**  
`v2_GetParseOptionsFromParser` is typed to each parser type, so its partial shaping of config for a “splitter” parser is easy to follow.

---

`227-296`: **Robust interface for the `v2_AdapterHandler`.**  
This interface effectively enumerates the entire adapter pipeline (urls → parse → transform → aggregate → output). The typed approach helps maintain consistent usage across multiple adapters. The optional `dependencies` array is also a welcome addition for orchestrating multi-step workflows.

---

`347-376`: **Fallback overload for the general `v2_AdapterHandler`.**  
The final overload has broad coverage for all possible configurations. Excellent for flexible usage. Just be mindful of wide type parameters to ensure code remains readable.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T03:02:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern.ts (1)</summary><blockquote>

`23-37`: _🛠️ Refactor suggestion_

**Remove console.warn statements and implement meaningful data transformation.**

The current implementation uses console.warn for debugging and returns placeholder data. For production code, implement actual data transformation logic and remove debugging statements.

```diff
  transform(_ctx, _data) {
    return {
-      foo: "bar",
+      // Implement actual metadata transformation
+      version: _ctx.emoji_version,
+      metadata: _data.map(item => ({
+        // Transform actual metadata fields
+        code: item.fields[0]?.trim(),
+        name: item.fields[1]?.trim(),
+        group: item.property || "unknown"
+      }))
    };
  },
  aggregate(_ctx, data) {
-    console.warn("data.foo", data[0].foo);
    return {
-      aggregatedFoo: data.map((d) => d.foo),
+      // Aggregate transformed metadata meaningfully
+      groups: [...new Set(data.flatMap(d => d.metadata.map(m => m.group)))],
+      totalEntries: data.reduce((sum, d) => sum + d.metadata.length, 0)
    };
  },
  output(_ctx, transformed) {
-    console.warn("transformed.aggregatedFoo", transformed.aggregatedFoo);
    return transformed;
  },
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>packages/adapters/src/v2_adapters.ts (1)</summary><blockquote>

`8-10`: **Consider using more specific types instead of `any`.**

Using `any` for all generic parameters reduces type safety. If possible, provide more specific types that accurately represent the data structures being processed.

```diff
-export const v2_metadata_handlers = [
-  modern_metadata_handler,
-] satisfies v2_AdapterHandler<"metadata", any, any, any, any>[];
+export const v2_metadata_handlers = [
+  modern_metadata_handler,
+] satisfies v2_AdapterHandler<"metadata", EmojiMetadata, Record<string, string>, Record<string, string[]>, Record<string, string[]>>[];
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handlers/modern.ts (1)</summary><blockquote>

`19-22`: **Consider implementing validation for parser configuration.**

There's no validation to ensure the specified parser exists. Implement the previously mentioned TODO to validate that the parser is a built-in one or provide a custom implementation.

```diff
  parser: "splitter",
+  // Validate that the parser exists before using it
  parserOptions: {
    separator: ";",
  },
```

</blockquote></details>
<details>
<summary>packages/internal-utils/src/parse.ts (3)</summary><blockquote>

`85-93`: **Consider adding error handling for malformed totals.**

The current implementation silently ignores non-numeric totals. Consider adding error handling or logging for malformed total values to help with debugging.

```diff
  if (line.includes("Total") && line.includes(":")) {
    const parts = line.split(":");
    if (parts.length > 1 && parts[1] != null) {
      const total = Number.parseInt(parts[1].trim(), 10);
      if (!Number.isNaN(total)) {
        totals[currentProperty] = total;
+     } else {
+       console.warn(`Malformed total value in line: ${line}`);
      }
    }
  }
```

---

`107-111`: **Consider handling quoted fields with separators.**

The current implementation splits fields based solely on the separator character, which doesn't handle cases where the separator might appear within quoted fields. Consider enhancing the parsing logic to handle quoted text.

```diff
  // split into fields based on the separator
- const fields = line
-   .split(separator)
-   .map((field) => field.trim());
+ // Handle potential quoted fields that might contain separators
+ const fields = [];
+ let currentField = '';
+ let inQuotes = false;
+ 
+ for (let i = 0; i < line.length; i++) {
+   const char = line[i];
+   if (char === '"' && (i === 0 || line[i-1] !== '\\')) {
+     inQuotes = !inQuotes;
+   } else if (char === separator && !inQuotes) {
+     fields.push(currentField.trim());
+     currentField = '';
+   } else {
+     currentField += char;
+   }
+ }
+ 
+ // Don't forget the last field
+ fields.push(currentField.trim());
```

---

`1-5`: **Consider making property required in ParsedLine interface.**

The `property` field is marked as optional in the `ParsedLine` interface, but it's always assigned a value in the implementation (defaulting to an empty string if not specified). Consider making it required to better reflect the actual usage.

```diff
export interface ParsedLine {
  comment: string;
  fields: string[];
-  property?: string;
+  property: string;
}
```

</blockquote></details>
<details>
<summary>packages/adapters/src/define.ts (2)</summary><blockquote>

`10-29`: **Avoid using `any` for output type.**
In line 20, the definition for `output` uses an `any` for its final type parameter. Consider introducing a more precise type parameter to maintain strong type safety:

```diff
-    output: v2_OutputFn<TContext, TExtraContext, TTransformOutput, any>;
+    output: v2_OutputFn<TContext, TExtraContext, TTransformOutput, TSomeOutput>;
```

---

`30-59`: **Consolidate function overload if feasible.**
The two overload signatures present a sophisticated type structure but perform an identical runtime operation. If possible, consider collapsing them into a single, more flexible signature to simplify maintenance and reduce cognitive load for future contributors.

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary><blockquote>

`90-159`: **v2_AdapterHandler interface is large but well documented.**
While the interface enables high flexibility, consider splitting off distinct concerns (e.g., parser-related fields) if future growth makes it unwieldy. Currently, the documentation is sufficiently detailed.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 958cd5e7646729e1857ef53a4bbafca1764fcc2a and 40b58cd147e6c4823a10685d9a5e6c1592ee7a78.

</details>

<details>
<summary>📒 Files selected for processing (11)</summary>

* `packages/adapters/src/adapter.ts` (0 hunks)
* `packages/adapters/src/adapters/base/adapter.ts` (0 hunks)
* `packages/adapters/src/adapters/modern/adapter.ts` (0 hunks)
* `packages/adapters/src/adapters/pre-alignment/adapter.ts` (0 hunks)
* `packages/adapters/src/define.ts` (1 hunks)
* `packages/adapters/src/handlers/modern.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/adapters/src/v2_adapters.ts` (1 hunks)
* `packages/internal-utils/src/parse.ts` (1 hunks)
* `packages/internal-utils/test/parse.test.ts` (1 hunks)
* `packages/internal-utils/test/versions.test.ts` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (4)</summary>

* packages/adapters/src/adapters/pre-alignment/adapter.ts
* packages/adapters/src/adapters/modern/adapter.ts
* packages/adapters/src/adapters/base/adapter.ts
* packages/adapters/src/adapter.ts

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* packages/internal-utils/test/versions.test.ts

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>packages/adapters/src/v2_adapters.ts (1)</summary>

`5-5`: **Remove unused import.**

The `v2_AdapterHandlerType` is imported but never used in this file, which can cause linting errors.

```diff
-import type { v2_AdapterHandler, v2_AdapterHandlerType } from "./types";
+import type { v2_AdapterHandler } from "./types";
```

</details>
<details>
<summary>packages/internal-utils/test/parse.test.ts (1)</summary>

`1-126`: **Well-structured and comprehensive test suite.**

The test suite effectively covers the key functionality of the parse function with clear test cases and assertions. Good job including tests for edge cases like empty content, empty lines, and lines with only comments.

</details>
<details>
<summary>packages/adapters/src/handlers/modern.ts (2)</summary>

`3-5`: **Good documentation for disallowed emoji versions.**

The comment explaining why certain emoji versions are disallowed is helpful for maintenance and future development.

---

`9-18`: **Good implementation of URL generation with version checks.**

The implementation correctly handles disallowed emoji versions by returning undefined, and constructs appropriate URLs and cache keys for allowed versions.

</details>
<details>
<summary>packages/internal-utils/src/parse.ts (1)</summary>

`21-40`: **Well-documented function with clear JSDoc.**

The JSDoc documentation is thorough, including parameter descriptions, return value explanation, and a usage example. This will help other developers understand how to use this function.

</details>
<details>
<summary>packages/adapters/src/define.ts (1)</summary>

`1-8`: **Imports look clear and consistent.**
No issues noted; the listed imports from `"./types"` align well with their subsequent usage below.

</details>
<details>
<summary>packages/adapters/src/types.ts (3)</summary>

`9-10`: **Generic utility types look good.**
Both `Promisable<T>` and `Arrayable<T>` are idiomatic ways to handle async and array variants in TypeScript.

---

`12-28`: **Interface expansion is appropriate.**
The newly introduced `emoji_version` and `unicode_version` fields help clarify version dependencies for adapters. The inline documentation is also clear.

---

`30-88`: **Auxiliary type definitions are comprehensive.**
All added or modified types (`v2_AdapterHandlerType`, `UrlBuilder`, `v2_ShouldExecute`, `v2_UrlWithCache`, `v2_TransformFn`, `v2_AggregateFn`, etc.) are well-structured and suitable for flexible adapter design.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T03:45:42Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>packages/adapters/src/adapter.ts (1)</summary><blockquote>

`8-13`: **Consider replacing `any` with stricter generics**  
The usage of `any` within `AdapterHandler<"metadata", any, ...>` may hamper type safety. If you can define more specific types for the extra context and transformations, it would improve maintainability.

</blockquote></details>
<details>
<summary>packages/adapters/src/define.ts (1)</summary><blockquote>

`10-59`: **Multiple overloads can add complexity**  
While these overloaded signatures are powerful, consider whether they can be unified or simplified to reduce cognitive complexity.

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (3)</summary><blockquote>

`26-33`: **Consider using an enum for `AdapterHandlerType`**  
String literals can be more error-prone. An enum might provide stronger expressiveness and reduce typos. The `UrlBuilder` and `ShouldExecute` definitions appear otherwise solid.

---

`68-70`: **Follow up on TODO for 'splitter'**  
Consider renaming the 'splitter' parser to something more descriptive. Let me know if you'd like suggestions or a PR to address this.

---

`86-155`: **Large 'AdapterHandler' interface could be modularized**  
While thorough, consider splitting it into smaller interfaces or using TypeScript composition to simplify maintainability. The current design is flexible but quite large.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 40b58cd147e6c4823a10685d9a5e6c1592ee7a78 and ee8505ed32744aa117fc9783a2e39607d591154e.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `packages/adapters/src/adapter.ts` (1 hunks)
* `packages/adapters/src/define.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>packages/adapters/src/adapter.ts (1)</summary>

`2-6`: **No issues with new import statements**  
All imports appear valid and correctly reference the necessary symbols.

</details>
<details>
<summary>packages/adapters/src/define.ts (1)</summary>

`1-8`: **Imports look correct**  
No issues found with the import statements.

</details>
<details>
<summary>packages/adapters/src/types.ts (8)</summary>

`5-6`: **Promisable and Arrayable are straightforward**  
These utility types elegantly handle asynchronous returns and single-or-multiple items.

---

`8-24`: **AdapterContext interface is well-documented**  
The doc comments for `emoji_version`, `unicode_version`, and `force` are helpful for contributors. Keep it consistent across all fields.

---

`35-45`: **UrlWithCache shape is clear**  
This interface is self-explanatory and easy to consume.

---

`47-52`: **TransformFn signature looks correct**  
Combining context with parsed input is logical, and the typed return clarifies the output.

---

`54-60`: **AggregateFn approach is robust**  
Requiring at least one item in the data array ensures valid aggregation. This design helps avoid edge-case handling of empty arrays.

---

`61-66`: **OutputFn design is consistent**  
Leveraging the flow from transform/aggregate to output function is well-structured.

---

`72-80`: **ParserFn and GetParseOutputFromBuiltInParser**  
The types and generics for both custom and built-in parsers appear consistent. No immediate concerns.

---

`82-84`: **Parser options for built-in parser**  
Specifying the separator for the 'splitter' parser is straightforward. No issues here.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T03:48:41Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>packages/adapters/test/utils.test.ts (1)</summary><blockquote>

`1-17`: **Well-structured tests for isUrlBuilder function.**

The test cases effectively validate that functions returning URL strings are recognized as URL builders, while plain strings are not. This is a good practice for type guards.


Consider adding test cases for edge cases like `undefined`, `null`, or non-function objects to ensure robustness.

</blockquote></details>
<details>
<summary>packages/adapters/src/_handlers.ts (1)</summary><blockquote>

`8-13`: **Good use of TypeScript's `satisfies` operator for type safety.**

The metadata_handlers array is properly typed using the `satisfies` operator, which provides type checking without widening the type. However, there are several `any` type parameters that could potentially be made more specific for better type safety.


Consider replacing the `any` type parameters with more specific types if possible:

```diff
-] satisfies AdapterHandler<"metadata", any, any, any, any, {
+] satisfies AdapterHandler<"metadata", SpecificType1, SpecificType2, SpecificType3, SpecificType4, {
```

</blockquote></details>
<details>
<summary>packages/adapters/src/utils.ts (2)</summary><blockquote>

`16-18`: **Consider using a set for membership checks.**

Using a set rather than an array for `BUILTIN_PARSERS` may improve membership checking performance when the list grows.

---

`28-30`: **Light optimization suggestion.**

If `BUILTIN_PARSERS` ever expands, conversion to a set could speed up the `.includes` logic, particularly for larger arrays.

</blockquote></details>
<details>
<summary>packages/cli/src/cli.ts (1)</summary><blockquote>

`161-203`: **Remove or document large blocks of commented code.**

Leaving large commented sections can lead to confusion and maintenance overhead. If these generators are intended to be reintroduced later, consider adding a clear `TODO` note or removing the code entirely until needed.

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (2)</summary><blockquote>

`85-94`: **Consider simplifying the complex generic interface**

The `AdapterHandler` interface uses 8 generic type parameters, which makes it quite complex to understand and use. Consider breaking this down into smaller, more focused interfaces or providing type aliases for common use cases to improve developer experience.

---

`153-153`: **Consider simplifying the complex conditional type**

The output function's type uses a complex conditional type:
`TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput`

This could potentially be confusing. Consider simplifying this by using a separate type parameter or a more descriptive type alias.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ee8505ed32744aa117fc9783a2e39607d591154e and 967f0abec08cc103579906f90fb4630857ec33d3.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern.ts` (1 hunks)
* `packages/adapters/src/index.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/adapters/src/utils.ts` (1 hunks)
* `packages/adapters/test/utils.test.ts` (1 hunks)
* `packages/cli/src/cli.ts` (3 hunks)
* `packages/internal-utils/src/cache.ts` (1 hunks)
* `packages/internal-utils/src/index.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* packages/internal-utils/src/cache.ts

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/adapters/src/handlers/modern.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handler.ts</summary>

[error] 61-61: error TS2345: Argument of type 'TContext' is not assignable to parameter of type 'AdapterContext & Record<string, unknown>'.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (16)</summary><blockquote>

<details>
<summary>packages/internal-utils/src/index.ts (2)</summary>

`6-6`: **New export for parsing utilities looks good.**

This addition aligns well with the export pattern used for other modules. The parse module appears to provide structured data extraction utilities that will support the adapter operations.

---

`1-2`: **Consider addressing the TODO comment.**

There's an existing TODO about fixing exports to remove this barrel file approach. Since you're refactoring adapters, this might be a good opportunity to address this technical debt if it's related to your changes.

</details>
<details>
<summary>packages/adapters/src/index.ts (1)</summary>

`1-1`: <details>
<summary>❓ Verification inconclusive</summary>

**Core architectural change from adapter to handler-based approach.**

This modification shifts the exports from the adapter module to the new handler module, which appears to be a key part of the adapter improvements mentioned in the PR title. This change aligns with the introduction of the new metadata handler system.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any remaining references to the old adapter module in the codebase
echo "Checking for any remaining references to the old adapter module:"
rg -l "from \"@mojis/adapters/adapter\"" --type ts
```

Length of output: 189

---



</details>

**Architectural Update: Transition Exports from Adapter to Handler**

The change in `packages/adapters/src/index.ts` now exports from `"./handler"` rather than `"./adapter"`, which aligns with the new metadata handler approach introduced in this refactoring. The initial search for references to the old adapter module returned no output; however, due to the low-quality inference from that script output, please manually verify that no remaining dependencies reference `"./adapter"`.

- Confirm that all dependent code has been updated to use `"./handler"`.
- Verify that the new handler (likely implemented in `packages/adapters/src/handlers/modern.ts`) fully satisfies its intended role.

</details>
<details>
<summary>packages/adapters/test/utils.test.ts (1)</summary>

`19-35`: **Comprehensive test coverage for isBuiltinParser.**

Using `it.each` with the BUILTIN_PARSERS array is an efficient way to test all valid parsers. The test also correctly handles negative cases for invalid parsers.

</details>
<details>
<summary>packages/adapters/src/_handlers.ts (1)</summary>

`15-17`: **Well-organized structure for handler management.**

The `all_handlers` object provides a clean way to organize handlers by type, making it easy to add more handler types in the future. This structure will support good extensibility as the adapter system evolves.

</details>
<details>
<summary>packages/adapters/src/utils.ts (2)</summary>

`12-14`: **Function looks good.**

The `isUrlBuilder` predicate and return type annotation indicate correct usage and a clean approach for identifying callable objects.

---

`32-75`: **Verify usage across your codebase.**

The logic in `getHandlerUrls` appears thorough, handling a wide range of input possibilities. Ensure consistent usage of this function throughout adapters and verify that all call sites supply the expected input (e.g., a valid `UrlBuilder` vs. an array of URLs).

</details>
<details>
<summary>packages/cli/src/cli.ts (2)</summary>

`3-3`: **New import looks correct.**

Importing `runAdapterHandler` from `@mojis/adapters` is properly referenced in this file.

---

`139-159`: **Generator usage for metadata is simplified well.**

Passing `"metadata"` directly to `runAdapterHandler` aligns with the new, streamlined adapter approach. This reduces overhead in your CLI logic.

</details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary>

`57-63`: **Unused return value from transformation.**

`transformedData` is never stored or returned from `runAdapterHandler`. If this is intentional, consider documenting why the transformed data is not surfaced. Otherwise, returning or accumulating the result may help with further processing.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 61-61: error TS2345: Argument of type 'TContext' is not assignable to parameter of type 'AdapterContext & Record<string, unknown>'.

</details>

</details>

</details>
<details>
<summary>packages/adapters/src/types.ts (6)</summary>

`77-84`: **Good type safety for parsers**

The conditional types `GetParseOutputFromBuiltInParser` and `GetParseOptionsFromParser` provide excellent type safety by ensuring the correct output type and options for each parser. This is a good practice for maintaining type consistency across the codebase.

---

`6-7`: **Well-designed utility types**

The `Promisable<T>` and `Arrayable<T>` utility types are well-defined and will help simplify type declarations throughout the codebase.

---

`47-67`: **Well-structured function type definitions**

The transformation pipeline types (`TransformFn`, `AggregateFn`, and `OutputFn`) are well-designed with appropriate generic parameters. This approach provides good type safety while maintaining flexibility for implementers.

---

`141-153`: **Comprehensive documentation for transformation pipeline**

The documentation comments for the transform, aggregate, and output functions clearly explain the role and responsibility of each function in the adapter pipeline. This is excellent practice for maintaining code clarity.

---

`4-4`: <details>
<summary>✅ Verification successful</summary>

**Verify BUILTIN_PARSERS usage**

Ensure that the imported `BUILTIN_PARSERS` is properly exported from "./utils" and contains the expected values (at least "splitter").

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the definition of BUILTIN_PARSERS in the utils.ts file
cat ./packages/adapters/src/utils.ts | grep -A 10 "BUILTIN_PARSERS"
```

Length of output: 862

---



</details>

**BUILTIN_PARSERS Export Validated**

- The verification confirms that `BUILTIN_PARSERS` is correctly exported from `./packages/adapters/src/utils.ts`.
- The array includes the expected value `"splitter"`.

No further changes are required.

---

`1-154`: <details>
<summary>❌ Incorrect review comment</summary>

**Missing defineAdapterHandler function**

According to the summary, a `defineAdapterHandler` function should be defined in this file, but it's missing from the provided code. This function is mentioned as being crucial for creating adapter handlers with the correct type structures.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for any references to defineAdapterHandler in the codebase
grep -r "defineAdapterHandler" --include="*.ts" .
```

Length of output: 391

---



</details>

**Clarification on `defineAdapterHandler` location**  
The review comment suggests that the `defineAdapterHandler` function should be defined in this file (`types.ts`). However, our investigation shows that this function is correctly defined and exported in `packages/adapters/src/define.ts` (and subsequently imported where needed in, for example, `packages/adapters/src/handlers/modern.ts`). This aligns with the intended separation of type definitions from implementation. Please update the documentation/summary accordingly.



> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T05:01:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 5**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`6-9`: _⚠️ Potential issue_

**Update type definition to match required constraint.**

The type definition doesn't match the constraint required by function usage, which causes the TS2345 error reported in the pipeline.



```diff
export async function runAdapterHandler<
  THandlerType extends AdapterHandlerType,
- TContext extends AdapterContext,
+ TContext extends AdapterContext & Record<string, unknown>,
  TExtraContext extends Record<string, unknown>,
>(
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>packages/adapters/src/define.ts (2)</summary><blockquote>

`18-27`: **Consider strongly typing the output parameter.**  
Using `any` for the output function's return type can weaken type safety. A more specific generic parameter (e.g., `TOutput`) may help preserve strict type integrity.  

Proposed change:
```diff
- output: OutputFn<TContext, TExtraContext, TAggregateOutput, any>;
+ output: OutputFn<TContext, TExtraContext, TAggregateOutput, TOutput>;
```

---

`36-55`: **Avoid using `any` for output.**  
As with the first overload, relying on `any` for the output function's return type risks losing compile-time benefits. Consider introducing an additional generic parameter to maintain type safety.  

Example tweak:
```diff
- output: OutputFn<TContext, TExtraContext, TTransformOutput, any>;
+ output: OutputFn<TContext, TExtraContext, TTransformOutput, TOutput>;
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handler.ts (3)</summary><blockquote>

`70-70`: **Remove debug console.log statement.**

Debug logging should be removed or replaced with a proper logging system for production code.


```diff
-      console.log("transformedData", transformedData);
```

---

`89-94`: **Avoid mutating the original context object.**

The current implementation of `buildContext` mutates the original context, which could lead to unexpected behavior if the context is used elsewhere.


```diff
export function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
  ctx: TContext,
  extraContext: TExtraContext,
): TContext {
-  return Object.assign(ctx, extraContext);
+  return Object.assign({}, ctx, extraContext) as TContext;
}
```

---

`13-13`: **Consider using a more specific return type.**

Using `any` as a return type loses type information and can lead to type-related bugs. Consider defining a more specific return type or using a generic type parameter.


```diff
- ): Promise<any> {
+ ): Promise<unknown> {
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 967f0abec08cc103579906f90fb4630857ec33d3 and 49861467ca8218187be425ab6280c6fdc81493ce.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `packages/adapters/src/define.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* packages/adapters/src/handlers/modern.ts
* packages/adapters/src/types.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handler.ts</summary>

[error] 76-76: Cannot invoke an object which is possibly 'undefined'.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>packages/adapters/src/define.ts (4)</summary>

`1-9`: **Imports look good.**  
No issues with these import statements; they are properly referencing the required types and appear consistent with the rest of the code.

---

`11-17`: **Generics are well-defined.**  
The type parameters for this overload of `defineAdapterHandler` appear logically structured, clearly distinguishing between transformation and aggregation outputs.

---

`28-35`: **Return types are consistent.**  
The specialized return type aligns well with the aggregator logic, providing a coherent, strongly-typed interface.

---

`84-85`: **Identity function approach is acceptable.**  
Returning the passed-in `handler` directly is a valid choice if no runtime checks are needed. This pass-through style fits well with a purely type-driven approach.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T04:13:48Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/cli/src/cli.ts (1)</summary><blockquote>

`163-205`: **Consider removing or documenting commented code.**

Significant portions of code related to "sequences" and "variations" generators have been commented out rather than removed. If these features are permanently disabled or replaced by the new adapter system, consider removing the commented code entirely. If they're temporarily disabled with plans to restore them later, add a brief comment explaining why they're disabled and any plans for future implementation.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 49861467ca8218187be425ab6280c6fdc81493ce and b5535c9f4112668a811962c8deeb98b402c8578c.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/cli/src/cli.ts` (3 hunks)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>packages/cli/src/cli.ts (2)</summary>

`5-5`: **Import changes reflect the new adapter system design.**

The import statement now only includes `runAdapterHandler` without the previously used `resolveAdapter` function, which aligns with the refactored adapter system mentioned in the PR description.

---

`142-142`: **Updated function signature for runAdapterHandler is cleaner.**

The function call has been simplified to no longer require the adapter parameter, indicating that the handler now internally determines the appropriate adapter based on the provided context. This change streamlines the code and reduces coupling between the CLI and adapter implementation details.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T05:03:06Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (2)</summary><blockquote>

`14-14`: **Specify a more precise return type.**

Using `any` as a return type decreases type safety. Consider using a generic type parameter or a more specific type based on the expected output of the handlers.


```diff
  ctx: TContext & TExtraContext,
-): Promise<any> {
+): Promise<unknown> {
```

---

`90-95`: **Avoid mutating the original context object.**

The current implementation of `buildContext` uses `Object.assign()` which mutates the first argument. This could lead to unexpected side effects if the caller expects the original context to remain unchanged.


```diff
export function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
  ctx: TContext,
  extraContext: TExtraContext,
): TContext {
-  return Object.assign(ctx, extraContext);
+  return Object.assign({}, ctx, extraContext);
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b5535c9f4112668a811962c8deeb98b402c8578c and 234304b60b72036991024b5dae09593aa323a245.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/adapters/src/handler.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (5)</summary>

`9-13`: **Address the type mismatch in context parameter.**

The function signature can cause type errors as TContext is not automatically assignable to the required type in handler methods. Update the generic constraint to ensure type safety.


```diff
export async function runAdapterHandler<
  THandlerType extends AdapterHandlerType,
-  TContext extends AdapterContext,
+  TContext extends AdapterContext & Record<string, unknown>,
  TExtraContext extends Record<string, unknown>,
>(
  type: THandlerType,
  ctx: TContext & TExtraContext,
```

---

`67-69`: **Add null check for handler.transform method.**

For consistency and to prevent potential runtime errors, add a null check for the transform method as well.


```diff
      // run transform for each data in list
+     if (typeof handler.transform !== 'function') {
+       throw new Error(`Handler for type "${type}" is missing the transform method`);
+     }
      const transformedData = await handler.transform(buildContext(ctx, {
        key,
      }), data);
```

---

`77-79`: **Add null check for handler.aggregate method.**

The pipeline failure indicates that `handler.aggregate` might be undefined. Add a null check to prevent runtime errors.


```diff
    // run aggregate
+   if (typeof handler.aggregate !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the aggregate method`);
+   }
    const aggregatedData = await handler.aggregate(buildContext(ctx, {
      data: transformedDataList,
    }), transformedDataList);
```

---

`82-84`: **Add null check for handler.output method.**

Similar to the aggregate method, you should check if the output method exists before calling it to prevent runtime errors.


```diff
    // run output
+   if (typeof handler.output !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the output method`);
+   }
    const output = await handler.output(buildContext(ctx, {
      data: aggregatedData,
    }), aggregatedData);
```

---

`86-88`: **Handle the case where no handlers execute.**

Currently, if none of the handlers in the group execute (all `shouldExecute` calls return false), the function will implicitly return `undefined`. This might not be expected if the caller is expecting a specific return type.


```diff
    return output;
  }
+ 
+ throw new Error(`No handler executed for type: ${type}`);
}
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T05:06:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (10)</summary><blockquote>

<details>
<summary>packages/adapters/test/utils.test.ts (1)</summary><blockquote>

`4-17`: **Refine test description placeholders**  
Currently, the test description uses a placeholder format `"%p for %p"`, but the parameters are `(value, expected)`. Consider making the message clearer by adjusting the placeholder order or wording:

```diff
-  ])("should return %p for %p", (value, expected) => {
+  ])("should return %s for %s", (value, expected) => {
```

</blockquote></details>
<details>
<summary>packages/adapters/src/_handlers.ts (1)</summary><blockquote>

`1-23`: **Consider adding tests for `all_handlers` usage**  
While the code looks solid, creating unit tests for `all_handlers` (or integration tests ensuring metadata and sequence handlers are both invoked as expected) would help avoid regressions.

</blockquote></details>
<details>
<summary>packages/adapters/src/utils.ts (2)</summary><blockquote>

`16-18`: **Add clarity on `BUILTIN_PARSERS` usage**  
`BUILTIN_PARSERS` currently only includes `"generic"`. If you plan to introduce additional parsers in the future, consider documenting the purpose of this array or enumerating likely parser names.

---

`32-83`: <details>
<summary>❓ Verification inconclusive</summary>

**Improve error handling in `getHandlerUrls`**  
When calling `await urls(ctx)`, this function doesn’t handle possible promise rejections. Consider wrapping it in a `try-catch` or rethrowing errors to ensure failures are handled gracefully.

  


Below is a script to locate usages of `getHandlerUrls` and confirm whether errors are handled by its callers:

---

🏁 Script executed:

```shell
#!/bin/bash
fd -e ts -e js | xargs rg "getHandlerUrls"
```

Length of output: 341

---



</details>

**Action: Enhance Error Handling in `getHandlerUrls`**

There’s a potential issue when calling `await urls(ctx)` inside the `isUrlBuilder(urls)` branch. If the Promise rejects, it will result in an unhandled exception since there’s no `try-catch` around it. The usage in `packages/adapters/src/handler.ts` (via `const urls = await getHandlerUrls(handler.urls, ctx);`) does not provide additional error wrapping either.

To improve this, consider wrapping the asynchronous call in a `try-catch` block. For example:

```diff
if (isUrlBuilder(urls)) {
-  const urlsResults = await urls(ctx);
-  if (urlsResults == null) {
-    return result;
-  }
+  let urlsResults;
+  try {
+    urlsResults = await urls(ctx);
+  } catch (error) {
+    // Optionally log the error or rethrow it as needed
+    throw error;
+  }
+
+  if (urlsResults == null) {
+    return result;
+  }
```

Please verify if error handling should be centralized (letting exceptions propagate) or managed locally within this function based on the broader error management strategy.

</blockquote></details>
<details>
<summary>packages/adapters/src/handlers/modern.ts (3)</summary><blockquote>

`3-5`: **Minor grammar correction**  
"These emoji versions doesn't seem..." should read "These emoji versions don't seem...".

---

`7-49`: **Reduce console statements and review aggregator logic**  
Excessive console output can clutter production logs. Consider using a more robust logging approach or removing them if they’re only for debugging.  

Additionally, verify that returning `data[0]` during aggregation aligns with your intent. If you plan to combine or merge more than one result, you may lose data by returning only the first element.

---

`51-83`: **Consolidate or refine aggregator logic**  
This handler flattens all data arrays in `aggregate`. Ensure that merging separate arrays is the desired behavior and won’t produce unexpected results if the structures differ.  

Also, consider limiting console statements or using a configurable logger for fewer side effects in production.

</blockquote></details>
<details>
<summary>packages/cli/src/cli.ts (1)</summary><blockquote>

`183-200`: **Consider removing the commented-out “variations” code.**

Stale and commented-out code can lead to confusion for future maintainers. If the “variations” generator is not intended to be reintroduced soon, removing it or adding a clear TODO rationale would simplify the file.

</blockquote></details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`90-95`: **Avoid mutating the original context.**

Currently, `Object.assign(ctx, extraContext)` mutates `ctx`. For safer composition, consider returning a new object:
```diff
-function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
-  ctx: TContext,
-  extraContext: TExtraContext,
-): TContext {
-  return Object.assign(ctx, extraContext);
-}
+function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
+  ctx: TContext,
+  extraContext: TExtraContext,
+): TContext & TExtraContext {
+  return { ...ctx, ...extraContext };
+}
```

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary><blockquote>

`28-28`: **Confirm inclusion of other handler types if needed.**

Currently limited to `"metadata" | "sequence"`. If variations or other types are reintroduced, remember to extend `AdapterHandlerType`.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 234304b60b72036991024b5dae09593aa323a245 and cab4df9a4a431ff73c3e925a4f8ef679690703d1.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/adapters/src/utils.ts` (1 hunks)
* `packages/adapters/test/utils.test.ts` (1 hunks)
* `packages/cli/src/cli.ts` (4 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handler.ts</summary>

[error] 3-3: Build failed: Could not resolve '@mojis/parsers'.

---

[error] 3-3: TypeScript error TS2307: Cannot find module '@mojis/parsers' or its corresponding type declarations.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (19)</summary><blockquote>

<details>
<summary>packages/adapters/test/utils.test.ts (1)</summary>

`19-47`: **Test coverage looks good**  
Your test cases for `isBuiltinParser` are thorough, covering valid and invalid parser inputs. Nice job!

</details>
<details>
<summary>packages/cli/src/cli.ts (3)</summary>

`5-5`: **Confirmed correct import usage.**

Importing `runAdapterHandler` from `@mojis/adapters` aligns well with the new adapter-based design. No issues detected.

---

`142-161`: **Use of `runAdapterHandler` for "metadata" looks good.**

The arguments passed to `"metadata"` match the context (emoji version, unicode version), and the subsequent file writes are consistent with the returned data structure.

---

`164-181`: **Consistent approach for "sequence" generator.**

Calling `runAdapterHandler("sequence", { ... })` follows the same structured pattern as `"metadata"`, ensuring consistency across generators.

</details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary>

`7-88`: **Add null checks for optional methods.**

Per past review comments, methods like `handler.transform`, `handler.aggregate`, and `handler.output` should be verified for null or undefined before invocation to avoid runtime errors. This is still unaddressed:

</details>
<details>
<summary>packages/adapters/src/types.ts (14)</summary>

`4-5`: **Resolve missing parser module.**

References to `@mojis/parsers` can cause build failures if the module or type definitions are missing. Confirm its installation and type declarations.

---

`7-8`: **Generic utility types look good.**

`Promisable` and `Arrayable` neatly handle async and plural forms.

---

`10-26`: **Updates to `AdapterContext` documentation are clear.**

Good clarity on `emoji_version`, `unicode_version`, and `force` fields.

---

`30-30`: **Revisit `Arrayable<undefined>` usage.**

A previous review suggested removing `undefined` from `UrlBuilder`’s return type to reduce ambiguity. Verify if returning `null` or an empty array is more appropriate.

---

`32-34`: **`ShouldExecute` signature is straightforward.**

Accepting a context and returning a boolean promise is consistent with optional async checks.

---

`36-53`: **`UrlWithCache` interface is well-defined.**

Including an optional `key` for improved traceability is a nice touch.

---

`56-62`: **`TransformFn` definition is appropriate.**

Cleanly typed to ensure transformations can handle input and produce typed output.

---

`63-69`: **`AggregateFn` properly aggregates multiple transformed results.**

No immediate issues; just ensure the runtime checks for optional aggregator usage in handlers.

---

`70-75`: **`OutputFn` extends the pipeline elegantly.**

Outputs the final result in a typed manner.

---

`77-77`: **`BuiltinParser` captures allowed parser names.**

No findings here, the approach is robust.

---

`79-82`: **`ParserFn` accommodates custom parsing logic.**

Allows extension for specialized parsing needs.

---

`84-86`: **`GetParseOutputFromBuiltInParser` is useful.**

Handles generic parser’s return type with a type-level condition.

---

`88-90`: **`GetParseOptionsFromParser` covers built-in parser config.**

Straightforward approach to unify parser options with parser type.

---

`92-161`: **`AdapterHandler` interface is robust but optional methods need runtime checks.**

Properties like `aggregate` are optional but invoked in `runAdapterHandler` without null checks. Ensure consistency in usage or handle undefined gracefully.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T06:27:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/pre-alignment/metadata.ts (1)</summary><blockquote>

`15-21`: **Empty output arrays without documentation.**

The `output` method returns empty arrays for `groups` and `emojis`. While this might be intentional for a pre-alignment handler, consider adding a comment explaining why these are empty.

```diff
  output() {
+   // Pre-alignment metadata starts with empty groups and emojis
+   // These will be populated later in the processing pipeline
    return {
      groups: [],
      emojis: [],
    };
  },
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between dc3c99cbaca593a4892b7c0c5c8fd10eda89ef14 and 06ba808cbdf0bf6991a242190839fc1a7d505df3.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/adapters/src/handlers/pre-alignment/metadata.ts` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/pre-alignment/metadata.ts (1)</summary>

`8-14`: **Empty implementations look intentional.**

The `urls` and `transform` methods returning `undefined` appears to be intentional for this pre-alignment metadata handler, which aligns with the refactoring to a more focused handler-based design.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T08:13:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c97e8dc601d3d286ba04c915779c776b445979a2 and d07819568811756d4d4e456d582ce7b1c7db7813.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/adapters/src/handlers/pre-alignment/metadata.ts` (1 hunks)
* `packages/internal-utils/src/predicates.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/adapters/src/handlers/pre-alignment/metadata.ts

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>packages/internal-utils/src/predicates.ts (1)</summary>

`10-19`: **Good implementation of robust version checking.**

The updated implementation is more robust with proper null checking and version coercion. Using the semver library for version comparison is a good practice that avoids potential string comparison issues.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T08:49:44Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/internal-utils/src/predicates.ts (1)</summary><blockquote>

`22-31`: **Add explicit parameter validation in isAfterAlignment.**

The `isAfterAlignment` function delegates all parameter validation to `isBeforeAlignment`. While this works, it may be less obvious to other developers how null/invalid values are handled. Consider adding explicit parameter validation for consistency.


```diff
/**
 * Checks if the given version is after the alignment.
 * It is the inverse of `isBeforeAlignment`.
 *
 * @param {string} version - The version string to check
 * @returns {boolean} `true` if the version is after alignment, `false` otherwise
 */
export function isAfterAlignment(version: string): boolean {
+  if (version == null) {
+    return false;
+  }
+
+  const coerced = semver.coerce(version);
+  if (coerced == null) {
+    return false;
+  }
+
+  return coerced.major >= 11;
-  return !isBeforeAlignment(version);
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6ab4479ebafc91299c8f79fc1ddbc311c3cf62a9 and 854a86a3af4f1e75b473c8b19e21a8a6a8181bba.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/metadata.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/sequence.ts` (1 hunks)
* `packages/internal-utils/src/predicates.ts` (1 hunks)
* `packages/internal-utils/test/predicates.test.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* packages/internal-utils/test/predicates.test.ts
* packages/adapters/src/_handlers.ts

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/internal-utils/src/predicates.ts (1)</summary>

`3-7`: **Update JSDoc to match implementation.**

The JSDoc comment states that the function checks if a version is less than "11.0.0", but the implementation actually checks if the major version is less than 11. Additionally, the JSDoc doesn't mention the null checking or version coercion behavior that has been added.


```diff
/**
- * Checks if a version is less than "11.0.0".
+ * Checks if a version is less than "11.0.0".
 *
 * @param {string} version - The version string to check
- * @returns {boolean} `true` if the version is less than "11.0.0", `false` otherwise
+ * @returns {boolean} `true` if the version is less than "11.0.0", `false` otherwise or if version is null/invalid
 */
```

</details>
<details>
<summary>packages/adapters/src/handler.ts (4)</summary>

`7-14`: **Address the type mismatch.**

The pipeline logs show a `TS2345` error indicating that `TContext` is not assignable to the parameter type `'AdapterContext & Record<string, unknown>'`. Update the generic constraint on `TContext` or cast to the required type.


```diff
export async function runAdapterHandler<
  THandlerType extends AdapterHandlerType,
-  TContext extends AdapterContext,
+  TContext extends AdapterContext & Record<string, unknown>,
  TExtraContext extends Record<string, unknown>,
>(
  type: THandlerType,
  ctx: TContext & TExtraContext,
): Promise<any> {
```

---

`64-74`: **Add null check for handler.transform method and remove debug logging.**

For consistency and to prevent potential runtime errors, add a null check for the transform method. Also, remove the console.log statement from production code.


```diff
// run transform for each data in list
for (const [key, data] of dataResults) {
  // run transform for each data in list
+  if (typeof handler.transform !== 'function') {
+    throw new Error(`Handler for type "${type}" is missing the transform method`);
+  }
  const transformedData = await handler.transform(buildContext(ctx, {
    key,
  }), data);

-  console.log("transformedData", transformedData);

  transformedDataList.push(transformedData);
}
```

---

`76-79`: **Add null check for handler.aggregate method.**

The pipeline failure indicates that `handler.aggregate` might be undefined. Add a null check to prevent runtime errors.


```diff
// run aggregate
+if (typeof handler.aggregate !== 'function') {
+  throw new Error(`Handler for type "${type}" is missing the aggregate method`);
+}
const aggregatedData = await handler.aggregate(buildContext(ctx, {
  data: transformedDataList,
}), transformedDataList);
```

---

`81-84`: **Add null check for handler.output method.**

Similar to the aggregate method, you should check if the output method exists before calling it to prevent runtime errors.


```diff
// run output
+if (typeof handler.output !== 'function') {
+  throw new Error(`Handler for type "${type}" is missing the output method`);
+}
const output = await handler.output(buildContext(ctx, {
  data: aggregatedData,
}), aggregatedData);
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T09:05:40Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`77-79`: _⚠️ Potential issue_

**Add null check for handler.aggregate method.**

The pipeline failure indicates that `handler.aggregate` might be undefined. Add a null check to prevent runtime errors.


```diff
    // run aggregate
+   if (typeof handler.aggregate !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the aggregate method`);
+   }
    const aggregatedData = await handler.aggregate(buildContext(ctx, {
      data: transformedDataList,
    }), transformedDataList);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 77-77: error TS2722: Cannot invoke an object which is possibly 'undefined'.

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/variation.ts (2)</summary><blockquote>

`24-25`: **Remove debug console warning from production code.**

Debug logging statements should not be present in production code as they can clutter the console and potentially expose sensitive information.

```diff
-    console.warn("key", ctx.key);
     return data;
```

---

`28-29`: **Remove debug console logging from production code.**

Debug logging statements should not be present in production code as they can clutter the console and potentially expose sensitive information.

```diff
-    console.log("aggregate", data);
     return data.flat();
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`89-89`: **Clarify the error message for when no handler executes.**

The current error message is the same as the one in line 18, which could be confusing. Update it to indicate that no handler executed, rather than no handler was found.

```diff
-  throw new Error(`No handler found for type: ${type}`);
+  throw new Error(`No handler executed for type: ${type}`);
```

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (5)</summary><blockquote>

`84-86`: **Improve type safety for built-in parser output types**

The `GetParseOutputFromBuiltInParser` type only handles the "generic" parser case and defaults everything else to `never`. This could be problematic if new parsers are added in the future.

Consider adding a more reasonable fallback (like `unknown`) or adding a comment to remind developers to update this type when adding new parsers:

```diff
export type GetParseOutputFromBuiltInParser<TParser extends string> =
  TParser extends "generic" ? ParseResult :
-    never;
+    unknown; // Add other parsers here as they are implemented
```

---

`88-90`: **Improve type safety for parser options**

Similar to the output type mapping, `GetParseOptionsFromParser` only handles the "generic" parser case. This pattern should be consistent with how new parsers are added.

```diff
export type GetParseOptionsFromParser<TParser extends string> =
  TParser extends "generic" ? { separator: string } :
-    never;
+    Record<string, never>; // Empty object as default for parsers without options
```

---

`92-101`: **Consider simplifying generic type parameters**

The `AdapterHandler` interface has 8 generic type parameters, which makes it complex to understand and use. The relationship between `TTransformOutput`, `TAggregateOutput`, and `TOutput` is particularly intricate.

Consider:
1. Providing detailed documentation for each type parameter
2. Using default type parameters where possible to simplify common use cases
3. Grouping related parameters into separate interfaces

---

`160-160`: **Simplify conditional type in output function**

The type signature for the `output` property is complex:
```typescript
output: OutputFn<TContext, TExtraContext, TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput, TOutput>;
```

This conditional type (`TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput`) makes the code harder to understand. Consider introducing a type alias for this condition or restructuring the type parameters to avoid this complexity.

---

`136-136`: **Consider adding validation for the parser property**

The `parser` property can be either a function or a string representing a built-in parser. It would be helpful to add a runtime validation mechanism to ensure that when a string is provided, it corresponds to an actual built-in parser.

A utility function like `isValidBuiltinParser(parser: string): boolean` could be helpful for this purpose.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 854a86a3af4f1e75b473c8b19e21a8a6a8181bba and 340bd58f32b5325e9d3d122b76a5242ae550aef7.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/variation.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/adapters/src/utils.ts` (1 hunks)
* `packages/adapters/test/utils.test.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/adapters/test/utils.test.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handler.ts</summary>

[error] 77-77: error TS2722: Cannot invoke an object which is possibly 'undefined'.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>packages/adapters/src/_handlers.ts (1)</summary>

`1-33`: **The handler structure looks good.**

Good job organizing the handlers into separate collections by type and exposing them through a unified `all_handlers` object. The usage of the `satisfies` operator ensures type safety while maintaining explicit types for consumers.

</details>
<details>
<summary>packages/adapters/src/utils.ts (2)</summary>

`32-35`: **Well-implemented utility for URL cache key generation.**

The function properly creates a standardized URL object with cache key, making it consistent throughout the codebase.

---

`60-104`: **Thorough implementation of URL processing.**

The `getHandlerUrls` function handles multiple input formats comprehensively and is well-documented. The extensive error handling ensures the function is robust against various edge cases.

</details>
<details>
<summary>packages/adapters/src/handler.ts (5)</summary>

`7-14`: **Address the type mismatch in function signature.**

The pipeline logs previously showed a `TS2345` error indicating that `TContext` is not assignable to the parameter type `'AdapterContext & Record<string, unknown>'`.

Update the generic constraint on `TContext` or cast to the required type:

```diff
export async function runAdapterHandler<
  THandlerType extends AdapterHandlerType,
-  TContext extends AdapterContext,
+  TContext extends AdapterContext & Record<string, unknown>,
  TExtraContext extends Record<string, unknown>,
>(
  type: THandlerType,
  ctx: TContext & TExtraContext,
): Promise<any> {
```

---

`67-69`: **Add null check for handler.transform method.**

For consistency and to prevent potential runtime errors, add a null check for the transform method.

```diff
      // run transform for each data in list

+     if (typeof handler.transform !== 'function') {
+       throw new Error(`Handler for type "${type}" is missing the transform method`);
+     }
      const transformedData = await handler.transform(buildContext(ctx, {
        key,
      }), data);
```

---

`71-71`: **Remove debug console.log statement.**

Console logs should not be present in production code as they can clutter the console and potentially expose sensitive information.

```diff
-      console.log("transformedData", transformedData);
```

---

`82-84`: **Add null check for handler.output method.**

Similar to the aggregate method, you should check if the output method exists before calling it to prevent runtime errors.

```diff
    // run output
+   if (typeof handler.output !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the output method`);
+   }
    const output = await handler.output(buildContext(ctx, {
      data: aggregatedData,
    }), aggregatedData);
```

---

`92-97`: **Good job not mutating the original context.**

The `buildContext` function properly creates a new object that combines the properties of both objects, avoiding mutations of the original context.

</details>
<details>
<summary>packages/adapters/src/types.ts (4)</summary>

`30-30`: **Consider removing `Arrayable<undefined>` from `UrlBuilder` return type**

The current `UrlBuilder` return type includes `Arrayable<undefined>`. This could lead to ambiguity as mentioned in a previous review. When a function might return undefined, empty array, or null, it complicates the logic for consumers of this type.

Consider whether returning `null` or an empty array would be more appropriate to represent "no URLs" instead of `undefined`.

---

`7-8`: **Well-designed utility types**

The `Promisable<T>` and `Arrayable<T>` utility types are well-defined and provide good flexibility for the adapter system.

---

`10-26`: **Well-structured AdapterContext interface**

The `AdapterContext` interface is well-documented with clear comments for each property. The organization and naming are clean and intuitive.

---

`110-110`: <details>
<summary>✅ Verification successful</summary>

**Maintain consistency between `UrlBuilder` and `urls` property**

The `urls` property accepts `Arrayable<undefined>`, which aligns with the `UrlBuilder` return type. If you decide to update the `UrlBuilder` type as suggested earlier, make sure to also update this property to maintain consistency.

---

🏁 Script executed:

```shell
#!/bin/bash
# Find other places that use Arrayable<undefined>
rg "Arrayable<undefined>" --type ts
```

Length of output: 605

---



</details>

**Ensure consistency between `UrlBuilder` and the `urls` property**

The current implementation in both `packages/adapters/src/types.ts` and `packages/adapters/src/utils.ts` consistently uses the union type that includes `Arrayable<undefined>` for both `urls` and the return type of `UrlBuilder`. If you later decide to update the `UrlBuilder` type (e.g., removing or altering `Arrayable<undefined>`), please ensure that the `urls` property is updated accordingly to maintain consistency.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T09:28:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (3)</summary><blockquote>

`67-69`: _⚠️ Potential issue_

**Add null check for handler.transform method**

Add a null check for the transform method to prevent potential runtime errors.


```diff
      // run transform for each data in list
+     if (typeof handler.transform !== 'function') {
+       throw new Error(`Handler for type "${type}" is missing the transform method`);
+     }
      const transformedData = await handler.transform(buildContext(ctx, {
        key,
      }), data);
```

---

`80-82`: _⚠️ Potential issue_

**Add null check for handler.aggregate method**

Add a null check for the aggregate method to prevent potential runtime errors.


```diff
    // run aggregate
+   if (typeof handler.aggregate !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the aggregate method`);
+   }
    const aggregatedData = await handler.aggregate(buildContext(ctx, {
      data: transformedDataList,
    }), transformedDataList);
```

---

`85-87`: _⚠️ Potential issue_

**Add null check for handler.output method**

Add a null check for the output method to prevent potential runtime errors.


```diff
    // run output
+   if (typeof handler.output !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the output method`);
+   }
    const output = await handler.output(buildContext(ctx, {
      data: aggregatedData,
    }), aggregatedData);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (10)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (6)</summary><blockquote>

`8-8`: **Misleading character class in regex**

The static analysis tool flagged a misleading character class in the regex that removes diacritical marks.

```diff
-    .replace(/[\u0300-\u036F]/g, "")
+    .replace(/\u0300-\u036F/g, "")
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

---

`42-126`: **Consider breaking down the transform function**

The transform function is quite long (85 lines) and handles multiple responsibilities. Consider breaking it into smaller, more focused functions to improve readability and maintainability.

---

`72-74`: **Add error message detail for subgroup without group**

When throwing an error for a subgroup without a group, it would be helpful to include more context.

```diff
-          throw new Error(`subgroup ${subgroupName} without group`);
+          throw new Error(`Subgroup "${subgroupName}" found without an associated group. Each subgroup must belong to a group.`);
```

---

`85-87`: **Improve error message for invalid line format**

The error message for an invalid line could be more descriptive to help with debugging.

```diff
-        throw new Error(`invalid line: ${line}`);
+        throw new Error(`Invalid line format (missing semicolon separator): ${line}`);
```

---

`91-93`: **Improve error message for invalid line format**

Similar to the previous error, this could be more descriptive.

```diff
-        throw new Error(`invalid line: ${line}`);
+        throw new Error(`Invalid line format (missing # separator in comment section): ${line}`);
```

---

`127-129`: **Output function returns data without validation**

The output function simply returns the transformed data without any validation. Consider adding validation to ensure the data structure is as expected before returning.

```diff
  output(_ctx, transformed) {
-    return transformed;
+    // Validate the structure before returning
+    if (!transformed || !transformed.groups || !transformed.emojis) {
+      return { groups: [], emojis: {} };
+    }
+    return transformed;
  },
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`61-61`: **Use more specific type for transformedDataList**

The `transformedDataList` is typed as `any[]` which loses type safety. Consider using a more specific type based on the expected transformation output.

```diff
-    const transformedDataList: any[] = [];
+    const transformedDataList: ReturnType<typeof handler.transform>[] = [];
```

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (3)</summary><blockquote>

`83-85`: **Consider extending the parser type system**

The `GetParseOutputFromBuiltInParser` type only handles "generic" parser and defaults to `never` for other values. Consider adding more parser types for future extensibility.

```diff
export type GetParseOutputFromBuiltInParser<TParser extends string> =
  TParser extends "generic" ? ParseResult :
-    never;
+  TParser extends "json" ? any :
+  TParser extends "yaml" ? any :
+  never;
```

---

`154-154`: **Improve type safety for aggregate function**

The `aggregate` function is optional, but the `output` function expects the result of aggregation. Consider making the type system more robust to handle this case.

```diff
-  aggregate?: AggregateFn<TContext, TTransformOutput, TAggregateOutput>;
+  aggregate?: AggregateFn<TContext, TTransformOutput, TAggregateOutput> | null | undefined;
```

---

`182-182`: **Improve the type safety of InferOutputFromAdapterHandlerType**

The type currently only handles "metadata" handler types and returns `never` for others. Consider extending it to handle all defined handler types.

```diff
-export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> = THandlerType extends "metadata" ? ReturnType<MetadataAdapterHandler["output"]> : never;
+export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> = 
+  THandlerType extends "metadata" ? ReturnType<MetadataAdapterHandler["output"]> : 
+  THandlerType extends "sequence" ? any : // Replace with actual type when implemented
+  THandlerType extends "variation" ? any : // Replace with actual type when implemented
+  never;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 2753802d75262f3bd710285f77c4a7f0852fc6c9 and f9a7520837a2a6165964e7d954d23b105f977f3f.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/metadata.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/cli/src/cli.ts` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (2)</summary>

`4-13`: **Well-structured slugify function**

The slugify function successfully normalizes text for use in URLs and identifiers, handling diacritics, parentheses, and special characters.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

---

`33-41`: <details>
<summary>❓ Verification inconclusive</summary>

**Inconsistency between parser and parserOptions**

The `parser` function splits data into lines, but `parserOptions` specifies a separator that's never used in the parser function. This suggests a potential mismatch.

---

🏁 Script executed:

```shell
#!/bin/bash
# Find where parserOptions.separator is used in the codebase
rg -A 5 "parserOptions?.separator" --type ts
```

Length of output: 46

---



</details>

**Align Parser Behavior with Parser Options Usage**

It appears that the `parserOptions.separator` setting isn’t referenced anywhere in the codebase—the parser function still splits the data using a hard-coded newline (`"\n"`) regardless of the provided separator. This inconsistency could lead to unexpected behavior if the option is meant to influence the parsing. Please verify whether:

- The parser function should be updated to utilize `parserOptions.separator` (e.g., by splitting on the defined separator), or
- The `parserOptions.separator` property is unnecessary and should be removed.

</details>
<details>
<summary>packages/cli/src/cli.ts (1)</summary>

`142-142`: **Adapter handler interface update correctly implemented**

The code now uses the new `runAdapterHandler` function with the correct parameter structure, which aligns with the architecture refactoring.

</details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary>

`95-100`: **Improved context building function**

The function now creates a new object rather than mutating the original context, which is a good practice for avoiding side effects.

</details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary>

`32-32`: **Refine the UrlBuilder return type**

The return type of `UrlBuilder` includes `Arrayable<undefined>`, which might lead to ambiguity. Consider using `null` or an empty array to represent "no URL" state instead.


```diff
-export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<UrlWithCache> | null>;
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T08:52:02Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (5)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (5)</summary><blockquote>

`82-85`: _⚠️ Potential issue_

**Add null check for handler.aggregate method.**

The aggregate method is called without checking if it's a function first, which could lead to runtime errors.

 
```diff
+    if (typeof handler.aggregate !== 'function') {
+      throw new Error(`Handler for type "${type}" is missing the aggregate method`);
+    }
    const aggregatedData = handler.aggregate(buildContext(ctx, {
      data: transformedDataList,
    }), transformedDataList);
```

---

`87-90`: _⚠️ Potential issue_

**Add null check for handler.output method.**

The output method is called without checking if it's a function first, which could lead to runtime errors.

 
```diff
    // run output
+    if (typeof handler.output !== 'function') {
+      throw new Error(`Handler for type "${type}" is missing the output method`);
+    }
    const output = handler.output(buildContext(ctx, {
      data: aggregatedData,
    }), aggregatedData);
```

---

`77-80`: _🛠️ Refactor suggestion_

**Add empty check for transformedDataList.**

The code doesn't handle the case where transformedDataList is empty. This could lead to unexpected behavior in the output.


```diff
    // only run aggregate if defined, but still call output
    if (!handler.aggregate) {
+      if (transformedDataList.length === 0) {
+        throw new Error(`No data transformed for handler type: ${type}`);
+      }
      const data = transformedDataList.length === 1 ? transformedDataList[0] : transformedDataList;
      return handler.output(buildContext(ctx, {}), data);
    }
```

---

`7-14`: _⚠️ Potential issue_

**The type parameters should be updated to match pipeline requirements.**

The pipeline failure shows `TS2345` error indicating a type mismatch. The type parameter `TContext` needs to extend `AdapterContext & Record<string, unknown>` to ensure compatibility with the context parameters in the later functions.


```diff
export async function runAdapterHandler<
  THandlerType extends AdapterHandlerType,
-  TContext extends AdapterContext,
+  TContext extends AdapterContext & Record<string, unknown>,
  TExtraContext extends Record<string, unknown>,
>(
  type: THandlerType,
  ctx: TContext & TExtraContext,
): Promise<InferOutputFromAdapterHandlerType<THandlerType>> {
```

---

`69-73`: _⚠️ Potential issue_

**Add null check for handler.transform method and fix the type issue.**

There's a pipeline failure (TS error) on line 71 and no null check for the transform method, which could lead to runtime errors if transform is undefined.


```diff
      // run transform for each data in list
+      if (typeof handler.transform !== 'function') {
+        throw new Error(`Handler for type "${type}" is missing the transform method`);
+      }
      const transformedData = handler.transform(buildContext(ctx, {
        key,
-      }), data);
+      }) as TContext & { key: string }, data);
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 71-71: Argument of type 'ParseResult | { lines: string[]; }' is not assignable to parameter of type '{ lines: string[]; } & ParseResult'.

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`94-95`: **Error message is inconsistent with the one above.**

The error message on line 94 is different from the one on line 18, but they are both handling the same error condition.


```diff
-  throw new Error(`No handler found for type: ${type}`);
+  throw new Error(`No handler executed for type: ${type}`);
```

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (3)</summary><blockquote>

`76-76`: **Consider exporting BUILTIN_PARSERS directly.**

Currently, the `BuiltinParser` type relies on `BUILTIN_PARSERS` from "./utils", but it would be more maintainable to either define both in the same file or export `BUILTIN_PARSERS` directly from types.ts.


```diff
- export type BuiltinParser = typeof BUILTIN_PARSERS[number];
+ // Define and export both in the same file or export BUILTIN_PARSERS directly
+ export const BUILTIN_PARSERS = ["generic"] as const;
+ export type BuiltinParser = typeof BUILTIN_PARSERS[number];
```

---

`94-97`: **Consider making TAggregateOutput and TOutput more explicit.**

The default types for `TAggregateOutput` and `TOutput` might lead to confusion. Consider making these more explicit or providing more detailed documentation.


```diff
  TType extends AdapterHandlerType,
  TContext extends AdapterContext,
  TTransformOutput,
-  TAggregateOutput = TTransformOutput,
-  TOutput = TTransformOutput | TAggregateOutput,
+  // Explicit default to show that aggregation typically maintains the same type
+  TAggregateOutput = TTransformOutput, 
+  // Final output might be either transform or aggregate result
+  TOutput = TAggregateOutput, 
```

---

`108-108`: **Consider simplifying the urls type.**

The `urls` property has a complex type. Consider using a type alias to make it more readable.


```diff
+ /** Possible types for adapter URLs */
+ export type AdapterUrls = Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder;

  /**
   * The urls that will be fetched.
   */
-  urls: Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder;
+  urls: AdapterUrls;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f9a7520837a2a6165964e7d954d23b105f977f3f and d96845e91685a4f45f74ca5b7829dbb7c1e052b8.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handler.ts</summary>

[error] 71-71: Argument of type 'ParseResult | { lines: string[]; }' is not assignable to parameter of type '{ lines: string[]; } & ParseResult'.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (1)</summary>

`97-102`: **The buildContext function is now correctly implemented.**

The function now creates a new object instead of mutating the original context, which is good practice. This helps avoid unexpected side effects.

</details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary>

`32-32`: **Consider refining the `UrlBuilder` return type.**

The `UrlBuilder` type includes `Arrayable<undefined>` which might lead to confusion or potential errors. Consider whether `undefined` is truly needed or if `null` or an empty array would be more appropriate for signaling "no URL".

 
```diff
- export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+ export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<UrlWithCache> | null>;
```

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T09:30:30Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (1)</summary><blockquote>

`8-8`: **Consider a more robust approach for diacritic removal.**  
Using a single character class range might be incomplete. A more thorough method is to use `\p{M}` with the `u` flag (e.g., `/\p{M}/gu`) or a specialized library for diacritic removal to handle a wider range of combining characters.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary><blockquote>

`83-85`: **Anticipate additional built-in parser types.**  
Currently, only `"generic"` is matched. If new built-in parser types are introduced, expand this conditional type accordingly.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d96845e91685a4f45f74ca5b7829dbb7c1e052b8 and fcd124b162a0a36a09f077509814b2fc8a2c3a1b.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/define.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/metadata.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/adapters/src/_handlers.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (1)</summary>

`39-117`: **Well-structured parsing logic.**  
The grouping, subgroup parsing, and metadata extraction handle edge cases effectively, including handling malformed lines and unknown groups. Overall, this implementation is clear and robust.

</details>
<details>
<summary>packages/adapters/src/define.ts (1)</summary>

`10-40`: **Generic type usage is well-executed.**  
This design leverages TypeScript’s conditional types effectively, allowing flexible parser definitions. The approach provides strong type guarantees across different parsing and transformation steps.

</details>
<details>
<summary>packages/adapters/src/types.ts (2)</summary>

`112-112`: **Refine the UrlBuilder return type to avoid `undefined`.**  
Using `undefined` as part of the return type can be ambiguous. Consider returning `null` or an empty array to indicate “no URLs.”

---

`172-172`: **Expand InferOutputFromAdapterHandlerType for all handler types.**  
Currently, it returns `never` for “sequence” and “variation.” This might cause type mismatches when these handlers are used.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T09:51:34Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/sequence.ts (2)</summary><blockquote>

`1-38`: _🛠️ Refactor suggestion_

**Implement the recommendations from the previous review.**

The code now correctly uses semver.gte for version checking, but still contains console output statements that should be removed in production code.



```diff
  transform(ctx, data) {
-   console.warn("key", ctx.key);
-   console.log(data);
    return data;
  },
```

---

`22-22`: _🛠️ Refactor suggestion_

**Ensure emoji_version is coerced before comparison.**

The `shouldExecute` function is directly comparing the emoji_version to a semver string, but if the emoji_version is in an unexpected format, this might not work as intended.


```diff
  shouldExecute: (ctx) => {
-   return semver.gte(ctx.emoji_version, "16.0.0");
+   return semver.gte(semver.coerce(ctx.emoji_version) || "0.0.0", "16.0.0");
  },
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/sequence.ts (1)</summary><blockquote>

`29-34`: **Improve type safety in the aggregate method.**

The aggregate method makes assumptions about the structure and order of the data array, but doesn't validate these assumptions.


```diff
- aggregate(_, data) {
+ aggregate(_, data) {
+   if (!Array.isArray(data) || data.length < 2) {
+     throw new Error("Expected data to be an array with at least 2 elements");
+   }
    return {
      sequences: data[0],
      zwj: data[1],
    };
  },
```

</blockquote></details>
<details>
<summary>packages/cli/src/cli.ts (1)</summary><blockquote>

`141-184`: **Ensure consistent implementation across generators.**

The implementation is inconsistent between the metadata and sequence generators. The metadata generator writes files, while the sequence generator retrieves data but doesn't write files (the file writing is commented out).


This creates an inconsistent experience when using different generators. Consider either:
1. Implementing file writing for all enabled generators, or
2. Adding a feature flag to specifically control file writing behavior

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between fcd124b162a0a36a09f077509814b2fc8a2c3a1b and 31631969023a7779750c75cdb61b772094765fd5.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/adapters/src/handlers/modern/sequence.ts` (1 hunks)
* `packages/cli/src/cli.ts` (3 hunks)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>packages/cli/src/cli.ts (2)</summary>

`173-184`: **Uncomment file writing or remove commented code.**

Either uncomment the file writing code to make the sequence handler functional, or remove the commented code entirely if it's not needed.


```diff
-        // await fs.writeFile(
-        //   join(baseDir, "zwj-sequences.json"),
-        //   JSON.stringify(zwj, null, 2),
-        //   "utf-8",
-        // );
-
-        // await fs.writeFile(
-        //   join(baseDir, "sequences.json"),
-        //   JSON.stringify(sequences, null, 2),
-        //   "utf-8",
-        // );
+        await fs.writeFile(
+          join(baseDir, "zwj-sequences.json"),
+          JSON.stringify(zwj, null, 2),
+          "utf-8",
+        );
+
+        await fs.writeFile(
+          join(baseDir, "sequences.json"),
+          JSON.stringify(sequences, null, 2),
+          "utf-8",
+        );
```

---

`186-203`: **Remove large blocks of commented code.**

Large blocks of commented code should be removed rather than kept in the codebase. If this functionality is temporarily disabled but will be re-enabled later, consider using feature flags instead of comments.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T09:56:05Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>packages/parsers/test/generic.test.ts (1)</summary><blockquote>

`4-4`: **Update test suite description to match new function name.**

The test suite description still refers to "parse function" even though the function has been renamed to `genericParse`. Consider updating it to maintain consistency.


```diff
-describe("parse function", () => {
+describe("genericParse function", () => {
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (1)</summary><blockquote>

`34-36`: **Consider using the generic parser.**

You're implementing a simple line parser here, but you already have a `genericParse` function that could potentially be reused. Consider whether leveraging the existing parser would be beneficial.

</blockquote></details>
<details>
<summary>packages/adapters/src/_handlers.ts (1)</summary><blockquote>

`1-9`: **Consider adding JSDoc comments.**

Adding JSDoc comments for the exported constants would improve documentation and provide better context for developers who will use these handlers.


```diff
+/**
+ * Collection of handlers for processing emoji metadata.
+ */
export const METADATA_HANDLERS = [
  modernMetadataHandler,
  preAlignmentMetadataHandler,
];

+/**
+ * Collection of handlers for processing emoji sequences.
+ */
export const SEQUENCE_HANDLERS = [
  modernSequenceHandler,
];

+/**
+ * Collection of handlers for processing emoji variations.
+ */
export const VARIATION_HANDLERS = [
  modernVariationHandler,
];

+/**
+ * Combined collection of all handlers organized by type.
+ */
export const ALL_HANDLERS = {
  metadata: METADATA_HANDLERS,
  sequence: SEQUENCE_HANDLERS,
  variation: VARIATION_HANDLERS,
};
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handlers/modern/sequence.ts (2)</summary><blockquote>

`49-51`: **Consider a version parsing fallback or coercion.**
Currently, `"${ctx.emoji_version}.0"` is passed to `semver.gte`. While this works for standard versions (e.g., "16.0"), it may fail if `ctx.emoji_version` is an unexpected format (e.g., "16" or "15.2"). Consider using `semver.coerce` for more robust parsing of partial versions.

```diff
-  return semver.gte(`${ctx.emoji_version}.0`, "16.0.0");
+  const coercedVersion = semver.coerce(ctx.emoji_version) || "0.0.0";
+  return semver.gte(coercedVersion, "16.0.0");
```

---

`22-47`: **Streamline property map entries.**
The property map repeats similar keys (e.g., “# Emoji Flag Sequence” and “# Flag sequences”) which could lead to confusion or duplication. Consider consolidating them or storing property map definitions externally, then merging or de-duplicating keys before usage.

</blockquote></details>
<details>
<summary>packages/cli/src/cli.ts (1)</summary><blockquote>

`183-201`: **Remove or refactor commented-out “variations” logic.**
Since these lines are fully commented out, either remove them if no longer needed or introduce a feature flag if you plan to re-enable them. Retaining large commented blocks can create confusion in the codebase.

```diff
-      // if (isGeneratorEnabled("variations")) {
-      //   if (adapter.variations == null) {
-      //     throw new MojisNotImplemented("variations");
-      //   }
-      //   ...
-      // }
+      // Use a feature flag or remove entirely if no longer applicable.
```

</blockquote></details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`95-95`: **Rename the final error for clarity.**
The error at line 95 is the same message used at line 18. Consider revising it to reflect that no handler's `shouldExecute` returned true, rather than implying no handler for the type at all.

```diff
-  throw new Error(`No handler found for type: ${type}`);
+  throw new Error(`No handler executed for type: ${type} (all handlers skipped by shouldExecute).`);
```

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary><blockquote>

`172-176`: **Define explicit types for `"sequence"` and `"variation"`.**  
Currently returning `unknown`; consider assigning a specific return type for those cases.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 31631969023a7779750c75cdb61b772094765fd5 and d2594a22506a7c270bccfcfd4b4fae6aa30ea97a.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/metadata.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/sequence.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/cli/src/cli.ts` (4 hunks)
* `packages/parsers/src/generic.ts` (4 hunks)
* `packages/parsers/src/index.ts` (1 hunks)
* `packages/parsers/test/generic.test.ts` (9 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

<details>
<summary>packages/adapters/src/handler.ts</summary>

[error] 72-72: error TS2345: Argument of type 'string[] | GenericParseResult' is not assignable to parameter of type 'string[] & GenericParseResult'.

</details>

</details>
<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (26)</summary><blockquote>

<details>
<summary>packages/parsers/test/generic.test.ts (2)</summary>

`2-2`: **Correct import renaming.**

The import has been correctly updated from `parse` to `genericParse` to match the function renaming in the source file.

---

`6-6`: **Function calls correctly updated.**

All instances where the `parse` function was called have been consistently replaced with calls to `genericParse`, maintaining the same parameters and behavior.



Also applies to: 12-12, 24-24, 36-36, 48-48, 60-60, 73-73, 86-86, 100-100, 112-112, 118-118

</details>
<details>
<summary>packages/parsers/src/index.ts (1)</summary>

`1-6`: **Export statement properly updated.**

The export statement has been correctly updated to reflect the function renaming from `parse` to `genericParse` and the related type updates from `ParseOptions` to `GenericParseOptions` and `ParseResult` to `GenericParseResult`. The `ParsedLine` type is still being exported as before.

</details>
<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (3)</summary>

`19-33`: **Good handler configuration.**

The handler is well-configured with appropriate checks for disallowed emoji versions and proper URL construction. The URL pattern and cacheKey format will enable efficient data fetching and caching.

---

`37-121`: **Well-structured transformation logic.**

The transform function does a good job of processing the emoji metadata by:
- Properly organizing emojis into groups and subgroups
- Handling error cases for invalid lines
- Extracting and formatting emoji data

The previous debug console logs have been removed, and the function now correctly processes the input data instead of returning hardcoded values.

---

`122-124`: **Direct pass-through in output function.**

The output function simply returns the transformed data without modification. Ensure this is the intended behavior and that there's no additional processing needed before delivering the final output.

</details>
<details>
<summary>packages/adapters/src/_handlers.ts (1)</summary>

`11-28`: **Well-organized handler collections.**

The code establishes a clean organization of handlers by type (metadata, sequence, variation) and provides a centralized access point through the `ALL_HANDLERS` object. This structure will make it easier to manage and extend handlers in the future.

</details>
<details>
<summary>packages/adapters/src/handlers/modern/sequence.ts (1)</summary>

`76-81`: **Clarify aggregator logic when more URLs are added.**
The aggregation method assumes exactly two data entries (`data[0]` as sequences and `data[1]` as zwj). If additional URLs are introduced in the future, this indexing-based approach may lead to out-of-bounds or data-misalignment issues. Consider making this more robust.

</details>
<details>
<summary>packages/parsers/src/generic.ts (3)</summary>

`7-12`: **Renaming to “GenericParseResult” looks good.**
Renaming the interface clarifies its broader applicability. Ensure other references within the codebase and tests are updated accordingly.

---

`14-19`: **Consistent naming for “GenericParseOptions.”**
Introducing “GenericParseOptions” aligns with the renamed “GenericParseResult.” This improves readability. Make sure all references have been updated.

---

`41-44`: **Great refactor to a single export function “genericParse.”**
This consolidates parsing logic under one name. Nice improvement for code organization.

</details>
<details>
<summary>packages/cli/src/cli.ts (1)</summary>

`5-5`: **Verify adapter import consistency.**
Now that `runAdapterHandler` is used directly, ensure that all references to legacy adapter resolution or usage have been removed throughout the CLI to avoid dead code.

</details>
<details>
<summary>packages/adapters/src/handler.ts (1)</summary>

`98-103`: **Avoid mutating the original context**  
The `buildContext` function properly returns a new object via `Object.assign({}, ...)`. Good practice for preventing side effects in the shared context.

</details>
<details>
<summary>packages/adapters/src/types.ts (13)</summary>

`6-7`: **Imports look well-structured.**  
These new imports from `@mojis/parsers` and local `./utils` appear to be used correctly in the subsequent type definitions.

---

`9-10`: **Utility type definitions are concise and clear.**  
Defining `Promisable<T>` and `Arrayable<T>` simplifies handling of both synchronous/asynchronous and singular/plural data. No issues found.

---

`14-16`: **Validate necessity of mandatory adapter context fields.**  
The `emoji_version` and `unicode_version` fields are introduced as required. Verify if all call sites ensure these fields are always provided, or consider marking them optional if there are contexts where they may not be available.  



Also applies to: 19-20, 22-22, 28-28

---

`30-30`: **Union type for handler types.**  
Defining `"metadata" | "sequence" | "variation"` as `AdapterHandlerType` is straightforward. No concerns here.

---

`32-32`: **Consider constraining or replacing `undefined` in `UrlBuilder`.**  
Returning `Arrayable<undefined>` can be ambiguous; consider using `null` or an empty array for clarity.

---

`34-36`: **Simple and flexible execution predicate.**  
The `ShouldExecute` signature allowing both synchronous and async evaluation is consistent with typical adapter usage.

---

`38-56`: **`UrlWithCache` interface is well-documented.**  
Each field has a clear purpose. The optional `key` with a fallback to `cacheKey` is a sensible approach.

---

`58-63`: **`TransformFn` type is logically sound.**  
Defines a function that converts parsed data into a transform output. No issues noted.

---

`64-68`: **Revisit empty array handling in `AggregateFn`.**  
This type enforces `[TTransformOutput, ...TTransformOutput[]]`, risking potential runtime issues if no data is produced.

---

`70-75`: **Output function definition.**  
`OutputFn` is straightforward, returning a final output from the transformed or aggregated data. No issues noted.

---

`76-89`: **Parser-related type definitions look consistent.**  
`BuiltinParser`, `ParserFn`, `GetParseOutputFromBuiltInParser`, and `GetParseOptionsFromParser` collectively define a strong typed foundation for parsing logic.

---

`91-161`: **`AdapterHandler` interface provides a robust structure.**  
All major stages—fetch, parse, transform, optional aggregation, and final output—are typed thoroughly. Including the `shouldExecute` predicate is a nice touch for conditional logic. Overall, this design is flexible and covers various adapter needs.

---

`162-170`: **The `MetadataAdapterHandler` specialization.**  
This specialized type is a solid example of how to extend `AdapterHandler` for specific use cases (metadata in this case). No immediate concerns.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T10:45:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handler.ts (1)</summary><blockquote>

`76-81`: _⚠️ Potential issue_

**Conditional aggregate logic is helpful, but missing a null check.**

You already mention a `@ts-expect-error`. If `handler.output` is potentially undefined, consider adding a check like for `handler.aggregate`.


```diff
+ if (!handler.output) {
+   throw new Error(`Handler for type "${type}" is missing the output method`);
+ }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/variation.ts (2)</summary><blockquote>

`23-26`: **Remove or downgrade console.warn in production code.**

Although the log may be useful for debugging, consider removing or using a more robust logger in production to avoid cluttering logs.

---

`27-29`: **Flatten usage is straightforward.**

The `.flat()` approach is succinct. Just ensure the data structure is always an array. If `data` might sometimes be non-array, add a safe check.

</blockquote></details>
<details>
<summary>packages/adapters/src/define.ts (1)</summary><blockquote>

`11-19`: **Good usage of advanced TypeScript generics.**

The layered generic constraints make the function flexible. However, ensure that each type parameter is necessary and does not overcomplicate usage for implementers.

</blockquote></details>
<details>
<summary>packages/adapters/src/handler.ts (3)</summary><blockquote>

`21-27`: **Check multiple handlers elegantly, but watch performance.**

Continuing to the next handler if `shouldExecute` is false is correct. For large sets of handlers, ensure there’s no noticeable overhead in repeated `shouldExecute` checks.

---

`62-75`: **Transformation flow is straightforward but watch type casting.**

The `as any` cast for `data` bypasses compiler checks. If possible, refine the parser’s output type to avoid such casts.

---

`98-98`: **Duplicate error message.**

This error message duplicates the one at lines 17-19. Consider clarifying which scenario triggers each.

</blockquote></details>
<details>
<summary>packages/adapters/src/types.ts (1)</summary><blockquote>

`168-172`: **Consider specifying actual return types for “sequence” or “variation”.**  
Currently returning `unknown` for these handler types may limit type safety. Define real return types if each type yields distinct outputs.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d2594a22506a7c270bccfcfd4b4fae6aa30ea97a and b2dcb7af3c23626d36d68df8b49797f90e5acc8e.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `packages/adapters/src/define.ts` (1 hunks)
* `packages/adapters/src/handler.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/variation.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/adapters/test/types.test-d.ts` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/adapters/test/types.test-d.ts

</details>

<details>
<summary>🔇 Additional comments (20)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/variation.ts (4)</summary>

`4-18`: **Ensure consistent URLs for all Unicode versions.**

The logic returning different URLs for older vs. newer versions is correct, but be mindful of potential changes in how the Unicode consortium hosts files in the future.

---

`19-19`: **Parser type is well-chosen.**

Using `"generic"` is compatible with your built-in parsing approach, making it easier to maintain consistency across handlers.

---

`20-22`: **Single-liner return is fine, but double-check boundary versions.**

Semver boundary checks, like `"11.0.0"`, can cause unexpected results if `ctx.unicode_version` is not strictly following semver rules (e.g., "11" or "12.1"). Raise an error or default to a fallback if parsing fails.

---

`30-33`: **Output logic is straightforward and maintainable.**

Returning transformed data directly is appropriate for smaller transformations. If future logic becomes more complex, this pattern remains extensible.

</details>
<details>
<summary>packages/adapters/src/define.ts (2)</summary>

`1-9`: **Imports are consistent with type usage.**

All necessary types are imported, and no extraneous imports are visible. This keeps the file tidy.

---

`20-37`: **Returning the handler preserves type signatures correctly.**

The function simply returns the handler, ensuring no additional validation or transformation. This is appropriate if validation is handled elsewhere. If not, consider adding runtime checks or constraints here.

</details>
<details>
<summary>packages/adapters/src/handler.ts (7)</summary>

`1-6`: **Imports are correct and consistent.**

You’ve pulled in only what is needed, and the references to the internal utilities and parser modules align well with this file’s functionality.

---

`7-14`: **Flexible function signature for runAdapterHandler.**

Using generics for both `TContext` and `TExtraContext` allows context extension without restricting the handler’s usage. This is beneficial for dynamic contexts in more complex systems.

---

`15-19`: **User-friendly error messaging on missing handler group.**

Throwing an explicit error clarifies that no handler was found for the given type. This should help debugging.

---

`29-32`: **URL gathering logic is concise.**

Delegating the URL-building logic to `getHandlerUrls` helps maintain separation of concerns. Great approach.

---

`33-60`: **Comprehensive data fetching with built-in and custom parser branching.**

Handling both built-in and custom parser cases is well-structured. Consider verifying that `fetchCache` properly handles rejection or network errors (e.g., 404, 500).

---

`83-95`: **Aggregate step handles multi-part transformations well.**

Aggregating intermediate data and running final output is a clean separation of concerns.

---

`101-106`: **Excellent approach to avoid mutating the original context.**

Using a shallow copy with `Object.assign({}, ctx, extraContext)` prevents side effects on the source context.

</details>
<details>
<summary>packages/adapters/src/types.ts (7)</summary>

`6-7`: **No issues with new imports.**  
These imports cleanly integrate with the rest of the file.

---

`9-10`: **Utility type definitions look good.**  
Defining `Promisable` and `Arrayable` is a clean, commonly used pattern.

---

`12-28`: **Context interface updates appear accurate.**  
Adding `emoji_version` and `unicode_version` fields along with their documentation is consistent and well-organized.

---

`32-32`: **Consider removing `undefined` from UrlBuilder return type.**  
Returning `undefined` can be ambiguous; favor returning `null` or an empty array for clearer semantics.

---

`64-68`: **Revisit enforcing non-empty data in AggregateFn.**  
This type enforces at least one item in the array, but runtime scenarios may yield an empty array, causing potential errors.

---

`158-166`: **Specialized handler definition aligns with usage.**  
Defining `MetadataAdapterHandler` with a concrete parser and transform output addresses prior concerns about mismatched parser types.

---

`91-157`: **Overall interface expansion looks comprehensive.**  
The new properties (`urls`, `fetchOptions`, `cacheOptions`, etc.) make the handler more flexible and configurable. No immediate concerns.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T11:24:55Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (1)</summary><blockquote>

`5-14`: _⚠️ Potential issue_

**Fix misleading character class in regex.**

The character class in your regular expression on line 9 doesn't behave as expected according to static analysis. A character class cannot match both a character and a combining character.

```diff
-    .replace(/[\u0300-\u036F]/g, "")
+    .replace(/\u0300-\u036F/g, "")
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 9-9: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (2)</summary><blockquote>

`110-111`: **Potential improvement for emoji and unicode version handling.**

The code uses `emojiVersion || null` but could be more explicit about handling undefined values from `extractEmojiVersion`.

```diff
-        emojiVersion: emojiVersion || null,
+        emojiVersion: emojiVersion ? emojiVersion : null,
```

---

`35-37`: **Consider using a standard parser.**

Instead of defining an inline parser that simply splits the string by newlines, consider using the "generic" parser with appropriate options, which would be more consistent with the type definitions.

```diff
-  parser: (_, data) => {
-    return data.split("\n");
-  },
+  parser: "generic",
+  parserOptions: {
+    separator: "\n",
+  },
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b2dcb7af3c23626d36d68df8b49797f90e5acc8e and 3d9d9d870ac1b873b559d6f4e0ef458240ba2537.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `.changeset/lucky-pears-wonder.md` (1 hunks)
* `packages/adapters/src/_handlers.ts` (1 hunks)
* `packages/adapters/src/define.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/metadata.ts` (1 hunks)
* `packages/adapters/src/handlers/modern/sequence.ts` (1 hunks)
* `packages/adapters/src/types.ts` (1 hunks)
* `packages/adapters/test/utils.test.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* .changeset/lucky-pears-wonder.md

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* packages/adapters/src/_handlers.ts
* packages/adapters/src/handlers/modern/sequence.ts
* packages/adapters/test/utils.test.ts

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts</summary>

[error] 9-9: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/adapters/src/types.ts (3)</summary>

`33-33`: **Consider refining the `UrlBuilder` return type.**

The `UrlBuilder` return type includes `Arrayable<undefined>`, which might lead to confusion or potential errors. Consider whether `undefined` is truly needed or if `null` or an empty array would be more appropriate for the use case.


```shell
#!/bin/bash
# Find implementations of UrlBuilder to see if `undefined` is actually needed
rg -A 5 "urls:" --type ts --glob "packages/adapters/src/handlers/**/*.ts"
```

---

`65-69`: **Ensure non-empty data is guaranteed in AggregateFn.**

The signature enforces at least one item, but in scenarios where no data is returned by the transform step, the aggregator could receive an empty array. Consider adding runtime validation or update the type to allow empty arrays.


```shell
#!/bin/bash
# Check if any aggregate implementations handle empty arrays
rg -n "aggregate.*\(.*\).*\{" --type ts --glob "packages/adapters/src/handlers/**/*.ts" -A 5
```

---

`169-173`: **Great job fixing the InferOutputFromAdapterHandlerType.**

The type now properly handles all handler types including "metadata", "sequence", and "variation", resolving the previous issue where only "metadata" was supported.

</details>
<details>
<summary>packages/adapters/src/handlers/modern/metadata.ts (1)</summary>

`94-96`: <details>
<summary>✅ Verification successful</summary>

**Handle potential missing emoji version.**

If `extractEmojiVersion` returns an empty string or undefined, the subsequent split using `` ` E${emojiVersion} ` `` could yield unexpected results. Add a safety check to handle this case.


```diff
       const emojiVersion = extractEmojiVersion(comment.trim());
-      const [emoji, trimmedComment] = comment.trim().split(` E${emojiVersion} `);
+      let emoji = comment.trim();
+      let trimmedComment = "";
+      
+      if (emojiVersion) {
+        const parts = comment.trim().split(` E${emojiVersion} `);
+        emoji = parts[0];
+        trimmedComment = parts[1] || "";
+      }
```

---



</details>

**Action Required: Validate and Update Emoji Extraction Handling**

The current logic doesn’t account for cases when `extractEmojiVersion` returns a falsy value (empty string/undefined), which may lead to incorrect splitting of the comment. To safeguard against this, please add a check to verify that an emoji version is present before performing the split. This prevents unexpected results and ensures the code handles comments without a valid emoji version correctly.

```diff
       const emojiVersion = extractEmojiVersion(comment.trim());
-      const [emoji, trimmedComment] = comment.trim().split(` E${emojiVersion} `);
+      let emoji = comment.trim();
+      let trimmedComment = "";
+      
+      if (emojiVersion) {
+        const parts = comment.trim().split(` E${emojiVersion} `);
+        emoji = parts[0];
+        trimmedComment = parts[1] || "";
+      }
```

</details>
<details>
<summary>packages/adapters/src/define.ts (1)</summary>

`9-35`: **Clean type-safe handler definition function.**

The `defineAdapterHandler` function provides good type safety through extensive use of generics while maintaining a simple implementation. This is a positive pattern for ensuring type correctness in TypeScript.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-08T11:31:16Z

Review comments:
diff_hunk: @@ -0,0 +1,6 @@
+import type { v2_AdapterHandler, v2_AdapterHandlerType } from "./types";
path: packages/adapters/src/v2_adapters.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Remove unused import.**

The `v2_AdapterHandlerType` is imported but never used in this file, which is causing linting errors in the pipeline.

```diff
-import type { v2_AdapterHandler, v2_AdapterHandlerType } from "./types";
+import type { v2_AdapterHandler } from "./types";
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import type { v2_AdapterHandler } from "./types";
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 1-1: 
'v2_AdapterHandlerType' is defined but never used

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T03:02:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+import { defineAdapterHandler, type v2_AdapterHandler } from "../types";
path: packages/adapters/src/handlers/modern.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Remove unused import.**

The `v2_AdapterHandler` type is imported but never used, causing linting errors in the pipeline.

```diff
-import { defineAdapterHandler, type v2_AdapterHandler } from "../types";
+import { defineAdapterHandler } from "../types";
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import { defineAdapterHandler } from "../types";
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Check: build</summary>

[failure] 1-1: 
'v2_AdapterHandler' is defined but never used

</details>
<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 1-1: 'v2_AdapterHandler' is defined but never used  unused-imports/no-unused-imports

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T03:02:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+import { defineAdapterHandler, type v2_AdapterHandler } from "../types";
+
+// These emoji versions doesn't seem to have a emoji-test,
+// where we can extract the metadata from.
+const DISALLOWED_EMOJI_VERSIONS = ["1.0", "2.0", "3.0"];
+
+export const modern_metadata_handler = defineAdapterHandler({
+  type: "metadata",
+  urls: (ctx) => {
+    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
+      return undefined;
+    }
+
+    return {
+      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
+      cacheKey: `v${ctx.emoji_version}/metadata`,
+    };
+  },
+  // parser(ctx, data) {
+  //   return "hello";
+  // },
+  parser: "splitter",
+  // TODO: make thos fail if no a builtin parser
+  parserOptions: {
+    separator: ";",
+  },
path: packages/adapters/src/handlers/modern.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Address the TODO comment and clean up unused code.**

There's commented-out code and a TODO item that should be implemented or removed. 


Implement the parser validation mentioned in the TODO comment:

```diff
-  // parser(ctx, data) {
-  //   return "hello";
-  // },
  parser: "splitter",
-  // TODO: make thos fail if no a builtin parser
+  // Built-in parser options
  parserOptions: {
    separator: ";",
  },
```

If you need custom parser validation, consider adding helper functions to verify parser types.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  parser: "splitter",
  // Built-in parser options
  parserOptions: {
    separator: ";",
  },
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T03:02:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,42 @@
+import { defineAdapterHandler, type v2_AdapterHandler } from "../types";
+
+// These emoji versions doesn't seem to have a emoji-test,
+// where we can extract the metadata from.
+const DISALLOWED_EMOJI_VERSIONS = ["1.0", "2.0", "3.0"];
+
+export const modern_metadata_handler = defineAdapterHandler({
+  type: "metadata",
+  urls: (ctx) => {
+    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
+      return undefined;
+    }
+
+    return {
+      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
+      cacheKey: `v${ctx.emoji_version}/metadata`,
+    };
+  },
+  // parser(ctx, data) {
+  //   return "hello";
+  // },
+  parser: "splitter",
+  // TODO: make thos fail if no a builtin parser
+  parserOptions: {
+    separator: ";",
+  },
+  transform(_ctx, _data) {
+    return {
+      foo: "bar",
+    };
+  },
+  aggregate(_ctx, data) {
+    console.warn("data.foo", data[0].foo);
+    return {
+      aggregatedFoo: data.map((d) => d.foo),
+    };
+  },
+  output(_ctx, transformed) {
+    console.warn("transformed.aggregatedFoo", transformed.aggregatedFoo);
+    return transformed;
+  },
path: packages/adapters/src/handlers/modern.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove console.warn statements and implement meaningful data transformation.**

The current implementation uses console.warn for debugging and returns placeholder data. For production code, consider:

1. Removing console.warn statements or replacing them with proper logging
2. Implementing a more meaningful data transformation

```diff
  transform(_ctx, _data) {
    return {
-      foo: "bar",
+      // Transform the real data here
+      // Example: parse and extract relevant metadata fields
+      version: _ctx.emoji_version,
+      entries: _data.map(entry => parseEntry(entry))
    };
  },
  aggregate(_ctx, data) {
-    console.warn("data.foo", data[0].foo);
    return {
-      aggregatedFoo: data.map((d) => d.foo),
+      // Aggregate the transformed data meaningfully
+      totalEntries: data.reduce((total, d) => total + d.entries.length, 0),
+      categories: data.flatMap(d => d.entries.map(entry => entry.category))
+        .filter((v, i, a) => a.indexOf(v) === i)
    };
  },
  output(_ctx, transformed) {
-    console.warn("transformed.aggregatedFoo", transformed.aggregatedFoo);
    return transformed;
  },
```

If this is just test code, please add a comment indicating it's not meant for production.


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T03:02:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,64 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache, parse } from "@mojis/internal-utils";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+>(
+  type: THandlerType,
+  ctx: TContext,
+) {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const fetchPromises = urls.map(async (url) => {
+      return fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "splitter") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+    });
+
+    const dataList = await Promise.all(fetchPromises);
+
+    // run transform for each data in list
+    for (const data of dataList) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(ctx, data);
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Address the type mismatch.**

The pipeline logs show a `TS2345` error indicating that `TContext` is not assignable to the parameter type `'AdapterContext & Record<string, unknown>'`. Update the generic constraint on `TContext` or cast to the required type:

```diff
- export async function runAdapterHandler<
-   THandlerType extends AdapterHandlerType,
-   TContext extends AdapterContext,
- >(
+ export async function runAdapterHandler<
+   THandlerType extends AdapterHandlerType,
+   TContext extends AdapterContext & Record<string, unknown>,
+ >(
    type: THandlerType,
    ctx: TContext,
) {
```




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export async function runAdapterHandler<
  THandlerType extends AdapterHandlerType,
  TContext extends AdapterContext & Record<string, unknown>,
>(
  type: THandlerType,
  ctx: TContext,
) {
  const transformedData = await handler.transform(ctx, data);
  // ... rest of the function implementation
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 61-61: error TS2345: Argument of type 'TContext' is not assignable to parameter of type 'AdapterContext & Record<string, unknown>'.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T05:01:03Z
author_association: NONE
diff_hunk: @@ -1,149 +1,154 @@
 import type {
-  EmojiGroup,
-  EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
+
+export interface AdapterContext {
   /**
-   * The name of the adapter.
+   * The emoji version.
    */
-  name: string;
+  emoji_version: string;
 
   /**
-   * A description of the adapter.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  description: string;
+  unicode_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * Whether or not the force mode was enabled.
    */
-  range: string;
+  force: boolean;
+}
+
+export type AdapterHandlerType = "metadata";
 
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The name of the adapter to extend from.
+   * The url to fetch the data.
    */
-  extend?: string;
+  url: string;
 
   /**
-   * The metadata handler for the adapter.
+   * The cache key for the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  cacheKey: string;
+}
 
+export type TransformFn<
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext & TExtraContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext & TExtraContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
+
+export type OutputFn<
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+  TTransformOutput,
+  TOutput,
+> = (ctx: TContext & TExtraContext, data: TTransformOutput) => TOutput;
+
+// TODO: find a better name for the splitter parser
+// The splitter parser is just a simple parser, that will split the data based on a separater character.
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Address TODO comment and fix typo**

There's a TODO comment to find a better name for the "splitter" parser, which should be resolved before finalizing. Also, there's a typo: "separater" should be "separator".

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for usage of the splitter parser in the codebase
grep -r "splitter" --include="*.ts" .
```

Length of output: 1048

---



</details>

**Renaming Parser & Fixing Typo**

- In **packages/adapters/src/types.ts** (lines 68-69), update the TODO comment to either remove or replace "splitter" with a more descriptive name that better reflects the parser’s purpose.
- Correct the typo in the comment by changing "separater" to "separator."
- If you decide to rename the parser, ensure that all references (e.g., in **packages/adapters/src/handlers/modern.ts**, **packages/adapters/src/handler.ts**, and others found via grep) are updated consistently.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T05:01:04Z
author_association: NONE
diff_hunk: @@ -1,149 +1,154 @@
 import type {
-  EmojiGroup,
-  EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
+
+export interface AdapterContext {
   /**
-   * The name of the adapter.
+   * The emoji version.
    */
-  name: string;
+  emoji_version: string;
 
   /**
-   * A description of the adapter.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  description: string;
+  unicode_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * Whether or not the force mode was enabled.
    */
-  range: string;
+  force: boolean;
+}
+
+export type AdapterHandlerType = "metadata";
 
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider constraining the return type**

The `UrlBuilder` return type includes `Arrayable<undefined>`, which might lead to confusion or potential errors. Consider whether `undefined` is truly needed or if `null` or an empty array would be more appropriate for the use case.

---

🏁 Script executed:

```shell
#!/bin/bash
# Find usages of UrlBuilder to see how it's typically implemented
rg -A 5 "UrlBuilder" --type ts
```

Length of output: 4109

---



</details>

**Action Needed: Refine the `UrlBuilder` Return Type**

After reviewing the usages of `UrlBuilder` across the codebase, it appears that allowing `undefined` (i.e. using `Arrayable<undefined>`) in its return type might introduce ambiguity. In many cases—such as in `packages/adapters/src/utils.ts`—the code simply checks for a nullish value (using `== null`) to decide whether URLs have been provided. This indicates that an explicit “no URL” value (for example, a `null` or an empty array) might be clearer than returning `undefined`.

**Recommendations:**
- Reassess if `undefined` is needed. If its intent is to signal “no URL”, then consider using `null` or an empty array to represent that state.
- Update the union type in `packages/adapters/src/types.ts` (line 29) to remove `Arrayable<undefined>` if appropriate.
- Verify that all consumers of the `UrlBuilder` type across the codebase handle the new return semantics correctly.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-06T05:01:04Z
author_association: NONE
diff_hunk: @@ -1,72 +1,85 @@
-import type { CacheableUrlRequestReturnType, MojiAdapter } from "./types";
-import semver from "semver";
+import type {
+  AdapterContext,
+  AdapterHandler,
+  AdapterHandlerType,
+  AggregateFn,
+  BuiltinParser,
+  GetParseOutputFromBuiltInParser,
+  OutputFn,
+} from "./types";
 
-/**
- * Validates and defines a Moji adapter configuration.
- *
- * @param {MojiAdapter} adapter - The adapter configuration object to validate
- * @throws {Error} If adapter.name is missing
- * @throws {Error} If adapter.description is missing
- * @throws {Error} If adapter.range is missing
- * @throws {Error} If adapter.range is not a valid semver range
- * @throws {Error} If required functions are missing when adapter.extend is null
- * @returns {MojiAdapter} The validated adapter configuration
- *
- * @example
- * ```ts
- * const adapter = defineMojiAdapter({
- *   name: 'my-adapter',
- *   description: 'My adapter',
- *   range: '>=1.0.0',
- * });
- * ```
- */
-export function defineMojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
->(adapter: MojiAdapter<
-  TMetadataUrlReturn,
-  TSequencesUrlReturn,
-  TVariationsUrlReturn
->): MojiAdapter<
-    TMetadataUrlReturn,
-    TSequencesUrlReturn,
-    TVariationsUrlReturn
-  > {
-  // validate the adapter has name, description, range.
-  if (!adapter.name) {
-    throw new Error(`adapter.name is required`);
-  }
-
-  if (!adapter.description) {
-    throw new Error(`adapter.description is required`);
-  }
-
-  if (!adapter.range) {
-    throw new Error(`adapter.range is required`);
-  }
-
-  // verify the adapter.range is a valid semver range.
-  if (semver.validRange(adapter.range) === null) {
-    throw new Error(`adapter.range is not a valid semver range ${adapter.range}`);
+export function defineAdapterHandler<
+  TType extends AdapterHandlerType,
+  TExtraContext extends Record<string, unknown>,
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+>(
+  handler: Omit<AdapterHandler<
+    TType,
+    TExtraContext,
+    TContext,
+    TTransformOutput,
+    TAggregateOutput
+  >, "output" | "aggregate"> & {
+    aggregate: AggregateFn<TContext, TExtraContext, TTransformOutput, TAggregateOutput>;
+    output: OutputFn<TContext, TExtraContext, TAggregateOutput, any>;
   }
-
-  if (adapter.extend == null) {
-    // verify the adapter has the required functions.
-
-    // TODO: figure out how we can make it throw type error if the adapter is missing a required function
-    // const REQUIRED_FUNCTIONS = [
-    //   "sequences",
-    //   "metadata",
-    //   "variations",
-    // ] satisfies NonNullable<MojiAdapterFunctionNames<MojiAdapter>>[];
-
-    // const missingFunctions = REQUIRED_FUNCTIONS.filter((fn) => adapter[fn] == null);
-    // if (missingFunctions.length > 0) {
-    //   throw new Error(`adapter ${adapter.name} is missing required functions: ${missingFunctions.join(", ")}`);
-    // }
+): AdapterHandler<
+  TType,
+  TExtraContext,
+  TContext,
+  TTransformOutput,
+  TAggregateOutput,
+  ReturnType<typeof handler.output>
+>;
+export function defineAdapterHandler<
+  TType extends AdapterHandlerType,
+  TExtraContext extends Record<string, unknown>,
+  TContext extends AdapterContext,
+  TTransformOutput,
+>(
+  handler: Omit<
+    AdapterHandler<TType, TExtraContext, TContext, TTransformOutput>,
+    "output"
+  > & {
+    output: OutputFn<TContext, TExtraContext, TTransformOutput, any>;
   }
-
-  return adapter;
+): AdapterHandler<
+  TType,
+  TExtraContext,
+  TContext,
+  TTransformOutput,
+  TTransformOutput,
+  ReturnType<typeof handler.output>
+>;
+export function defineAdapterHandler<
+  TType extends AdapterHandlerType,
+  TExtraContext extends Record<string, unknown>,
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput = TTransformOutput,
+  TOutput = TTransformOutput | TAggregateOutput,
+  TBuiltinParser extends BuiltinParser = BuiltinParser,
+  TParseOutput = GetParseOutputFromBuiltInParser<BuiltinParser>,
+>(handler: AdapterHandler<
+  TType,
+  TExtraContext,
+  TContext,
+  TTransformOutput,
+  TAggregateOutput,
+  TOutput,
+  TBuiltinParser,
+  TParseOutput
+>): AdapterHandler<
+    TType,
+    TExtraContext,
+    TContext,
+    TTransformOutput,
+    TAggregateOutput,
+    TOutput,
+    TBuiltinParser,
+    TParseOutput
+  > {
path: packages/adapters/src/define.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Refine `TParseOutput` defaulting behavior.**  
Since you have `TBuiltinParser` in the generics, defaulting `TParseOutput` to `GetParseOutputFromBuiltInParser<BuiltinParser>` may prevent customized parser types from evolving correctly. Consider referencing `TBuiltinParser` in that constraint.  

Proposed adjustment:
```diff
- TParseOutput = GetParseOutputFromBuiltInParser<BuiltinParser>
+ TParseOutput = GetParseOutputFromBuiltInParser<TBuiltinParser>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export function defineAdapterHandler<
  TType extends AdapterHandlerType,
  TExtraContext extends Record<string, unknown>,
  TContext extends AdapterContext,
  TTransformOutput,
  TAggregateOutput = TTransformOutput,
  TOutput = TTransformOutput | TAggregateOutput,
  TBuiltinParser extends BuiltinParser = BuiltinParser,
  TParseOutput = GetParseOutputFromBuiltInParser<TBuiltinParser>,
>(handler: AdapterHandler<
  TType,
  TExtraContext,
  TContext,
  TTransformOutput,
  TAggregateOutput,
  TOutput,
  TBuiltinParser,
  TParseOutput
>): AdapterHandler<
    TType,
    TExtraContext,
    TContext,
    TTransformOutput,
    TAggregateOutput,
    TOutput,
    TBuiltinParser,
    TParseOutput
  > {
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T04:13:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,94 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache, parse } from "@mojis/internal-utils";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<any> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "splitter") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add null check for handler.transform method.**

For consistency and to prevent potential runtime errors, add a null check for the transform method as well.


```diff
      // run transform for each data in list
+     if (typeof handler.transform !== 'function') {
+       throw new Error(`Handler for type "${type}" is missing the transform method`);
+     }
      const transformedData = await handler.transform(buildContext(ctx, {
        key,
      }), data);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      // run transform for each data in list
      if (typeof handler.transform !== 'function') {
        throw new Error(`Handler for type "${type}" is missing the transform method`);
      }
      const transformedData = await handler.transform(buildContext(ctx, {
        key,
      }), data);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T04:13:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,94 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache, parse } from "@mojis/internal-utils";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<any> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "splitter") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
+
+      console.log("transformedData", transformedData);
+
+      transformedDataList.push(transformedData);
+    }
+
+    // run aggregate
+    const aggregatedData = await handler.aggregate(buildContext(ctx, {
+      data: transformedDataList,
+    }), transformedDataList);
+
+    // run output
+    const output = await handler.output(buildContext(ctx, {
+      data: aggregatedData,
+    }), aggregatedData);
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add null check for handler.output method.**

Similar to the aggregate method, you should check if the output method exists before calling it to prevent runtime errors.


```diff
    // run output
+   if (typeof handler.output !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the output method`);
+   }
    const output = await handler.output(buildContext(ctx, {
      data: aggregatedData,
    }), aggregatedData);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    // run output
    if (typeof handler.output !== 'function') {
      throw new Error(`Handler for type "${type}" is missing the output method`);
    }
    const output = await handler.output(buildContext(ctx, {
      data: aggregatedData,
    }), aggregatedData);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T04:13:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,94 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache, parse } from "@mojis/internal-utils";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<any> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "splitter") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
+
+      console.log("transformedData", transformedData);
+
+      transformedDataList.push(transformedData);
+    }
+
+    // run aggregate
+    const aggregatedData = await handler.aggregate(buildContext(ctx, {
+      data: transformedDataList,
+    }), transformedDataList);
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Add null check for handler.aggregate method.**

The pipeline failure indicates that `handler.aggregate` might be undefined. Add a null check to prevent runtime errors.


```diff
    // run aggregate
+   if (typeof handler.aggregate !== 'function') {
+     throw new Error(`Handler for type "${type}" is missing the aggregate method`);
+   }
    const aggregatedData = await handler.aggregate(buildContext(ctx, {
      data: transformedDataList,
    }), transformedDataList);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    // run aggregate
    if (typeof handler.aggregate !== 'function') {
      throw new Error(`Handler for type "${type}" is missing the aggregate method`);
    }
    const aggregatedData = await handler.aggregate(buildContext(ctx, {
      data: transformedDataList,
    }), transformedDataList);
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 76-76: Cannot invoke an object which is possibly 'undefined'.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T04:13:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,94 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache, parse } from "@mojis/internal-utils";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<any> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "splitter") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
+
+      console.log("transformedData", transformedData);
+
+      transformedDataList.push(transformedData);
+    }
+
+    // run aggregate
+    const aggregatedData = await handler.aggregate(buildContext(ctx, {
+      data: transformedDataList,
+    }), transformedDataList);
+
+    // run output
+    const output = await handler.output(buildContext(ctx, {
+      data: aggregatedData,
+    }), aggregatedData);
+
+    return output;
+  }
+}
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Handle the case where no handlers execute.**

Currently, if none of the handlers in the group execute (all `shouldExecute` calls return false), the function will implicitly return `undefined`. This might not be expected if the caller is expecting a specific return type.


```diff
    return output;
  }
+ 
+ throw new Error(`No handler executed for type: ${type}`);
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    return output;
  }
  
  throw new Error(`No handler executed for type: ${type}`);
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T04:13:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,95 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache } from "@mojis/internal-utils";
+import { parse } from "@mojis/parsers";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<any> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "splitter") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
+
+      console.log("transformedData", transformedData);
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove debug console.log statement.**

Console logs should not be present in production code as they can clutter the console and potentially expose sensitive information.


```diff
-      console.log("transformedData", transformedData);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
// (The following line has been removed as per the review comment)
//      console.log("transformedData", transformedData);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T05:06:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,95 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache } from "@mojis/internal-utils";
+import { parse } from "@mojis/parsers";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing module @mojis/parsers blocks build.**

The pipeline failure indicates that `@mojis/parsers` cannot be resolved. Ensure it is installed or properly declared in your package configuration.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: CI</summary>

[error] 3-3: Build failed: Could not resolve '@mojis/parsers'.

---

[error] 3-3: TypeScript error TS2307: Cannot find module '@mojis/parsers' or its corresponding type declarations.

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T06:27:03Z
author_association: NONE
diff_hunk: @@ -0,0 +1,21 @@
+import { defineAdapterHandler } from "../../define";
+
+export const preAlignmentMetadataHandler = defineAdapterHandler({
+  type: "metadata",
+  shouldExecute: (ctx) => {
+    return isBeforeAlignment(ctx.emoji_version);
+  },
path: packages/adapters/src/handlers/pre-alignment/metadata.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Handler declaration looks good, but referenced function is undefined.**

The handler correctly uses `defineAdapterHandler` with a metadata type, but the `shouldExecute` method references `isBeforeAlignment` which isn't imported or defined in this file.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T08:13:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,21 @@
+import { defineAdapterHandler } from "../../define";
path: packages/adapters/src/handlers/pre-alignment/metadata.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Import looks good, but consider adding isBeforeAlignment.**

The file imports `defineAdapterHandler` but is missing an import for `isBeforeAlignment` which is used on line 6.

```diff
-import { defineAdapterHandler } from "../../define";
+import { defineAdapterHandler } from "../../define";
+import { isBeforeAlignment } from "../../utils"; // Adjust the import path as needed
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import { defineAdapterHandler } from "../../define";
import { isBeforeAlignment } from "../../utils"; // Adjust the import path as needed
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T08:13:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,21 @@
+import { defineAdapterHandler } from "../../define";
+
+export const preAlignmentMetadataHandler = defineAdapterHandler({
+  type: "metadata",
+  shouldExecute: (ctx) => {
+    return isBeforeAlignment(ctx.emoji_version);
+  },
+  urls: () => {
+    return undefined;
+  },
+  parser: "generic",
+  transform() {
+    return undefined;
+  },
+  output() {
+    return {
+      groups: [],
+      emojis: [],
+    };
+  },
+});
path: packages/adapters/src/handlers/pre-alignment/metadata.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider adding unit tests for the new handler.**

This new handler would benefit from unit tests to ensure it behaves correctly, particularly checking that `shouldExecute` processes the emoji version correctly.

---

🌐 Web query:

```web
What's the best practice for testing TypeScript adapter handlers?
```

💡 Result:

When testing TypeScript adapter handlers, follow these best practices to ensure robust, maintainable tests that validate both interface translation and error handling:

### **Core Testing Strategies**
1. **Isolate Dependencies with Mocks/Stubs**  
   Use Jest's mocking system to isolate the adapter from external services:  
   ```typescript
   // Mocking an external payment service
   const mockPaymentService = {
     processPayment: jest.fn().mockResolvedValue({ success: true })
   };
   
   test('Adapter converts legacy request format', async () => {
     const adapter = new PaymentAdapter(mockPaymentService);
     await adapter.handleLegacyRequest({ amount: 100 });
     expect(mockPaymentService.processPayment).toHaveBeenCalledWith(10000); // cents conversion
   });
   ```

2. **Validate Interface Contracts**  
   Use TypeScript type assertions to verify input/output formats:  
   ```typescript
   interface ModernAPIResponse {
     transactionId: string;
     status: 'success' | 'failure';
   }

   test('Adapter output matches modern API spec', () => {
     const response = adapter.convertResponse({ id: '123', ok: true });
     expect(response).toMatchObject<ModernAPIResponse>({
       transactionId: expect.any(String),
       status: 'success'
     });
   });
   ```

---

### **Error Handling & Edge Cases**
**Simulate Failure Scenarios**  
Test adapter behavior with error conditions and malformed data:
```typescript
test('Handles service timeouts', async () => {
  mockPaymentService.processPayment.mockRejectedValue(new Error('Timeout'));
  await expect(adapter.handleRequest({ amount: 50 }))
    .rejects.toThrow('Payment processing timed out');
});

test('Rejects invalid currency inputs', () => {
  expect(() => adapter.validateInput({ amount: 'USD100' }))
    .toThrowErrorMatchingInlineSnapshot(`"Invalid currency format"`);
});
```

---

### **Structural Best Practices**
1. **AAA Pattern (Arrange-Act-Assert)**  
   Structure tests for clarity:
   ```typescript
   describe('CurrencyAdapter', () => {
     let adapter: CurrencyAdapter;
     let mockFXService: jest.Mocked<FXService>;

     beforeEach(() => { // Arrange
       mockFXService = { getRate: jest.fn() };
       adapter = new CurrencyAdapter(mockFXService);
     });

     test('Converts EUR to USD correctly', async () => {
       mockFXService.getRate.mockResolvedValue(1.18); // Arrange
       
       const result = await adapter.convert(100, 'EUR', 'USD'); // Act
       
       expect(result).toEqual(118); // Assert
       expect(mockFXService.getRate).toHaveBeenCalledWith('EUR', 'USD');
     });
   });
   ```

2. **Leverage TypeScript Utility Types**  
   Use `Partial<T>` and `jest.Mocked<T>` for flexible mocking:
   ```typescript
   type PartialLogger = Partial<Console> & {
     error: jest.MockedFunction<Console['error']>
   };

   const mockLogger: PartialLogger = {
     error: jest.fn()
   };
   ```

---

### **Tooling Configuration**  
1. **Jest + TypeScript Setup**  
   Essential `jest.config.js`:
   ```javascript
   module.exports = {
     preset: 'ts-jest',
     testEnvironment: 'node',
     testRegex: '/tests/.*\\.(test|spec)\\.ts$',
     moduleNameMapper: {
       '^@adapters/(.*)$': '<rootDir>/src/adapters/$1'
     }
   };
   ```

2. **Test Coverage for Type Guards**  
   Ensure type predicates are validated:
   ```typescript
   function isLegacyResponse(obj: any): obj is LegacyResponse {
     return 'statusCode' in obj && 'body' in obj;
   }

   test('Type guard detects legacy responses', () => {
     expect(isLegacyResponse({ statusCode: 200, body: {} })).toBe(true);
     expect(isLegacyResponse({ status: 'ok' })).toBe(false);
   });
   ```

---

By combining strict type validation with scenario-based testing, you ensure adapters correctly bridge interface gaps while maintaining type safety and error resilience[1][4][7]. Focus on testing both successful conversions and failure propagation to create robust adapter implementations.

Citations:

- 1: https://testomat.io/blog/typescript-best-practices-tools-for-qa-engineer/
- 2: https://anuj1.hashnode.dev/testing-with-typescript
- 3: https://forum.heroiclabs.com/t/unit-testing-typescript-rpc-and-match-handlers/2639
- 4: https://github.com/goldbergyoni/javascript-testing-best-practices
- 5: https://www.testim.io/blog/typescript-unit-testing-101/
- 6: https://www.youtube.com/watch?v=HyqHhjKMnyE
- 7: https://applitools.com/blog/typescript-is-not-only-for-developers-anymore/
- 8: https://docs.aws.amazon.com/prescriptive-guidance/latest/best-practices-cdk-typescript-iac/typescript-best-practices.html
- 9: https://dev.to/sovannaro/typescript-best-practices-2025-elevate-your-code-quality-1gh3
- 10: https://refactoring.guru/design-patterns/adapter/typescript/example

---



</details>

**Unit Test Coverage Enhancement for preAlignmentMetadataHandler**

The implementation looks clean; however, to ensure robust behavior—particularly for the `shouldExecute` function that processes `emoji_version`—please add comprehensive unit tests. Consider the following testing strategies based on TypeScript adapter handler best practices:

- **Isolate Dependencies:** Use Jest mocks/stubs (if any dependencies are introduced later) to isolate `shouldExecute` from external influences.
- **AAA Pattern:** Organize your tests in an Arrange-Act-Assert structure. For example, feed various `emoji_version` values to the handler and assert that `shouldExecute` responds correctly.
- **Interface Validation:** Verify that the handler’s `output()` conforms to the expected structure (i.e., empty groups and emojis).
- **Error and Edge Cases:** If applicable, add tests to simulate any unexpected or edge case inputs for improved resilience.

Following these best practices will boost confidence in the adapter’s correctness and maintainability.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T08:13:16Z
author_association: NONE
diff_hunk: @@ -1,9 +1,20 @@
+import semver from "semver";
+
 /**
  * Checks if a version is less than "13.0.0".
  *
  * @param {string} version - The version string to check
  * @returns {boolean} `true` if the version is less than "11.0.0", `false` otherwise
  */
path: packages/internal-utils/src/predicates.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Update JSDoc to match implementation.**

The JSDoc comment states that the function checks if a version is less than "11.0.0", but the implementation actually checks if the major version is less than 13. Additionally, the JSDoc doesn't mention the null checking or version coercion behavior that has been added.


```diff
 /**
  * Checks if a version is less than "13.0.0".
  *
  * @param {string} version - The version string to check
- * @returns {boolean} `true` if the version is less than "11.0.0", `false` otherwise
+ * @returns {boolean} `true` if the version is less than "13.0.0", `false` otherwise or if version is null/invalid
  */
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
/**
 * Checks if a version is less than "13.0.0".
 *
 * @param {string} version - The version string to check
 * @returns {boolean} `true` if the version is less than "13.0.0", `false` otherwise or if version is null/invalid
 */
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T08:49:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,34 @@
+import { defineAdapterHandler } from "../../define";
+
+export const modernSequenceHandler = defineAdapterHandler({
+  type: "sequence",
+  urls: (ctx) => {
+    return [
+      {
+        key: "sequences",
+        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-sequences.txt`,
+        cacheKey: `v${ctx.emoji_version}/sequences`,
+      },
+      {
+        key: "zwj",
+        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-zwj-sequences.txt`,
+        cacheKey: `v${ctx.emoji_version}/zwj-sequences`,
+      },
+    ];
+  },
+  parser: "generic",
+  shouldExecute: (ctx) => {
+    return ctx.emoji_version === "16.0";
+  },
+  transform(ctx, data) {
+    console.warn("key", ctx.key);
+    return data;
+  },
+  aggregate(ctx, data) {
+    console.log("aggregate", data);
+    return data.flat();
+  },
+  output(_ctx, transformed) {
+    return transformed;
+  },
+});
path: packages/adapters/src/handlers/modern/sequence.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add type safety and remove console output statements.**

While the handler implementation is functionally correct, there are several opportunities for improvement:

1. The handler is too restrictive as it only executes for emoji version "16.0" - consider making this more flexible for future versions
2. Console statements should be removed in production code
3. No meaningful transformation is happening in the transform method


```diff
export const modernSequenceHandler = defineAdapterHandler({
  type: "sequence",
  urls: (ctx) => {
    return [
      {
        key: "sequences",
        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-sequences.txt`,
        cacheKey: `v${ctx.emoji_version}/sequences`,
      },
      {
        key: "zwj",
        url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-zwj-sequences.txt`,
        cacheKey: `v${ctx.emoji_version}/zwj-sequences`,
      },
    ];
  },
  parser: "generic",
  shouldExecute: (ctx) => {
-   return ctx.emoji_version === "16.0";
+   return semver.gte(semver.coerce(ctx.emoji_version) || "0.0.0", "16.0.0");
  },
  transform(ctx, data) {
-   console.warn("key", ctx.key);
    return data;
  },
  aggregate(ctx, data) {
-   console.log("aggregate", data);
    return data.flat();
  },
  output(_ctx, transformed) {
    return transformed;
  },
});
```

Remember to import `semver` at the top of the file:
```typescript
import semver from "semver";
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T09:05:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,49 @@
+import { defineAdapterHandler } from "../../define";
+
+// These emoji versions doesn't seem to have a emoji-test,
+// where we can extract the metadata from.
+const DISALLOWED_EMOJI_VERSIONS = ["1.0", "2.0", "3.0"];
+
+export const modernMetadataHandler = defineAdapterHandler({
+  type: "metadata",
+  shouldExecute: (ctx) => {
+    return !DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version);
+  },
+  urls: (ctx) => {
+    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
+      return undefined;
+    }
+
+    return {
+      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
+      cacheKey: `v${ctx.emoji_version}/metadata`,
+    };
+  },
+  parser: "generic",
+  parserOptions: {
+    separator: ";",
+  },
+  transform(ctx, data) {
+    console.warn("key", ctx.key);
+
+    console.log(data);
+
+    for (const line of data.lines) {
+      console.log(line);
+    }
+
+    return {
+      foo: "bar",
+    };
+  },
+  aggregate(ctx, data) {
+    return data[0];
+  },
+  output(_ctx, transformed) {
+    console.warn("transformed.aggregatedFoo", transformed.foo);
+    return {
+      groups: [],
+      emojis: [],
+    };
+  },
+});
path: packages/adapters/src/handlers/modern/metadata.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Remove debug logging and implement actual transformation.**

The handler has several issues:

1. Multiple console logging statements should be removed from production code
2. The transform method returns a hardcoded object `{ foo: "bar" }` instead of processing the input data
3. The output method returns empty arrays regardless of input


```diff
export const modernMetadataHandler = defineAdapterHandler({
  type: "metadata",
  shouldExecute: (ctx) => {
    return !DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version);
  },
  urls: (ctx) => {
    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
      return undefined;
    }

    return {
      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
      cacheKey: `v${ctx.emoji_version}/metadata`,
    };
  },
  parser: "generic",
  parserOptions: {
    separator: ";",
  },
  transform(ctx, data) {
-   console.warn("key", ctx.key);
-
-   console.log(data);
-
-   for (const line of data.lines) {
-     console.log(line);
-   }
-
-   return {
-     foo: "bar",
-   };
+   // Process the data and extract relevant emoji metadata
+   // This is a simplified example - implement actual transformation logic
+   const metadata = data.lines.map(line => ({
+     raw: line,
+     // Add additional processing as needed
+   }));
+   
+   return metadata;
  },
  aggregate(ctx, data) {
    return data[0];
  },
  output(_ctx, transformed) {
-   console.warn("transformed.aggregatedFoo", transformed.foo);
+   // Process the transformed data into groups and emojis
    return {
-     groups: [],
-     emojis: [],
+     groups: [], // Populate with actual group data
+     emojis: transformed || [], // Use transformed data or default to empty array
    };
  },
});
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export const modernMetadataHandler = defineAdapterHandler({
  type: "metadata",
  shouldExecute: (ctx) => {
    return !DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version);
  },
  urls: (ctx) => {
    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
      return undefined;
    }

    return {
      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
      cacheKey: `v${ctx.emoji_version}/metadata`,
    };
  },
  parser: "generic",
  parserOptions: {
    separator: ";",
  },
  transform(ctx, data) {
    // Process the data and extract relevant emoji metadata
    // This is a simplified example - implement actual transformation logic
    const metadata = data.lines.map(line => ({
      raw: line,
      // Add additional processing as needed
    }));
    
    return metadata;
  },
  aggregate(ctx, data) {
    return data[0];
  },
  output(_ctx, transformed) {
    // Process the transformed data into groups and emojis
    return {
      groups: [], // Populate with actual group data
      emojis: transformed || [], // Use transformed data or default to empty array
    };
  },
});
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T09:05:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,97 @@
+import type { AdapterContext, AdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache } from "@mojis/internal-utils";
+import { parse } from "@mojis/parsers";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<any> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "generic") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
+
+      console.log("transformedData", transformedData);
+
+      transformedDataList.push(transformedData);
+    }
+
+    // run aggregate
+    const aggregatedData = await handler.aggregate(buildContext(ctx, {
+      data: transformedDataList,
+    }), transformedDataList);
+
+    // run output
+    const output = await handler.output(buildContext(ctx, {
+      data: aggregatedData,
+    }), aggregatedData);
+
+    return output;
+  }
+
+  throw new Error(`No handler found for type: ${type}`);
+}
+
+function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
+  ctx: TContext,
+  extraContext: TExtraContext,
+): TContext {
+  return Object.assign(ctx, extraContext);
+}
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Avoid mutating the original context.**

The `buildContext` function currently modifies the original context object by using `Object.assign` with the original object as the target. This can lead to unexpected side effects, as the original context is being mutated. Instead, create a new object that combines the properties of both objects.


```diff
function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
  ctx: TContext,
  extraContext: TExtraContext,
): TContext {
-  return Object.assign(ctx, extraContext);
+  return Object.assign({}, ctx, extraContext);
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
function buildContext<TContext extends AdapterContext, TExtraContext extends Record<string, unknown>>(
  ctx: TContext,
  extraContext: TExtraContext,
): TContext {
  return Object.assign({}, ctx, extraContext);
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-07T09:05:40Z
author_association: NONE
diff_hunk: @@ -170,48 +160,44 @@ cli.command(
         )));
       }
 
-      if (isGeneratorEnabled("sequences")) {
-        if (adapter.sequences == null) {
-          throw new MojisNotImplemented("sequences");
-        }
-
-        const { sequences, zwj } = await runAdapterHandler(adapter, "sequences", {
-          force,
-          emoji_version: version.emoji_version,
-          unicode_version: version.unicode_version,
-        });
+      // if (isGeneratorEnabled("sequences")) {
+      //   const { sequences, zwj } = await runAdapterHandler("sequence", {
+      //     force,
+      //     emoji_version: version.emoji_version,
+      //     unicode_version: version.unicode_version,
+      //   });
 
-        await fs.writeFile(
-          join(baseDir, "zwj-sequences.json"),
-          JSON.stringify(zwj, null, 2),
-          "utf-8",
-        );
+      //   await fs.writeFile(
+      //     join(baseDir, "zwj-sequences.json"),
+      //     JSON.stringify(zwj, null, 2),
+      //     "utf-8",
+      //   );
 
-        await fs.writeFile(
-          join(baseDir, "sequences.json"),
-          JSON.stringify(sequences, null, 2),
-          "utf-8",
-        );
-      }
+      //   await fs.writeFile(
+      //     join(baseDir, "sequences.json"),
+      //     JSON.stringify(sequences, null, 2),
+      //     "utf-8",
+      //   );
+      // }
 
-      if (isGeneratorEnabled("variations")) {
-        if (adapter.variations == null) {
-          throw new MojisNotImplemented("variations");
-        }
+      // if (isGeneratorEnabled("variations")) {
+      //   if (adapter.variations == null) {
+      //     throw new MojisNotImplemented("variations");
+      //   }
 
-        const variations = await runAdapterHandler(adapter, "variations", {
-          force,
-          emoji_version: version.emoji_version,
-          unicode_version: version.unicode_version,
-        });
+      //   const variations = await runAdapterHandler(adapter, "variations", {
+      //     force,
+      //     emoji_version: version.emoji_version,
+      //     unicode_version: version.unicode_version,
+      //   });
 
-        await fs.ensureDir(`./data/v${version.emoji_version}`);
-        await fs.writeFile(
-          `./data/v${version.emoji_version}/variations.json`,
-          JSON.stringify(variations, null, 2),
-          "utf-8",
-        );
-      }
+      //   await fs.ensureDir(`./data/v${version.emoji_version}`);
+      //   await fs.writeFile(
+      //     `./data/v${version.emoji_version}/variations.json`,
+      //     JSON.stringify(variations, null, 2),
+      //     "utf-8",
+      //   );
+      // }
path: packages/cli/src/cli.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove commented out code**

Large blocks of commented code should be removed rather than kept in the codebase. If this functionality is temporarily disabled but will be re-enabled later, consider using feature flags instead of comments.

```diff
-      // if (isGeneratorEnabled("sequences")) {
-      //   const { sequences, zwj } = await runAdapterHandler("sequence", {
-      //     force,
-      //     emoji_version: version.emoji_version,
-      //     unicode_version: version.unicode_version,
-      //   });
-
-      //   await fs.writeFile(
-      //     join(baseDir, "zwj-sequences.json"),
-      //     JSON.stringify(zwj, null, 2),
-      //     "utf-8",
-      //   );
-
-      //   await fs.writeFile(
-      //     join(baseDir, "sequences.json"),
-      //     JSON.stringify(sequences, null, 2),
-      //     "utf-8",
-      //   );
-      // }
-
-      // if (isGeneratorEnabled("variations")) {
-      //   if (adapter.variations == null) {
-      //     throw new MojisNotImplemented("variations");
-      //   }
-
-      //   const variations = await runAdapterHandler(adapter, "variations", {
-      //     force,
-      //     emoji_version: version.emoji_version,
-      //     unicode_version: version.unicode_version,
-      //   });
-
-      //   await fs.ensureDir(`./data/v${version.emoji_version}`);
-      //   await fs.writeFile(
-      //     `./data/v${version.emoji_version}/variations.json`,
-      //     JSON.stringify(variations, null, 2),
-      //     "utf-8",
-      //   );
-      // }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T08:52:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,100 @@
+import type { AdapterContext, AdapterHandlerType, InferOutputFromAdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache } from "@mojis/internal-utils";
+import { parse } from "@mojis/parsers";
+import { all_handlers } from "./_handlers";
+import { getHandlerUrls, isBuiltinParser, isUrlBuilder } from "./utils";
+
+export async function runAdapterHandler<
+  THandlerType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TExtraContext extends Record<string, unknown>,
+>(
+  type: THandlerType,
+  ctx: TContext & TExtraContext,
+): Promise<InferOutputFromAdapterHandlerType<THandlerType>> {
+  const handlerGroup = all_handlers[type];
+
+  if (!handlerGroup) {
+    throw new Error(`No handler found for type: ${type}`);
+  }
+
+  for (const handler of handlerGroup) {
+    // should we execute?
+    const shouldExecute = await handler.shouldExecute(ctx);
+
+    if (!shouldExecute) {
+      continue;
+    }
+
+    const urls = await getHandlerUrls(handler.urls, ctx);
+    // fetch all the data from the urls
+    const dataRequests = urls.map(async (url) => {
+      const key = url.cacheKey;
+      const result = await fetchCache(url.url, {
+        cacheKey: url.cacheKey,
+        parser(data) {
+          if (isBuiltinParser(handler.parser)) {
+            if (handler.parser !== "generic") {
+              throw new Error(`Parser "${handler.parser}" is not implemented.`);
+            }
+
+            const separator = handler.parserOptions?.separator ?? ";";
+            return parse(data, {
+              separator,
+              commentPrefix: "#",
+            });
+          }
+
+          return handler.parser(ctx, data);
+        },
+        ttl: handler.cacheOptions?.ttl,
+        cacheFolder: handler.cacheOptions?.cacheFolder,
+        encoding: handler.cacheOptions?.encoding,
+        options: handler.fetchOptions,
+        bypassCache: ctx.force,
+      });
+      return [key, result] as [string, typeof result];
+    });
+
+    const dataResults = await Promise.all(dataRequests);
+
+    const transformedDataList: any[] = [];
+
+    // run transform for each data in list
+    for (const [key, data] of dataResults) {
+      // run transform for each data in list
+
+      const transformedData = await handler.transform(buildContext(ctx, {
+        key,
+      }), data);
+
+      transformedDataList.push(transformedData);
+    }
+
+    // only run aggregate if defined, but still call output
+    if (!handler.aggregate) {
+      const data = transformedDataList.length === 1 ? transformedDataList[0] : transformedDataList;
+      return await handler.output(buildContext(ctx, {}), data);
+    }
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Handle empty transformedDataList case**

The code doesn't handle the case where `transformedDataList` is empty. This could lead to unexpected behavior.

```diff
    // only run aggregate if defined, but still call output
    if (!handler.aggregate) {
+     if (transformedDataList.length === 0) {
+       throw new Error(`No data transformed for handler type: ${type}`);
+     }
      const data = transformedDataList.length === 1 ? transformedDataList[0] : transformedDataList;
      return await handler.output(buildContext(ctx, {}), data);
    }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    // only run aggregate if defined, but still call output
    if (!handler.aggregate) {
      if (transformedDataList.length === 0) {
        throw new Error(`No data transformed for handler type: ${type}`);
      }
      const data = transformedDataList.length === 1 ? transformedDataList[0] : transformedDataList;
      return await handler.output(buildContext(ctx, {}), data);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T08:52:01Z
author_association: NONE
diff_hunk: @@ -1,149 +1,177 @@
 import type {
   EmojiGroup,
   EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { ParseResult } from "@mojis/parsers";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
-  /**
-   * The name of the adapter.
-   */
-  name: string;
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
 
+export interface AdapterContext {
   /**
-   * A description of the adapter.
+   * The emoji version.
    */
-  description: string;
+  emoji_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  range: string;
+  unicode_version: string;
 
   /**
-   * The name of the adapter to extend from.
+   * Whether or not the force mode was enabled.
    */
-  extend?: string;
+  force: boolean;
+}
 
+export type AdapterHandlerType = "metadata" | "sequence" | "variation";
+
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The metadata handler for the adapter.
+   * The url to fetch the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  url: string;
 
   /**
-   * The sequences handler for the adapter.
+   * The cache key for the data.
    */
-  sequences?: AdapterHandler<
-    TSequencesUrlReturn,
-    ExtraContext<TSequencesUrlReturn>,
-    EmojiSequence[],
-    any
-  >;
+  cacheKey: string;
 
   /**
-   * The variations handler for the adapter.
+   * The key to identify the data.
+   *
+   * NOTE:
+   * If not set it will be generated from the cache key.
    */
-  variations?: AdapterHandler<
-    TVariationsUrlReturn,
-    ExtraContext<TVariationsUrlReturn>,
-    EmojiVariation[],
-    any
-  >;
+  key?: string;
 }
 
-type ExtraContext<T> = {
-  key: string;
-} & (T extends CacheableUrlRequest
-  ? T["extraContext"] extends Record<string, unknown>
-    ? T["extraContext"]
-    : Record<string, never>
-  : Record<string, never>);
+export type TransformFn<
+  TContext extends AdapterContext,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
+
+export type OutputFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TOutput,
+> = (ctx: TContext, data: TTransformOutput) => TOutput;
+
+export type BuiltinParser = typeof BUILTIN_PARSERS[number];
 
-export interface CacheableUrlRequest {
+export type ParserFn<
+  TContext extends AdapterContext,
+  TOutput,
+> = (ctx: TContext, data: string) => TOutput;
+
+export type GetParseOutputFromBuiltInParser<TParser extends string> =
+  TParser extends "generic" ? ParseResult :
+    never;
+
+export type GetParseOptionsFromParser<TParser extends string> =
+  TParser extends "generic" ? { separator: string } :
+    never;
+
+export interface AdapterHandler<
+  TType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput = TTransformOutput,
+  TOutput = TTransformOutput | TAggregateOutput,
+  TBuiltinParser extends BuiltinParser = BuiltinParser,
+  TParseOutput = GetParseOutputFromBuiltInParser<TBuiltinParser>,
+> {
   /**
-   * The key for the data.
-   * If not provided, a key will be generated based on the url.
+   * The type of the handler, this is used to identify the handler.
    */
-  key?: string;
+  type: TType;
 
   /**
-   * The url to fetch the data
-   * @example "https://example.com/data.json"
+   * The urls that will be fetched.
    */
-  url: string;
+  urls: Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder;
 
   /**
-   * The cache key for the data.
-   * If not provided, a key will be generated based on the url.
+   * Options that will be passed to the fetch function.
    */
-  cacheKey?: string;
+  fetchOptions?: RequestInit;
 
   /**
-   * Extra data to be passed to the handler.
-   * @default {}
+   * Options that will be passed to the cache function.
    */
-  extraContext?: Record<string, unknown>;
+  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
 
   /**
-   * The fetch options for the request.
-   * @default {}
-   *
-   * NOTE:
-   * This will be merged together with the `fetchOptions` of the handler.
+   * Whether or not that this handler should handle the request.
    */
-  fetchOptions?: RequestInit;
+  shouldExecute: ShouldExecute<TContext>;
 
   /**
-   * The cache options for the data.
-   * @default {}
+   * A parse function or a reference to a builtin parser.
    *
-   * NOTE:
-   * This will be merged together with the `cacheOptions` of the handler.
    */
-  cacheOptions?: Omit<WriteCacheOptions<any>, "transform">;
-}
-
-export type CacheableUrlRequestReturnType = CacheableUrlRequest | CacheableUrlRequest[] | undefined;
+  parser: ParserFn<TContext, TParseOutput> | TBuiltinParser;
 
-export type ExtractDataTypeFromUrls<T extends CacheableUrlRequestReturnType> =
-T extends undefined ? undefined :
-  T extends CacheableUrlRequest ? string :
-    T extends CacheableUrlRequest[] ? string :
-      never;
+  /**
+   * Options that will be passed to the parser.
+   * This will only be used if the parser is a builtin parser.
+   */
+  parserOptions?: GetParseOptionsFromParser<TBuiltinParser>;
 
-export interface AdapterHandler<
-  TUrlsReturn extends CacheableUrlRequestReturnType,
-  TExtraContext extends Record<string, unknown>,
-  TTransformOutput,
-  TOutput = TTransformOutput,
-> {
-  urls: (ctx: AdapterContext) => Promise<TUrlsReturn> | TUrlsReturn;
-  fetchOptions?: RequestInit;
-  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
-  transform: (ctx: AdapterContext & TExtraContext, data: ExtractDataTypeFromUrls<TUrlsReturn>) => TTransformOutput;
-  aggregate?: (ctx: AdapterContext, data: [TTransformOutput, ...TTransformOutput[]]) => TOutput;
-}
+  /**
+   * A transform function that will run for each of the urls.
+   * It will pass the returned type downwards to either the aggregate function or the output.
+   */
+  transform: TransformFn<TContext & {
+    key: string;
+  }, TParseOutput, TTransformOutput>;
 
-export type AdapterHandlers<TAdapter = MojiAdapter<any, any, any>> = {
-  [K in keyof TAdapter]: NonNullable<TAdapter[K]> extends AdapterHandler<any, any, any, any> ? K : never;
-}[keyof TAdapter];
+  /**
+   * An aggregate function that will run if defined.
+   * It will receive all the transformed results as parameters, which it can then aggregate the results into a final output.
+   */
+  aggregate?: AggregateFn<TContext, TTransformOutput, TAggregateOutput>;
 
-export interface AdapterContext {
-  force: boolean;
-  emoji_version: string;
-  unicode_version: string;
+  /**
+   * Output function which will receive the transformed result from the transform function, or from the aggregate function.
+   * It will then output the result.
+   */
+  output: OutputFn<TContext, TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput, TOutput>;
 }
+
+export type MetadataAdapterHandler = AdapterHandler<
+  "metadata", // type
+  AdapterContext, // context
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }, // transform output
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }, // aggregate output
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }, // output
+  never, // parser
+  { lines: string[] }
+>;
+
+export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> = THandlerType extends "metadata" ? ReturnType<MetadataAdapterHandler["output"]> : never;
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**The InferOutputFromAdapterHandlerType should handle all handler types.**

The type only handles "metadata" but the `AdapterHandlerType` includes "sequence" and "variation" as well. This could lead to type errors when using these handlers.


```diff
- export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> = THandlerType extends "metadata" ? ReturnType<MetadataAdapterHandler["output"]> : never;
+ export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> = 
+   THandlerType extends "metadata" ? ReturnType<MetadataAdapterHandler["output"]> : 
+   THandlerType extends "sequence" ? unknown : // Define appropriate return type
+   THandlerType extends "variation" ? unknown : // Define appropriate return type
+   never;
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export type InferOutputFromAdapterHandlerType<THandlerType extends AdapterHandlerType> = 
  THandlerType extends "metadata" ? ReturnType<MetadataAdapterHandler["output"]> : 
  THandlerType extends "sequence" ? unknown : // Define appropriate return type
  THandlerType extends "variation" ? unknown : // Define appropriate return type
  never;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T09:30:29Z
author_association: NONE
diff_hunk: @@ -1,149 +1,177 @@
 import type {
   EmojiGroup,
   EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { ParseResult } from "@mojis/parsers";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
-  /**
-   * The name of the adapter.
-   */
-  name: string;
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
 
+export interface AdapterContext {
   /**
-   * A description of the adapter.
+   * The emoji version.
    */
-  description: string;
+  emoji_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  range: string;
+  unicode_version: string;
 
   /**
-   * The name of the adapter to extend from.
+   * Whether or not the force mode was enabled.
    */
-  extend?: string;
+  force: boolean;
+}
 
+export type AdapterHandlerType = "metadata" | "sequence" | "variation";
+
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The metadata handler for the adapter.
+   * The url to fetch the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  url: string;
 
   /**
-   * The sequences handler for the adapter.
+   * The cache key for the data.
    */
-  sequences?: AdapterHandler<
-    TSequencesUrlReturn,
-    ExtraContext<TSequencesUrlReturn>,
-    EmojiSequence[],
-    any
-  >;
+  cacheKey: string;
 
   /**
-   * The variations handler for the adapter.
+   * The key to identify the data.
+   *
+   * NOTE:
+   * If not set it will be generated from the cache key.
    */
-  variations?: AdapterHandler<
-    TVariationsUrlReturn,
-    ExtraContext<TVariationsUrlReturn>,
-    EmojiVariation[],
-    any
-  >;
+  key?: string;
 }
 
-type ExtraContext<T> = {
-  key: string;
-} & (T extends CacheableUrlRequest
-  ? T["extraContext"] extends Record<string, unknown>
-    ? T["extraContext"]
-    : Record<string, never>
-  : Record<string, never>);
+export type TransformFn<
+  TContext extends AdapterContext,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**The AggregateFn type has a potential issue with empty arrays.**

The `AggregateFn` type assumes there will be at least one item in the data array through the `[TTransformOutput, ...TTransformOutput[]]` syntax, but the implementation might receive an empty array if no data is transformed.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check implementation of aggregate method to see if it handles empty arrays
rg -n "aggregate.*\(.*\).*\{" --type ts --glob "packages/adapters/src/**"
```

Length of output: 232

---



</details>

**Attention: Potential Empty Array Handling in AggregateFn**

The current type definition for `AggregateFn` enforces that the `data` parameter is a non-empty tuple (`[TTransformOutput, ...TTransformOutput[]]`), but our implementations in:

- `packages/adapters/src/handlers/modern/variation.ts` (line ~27)
- `packages/adapters/src/handlers/modern/sequence.ts` (line ~27)

do not appear to include explicit checks for an empty array. This mismatch can be problematic if the function is ever invoked with an empty array when no data is transformed.

**Recommendations:**
- **Type Revision:** Consider updating the `AggregateFn` type to allow empty arrays (e.g. use `TTransformOutput[]`) if empty data is possible.
- **Implementation Safeguard:** Alternatively, if empty arrays are unintended, add early runtime checks in the `aggregate` function implementations and provide error handling or appropriate defaults.
- **Documentation:** Clarify in both the type definition and code comments the expected behavior regarding empty data arrays.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T09:30:29Z
author_association: NONE
diff_hunk: @@ -1,149 +1,177 @@
 import type {
   EmojiGroup,
   EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { ParseResult } from "@mojis/parsers";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
-  /**
-   * The name of the adapter.
-   */
-  name: string;
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
 
+export interface AdapterContext {
   /**
-   * A description of the adapter.
+   * The emoji version.
    */
-  description: string;
+  emoji_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  range: string;
+  unicode_version: string;
 
   /**
-   * The name of the adapter to extend from.
+   * Whether or not the force mode was enabled.
    */
-  extend?: string;
+  force: boolean;
+}
 
+export type AdapterHandlerType = "metadata" | "sequence" | "variation";
+
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The metadata handler for the adapter.
+   * The url to fetch the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  url: string;
 
   /**
-   * The sequences handler for the adapter.
+   * The cache key for the data.
    */
-  sequences?: AdapterHandler<
-    TSequencesUrlReturn,
-    ExtraContext<TSequencesUrlReturn>,
-    EmojiSequence[],
-    any
-  >;
+  cacheKey: string;
 
   /**
-   * The variations handler for the adapter.
+   * The key to identify the data.
+   *
+   * NOTE:
+   * If not set it will be generated from the cache key.
    */
-  variations?: AdapterHandler<
-    TVariationsUrlReturn,
-    ExtraContext<TVariationsUrlReturn>,
-    EmojiVariation[],
-    any
-  >;
+  key?: string;
 }
 
-type ExtraContext<T> = {
-  key: string;
-} & (T extends CacheableUrlRequest
-  ? T["extraContext"] extends Record<string, unknown>
-    ? T["extraContext"]
-    : Record<string, never>
-  : Record<string, never>);
+export type TransformFn<
+  TContext extends AdapterContext,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
+
+export type OutputFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TOutput,
+> = (ctx: TContext, data: TTransformOutput) => TOutput;
+
+export type BuiltinParser = typeof BUILTIN_PARSERS[number];
 
-export interface CacheableUrlRequest {
+export type ParserFn<
+  TContext extends AdapterContext,
+  TOutput,
+> = (ctx: TContext, data: string) => TOutput;
+
+export type GetParseOutputFromBuiltInParser<TParser extends string> =
+  TParser extends "generic" ? ParseResult :
+    never;
+
+export type GetParseOptionsFromParser<TParser extends string> =
+  TParser extends "generic" ? { separator: string } :
+    never;
+
+export interface AdapterHandler<
+  TType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput = TTransformOutput,
+  TOutput = TTransformOutput | TAggregateOutput,
+  TBuiltinParser extends BuiltinParser = BuiltinParser,
+  TParseOutput = GetParseOutputFromBuiltInParser<TBuiltinParser>,
+> {
   /**
-   * The key for the data.
-   * If not provided, a key will be generated based on the url.
+   * The type of the handler, this is used to identify the handler.
    */
-  key?: string;
+  type: TType;
 
   /**
-   * The url to fetch the data
-   * @example "https://example.com/data.json"
+   * The urls that will be fetched.
    */
-  url: string;
+  urls: Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder;
 
   /**
-   * The cache key for the data.
-   * If not provided, a key will be generated based on the url.
+   * Options that will be passed to the fetch function.
    */
-  cacheKey?: string;
+  fetchOptions?: RequestInit;
 
   /**
-   * Extra data to be passed to the handler.
-   * @default {}
+   * Options that will be passed to the cache function.
    */
-  extraContext?: Record<string, unknown>;
+  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
 
   /**
-   * The fetch options for the request.
-   * @default {}
-   *
-   * NOTE:
-   * This will be merged together with the `fetchOptions` of the handler.
+   * Whether or not that this handler should handle the request.
    */
-  fetchOptions?: RequestInit;
+  shouldExecute: ShouldExecute<TContext>;
 
   /**
-   * The cache options for the data.
-   * @default {}
+   * A parse function or a reference to a builtin parser.
    *
-   * NOTE:
-   * This will be merged together with the `cacheOptions` of the handler.
    */
-  cacheOptions?: Omit<WriteCacheOptions<any>, "transform">;
-}
-
-export type CacheableUrlRequestReturnType = CacheableUrlRequest | CacheableUrlRequest[] | undefined;
+  parser: ParserFn<TContext, TParseOutput> | TBuiltinParser;
 
-export type ExtractDataTypeFromUrls<T extends CacheableUrlRequestReturnType> =
-T extends undefined ? undefined :
-  T extends CacheableUrlRequest ? string :
-    T extends CacheableUrlRequest[] ? string :
-      never;
+  /**
+   * Options that will be passed to the parser.
+   * This will only be used if the parser is a builtin parser.
+   */
+  parserOptions?: GetParseOptionsFromParser<TBuiltinParser>;
 
-export interface AdapterHandler<
-  TUrlsReturn extends CacheableUrlRequestReturnType,
-  TExtraContext extends Record<string, unknown>,
-  TTransformOutput,
-  TOutput = TTransformOutput,
-> {
-  urls: (ctx: AdapterContext) => Promise<TUrlsReturn> | TUrlsReturn;
-  fetchOptions?: RequestInit;
-  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
-  transform: (ctx: AdapterContext & TExtraContext, data: ExtractDataTypeFromUrls<TUrlsReturn>) => TTransformOutput;
-  aggregate?: (ctx: AdapterContext, data: [TTransformOutput, ...TTransformOutput[]]) => TOutput;
-}
+  /**
+   * A transform function that will run for each of the urls.
+   * It will pass the returned type downwards to either the aggregate function or the output.
+   */
+  transform: TransformFn<TContext & {
+    key: string;
+  }, TParseOutput, TTransformOutput>;
 
-export type AdapterHandlers<TAdapter = MojiAdapter<any, any, any>> = {
-  [K in keyof TAdapter]: NonNullable<TAdapter[K]> extends AdapterHandler<any, any, any, any> ? K : never;
-}[keyof TAdapter];
+  /**
+   * An aggregate function that will run if defined.
+   * It will receive all the transformed results as parameters, which it can then aggregate the results into a final output.
+   */
+  aggregate?: AggregateFn<TContext, TTransformOutput, TAggregateOutput>;
 
-export interface AdapterContext {
-  force: boolean;
-  emoji_version: string;
-  unicode_version: string;
+  /**
+   * Output function which will receive the transformed result from the transform function, or from the aggregate function.
+   * It will then output the result.
+   */
+  output: OutputFn<TContext, TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput, TOutput>;
 }
+
+export type MetadataAdapterHandler = AdapterHandler<
+  "metadata", // type
+  AdapterContext, // context
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }, // transform output
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }, // aggregate output
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }, // output
+  never, // parser
+  { lines: string[] }
+>;
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**The MetadataAdapterHandler has inconsistent parser types.**

The MetadataAdapterHandler specifies `never` for the parser but then defines `{ lines: string[] }` as the parser output. This seems inconsistent and might lead to runtime errors.


```diff
  }, // output
-  never, // parser
+  "generic", // parser
  { lines: string[] }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  }, // output
  "generic", // parser
  { lines: string[] }
>;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T09:30:29Z
author_association: NONE
diff_hunk: @@ -1,149 +1,172 @@
 import type {
   EmojiGroup,
   EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { ParseResult } from "@mojis/parsers";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
-  /**
-   * The name of the adapter.
-   */
-  name: string;
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
 
+export interface AdapterContext {
   /**
-   * A description of the adapter.
+   * The emoji version.
    */
-  description: string;
+  emoji_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  range: string;
+  unicode_version: string;
 
   /**
-   * The name of the adapter to extend from.
+   * Whether or not the force mode was enabled.
    */
-  extend?: string;
+  force: boolean;
+}
 
+export type AdapterHandlerType = "metadata" | "sequence" | "variation";
+
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The metadata handler for the adapter.
+   * The url to fetch the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  url: string;
 
   /**
-   * The sequences handler for the adapter.
+   * The cache key for the data.
    */
-  sequences?: AdapterHandler<
-    TSequencesUrlReturn,
-    ExtraContext<TSequencesUrlReturn>,
-    EmojiSequence[],
-    any
-  >;
+  cacheKey: string;
 
   /**
-   * The variations handler for the adapter.
+   * The key to identify the data.
+   *
+   * NOTE:
+   * If not set it will be generated from the cache key.
    */
-  variations?: AdapterHandler<
-    TVariationsUrlReturn,
-    ExtraContext<TVariationsUrlReturn>,
-    EmojiVariation[],
-    any
-  >;
+  key?: string;
 }
 
-type ExtraContext<T> = {
-  key: string;
-} & (T extends CacheableUrlRequest
-  ? T["extraContext"] extends Record<string, unknown>
-    ? T["extraContext"]
-    : Record<string, never>
-  : Record<string, never>);
+export type TransformFn<
+  TContext extends AdapterContext,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Ensure non-empty data is guaranteed in AggregateFn.**  
The signature enforces at least one item, but in scenarios where no data is returned by the transform step, the aggregator could receive an empty array. Consider clarifying or validating this at runtime.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T09:51:33Z
author_association: NONE
diff_hunk: @@ -171,47 +161,46 @@ cli.command(
       }
 
       if (isGeneratorEnabled("sequences")) {
-        if (adapter.sequences == null) {
-          throw new MojisNotImplemented("sequences");
-        }
-
-        const { sequences, zwj } = await runAdapterHandler(adapter, "sequences", {
+        const { sequences, zwj } = await runAdapterHandler("sequence", {
           force,
           emoji_version: version.emoji_version,
           unicode_version: version.unicode_version,
         });
 
-        await fs.writeFile(
-          join(baseDir, "zwj-sequences.json"),
-          JSON.stringify(zwj, null, 2),
-          "utf-8",
-        );
+        console.log("sequences", sequences);
+        console.log("zwj", zwj);
path: packages/cli/src/cli.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove console.log statements.**

These debug statements should be removed from production code.


```diff
-        console.log("sequences", sequences);
-        console.log("zwj", zwj);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T09:56:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,125 @@
+import { type EmojiGroup, type EmojiMetadata, extractEmojiVersion, extractUnicodeVersion } from "@mojis/internal-utils";
+import { defineAdapterHandler } from "../../define";
+
+function slugify(val: string): string {
+  return val.normalize("NFD")
+    .toLowerCase()
+    .trim()
+    .replace(/[\u0300-\u036F]/g, "")
+    .replace(/\(.+\)/g, "")
+    .replace("&", "")
+    .replace(/[\W_]+/g, "-")
+    .replace(/^-+|-+$/g, "");
+}
+
+// These emoji versions doesn't seem to have a emoji-test,
+// where we can extract the metadata from.
+const DISALLOWED_EMOJI_VERSIONS = ["1.0", "2.0", "3.0"];
+
+export const modernMetadataHandler = defineAdapterHandler({
+  type: "metadata",
+  shouldExecute: (ctx) => {
+    return !DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version);
+  },
+  urls: (ctx) => {
+    if (DISALLOWED_EMOJI_VERSIONS.includes(ctx.emoji_version)) {
+      return undefined;
+    }
+
+    return {
+      url: `https://unicode.org/Public/emoji/${ctx.emoji_version}/emoji-test.txt`,
+      cacheKey: `v${ctx.emoji_version}/metadata`,
+    };
+  },
+  parser: (_, data) => {
+    return data.split("\n");
+  },
+  transform(ctx, lines) {
+    let currentGroup: EmojiGroup | undefined;
+
+    const groups: EmojiGroup[] = [];
+
+    // [group-subgroup][hexcode] = metadata
+    const emojis: Record<string, Record<string, EmojiMetadata>> = {};
+
+    for (const line of lines) {
+      if (line.trim() === "") {
+        continue;
+      }
+
+      if (line.startsWith("# group:")) {
+        const groupName = line.slice(8).trim();
+
+        const group: EmojiGroup = {
+          name: groupName,
+          slug: slugify(groupName),
+          subgroups: [],
+        };
+
+        currentGroup = group;
+
+        groups.push(group);
+
+        continue;
+      } else if (line.startsWith("# subgroup:")) {
+        const subgroupName = line.slice(11).trim();
+
+        if (currentGroup == null) {
+          throw new Error(`subgroup ${subgroupName} without group`);
+        }
+
+        currentGroup.subgroups.push(slugify(subgroupName));
+
+        continue;
+      } else if (line.startsWith("#")) {
+        continue;
+      }
+
+      const [baseHexcode, trailingLine] = line.split(";");
+
+      if (baseHexcode == null || trailingLine == null) {
+        throw new Error(`invalid line: ${line}`);
+      }
+
+      const [baseQualifier, comment] = trailingLine.split("#");
+
+      if (baseQualifier == null || comment == null) {
+        throw new Error(`invalid line: ${line}`);
+      }
+
+      const hexcode = baseHexcode.trim().replace(/\s+/g, "-");
+      const qualifier = baseQualifier.trim();
+
+      const emojiVersion = extractEmojiVersion(comment.trim());
+      const [emoji, trimmedComment] = comment.trim().split(` E${emojiVersion} `);
+
path: packages/adapters/src/handlers/modern/metadata.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Verify emoji version extraction logic.**

This section extracts the emoji version and splits the comment, but what happens if `emojiVersion` is not found or empty? The split operation might behave unexpectedly.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if emoji-test.txt from a sample emoji version contains lines that don't match the expected format

# Download a sample emoji-test.txt file
curl -s "https://unicode.org/Public/emoji/13.0/emoji-test.txt" | head -n 50 > sample_emoji_test.txt

# Check if there are any comment lines without an emoji version
echo "Checking for comment lines without emoji version:"
cat sample_emoji_test.txt | grep -v "^#" | grep "#" | grep -v "E[0-9]" || echo "No lines found without emoji version"

# Check the format of lines to understand how emoji versions are formatted
echo "Sample comment format:"
cat sample_emoji_test.txt | grep -v "^#" | grep "#" | head -n 2
```

Length of output: 653

---



</details>

**Potential issue with missing or empty emoji version handling**

The sample data confirms that valid emoji versions (e.g., "E1.0", "E0.6") are present in the reference file. However, if the `extractEmojiVersion` function fails to extract a version (i.e. returns an empty string or undefined), the subsequent split using `` ` E${emojiVersion} ` `` may return an array with only one element. This can result in unexpected behavior when destructuring the result into `[emoji, trimmedComment]`.

- **Location:** `packages/adapters/src/handlers/modern/metadata.ts` at lines 93-95
- **Suggestion:** Include error handling or a guard for cases where `emojiVersion` is empty or invalid before performing the split. This ensures that the logic degrades gracefully for inputs that do not follow the expected comment format.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T10:45:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,125 @@
+import { type EmojiGroup, type EmojiMetadata, extractEmojiVersion, extractUnicodeVersion } from "@mojis/internal-utils";
+import { defineAdapterHandler } from "../../define";
+
+function slugify(val: string): string {
+  return val.normalize("NFD")
+    .toLowerCase()
+    .trim()
+    .replace(/[\u0300-\u036F]/g, "")
+    .replace(/\(.+\)/g, "")
+    .replace("&", "")
+    .replace(/[\W_]+/g, "-")
+    .replace(/^-+|-+$/g, "");
+}
path: packages/adapters/src/handlers/modern/metadata.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix misleading character class in regex.**

The character class in your regular expression on line 8 may not behave as expected according to static analysis. A character class cannot match both a character and a combining character.


```diff
-  .replace(/[\u0300-\u036F]/g, "")
+  .replace(/\u0300-\u036F/g, "")
```

Alternatively, consider using a more specific solution for removing diacritical marks or leveraging libraries like `lodash` for slugification.


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 8-8: A character class cannot match a character and a combining character.

A character and a combining character forms a new character. Replace the character class with an alternation.

(lint/suspicious/noMisleadingCharacterClass)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T10:45:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,103 @@
+import type { AdapterContext, AdapterHandlerType, InferOutputFromAdapterHandlerType, UrlBuilder, UrlWithCache } from "./types";
+import { createCacheKeyFromUrl, fetchCache } from "@mojis/internal-utils";
path: packages/adapters/src/handler.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Resolve pipeline build errors with parser return types.**
The pipeline error suggests `data` can be either `string[]` or `GenericParseResult`. Ensure `handler.transform`’s parameter type accommodates both or split the logic to handle each type distinctly.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T10:45:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,33 @@
+import semver from "semver";
+import { defineAdapterHandler } from "../../define";
+
path: packages/adapters/src/handlers/modern/variation.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Use caution when appending ".0" to the unicode_version string.**

Appending ".0" may create invalid semver strings if `ctx.unicode_version` already includes patch or pre-release parts. Consider validating or normalizing `ctx.unicode_version` before using `semver`.


You can verify the usage of `ctx.unicode_version` across the codebase to ensure uniform handling:

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Find all references to `ctx.unicode_version`
fd . --type f --exec rg "ctx\.unicode_version" {}
```

Length of output: 362

---



</details>

**Attention: Normalize `ctx.unicode_version` Before Appending ".0"**

The code currently appends ".0" in several places—such as in the `semver.lte`/`semver.gte` comparisons and URL construction—which can lead to an invalid semver string if `ctx.unicode_version` already contains patch or pre-release information. For example, we see usages like:

- `if (semver.lte(\`${ctx.unicode_version}.0\`, "12.1.0")) { ... }`
- `url: \`https://unicode.org/Public/\${ctx.unicode_version}.0/ucd/emoji/emoji-variation-sequences.txt\`,`
- `return semver.gte(\`${ctx.unicode_version}.0\`, "11.0.0");`

To ensure reliable semver operations and URL correctness:
- **Validate or normalize** `ctx.unicode_version` prior to appending ".0".
- Consider extracting the major version component or otherwise ensuring that no additional patch or pre-release segments exist before applying the ".0" suffix.

Addressing this will avoid potential bugs when `ctx.unicode_version` is not in the expected format.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T11:24:54Z
author_association: NONE
diff_hunk: @@ -1,149 +1,178 @@
 import type {
   EmojiGroup,
   EmojiMetadata,
-  EmojiSequence,
-  EmojiVariation,
   WriteCacheOptions,
 } from "@mojis/internal-utils";
+import type { GenericParseOptions, GenericParseResult } from "@mojis/parsers";
+import type { METADATA_HANDLERS, SEQUENCE_HANDLERS, VARIATION_HANDLERS } from "./_handlers";
+import type { BUILTIN_PARSERS } from "./utils";
 
-export interface MojiAdapter<
-  TMetadataUrlReturn extends CacheableUrlRequestReturnType,
-  TSequencesUrlReturn extends CacheableUrlRequestReturnType,
-  TVariationsUrlReturn extends CacheableUrlRequestReturnType,
-> {
-  /**
-   * The name of the adapter.
-   */
-  name: string;
+type Promisable<T> = T | Promise<T>;
+export type Arrayable<T> = T | T[];
 
+export interface AdapterContext {
   /**
-   * A description of the adapter.
+   * The emoji version.
    */
-  description: string;
+  emoji_version: string;
 
   /**
-   * A valid semver range for the emoji version this adapter supports.
+   * The unicode version.
+   * This will correspond to the emoji version.
    */
-  range: string;
+  unicode_version: string;
 
   /**
-   * The name of the adapter to extend from.
+   * Whether or not the force mode was enabled.
    */
-  extend?: string;
+  force: boolean;
+}
 
+export type AdapterHandlerType = "metadata" | "sequence" | "variation";
+
+export type UrlBuilder = (ctx: AdapterContext) => Promisable<Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache>>;
+
+export type ShouldExecute<
+  TContext extends AdapterContext,
+> = (ctx: TContext) => Promisable<boolean>;
+
+export interface UrlWithCache {
   /**
-   * The metadata handler for the adapter.
+   * The url to fetch the data.
    */
-  metadata?: AdapterHandler<
-    TMetadataUrlReturn,
-    ExtraContext<TMetadataUrlReturn>,
-    {
-      groups: EmojiGroup[];
-      emojis: Record<string, Record<string, EmojiMetadata>>;
-    },
-    any
-  >;
+  url: string;
 
   /**
-   * The sequences handler for the adapter.
+   * The cache key for the data.
    */
-  sequences?: AdapterHandler<
-    TSequencesUrlReturn,
-    ExtraContext<TSequencesUrlReturn>,
-    EmojiSequence[],
-    any
-  >;
+  cacheKey: string;
 
   /**
-   * The variations handler for the adapter.
+   * The key to identify the data.
+   *
+   * NOTE:
+   * If not set it will be generated from the cache key.
    */
-  variations?: AdapterHandler<
-    TVariationsUrlReturn,
-    ExtraContext<TVariationsUrlReturn>,
-    EmojiVariation[],
-    any
-  >;
+  key?: string;
 }
 
-type ExtraContext<T> = {
-  key: string;
-} & (T extends CacheableUrlRequest
-  ? T["extraContext"] extends Record<string, unknown>
-    ? T["extraContext"]
-    : Record<string, never>
-  : Record<string, never>);
+export type TransformFn<
+  TContext extends AdapterContext,
+  TParseInput,
+  TTransformOutput,
+> = (ctx: TContext, data: TParseInput) => TTransformOutput;
+
+export type AggregateFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TAggregateOutput,
+> = (ctx: TContext, data: [TTransformOutput, ...TTransformOutput[]]) => TAggregateOutput;
+
+export type OutputFn<
+  TContext extends AdapterContext,
+  TTransformOutput,
+  TOutput,
+> = (ctx: TContext, data: TTransformOutput) => TOutput;
+
+export type BuiltinParser = typeof BUILTIN_PARSERS[number];
+
+export type ParserFn<
+  TContext extends AdapterContext,
+  TOutput,
+> = (ctx: TContext, data: string) => TOutput;
 
-export interface CacheableUrlRequest {
+export type GetParseOutputFromBuiltInParser<TParser extends string> =
+  TParser extends "generic" ? GenericParseResult :
+    never;
+
+export type GetParseOptionsFromParser<TParser extends string> =
+  TParser extends "generic" ? GenericParseOptions :
+    never;
+
+export interface AdapterHandler<
+  TType extends AdapterHandlerType,
+  TContext extends AdapterContext,
+  TParser extends string | ParserFn<TContext, any>,
+  TTransformOutput,
+  TParseOutput = InferParseOutput<TContext, TParser>,
+  TAggregateOutput = TTransformOutput,
+  TOutput = TTransformOutput | TAggregateOutput,
+> {
   /**
-   * The key for the data.
-   * If not provided, a key will be generated based on the url.
+   * The type of the handler, this is used to identify the handler.
    */
-  key?: string;
+  type: TType;
 
   /**
-   * The url to fetch the data
-   * @example "https://example.com/data.json"
+   * The urls that will be fetched.
    */
-  url: string;
+  urls: Arrayable<string> | Arrayable<undefined> | Arrayable<UrlWithCache> | UrlBuilder;
 
   /**
-   * The cache key for the data.
-   * If not provided, a key will be generated based on the url.
+   * Options that will be passed to the fetch function.
    */
-  cacheKey?: string;
+  fetchOptions?: RequestInit;
 
   /**
-   * Extra data to be passed to the handler.
-   * @default {}
+   * Options that will be passed to the cache function.
    */
-  extraContext?: Record<string, unknown>;
+  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
 
   /**
-   * The fetch options for the request.
-   * @default {}
-   *
-   * NOTE:
-   * This will be merged together with the `fetchOptions` of the handler.
+   * Whether or not that this handler should handle the request.
    */
-  fetchOptions?: RequestInit;
+  shouldExecute: ShouldExecute<TContext>;
 
   /**
-   * The cache options for the data.
-   * @default {}
+   * A parse function or a reference to a builtin parser.
    *
-   * NOTE:
-   * This will be merged together with the `cacheOptions` of the handler.
    */
-  cacheOptions?: Omit<WriteCacheOptions<any>, "transform">;
-}
+  parser: TParser;
 
-export type CacheableUrlRequestReturnType = CacheableUrlRequest | CacheableUrlRequest[] | undefined;
-
-export type ExtractDataTypeFromUrls<T extends CacheableUrlRequestReturnType> =
-T extends undefined ? undefined :
-  T extends CacheableUrlRequest ? string :
-    T extends CacheableUrlRequest[] ? string :
-      never;
+  /**
+   * Options that will be passed to the parser.
+   * This will only be used if the parser is a builtin parser.
+   */
+  parserOptions?: TParser extends BuiltinParser ? GetParseOptionsFromParser<TParser> : never;
 
-export interface AdapterHandler<
-  TUrlsReturn extends CacheableUrlRequestReturnType,
-  TExtraContext extends Record<string, unknown>,
-  TTransformOutput,
-  TOutput = TTransformOutput,
-> {
-  urls: (ctx: AdapterContext) => Promise<TUrlsReturn> | TUrlsReturn;
-  fetchOptions?: RequestInit;
-  cacheOptions?: Omit<WriteCacheOptions<unknown>, "transform">;
-  transform: (ctx: AdapterContext & TExtraContext, data: ExtractDataTypeFromUrls<TUrlsReturn>) => TTransformOutput;
-  aggregate?: (ctx: AdapterContext, data: [TTransformOutput, ...TTransformOutput[]]) => TOutput;
-}
+  /**
+   * A transform function that will run for each of the urls.
+   * It will pass the returned type downwards to either the aggregate function or the output.
+   */
+  transform: TransformFn<TContext & {
+    key: string;
+  }, TParseOutput, TTransformOutput>;
 
-export type AdapterHandlers<TAdapter = MojiAdapter<any, any, any>> = {
-  [K in keyof TAdapter]: NonNullable<TAdapter[K]> extends AdapterHandler<any, any, any, any> ? K : never;
-}[keyof TAdapter];
+  /**
+   * An aggregate function that will run if defined.
+   * It will receive all the transformed results as parameters, which it can then aggregate the results into a final output.
+   */
+  aggregate?: AggregateFn<TContext, TTransformOutput, TAggregateOutput>;
 
-export interface AdapterContext {
-  force: boolean;
-  emoji_version: string;
-  unicode_version: string;
+  /**
+   * Output function which will receive the transformed result from the transform function, or from the aggregate function.
+   * It will then output the result.
+   */
+  output: OutputFn<TContext, TAggregateOutput extends TTransformOutput ? TTransformOutput : TAggregateOutput, TOutput>;
 }
+
+export type MetadataAdapterHandler = AdapterHandler<
+  "metadata", // type
+  AdapterContext, // context
+  () => { lines: string[] }, // parser
+  {
+    groups: EmojiGroup[];
+    emojis: Record<string, Record<string, EmojiMetadata>>;
+  }
+>;
path: packages/adapters/src/types.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Fix parser inconsistency in MetadataAdapterHandler.**

The `MetadataAdapterHandler` specifies a parser type of `() => { lines: string[] }`, but this doesn't align with the actual implementation in `metadata.ts` where the parser splits the input by newlines.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the parser implementation in the metadata handler
rg -n "parser: " --type ts --glob "packages/adapters/src/handlers/**/metadata.ts" -A 5
```

Length of output: 991

---



</details>

**Fix parser inconsistency in MetadataAdapterHandler**  
The type definition for the parser currently specifies a function returning an object with a `lines` property (`() => { lines: string[] }`), but in the actual implementation (e.g., in `packages/adapters/src/handlers/modern/metadata.ts`), the parser function splits the input and directly returns a string array. Please update the type in `packages/adapters/src/types.ts` (lines 159–167) so that it correctly reflects the parser’s behavior—either by changing the parser’s return type to `string[]` with an appropriate signature (such as `(arg1: unknown, data: string) => string[]`) or by modifying the implementation to wrap the result in an object with a `lines` property.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-08T11:31:15Z
author_association: NONE

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-05T20:00:47Z
body: ###  🦋  Changeset detected

Latest commit: 1fadcdb5848fdd92d294d4f92fa47217303e2a60

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 4 packages</summary>

  | Name                  | Type  |
| --------------------- | ----- |
| @mojis/internal-utils | Patch |
| @mojis/adapters       | Patch |
| @mojis/parsers        | Patch |
| @mojis/cli            | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/mojisdev/mojis/new/adapter-poc?filename=.changeset/twelve-ladybugs-mix.md&value=---%0A%22%40mojis%2Fadapters%22%3A%20patch%0A%22%40mojis%2Fcli%22%3A%20patch%0A%22%40mojis%2Finternal-utils%22%3A%20patch%0A%22%40mojis%2Fparsers%22%3A%20patch%0A---%0A%0Arefactor%3A%20improve%20adapters%0A)


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-05T20:00:51Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The changes introduce extensive refactoring of the adapter system. New types and interfaces are added in the types module, replacing legacy adapter definitions with a more flexible handler-based approach. Utility functions for URL building and parser verification are added, and their behavior is thoroughly tested. The legacy adapter implementations and the `defineMojiAdapter` function have been removed and replaced with new handler definitions and execution logic via `runAdapterHandler`. Additional modifications include CLI simplification, updated exports, improved version predicate functions, enhanced test configurations, and a new workspace dependency.

## Changes

| File(s) | Summary |
|---------|---------|
| `packages/adapters/src/types.ts` | Introduces new types (`Promisable`, `Arrayable`, `AdapterHandlerType`, etc.) and extends interfaces (e.g., `AdapterContext`, `AdapterHandler`) to support more flexible adapter handling. |
| `packages/adapters/src/utils.ts`<br>`packages/adapters/test/utils.test.ts` | Adds utility functions (`isUrlBuilder`, `isBuiltinParser`, `getHandlerUrls`) along with corresponding Vitest unit tests. |
| `packages/adapters/src/adapter.ts`<br>`packages/adapters/src/adapters/base/adapter.ts`<br>`packages/adapters/src/adapters/modern/adapter.ts`<br>`packages/adapters/src/adapters/pre-alignment/adapter.ts` | Removes legacy adapters, adapter interfaces, and resolution functions; replaces them with a simplified metadata-only export (`metadata_handlers`). |
| `packages/adapters/src/define.ts` | Removes `defineMojiAdapter` and introduces multiple overloads of `defineAdapterHandler`, leveraging advanced TypeScript generics for handler definitions. |
| `packages/adapters/src/_handlers.ts`<br>`packages/adapters/src/handler.ts`<br>`packages/adapters/src/handlers/**` (metadata & sequence) | Adds new handler implementations and a centralized registry (`all_handlers`), and introduces `runAdapterHandler` and `buildContext` for control flow in handling adapter requests. |
| `packages/adapters/src/index.ts`<br>`packages/cli/src/cli.ts` | Updates module exports to reference the new handler module and streamlines CLI logic by removing adapter resolution and disabling sequences/variations. |
| `packages/internal-utils/src/cache.ts`<br>`packages/internal-utils/src/predicates.ts`<br>`packages/internal-utils/test/versions.test.ts`<br>`packages/internal-utils/test/predicates.test.ts` | Enhances utility functions: adds a TODO in `fetchCache`; updates `isBeforeAlignment` with robust version coercion and adds the complementary `isAfterAlignment` function, along with new tests. |
| `vitest.config.ts`<br>`packages/adapters/package.json` | Broadens test file matching patterns and introduces a new `typecheck` config for package test files; adds the workspace dependency for `@mojis/parsers`. |
| `packages/adapters/test/types.test-d.ts` | Introduces tests for generic type utilities (`GetParseOutputFromBuiltInParser`, `GetParseOptionsFromParser`) ensuring proper type validation. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant C as Caller (runAdapterHandler)
    participant H as Handler Registry (all_handlers)
    participant U as URL Builder (getHandlerUrls)
    participant F as Fetcher (fetchCache)
    participant A as Adapter Handler Functions

    C->>H: Retrieve handlers for a given type
    loop For each handler in group
        C->>A: Invoke shouldExecute(ctx)
        alt Handler should execute
            C->>U: Call getHandlerUrls(handler.urls, ctx)
            U-->>C: Return list of UrlWithCache
            C->>F: Fetch data from URLs
            F-->>C: Return fetched data
            C->>A: Execute transform(ctx, data)
            C->>A: Execute aggregate(transformedData)
            C->>A: Execute output(finalData)
        else Handler skipped
            Note over C,A: Continue to next handler
        end
    end
    C-->>C: Return combined output
```

## Possibly related PRs
- **mojisdev/mojis#16**: Updates in this PR are closely related to modifications in the adapter system, particularly with changes to `AdapterContext` and the introduction of new handler functionalities that improve type safety and flow control.

## Poem
> Oh, what a change, a twist so neat,  
> My bunny paws tap a joyful beat!  
> New types and handlers hop in with grace,  
> Leaving old code in its proper place.  
> With ASCII twirls and a wink so sly,  
> I celebrate these changes—oh, my oh my! 🐇🌟

<!-- walkthrough_end -->
<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->

> [!WARNING]
> There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.
> 
> <details>
> <summary>🔧 ESLint</summary>
> 
> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.
> 
> <details>
> <summary>packages/cli/src/cli.ts</summary>
> 
> 
> Oops! Something went wrong! :(
> 
> ESLint: 9.21.0
> 
> ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
> 
> From ESLint v9.0.0, the default configuration file is now eslint.config.js.
> If you are using a .eslintrc.* file, please follow the migration guide
> to update your configuration file to the new format:
> 
> https://eslint.org/docs/latest/use/configure/migration-guide
> 
> If you still have problems after following the migration guide, please stop by
> https://eslint.org/chat/help to chat with the team.
> 
> 
> </details>
> 
> </details>

<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3d9d9d870ac1b873b559d6f4e0ef458240ba2537 and 1fadcdb5848fdd92d294d4f92fa47217303e2a60.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `packages/cli/src/cli.ts` (4 hunks)

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>packages/cli/src/cli.ts (6)</summary>

`5-5`: **Good addition of runAdapterHandler import**

This import aligns with the adapter refactoring and simplifies the code by removing the need for a separate adapter resolution step.

---

`142-146`: **Clean API simplification**

The signature change from `runAdapterHandler(adapter, "metadata", {...})` to `runAdapterHandler("metadata", {...})` simplifies the API and removes the need for adapter resolution.

---

`164-168`: **Note the handler name change from "sequences" to "sequence"**

The handler name has been changed from plural to singular form. Ensure that this is consistent with other handler names in the system and that it's documented for other developers.

---

`184-188`: **Note the handler name change from "variations" to "variation"**

The handler name has been changed from plural to singular form, consistent with the previous change to "sequence". This standardization improves code consistency.

---

`190-190`: **Good directory creation addition**

Adding the directory creation ensures the target directory exists before attempting to write files, which prevents potential errors.

---

`198-266`: _🛠️ Refactor suggestion_

**Remove commented-out code blocks**

Large blocks of commented code should be removed rather than kept in the codebase. If this functionality is temporarily disabled but will be re-enabled later, consider using feature flags instead of comments.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=mojisdev/mojis&utm_content=30):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE