@parseapi/sdk 1.9.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -451,3 +451,35 @@ Successful checks may be reused for up to 24 hours. `pretty` optionally formats
451
451
  Stack defaults to 35 seconds per attempt so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout applies to every operation; JavaScript also supports a per-call `timeoutMs` override.
452
452
 
453
453
  Vehicle lookups use `vin` as the input and response field. Existing VIN methods remain available for compatibility.
454
+
455
+ ## Emoji catalog additions
456
+
457
+ Resolve Unicode emoji or a named shortcode, search or browse a bounded catalog,
458
+ parse emoji positions in text, and convert between Unicode and a selected shortcode source.
459
+ The ordinary lookup remains four fields:
460
+
461
+ ```http
462
+ GET /emoji/rocket
463
+ ```
464
+
465
+ ```json
466
+ {"emoji":"🚀","name":"Rocket","shortcodes":[":rocket:","rocket"],"category":"Travel & Places"}
467
+ ```
468
+
469
+ Use `deep=true` for Unicode facts, keywords, tone families and available artwork.
470
+ Artwork is keyed by style: `deep.artwork.openmoji.svg` is the SVG URL, and
471
+ `deep.artwork.openmoji.png["72"]` is the 72×72 PNG URL (use your language’s map
472
+ access syntax). The same shape applies to `noto` and `twemoji`. Missing styles
473
+ and formats are omitted. URLs pin immutable artwork editions and retain their bytes.
474
+ Core values stay unchanged. Text parsing returns `emojis` with UTF-16 `positions`;
475
+ conversion returns `text` and unresolved tokens with positions and reasons.
476
+
477
+ The compact JSON catalog contains offline identity, qualification-form and
478
+ shortcode indexes. Request `profile=full` when artwork is needed and `lang` when
479
+ localized names and keywords are needed. `/emoji/metadata` carries dataset
480
+ coverage and shared source notices once. Artwork supports Google Noto, Twemoji
481
+ and OpenMoji, with real SVG/PNG availability and immutable first-party URLs.
482
+ A device's emoji font remains the ordinary display.
483
+
484
+ These additions are a local release candidate until the matching SDK and
485
+ backend are published. Existing lookup and search contracts are preserved.
package/dist/index.cjs CHANGED
@@ -20,11 +20,272 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ EmojiCatalog: () => EmojiCatalog,
24
+ EmojiCatalogError: () => EmojiCatalogError,
23
25
  ParseAPIError: () => ParseAPIError,
24
26
  parseAPI: () => parseAPI
25
27
  });
26
28
  module.exports = __toCommonJS(index_exports);
27
- var VERSION = "1.9.0";
29
+
30
+ // src/emoji-catalog.ts
31
+ var EmojiCatalogError = class extends Error {
32
+ constructor(code, message) {
33
+ super(message);
34
+ this.code = code;
35
+ this.name = "EmojiCatalogError";
36
+ }
37
+ };
38
+ var sources = ["cldr", "github", "slack", "discord"];
39
+ var stripColons = (value) => value.trim().replace(/^:+|:+$/g, "");
40
+ var normalizeName = (value) => stripColons(value).toLowerCase().replace(/[\s_-]+/g, "_");
41
+ var normalizeSearch = (value) => normalizeName(value).normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "").replace(/œ/g, "oe").replace(/æ/g, "ae").replace(/ß/g, "ss");
42
+ var legacyName = (value) => stripColons(value).toLowerCase().replace(/\s+/g, "_");
43
+ var filterKey = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
44
+ var versionCompare = (a, b) => {
45
+ const left = a.split(".").map(Number), right = b.split(".").map(Number);
46
+ return (left[0] ?? 0) - (right[0] ?? 0) || (left[1] ?? 0) - (right[1] ?? 0);
47
+ };
48
+ var invalid = (message) => {
49
+ throw new EmojiCatalogError("validation_error", message);
50
+ };
51
+ var EmojiCatalog = class {
52
+ data;
53
+ byGlyph = /* @__PURE__ */ new Map();
54
+ names = /* @__PURE__ */ new Map();
55
+ aliases = /* @__PURE__ */ new Map();
56
+ sourceAliases = /* @__PURE__ */ new Map();
57
+ encodings = /* @__PURE__ */ new Map();
58
+ searchFacts = /* @__PURE__ */ new Map();
59
+ categoryKeys = /* @__PURE__ */ new Set();
60
+ subcategoryKeys = /* @__PURE__ */ new Set();
61
+ filterCache = /* @__PURE__ */ new Map();
62
+ searchReady = false;
63
+ compareNames;
64
+ segmenter;
65
+ constructor(data) {
66
+ if (!data || !Array.isArray(data.emojis) || !data.forms || !data.shortcodes || !data.legacy || !data.metadata?.source_version) {
67
+ throw new EmojiCatalogError("invalid_catalog", "Use the JSON response from GET /emoji/catalog.");
68
+ }
69
+ this.data = structuredClone(data);
70
+ this.compareNames = new Intl.Collator(this.data.localization?.resolved_locale ?? void 0).compare;
71
+ const add = (map, key, glyph) => {
72
+ if (!key) return;
73
+ if (!map.has(key)) map.set(key, /* @__PURE__ */ new Set());
74
+ map.get(key).add(glyph);
75
+ };
76
+ for (const row of this.data.emojis) {
77
+ if (!row?.emoji || !row.deep?.version || !Array.isArray(row.deep.keywords)) {
78
+ throw new EmojiCatalogError("invalid_catalog", "Offline catalogs require the default deep=true export.");
79
+ }
80
+ this.byGlyph.set(row.emoji, row);
81
+ add(this.names, normalizeName(row.name), row.emoji);
82
+ for (const alias of row.shortcodes) add(this.aliases, normalizeName(alias), row.emoji);
83
+ this.categoryKeys.add(filterKey(row.category ?? ""));
84
+ this.subcategoryKeys.add(filterKey(row.deep.subcategory ?? ""));
85
+ }
86
+ for (const source of sources) {
87
+ if (!this.data.shortcodes[source]) throw new EmojiCatalogError("invalid_catalog", `Catalog is missing the ${source} shortcode map.`);
88
+ }
89
+ }
90
+ /** Build once on first search; shared normalized strings avoid repeated annotation copies. */
91
+ ensureSearch() {
92
+ if (this.searchReady) return;
93
+ const cache = /* @__PURE__ */ new Map(), normal = (value) => {
94
+ let result = cache.get(value);
95
+ if (result === void 0) {
96
+ result = normalizeSearch(value);
97
+ cache.set(value, result);
98
+ }
99
+ return result;
100
+ };
101
+ for (const source of sources) for (const [alias, glyphs] of Object.entries(this.data.shortcodes[source])) {
102
+ const value = normal(alias);
103
+ for (const glyph of glyphs) if (this.byGlyph.has(glyph)) {
104
+ if (!this.sourceAliases.has(glyph)) this.sourceAliases.set(glyph, /* @__PURE__ */ new Set());
105
+ this.sourceAliases.get(glyph).add(value);
106
+ }
107
+ }
108
+ const localizedKeywords = new Set(this.data.search?.localized_keywords ?? []);
109
+ for (const row of this.data.emojis) {
110
+ const native = this.data.search?.native[row.emoji];
111
+ const names = [...new Set([row.name, native?.name].filter((name) => typeof name === "string").map(normal))];
112
+ const primaryShorts = [...new Set(row.shortcodes.map(normal))];
113
+ const shorts = [.../* @__PURE__ */ new Set([...primaryShorts, ...this.sourceAliases.get(row.emoji) ?? []])];
114
+ const keywords = [...new Set([...row.deep?.keywords ?? [], ...native?.keywords ?? []].map(normal))];
115
+ const allNames = [...names, ...shorts];
116
+ this.searchFacts.set(row.emoji, { names, primaryShorts, shorts, keywords, translatedKeywords: this.data.localization?.resolved_locale && localizedKeywords.has(row.emoji) ? (row.deep?.keywords ?? []).map(normal) : [], allNames, blob: [.../* @__PURE__ */ new Set([...allNames, ...keywords])].join(" ") });
117
+ }
118
+ this.sourceAliases.clear();
119
+ this.searchReady = true;
120
+ }
121
+ encoding(source) {
122
+ let map = this.encodings.get(source);
123
+ if (!map) {
124
+ map = /* @__PURE__ */ new Map();
125
+ for (const [alias, glyphs] of Object.entries(this.data.shortcodes[source])) if (glyphs.length === 1 && this.byGlyph.has(glyphs[0]) && !map.has(glyphs[0])) map.set(glyphs[0], `:${stripColons(alias)}:`);
126
+ this.encodings.set(source, map);
127
+ }
128
+ return map;
129
+ }
130
+ /** Evidence for the full server inventory; scope() identifies this downloaded subset. */
131
+ metadata() {
132
+ return structuredClone(this.data.metadata);
133
+ }
134
+ /** Filters, local record count and whether this download contains the full server inventory. */
135
+ scope() {
136
+ return structuredClone({ ...this.data.scope, filters: this.data.scope?.filters ?? {}, total: this.data.total, complete_inventory: this.data.scope?.complete_inventory ?? this.data.total === this.data.metadata.total });
137
+ }
138
+ lookup(input, options = {}) {
139
+ if (typeof input !== "string" || !input.trim() || input.trim().length > 512) invalid("Emoji input must contain 1\u2013512 characters.");
140
+ const value = input.trim(), source = options.shortcodeSource;
141
+ if (source && !sources.includes(source)) invalid("shortcodeSource must be cldr, github, slack or discord.");
142
+ let glyph = this.byGlyph.has(value) ? value : void 0;
143
+ if (!glyph && !source && Object.hasOwn(this.data.legacy, legacyName(value))) glyph = this.data.legacy[legacyName(value)];
144
+ if (!glyph) {
145
+ let form = value;
146
+ if (/^(?:U\+)?[0-9A-F]{4,6}(?:[\s,-]+(?:U\+)?[0-9A-F]{4,6})*$/i.test(value)) {
147
+ const points = value.split(/[\s,-]+/).map((part) => Number.parseInt(part.replace(/^U\+/i, ""), 16));
148
+ if (points.length > 32 || points.some((cp) => cp > 1114111 || cp >= 55296 && cp <= 57343)) invalid("Unicode codepoints must be valid scalar values.");
149
+ form = String.fromCodePoint(...points);
150
+ } else if (/^U\+/i.test(value)) invalid("Use Unicode codepoints such as U+1F680 or U+2764 U+FE0F.");
151
+ const canonical = Object.hasOwn(this.data.forms, form) ? this.data.forms[form] : form;
152
+ if (canonical && this.byGlyph.has(canonical)) glyph = canonical;
153
+ }
154
+ if (!glyph) {
155
+ const candidates = /* @__PURE__ */ new Set();
156
+ for (const key of /* @__PURE__ */ new Set([stripColons(value).toLowerCase(), normalizeName(value)])) {
157
+ for (const name of source ? [source] : sources) {
158
+ const dictionary = this.data.shortcodes[name];
159
+ if (Object.hasOwn(dictionary, key)) {
160
+ for (const candidate of dictionary[key] ?? []) if (this.byGlyph.has(candidate)) candidates.add(candidate);
161
+ }
162
+ }
163
+ }
164
+ if (!source) for (const candidate of this.aliases.get(normalizeName(value)) ?? []) candidates.add(candidate);
165
+ if (!source || !candidates.size && !/^:.*:$/.test(value)) for (const candidate of this.names.get(normalizeName(value)) ?? []) candidates.add(candidate);
166
+ if (candidates.size > 1) invalid("Emoji shortcode is ambiguous. Set shortcodeSource to cldr, github, slack or discord.");
167
+ glyph = candidates.values().next().value;
168
+ }
169
+ const row = glyph ? this.byGlyph.get(glyph) : void 0;
170
+ if (!row) throw new EmojiCatalogError("not_found", "Emoji not found in this downloaded catalog.");
171
+ return this.project(row, options.deep === true);
172
+ }
173
+ search(query, options = {}) {
174
+ if (typeof query !== "string" || !query.trim() || query.trim().length > 256) invalid("Search query must contain 1\u2013256 characters.");
175
+ return this.page(query.trim(), options);
176
+ }
177
+ browse(options = {}) {
178
+ const { q, ...result } = this.page("", options);
179
+ return result;
180
+ }
181
+ /** Unknown and ambiguous inputs remain literal; positions refer to untouched input. */
182
+ convert(text, options) {
183
+ if (!options || !["to_shortcodes", "to_emoji"].includes(options.direction)) invalid("Direction must be to_shortcodes or to_emoji.");
184
+ if (!sources.includes(options.shortcodeSource)) invalid("shortcodeSource must be cldr, github, slack or discord.");
185
+ const parsed = this.parse(text), replacements = [], unresolved = [];
186
+ if (options.direction === "to_shortcodes") {
187
+ const encodings = this.encoding(options.shortcodeSource);
188
+ for (const row of parsed.emojis) {
189
+ const shortcode = encodings.get(row.emoji);
190
+ for (const position of row.positions) if (shortcode) replacements.push({ emoji: row.emoji, shortcode, ...position });
191
+ else unresolved.push({ emoji: row.emoji, ...position, reason: "unavailable" });
192
+ }
193
+ } else for (const match of text.matchAll(/:[^:\s]{1,128}:/gu)) {
194
+ const shortcode = match[0], start = match.index, end = start + shortcode.length;
195
+ try {
196
+ const row = this.lookup(shortcode, { shortcodeSource: options.shortcodeSource });
197
+ replacements.push({ emoji: row.emoji, shortcode, start, end });
198
+ } catch (error) {
199
+ if (!(error instanceof EmojiCatalogError)) throw error;
200
+ unresolved.push({ shortcode, start, end, reason: error.code === "not_found" ? "unknown" : "ambiguous" });
201
+ }
202
+ }
203
+ replacements.sort((a, b) => a.start - b.start);
204
+ unresolved.sort((a, b) => a.start - b.start);
205
+ let cursor = 0;
206
+ const chunks = [];
207
+ for (const row of replacements) {
208
+ chunks.push(text.slice(cursor, row.start), options.direction === "to_shortcodes" ? row.shortcode : row.emoji);
209
+ cursor = row.end;
210
+ }
211
+ chunks.push(text.slice(cursor));
212
+ return { text: chunks.join(""), unresolved };
213
+ }
214
+ /** Whole-grapheme predicate. Use parse() to extract emoji from prose with positions. */
215
+ regex(options = {}) {
216
+ const forms = options.qualifiedOnly ? [...this.byGlyph.keys()] : Object.keys(this.data.forms).filter((form) => this.byGlyph.has(this.data.forms[form]));
217
+ if (options.components) forms.push(...["\u{1F3FB}", "\u{1F3FC}", "\u{1F3FD}", "\u{1F3FE}", "\u{1F3FF}", "\u{1F9B0}", "\u{1F9B1}", "\u{1F9B2}", "\u{1F9B3}"]);
218
+ const escaped = [...new Set(forms)].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).map((form) => form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
219
+ return new RegExp(`^(?:${escaped.length ? escaped.join("|") : "(?!)"})$`, "u");
220
+ }
221
+ categories() {
222
+ const groups = /* @__PURE__ */ new Map();
223
+ for (const row of this.data.emojis) {
224
+ if (!row.category) continue;
225
+ if (!groups.has(row.category)) groups.set(row.category, /* @__PURE__ */ new Set());
226
+ if (row.deep?.subcategory) groups.get(row.category).add(row.deep.subcategory);
227
+ }
228
+ return { categories: [...groups].map(([category, subcategories]) => ({ category, subcategories: [...subcategories] })) };
229
+ }
230
+ parse(text, options = {}) {
231
+ if (typeof text !== "string" || text.length > 16e3) invalid("Text must be a string of at most 16000 UTF-16 code units.");
232
+ const found = /* @__PURE__ */ new Map();
233
+ this.segmenter ??= new Intl.Segmenter("en", { granularity: "grapheme" });
234
+ for (const { segment, index } of this.segmenter.segment(text)) {
235
+ const canonical = Object.hasOwn(this.data.forms, segment) ? this.data.forms[segment] : segment;
236
+ const row = canonical ? this.byGlyph.get(canonical) : void 0;
237
+ if (!row) continue;
238
+ if (!found.has(row.emoji)) found.set(row.emoji, { ...this.project(row, options.deep === true), positions: [] });
239
+ const result = found.get(row.emoji);
240
+ result.positions.push({ start: index, end: index + segment.length });
241
+ }
242
+ return { emojis: [...found.values()] };
243
+ }
244
+ project(row, deep) {
245
+ const { emoji, name, shortcodes, category } = row;
246
+ return { emoji, name, shortcodes: [...shortcodes], category, ...deep ? { deep: structuredClone(row.deep) } : {} };
247
+ }
248
+ page(query, options) {
249
+ if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 1)) invalid("Limit must be a positive integer.");
250
+ const limit = Math.min(options.limit ?? 20, 50), offset = options.offset ?? 0;
251
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > 1e5) invalid("Offset must be an integer between 0 and 100000.");
252
+ for (const [key, known] of [["category", this.categoryKeys], ["subcategory", this.subcategoryKeys]]) {
253
+ if (options[key] !== void 0 && (!options[key].trim() || !known.has(filterKey(options[key])))) invalid(`Unknown emoji ${key}. Use categories() to find supported values.`);
254
+ }
255
+ for (const key of ["version", "maxVersion"]) if (options[key] !== void 0 && (!/^\d{1,2}\.\d{1,2}$/.test(options[key]) || versionCompare(options[key], this.data.metadata.source_version) > 0)) invalid(`${key} must be an Emoji version no newer than ${this.data.metadata.source_version}.`);
256
+ let rows = this.filtered(options);
257
+ if (query) {
258
+ this.ensureSearch();
259
+ const needle = normalizeSearch(query), words = needle.split("_").filter(Boolean);
260
+ const scored = rows.flatMap((row) => {
261
+ const { names, primaryShorts, shorts, keywords, translatedKeywords, allNames, blob } = this.searchFacts.get(row.emoji);
262
+ if (!blob.includes(needle) && !(words.length > 1 && words.every((word) => blob.includes(word)))) return [];
263
+ const score = primaryShorts.includes(needle) || names.includes(needle) ? 0 : shorts.includes(needle) ? 0.5 : allNames.some((value) => value.startsWith(needle)) ? 1 : translatedKeywords.includes(needle) ? 1.5 : allNames.some((value) => value.includes(needle)) ? 2 : words.length > 1 && allNames.some((value) => words.every((word) => value.includes(word))) ? 2.5 : keywords.some((value) => value.includes(needle)) ? 3 : words.length > 1 && words.every((word) => blob.includes(word)) ? 4 : 100;
264
+ return score === 100 ? [] : [{ row, score }];
265
+ });
266
+ rows = scored.sort((a, b) => a.score - b.score || (this.data.localization?.resolved_locale ? this.compareNames(a.row.name, b.row.name) || a.row.emoji.localeCompare(b.row.emoji) : a.row.name.localeCompare(b.row.name))).map(({ row }) => row);
267
+ }
268
+ const page = rows.slice(offset, offset + limit), paged = !query || ["category", "subcategory", "version", "maxVersion", "includeSkinTones", "offset"].some((key) => options[key] !== void 0);
269
+ return { q: query, emojis: page.map((row) => this.project(row, options.deep === true)), ...paged ? { total: rows.length, next_offset: offset + page.length < rows.length ? offset + page.length : null } : {} };
270
+ }
271
+ filtered(options) {
272
+ for (const [key2, known] of [["category", this.categoryKeys], ["subcategory", this.subcategoryKeys]]) {
273
+ if (options[key2] !== void 0 && (!options[key2].trim() || !known.has(filterKey(options[key2])))) invalid(`Unknown emoji ${key2}. Use categories() to find supported values.`);
274
+ }
275
+ for (const key2 of ["version", "maxVersion"]) if (options[key2] !== void 0 && (!/^\d{1,2}\.\d{1,2}$/.test(options[key2]) || versionCompare(options[key2], this.data.metadata.source_version) > 0)) invalid(`${key2} must be an Emoji version no newer than ${this.data.metadata.source_version}.`);
276
+ if (options.includeSkinTones !== false && options.category === void 0 && options.subcategory === void 0 && options.version === void 0 && options.maxVersion === void 0) return this.data.emojis;
277
+ const key = JSON.stringify([options.includeSkinTones === false, options.category === void 0 ? null : filterKey(options.category), options.subcategory === void 0 ? null : filterKey(options.subcategory), options.version, options.maxVersion]);
278
+ const cached = this.filterCache.get(key);
279
+ if (cached) return cached;
280
+ const rows = this.data.emojis.filter((row) => (options.includeSkinTones !== false || !/[\u{1F3FB}-\u{1F3FF}]/u.test(row.emoji)) && (options.category === void 0 || filterKey(row.category ?? "") === filterKey(options.category)) && (options.subcategory === void 0 || filterKey(row.deep?.subcategory ?? "") === filterKey(options.subcategory)) && (options.version === void 0 || versionCompare(row.deep.version, options.version) === 0) && (options.maxVersion === void 0 || versionCompare(row.deep.version, options.maxVersion) <= 0));
281
+ if (this.filterCache.size >= 32) this.filterCache.delete(this.filterCache.keys().next().value);
282
+ this.filterCache.set(key, rows);
283
+ return rows;
284
+ }
285
+ };
286
+
287
+ // src/index.ts
288
+ var VERSION = "1.10.0";
28
289
  var API_VERSION = "2.0.0";
29
290
  var DEFAULT_BASE_URL = "https://api.parseapi.com";
30
291
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -214,6 +475,16 @@ function parseAPI(apiKey, options = {}) {
214
475
  }
215
476
  );
216
477
  return {
478
+ /** Create a bounded task. Reuse the same request ID and inputs on retries. */
479
+ task: Object.assign(
480
+ (input, requestId, opts) => request("/task", void 0, { "Idempotency-Key": requestId }, opts, input),
481
+ {
482
+ get: (id, opts) => request(`/task/${enc(id)}`, void 0, void 0, opts),
483
+ cancel: (id, opts) => request(`/task/${enc(id)}/cancel`, void 0, void 0, opts, {}),
484
+ /** Reuse requestId with identical inputs to recover the saved result without another lookup or charge. */
485
+ lookup: (id, input, requestId, opts) => request(`/task/${enc(id)}/lookup`, void 0, { "Idempotency-Key": requestId }, opts, input)
486
+ }
487
+ ),
217
488
  /** Estimate task access, capacity and additional charges. Requires a secret key. Reserves no units or money and does not enforce the supplied budget. */
218
489
  preflight: (task, opts) => request("/preflight", void 0, void 0, opts, task),
219
490
  /** Look up an IP. `deep: true` adds enrichment included with a paid plan, without a separate check meter. */
@@ -418,13 +689,23 @@ function parseAPI(apiKey, options = {}) {
418
689
  /** Get current conditions in metric and imperial units. Paid deep adds specialist current measurements, forecasts and related detail. With deep, date selects a past UTC day (YYYY-MM-DD) in deep.history alongside current conditions. Date alone does not request history. */
419
690
  weather: (lat, lon, opts) => request("/weather", { lat, lon, date: opts?.date, ...deepQuery(opts) }, void 0, opts),
420
691
  emoji: Object.assign(
421
- (emoji, opts) => request(`/emoji/${enc(emoji)}`, deepQuery(opts), void 0, opts),
692
+ (emoji, opts) => request(`/emoji/${enc(emoji)}`, { shortcode_source: opts?.shortcodeSource, ...deepQuery(opts) }, void 0, opts),
422
693
  {
423
- search: (query, opts) => request("/emoji", { q: query, limit: opts?.limit, ...deepQuery(opts) }, void 0, opts)
694
+ search: (query, opts) => request("/emoji", { q: query, ...emojiFilters(opts), ...deepQuery(opts) }, void 0, opts),
695
+ browse: (opts) => request("/emoji", { ...emojiFilters(opts), ...deepQuery(opts) }, void 0, opts),
696
+ categories: (opts) => request("/emoji/categories", void 0, void 0, requestControls(opts)),
697
+ convert: (text, opts) => request("/emoji/convert", { direction: opts.direction, shortcode_source: opts.shortcodeSource }, void 0, opts, { text }),
698
+ /** Extract complete sequences. Positions use UTF-16 code units; end is exclusive. */
699
+ catalog: (opts) => request("/emoji/catalog", { ...emojiFilters(opts), profile: opts?.profile, deep: opts?.deep ?? true }, void 0, opts),
700
+ metadata: (opts) => request("/emoji/metadata", void 0, void 0, requestControls(opts)),
701
+ parse: (text, opts) => request("/emoji/parse", deepQuery(opts), void 0, opts, { text })
424
702
  }
425
703
  )
426
704
  };
427
705
  }
706
+ function emojiFilters(opts) {
707
+ return { limit: opts?.limit, category: opts?.category, subcategory: opts?.subcategory, version: opts?.version, max_version: opts?.maxVersion, include_skin_tones: opts?.includeSkinTones, offset: opts?.offset };
708
+ }
428
709
  function timeSource(timezone, options = {}) {
429
710
  const values = { ip: options.ip, city: options.city, country: options.country, state: options.state, iata: options.iata, icao: options.icao, unlocode: options.unlocode, address: options.address };
430
711
  const primary = [options.ip, options.city, options.iata, options.icao, options.unlocode, options.address].filter((value) => value !== void 0);
@@ -445,6 +726,8 @@ function timeTargets(targets, to) {
445
726
  }
446
727
  // Annotate the CommonJS export names for ESM import in node:
447
728
  0 && (module.exports = {
729
+ EmojiCatalog,
730
+ EmojiCatalogError,
448
731
  ParseAPIError,
449
732
  parseAPI
450
733
  });