@parseapi/sdk 1.10.0 → 1.10.2

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
@@ -258,6 +258,8 @@ const units = await parse.measure.units({ unit: 'm' });
258
258
 
259
259
  `amount` is a decimal string, such as `"180.34"`. Without `to`, the API returns the canonical unit for the measurement type. Pass `locale` for number formatting and `system` (`us` or `imperial`) when a customary unit needs context. Ambiguous input returns `valid: false`, a `reason`, and available `choices`. Invalid or incompatible target units use the normal API error.
260
260
 
261
+ Successful HTTP JSON contains `measure`, `valid`, `type`, `amount`, and `unit`. Invalid input adds `reason`. `choices` appears only when unit alternatives are available. The typed client keeps `reason` readable as `null` and `choices` as `[]` when omitted, while JSON serialization retains the API response shape.
262
+
261
263
  Unit discovery accepts optional `query`, `type`, and `unit` filters. `unit` selects compatible targets. Omit the filters for the reviewed catalog. Both operations use pooled requests.
262
264
 
263
265
  ## Place statistics and optional detail
@@ -451,3 +453,35 @@ Successful checks may be reused for up to 24 hours. `pretty` optionally formats
451
453
  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
454
 
453
455
  Vehicle lookups use `vin` as the input and response field. Existing VIN methods remain available for compatibility.
456
+
457
+ ## Emoji catalog additions
458
+
459
+ Resolve Unicode emoji or a named shortcode, search or browse a bounded catalog,
460
+ parse emoji positions in text, and convert between Unicode and a selected shortcode source.
461
+ The ordinary lookup remains four fields:
462
+
463
+ ```http
464
+ GET /emoji/rocket
465
+ ```
466
+
467
+ ```json
468
+ {"emoji":"🚀","name":"Rocket","shortcodes":[":rocket:","rocket"],"category":"Travel & Places"}
469
+ ```
470
+
471
+ Use `deep=true` for Unicode facts, keywords, tone families and available artwork.
472
+ Artwork is keyed by style: `deep.artwork.openmoji.svg` is the SVG URL, and
473
+ `deep.artwork.openmoji.png["72"]` is the 72×72 PNG URL (use your language’s map
474
+ access syntax). The same shape applies to `noto` and `twemoji`. Missing styles
475
+ and formats are omitted. URLs pin immutable artwork editions and retain their bytes.
476
+ Core values stay unchanged. Text parsing returns `emojis` with UTF-16 `positions`;
477
+ conversion returns `text` and unresolved tokens with positions and reasons.
478
+
479
+ The compact JSON catalog contains offline identity, qualification-form and
480
+ shortcode indexes. Request `profile=full` when artwork is needed and `lang` when
481
+ localized names and keywords are needed. `/emoji/metadata` carries dataset
482
+ coverage and shared source notices once. Artwork supports Google Noto, Twemoji
483
+ and OpenMoji, with real SVG/PNG availability and immutable first-party URLs.
484
+ A device's emoji font remains the ordinary display.
485
+
486
+ These additions are a local release candidate until the matching SDK and
487
+ 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.10.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.2";
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;
@@ -351,7 +612,12 @@ function parseAPI(apiKey, options = {}) {
351
612
  },
352
613
  /** Parse a measurement or convert it to `to`. Without `to`, use its type's canonical unit. Invalid input is plain data with `valid: false`. */
353
614
  measure: Object.assign(
354
- (measure, opts) => request(`/measure/${enc(measure)}`, { to: opts?.to, locale: opts?.locale, system: opts?.system }, void 0, opts),
615
+ async (measure, opts) => {
616
+ const result = await request(`/measure/${enc(measure)}`, { to: opts?.to, locale: opts?.locale, system: opts?.system }, void 0, opts);
617
+ if (!Object.prototype.hasOwnProperty.call(result, "reason")) Object.defineProperty(result, "reason", { value: null, writable: true, configurable: true });
618
+ if (!Object.prototype.hasOwnProperty.call(result, "choices")) Object.defineProperty(result, "choices", { value: [], writable: true, configurable: true });
619
+ return result;
620
+ },
355
621
  {
356
622
  /** Discover reviewed units. Pass `unit` to find compatible conversion targets. */
357
623
  units: (opts) => request("/measure/units", { q: opts?.query, type: opts?.type, unit: opts?.unit }, void 0, opts)
@@ -428,13 +694,23 @@ function parseAPI(apiKey, options = {}) {
428
694
  /** 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. */
429
695
  weather: (lat, lon, opts) => request("/weather", { lat, lon, date: opts?.date, ...deepQuery(opts) }, void 0, opts),
430
696
  emoji: Object.assign(
431
- (emoji, opts) => request(`/emoji/${enc(emoji)}`, deepQuery(opts), void 0, opts),
697
+ (emoji, opts) => request(`/emoji/${enc(emoji)}`, { shortcode_source: opts?.shortcodeSource, ...deepQuery(opts) }, void 0, opts),
432
698
  {
433
- search: (query, opts) => request("/emoji", { q: query, limit: opts?.limit, ...deepQuery(opts) }, void 0, opts)
699
+ search: (query, opts) => request("/emoji", { q: query, ...emojiFilters(opts), ...deepQuery(opts) }, void 0, opts),
700
+ browse: (opts) => request("/emoji", { ...emojiFilters(opts), ...deepQuery(opts) }, void 0, opts),
701
+ categories: (opts) => request("/emoji/categories", void 0, void 0, requestControls(opts)),
702
+ convert: (text, opts) => request("/emoji/convert", { direction: opts.direction, shortcode_source: opts.shortcodeSource }, void 0, opts, { text }),
703
+ /** Extract complete sequences. Positions use UTF-16 code units; end is exclusive. */
704
+ catalog: (opts) => request("/emoji/catalog", { ...emojiFilters(opts), profile: opts?.profile, deep: opts?.deep ?? true }, void 0, opts),
705
+ metadata: (opts) => request("/emoji/metadata", void 0, void 0, requestControls(opts)),
706
+ parse: (text, opts) => request("/emoji/parse", deepQuery(opts), void 0, opts, { text })
434
707
  }
435
708
  )
436
709
  };
437
710
  }
711
+ function emojiFilters(opts) {
712
+ 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 };
713
+ }
438
714
  function timeSource(timezone, options = {}) {
439
715
  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 };
440
716
  const primary = [options.ip, options.city, options.iata, options.icao, options.unlocode, options.address].filter((value) => value !== void 0);
@@ -455,6 +731,8 @@ function timeTargets(targets, to) {
455
731
  }
456
732
  // Annotate the CommonJS export names for ESM import in node:
457
733
  0 && (module.exports = {
734
+ EmojiCatalog,
735
+ EmojiCatalogError,
458
736
  ParseAPIError,
459
737
  parseAPI
460
738
  });