@sorisdk/matcher 0.1.0
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 +56 -0
- package/dist/assets/model.pack +0 -0
- package/dist/generated/core.d.ts +26 -0
- package/dist/generated/core.js +9 -0
- package/dist/generated/core_bg.js +517 -0
- package/dist/generated/core_bg.wasm +0 -0
- package/dist/generated/core_bg.wasm.d.ts +25 -0
- package/dist/index.d.ts +145 -0
- package/dist/index.js +716 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var TypedEventEmitter = class {
|
|
3
|
+
listeners = /* @__PURE__ */ new Map();
|
|
4
|
+
on(eventName, listener) {
|
|
5
|
+
const bucket = this.listeners.get(eventName) ?? /* @__PURE__ */ new Set();
|
|
6
|
+
bucket.add(listener);
|
|
7
|
+
this.listeners.set(eventName, bucket);
|
|
8
|
+
return this;
|
|
9
|
+
}
|
|
10
|
+
off(eventName, listener) {
|
|
11
|
+
const bucket = this.listeners.get(eventName);
|
|
12
|
+
if (!bucket) {
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
bucket.delete(listener);
|
|
16
|
+
if (bucket.size === 0) {
|
|
17
|
+
this.listeners.delete(eventName);
|
|
18
|
+
}
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
once(eventName, listener) {
|
|
22
|
+
const wrapped = (payload) => {
|
|
23
|
+
this.off(eventName, wrapped);
|
|
24
|
+
listener(payload);
|
|
25
|
+
};
|
|
26
|
+
return this.on(eventName, wrapped);
|
|
27
|
+
}
|
|
28
|
+
emit(eventName, payload) {
|
|
29
|
+
const bucket = this.listeners.get(eventName);
|
|
30
|
+
if (!bucket) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const listener of bucket) {
|
|
34
|
+
listener(payload);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
clear() {
|
|
38
|
+
this.listeners.clear();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/auth.ts
|
|
43
|
+
function resolveFetch(fetcher) {
|
|
44
|
+
if (fetcher) {
|
|
45
|
+
return fetcher;
|
|
46
|
+
}
|
|
47
|
+
if (typeof globalThis.fetch !== "function") {
|
|
48
|
+
throw new Error("fetch is not available; pass `auth.fetch` in initMatcher options");
|
|
49
|
+
}
|
|
50
|
+
return globalThis.fetch.bind(globalThis);
|
|
51
|
+
}
|
|
52
|
+
function ensureString(name, value) {
|
|
53
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
54
|
+
throw new Error(`Matcher auth response missing ${name}`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function parseExchangeMetadata(payload) {
|
|
59
|
+
return {
|
|
60
|
+
expiresAt: typeof payload.expires_at === "string" ? payload.expires_at : typeof payload.expiresAt === "string" ? payload.expiresAt : null,
|
|
61
|
+
ttlSeconds: typeof payload.ttl_seconds === "number" ? payload.ttl_seconds : typeof payload.ttlSeconds === "number" ? payload.ttlSeconds : null,
|
|
62
|
+
raw: payload
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function exchangeEphemeralKey(options) {
|
|
66
|
+
if (!options.secretKey || !options.exchange) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Matcher auth requires either `ephemeralKey` or `secretKey` with `exchange.endpoint`"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const fetcher = resolveFetch(options.exchange.fetch ?? options.fetch);
|
|
72
|
+
const body = new URLSearchParams({
|
|
73
|
+
app_id: options.appId,
|
|
74
|
+
secret_key: options.secretKey
|
|
75
|
+
});
|
|
76
|
+
const response = await fetcher(options.exchange.endpoint, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
body
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`Matcher auth exchange failed: HTTP ${response.status}`);
|
|
82
|
+
}
|
|
83
|
+
let payload;
|
|
84
|
+
try {
|
|
85
|
+
payload = await response.json();
|
|
86
|
+
} catch {
|
|
87
|
+
throw new Error("Matcher auth exchange failed: invalid JSON response");
|
|
88
|
+
}
|
|
89
|
+
const exchanged = payload.ephemeral_key ?? payload.ephemeralKey ?? payload.secret_key ?? payload.secretKey;
|
|
90
|
+
return {
|
|
91
|
+
ephemeralKey: ensureString("ephemeral key", exchanged),
|
|
92
|
+
metadata: parseExchangeMetadata(payload)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function authenticateMatcher(options) {
|
|
96
|
+
const exchangeResult = options.ephemeralKey ? null : await exchangeEphemeralKey(options);
|
|
97
|
+
const ephemeralKey = options.ephemeralKey ?? exchangeResult?.ephemeralKey;
|
|
98
|
+
const fetcher = resolveFetch(options.fetch);
|
|
99
|
+
const body = new URLSearchParams({
|
|
100
|
+
app_id: options.appId,
|
|
101
|
+
secret_key: ensureString("ephemeral key", ephemeralKey),
|
|
102
|
+
session_id: options.sessionId,
|
|
103
|
+
v: options.version
|
|
104
|
+
});
|
|
105
|
+
const response = await fetcher(options.endpoint, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
body
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`Matcher auth failed: HTTP ${response.status}`);
|
|
111
|
+
}
|
|
112
|
+
let payload;
|
|
113
|
+
try {
|
|
114
|
+
payload = await response.json();
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error("Matcher auth failed: invalid JSON response");
|
|
117
|
+
}
|
|
118
|
+
const token = ensureString("token", payload.token);
|
|
119
|
+
return {
|
|
120
|
+
token,
|
|
121
|
+
tokenExpiresAt: typeof payload.token_expires_at === "string" ? payload.token_expires_at : typeof payload.tokenExpiresAt === "string" ? payload.tokenExpiresAt : null,
|
|
122
|
+
packUrl: typeof payload.url === "string" ? payload.url : null,
|
|
123
|
+
updated: typeof payload.updated === "number" ? payload.updated : 0,
|
|
124
|
+
forceUpdate: payload.force_update === 1,
|
|
125
|
+
exchange: exchangeResult?.metadata ?? null,
|
|
126
|
+
raw: payload
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/embedded-pack.ts
|
|
131
|
+
var EMBEDDED_PACK_URL = new URL("./assets/model.pack", import.meta.url);
|
|
132
|
+
var embeddedPackBytesPromise = null;
|
|
133
|
+
var testInjectedEmbeddedPackBytes = null;
|
|
134
|
+
function resolveFetch2(fetcher) {
|
|
135
|
+
if (fetcher) {
|
|
136
|
+
return fetcher;
|
|
137
|
+
}
|
|
138
|
+
if (typeof globalThis.fetch !== "function") {
|
|
139
|
+
throw new Error("fetch is not available; pass `fetch` in MatcherSession options");
|
|
140
|
+
}
|
|
141
|
+
return globalThis.fetch.bind(globalThis);
|
|
142
|
+
}
|
|
143
|
+
function cloneBytes(bytes) {
|
|
144
|
+
return new Uint8Array(bytes);
|
|
145
|
+
}
|
|
146
|
+
async function readPackBytes(fetcher) {
|
|
147
|
+
const response = await resolveFetch2(fetcher)(EMBEDDED_PACK_URL);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`Failed to load embedded pack: HTTP ${response.status}`);
|
|
150
|
+
}
|
|
151
|
+
const buffer = await response.arrayBuffer();
|
|
152
|
+
return new Uint8Array(buffer.slice(0));
|
|
153
|
+
}
|
|
154
|
+
async function loadEmbeddedPackBytes(fetcher) {
|
|
155
|
+
if (testInjectedEmbeddedPackBytes) {
|
|
156
|
+
return cloneBytes(testInjectedEmbeddedPackBytes);
|
|
157
|
+
}
|
|
158
|
+
if (!embeddedPackBytesPromise) {
|
|
159
|
+
embeddedPackBytesPromise = readPackBytes(fetcher).catch((error) => {
|
|
160
|
+
embeddedPackBytesPromise = null;
|
|
161
|
+
throw error;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const bytes = await embeddedPackBytesPromise;
|
|
165
|
+
return cloneBytes(bytes);
|
|
166
|
+
}
|
|
167
|
+
function __setEmbeddedPackBytesForTests(bytes) {
|
|
168
|
+
testInjectedEmbeddedPackBytes = cloneBytes(bytes);
|
|
169
|
+
embeddedPackBytesPromise = null;
|
|
170
|
+
}
|
|
171
|
+
function __resetEmbeddedPackLoaderForTests() {
|
|
172
|
+
embeddedPackBytesPromise = null;
|
|
173
|
+
testInjectedEmbeddedPackBytes = null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/source.ts
|
|
177
|
+
function hasCtor(name) {
|
|
178
|
+
return typeof globalThis[name] !== "undefined";
|
|
179
|
+
}
|
|
180
|
+
function copyToUint8Array(input) {
|
|
181
|
+
return new Uint8Array(input);
|
|
182
|
+
}
|
|
183
|
+
function toUint8Array(buffer) {
|
|
184
|
+
return new Uint8Array(buffer.slice(0));
|
|
185
|
+
}
|
|
186
|
+
function classifyPackSource(source) {
|
|
187
|
+
if (source instanceof Uint8Array) {
|
|
188
|
+
return "uint8array";
|
|
189
|
+
}
|
|
190
|
+
if (source instanceof ArrayBuffer) {
|
|
191
|
+
return "arraybuffer";
|
|
192
|
+
}
|
|
193
|
+
if (typeof source === "string") {
|
|
194
|
+
return "string-url";
|
|
195
|
+
}
|
|
196
|
+
if (source instanceof URL) {
|
|
197
|
+
return "url";
|
|
198
|
+
}
|
|
199
|
+
if (hasCtor("Request") && source instanceof Request) {
|
|
200
|
+
return "request";
|
|
201
|
+
}
|
|
202
|
+
if (hasCtor("Response") && source instanceof Response) {
|
|
203
|
+
return "response";
|
|
204
|
+
}
|
|
205
|
+
if (hasCtor("Blob") && source instanceof Blob) {
|
|
206
|
+
return "blob";
|
|
207
|
+
}
|
|
208
|
+
return "unknown";
|
|
209
|
+
}
|
|
210
|
+
function resolveFetch3(fetcher) {
|
|
211
|
+
if (fetcher) {
|
|
212
|
+
return fetcher;
|
|
213
|
+
}
|
|
214
|
+
if (typeof globalThis.fetch !== "function") {
|
|
215
|
+
throw new Error("fetch is not available; pass `fetch` in MatcherSession options");
|
|
216
|
+
}
|
|
217
|
+
return globalThis.fetch.bind(globalThis);
|
|
218
|
+
}
|
|
219
|
+
async function readResponseAsBytes(response) {
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
throw new Error(`Failed to load pack: HTTP ${response.status}`);
|
|
222
|
+
}
|
|
223
|
+
return toUint8Array(await response.arrayBuffer());
|
|
224
|
+
}
|
|
225
|
+
async function normalizePackSource(source, fetcher) {
|
|
226
|
+
const sourceType = classifyPackSource(source);
|
|
227
|
+
if (source instanceof Uint8Array) {
|
|
228
|
+
return { sourceType, bytes: copyToUint8Array(source) };
|
|
229
|
+
}
|
|
230
|
+
if (source instanceof ArrayBuffer) {
|
|
231
|
+
return { sourceType, bytes: toUint8Array(source) };
|
|
232
|
+
}
|
|
233
|
+
if (hasCtor("Blob") && source instanceof Blob) {
|
|
234
|
+
return { sourceType, bytes: toUint8Array(await source.arrayBuffer()) };
|
|
235
|
+
}
|
|
236
|
+
if (hasCtor("Response") && source instanceof Response) {
|
|
237
|
+
return { sourceType, bytes: await readResponseAsBytes(source) };
|
|
238
|
+
}
|
|
239
|
+
if (typeof source === "string" || source instanceof URL || hasCtor("Request") && source instanceof Request) {
|
|
240
|
+
const response = await resolveFetch3(fetcher)(source);
|
|
241
|
+
return { sourceType, bytes: await readResponseAsBytes(response) };
|
|
242
|
+
}
|
|
243
|
+
throw new Error("Unsupported pack source type");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/match-deduper.ts
|
|
247
|
+
var DEFAULT_NO_MATCH_CONFIRMATION_COUNT = 2;
|
|
248
|
+
function getMatchIdentity(match) {
|
|
249
|
+
return `${match.name}:${match.afpType}`;
|
|
250
|
+
}
|
|
251
|
+
var DistinctMatchDeduper = class {
|
|
252
|
+
lastIdentity = null;
|
|
253
|
+
pendingNoMatchCount = 0;
|
|
254
|
+
observe(best) {
|
|
255
|
+
if (!best) {
|
|
256
|
+
if (this.lastIdentity === null) {
|
|
257
|
+
return { kind: "repeat", best: null };
|
|
258
|
+
}
|
|
259
|
+
this.pendingNoMatchCount += 1;
|
|
260
|
+
if (this.pendingNoMatchCount < DEFAULT_NO_MATCH_CONFIRMATION_COUNT) {
|
|
261
|
+
return { kind: "repeat", best: null };
|
|
262
|
+
}
|
|
263
|
+
this.lastIdentity = null;
|
|
264
|
+
this.pendingNoMatchCount = 0;
|
|
265
|
+
return { kind: "nomatch" };
|
|
266
|
+
}
|
|
267
|
+
const nextIdentity = getMatchIdentity(best);
|
|
268
|
+
this.pendingNoMatchCount = 0;
|
|
269
|
+
if (this.lastIdentity === nextIdentity) {
|
|
270
|
+
return { kind: "repeat", best };
|
|
271
|
+
}
|
|
272
|
+
this.lastIdentity = nextIdentity;
|
|
273
|
+
return { kind: "match", best };
|
|
274
|
+
}
|
|
275
|
+
reset() {
|
|
276
|
+
this.lastIdentity = null;
|
|
277
|
+
this.pendingNoMatchCount = 0;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// src/wasm.ts
|
|
282
|
+
function getCallable(obj, names) {
|
|
283
|
+
for (const name of names) {
|
|
284
|
+
const candidate = obj[name];
|
|
285
|
+
if (typeof candidate === "function") {
|
|
286
|
+
return candidate;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
var defaultLoader = async () => {
|
|
292
|
+
const generatedModuleUrl = new URL("./generated/core.js", import.meta.url);
|
|
293
|
+
return import(
|
|
294
|
+
/* @vite-ignore */
|
|
295
|
+
generatedModuleUrl.href
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
var INTERNAL_DUPLICATE_MATCH_FLAG = "__matcherDuplicate";
|
|
299
|
+
var WrappedDatabase = class {
|
|
300
|
+
constructor(inner) {
|
|
301
|
+
this.inner = inner;
|
|
302
|
+
}
|
|
303
|
+
raw() {
|
|
304
|
+
return this.inner;
|
|
305
|
+
}
|
|
306
|
+
addEntry(name, afpType, fingerprint) {
|
|
307
|
+
const fn = getCallable(this.inner, ["addEntry", "add_entry"]);
|
|
308
|
+
if (!fn) {
|
|
309
|
+
throw new Error("WASM database is missing addEntry");
|
|
310
|
+
}
|
|
311
|
+
fn.call(this.inner, name, afpType, fingerprint);
|
|
312
|
+
}
|
|
313
|
+
addOrReplace(name, afpType, fingerprint) {
|
|
314
|
+
const fn = getCallable(this.inner, ["addOrReplace", "add_or_replace"]);
|
|
315
|
+
if (!fn) {
|
|
316
|
+
throw new Error("WASM database is missing addOrReplace");
|
|
317
|
+
}
|
|
318
|
+
fn.call(this.inner, name, afpType, fingerprint);
|
|
319
|
+
}
|
|
320
|
+
removeEntry(name) {
|
|
321
|
+
const fn = getCallable(this.inner, ["removeEntry", "remove_entry"]);
|
|
322
|
+
if (!fn) {
|
|
323
|
+
throw new Error("WASM database is missing removeEntry");
|
|
324
|
+
}
|
|
325
|
+
fn.call(this.inner, name);
|
|
326
|
+
}
|
|
327
|
+
clear() {
|
|
328
|
+
const fn = getCallable(this.inner, ["clear"]);
|
|
329
|
+
if (!fn) {
|
|
330
|
+
throw new Error("WASM database is missing clear");
|
|
331
|
+
}
|
|
332
|
+
fn.call(this.inner);
|
|
333
|
+
}
|
|
334
|
+
loadPackBytes(bytes) {
|
|
335
|
+
const fn = getCallable(this.inner, [
|
|
336
|
+
"loadPackBytes",
|
|
337
|
+
"loadPack",
|
|
338
|
+
"loadPackFromBytes",
|
|
339
|
+
"load_audiopack_bytes"
|
|
340
|
+
]);
|
|
341
|
+
if (!fn) {
|
|
342
|
+
throw new Error("WASM database is missing loadPackBytes-compatible method");
|
|
343
|
+
}
|
|
344
|
+
fn.call(this.inner, bytes);
|
|
345
|
+
}
|
|
346
|
+
loadPackBytesAppend(bytes) {
|
|
347
|
+
const appendFn = getCallable(this.inner, ["loadPackBytesAppend", "load_pack_bytes_append"]);
|
|
348
|
+
if (!appendFn) {
|
|
349
|
+
throw new Error("WASM database is missing loadPackBytesAppend-compatible method");
|
|
350
|
+
}
|
|
351
|
+
appendFn.call(this.inner, bytes);
|
|
352
|
+
}
|
|
353
|
+
destroy() {
|
|
354
|
+
const fn = getCallable(this.inner, ["destroy", "free"]);
|
|
355
|
+
if (fn) {
|
|
356
|
+
fn.call(this.inner);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var WrappedMatcher = class {
|
|
361
|
+
constructor(inner) {
|
|
362
|
+
this.inner = inner;
|
|
363
|
+
}
|
|
364
|
+
findMatches(database, query, config) {
|
|
365
|
+
const fn = getCallable(this.inner, ["findMatches", "find_matches", "matchQuery", "match_query", "match"]);
|
|
366
|
+
if (!fn) {
|
|
367
|
+
throw new Error("WASM matcher is missing findMatches-compatible method");
|
|
368
|
+
}
|
|
369
|
+
const dbRecord = typeof database.raw === "function" ? database.raw() : database;
|
|
370
|
+
const rawResults = fn.call(this.inner, dbRecord, query, config);
|
|
371
|
+
return normalizeResults(rawResults ?? []);
|
|
372
|
+
}
|
|
373
|
+
bestMatch(database, query, config) {
|
|
374
|
+
const fn = getCallable(this.inner, ["bestMatch", "best_match"]);
|
|
375
|
+
if (!fn) {
|
|
376
|
+
throw new Error("WASM matcher is missing bestMatch-compatible method");
|
|
377
|
+
}
|
|
378
|
+
const dbRecord = typeof database.raw === "function" ? database.raw() : database;
|
|
379
|
+
const raw = fn.call(this.inner, dbRecord, query, config);
|
|
380
|
+
if (!raw) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
if (typeof raw === "object" && raw !== null && raw.duplicate === true && raw.best) {
|
|
384
|
+
const normalized = normalizeResult(raw.best);
|
|
385
|
+
Object.defineProperty(normalized, INTERNAL_DUPLICATE_MATCH_FLAG, {
|
|
386
|
+
value: true,
|
|
387
|
+
enumerable: false,
|
|
388
|
+
configurable: false,
|
|
389
|
+
writable: false
|
|
390
|
+
});
|
|
391
|
+
return normalized;
|
|
392
|
+
}
|
|
393
|
+
return normalizeResult(raw);
|
|
394
|
+
}
|
|
395
|
+
resetDistinctMatchState() {
|
|
396
|
+
const fn = getCallable(this.inner, ["resetDistinctMatchState", "reset_distinct_match_state"]);
|
|
397
|
+
fn?.call(this.inner);
|
|
398
|
+
}
|
|
399
|
+
destroy() {
|
|
400
|
+
const fn = getCallable(this.inner, ["destroy", "free"]);
|
|
401
|
+
if (fn) {
|
|
402
|
+
fn.call(this.inner);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
function normalizeResult(raw) {
|
|
407
|
+
const afpType = Number(raw.afpType ?? raw.afp_type);
|
|
408
|
+
return {
|
|
409
|
+
name: String(raw.name),
|
|
410
|
+
score: Number(raw.score),
|
|
411
|
+
position: Number(raw.position),
|
|
412
|
+
afpType
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function normalizeResults(rawResults) {
|
|
416
|
+
return rawResults.filter((item) => typeof item === "object" && item !== null).map((item) => normalizeResult(item));
|
|
417
|
+
}
|
|
418
|
+
function isInternallyDuplicatedBestMatch(result) {
|
|
419
|
+
return Boolean(
|
|
420
|
+
result && typeof result === "object" && result[INTERNAL_DUPLICATE_MATCH_FLAG] === true
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
function resolveFactory(moduleLike, names) {
|
|
424
|
+
const fn = getCallable(moduleLike, names);
|
|
425
|
+
return fn ? fn.bind(moduleLike) : null;
|
|
426
|
+
}
|
|
427
|
+
function createBindingsFromModule(moduleLike) {
|
|
428
|
+
if (!moduleLike || typeof moduleLike !== "object") {
|
|
429
|
+
throw new Error("Invalid matcher-wasm module");
|
|
430
|
+
}
|
|
431
|
+
const mod = moduleLike;
|
|
432
|
+
const initFn = getCallable(mod, ["initMatcher"]) ?? (typeof mod.default === "function" ? mod.default.bind(mod) : null);
|
|
433
|
+
const createDatabaseFactory = resolveFactory(mod, ["createDatabase"]);
|
|
434
|
+
const createMatcherFactory = resolveFactory(mod, ["createMatcher"]);
|
|
435
|
+
const DatabaseCtor = mod.WasmMatcherDatabase ?? mod.MatcherDatabase ?? mod.Database;
|
|
436
|
+
const MatcherCtor = mod.WasmMatcherEngine ?? mod.MatcherEngine ?? mod.Matcher ?? mod.AudioMatcher;
|
|
437
|
+
if (!createDatabaseFactory && !DatabaseCtor) {
|
|
438
|
+
throw new Error("matcher wasm module is missing database export");
|
|
439
|
+
}
|
|
440
|
+
if (!createMatcherFactory && !MatcherCtor) {
|
|
441
|
+
throw new Error("matcher wasm module is missing matcher export");
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
async init(initInput) {
|
|
445
|
+
if (initFn) {
|
|
446
|
+
await Promise.resolve(initFn(initInput));
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
createDatabase() {
|
|
450
|
+
const db = createDatabaseFactory ? createDatabaseFactory() : new DatabaseCtor();
|
|
451
|
+
return new WrappedDatabase(db);
|
|
452
|
+
},
|
|
453
|
+
createMatcher() {
|
|
454
|
+
const matcher = createMatcherFactory ? createMatcherFactory() : new MatcherCtor();
|
|
455
|
+
return new WrappedMatcher(matcher);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/session.ts
|
|
461
|
+
var initializedBindingsPromise = null;
|
|
462
|
+
var testInjectedWasmModule = null;
|
|
463
|
+
async function getBindings(options) {
|
|
464
|
+
if (initializedBindingsPromise) {
|
|
465
|
+
return initializedBindingsPromise;
|
|
466
|
+
}
|
|
467
|
+
initializedBindingsPromise = (async () => {
|
|
468
|
+
try {
|
|
469
|
+
const moduleLike = options?.wasmModule ?? testInjectedWasmModule ?? (options?.loader ? await options.loader() : await defaultLoader());
|
|
470
|
+
const bindings = createBindingsFromModule(moduleLike);
|
|
471
|
+
await bindings.init(options?.wasmInitInput);
|
|
472
|
+
return bindings;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
initializedBindingsPromise = null;
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
})();
|
|
478
|
+
return initializedBindingsPromise;
|
|
479
|
+
}
|
|
480
|
+
function toUint8ArrayCopy(input) {
|
|
481
|
+
return new Uint8Array(input);
|
|
482
|
+
}
|
|
483
|
+
function handleError(emitter, phase, unknownError) {
|
|
484
|
+
const error = unknownError instanceof Error ? unknownError : new Error(String(unknownError));
|
|
485
|
+
emitter.emit("error", { phase, error });
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
async function initMatcher(options) {
|
|
489
|
+
await getBindings(options);
|
|
490
|
+
if (!options?.auth) {
|
|
491
|
+
return void 0;
|
|
492
|
+
}
|
|
493
|
+
return authenticateMatcher(options.auth);
|
|
494
|
+
}
|
|
495
|
+
var MatcherSession = class {
|
|
496
|
+
events = new TypedEventEmitter();
|
|
497
|
+
fetcher;
|
|
498
|
+
initOptions;
|
|
499
|
+
readyPromise;
|
|
500
|
+
bindings = null;
|
|
501
|
+
database = null;
|
|
502
|
+
matcher = null;
|
|
503
|
+
destroyed = false;
|
|
504
|
+
distinctMatchDeduper = new DistinctMatchDeduper();
|
|
505
|
+
constructor(options) {
|
|
506
|
+
this.fetcher = options?.fetch;
|
|
507
|
+
this.initOptions = {
|
|
508
|
+
loader: options?.loader,
|
|
509
|
+
wasmModule: options?.wasmModule,
|
|
510
|
+
wasmInitInput: options?.wasmInitInput
|
|
511
|
+
};
|
|
512
|
+
this.readyPromise = this.bootstrap();
|
|
513
|
+
}
|
|
514
|
+
on(eventName, listener) {
|
|
515
|
+
this.events.on(eventName, listener);
|
|
516
|
+
return this;
|
|
517
|
+
}
|
|
518
|
+
off(eventName, listener) {
|
|
519
|
+
this.events.off(eventName, listener);
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
once(eventName, listener) {
|
|
523
|
+
this.events.once(eventName, listener);
|
|
524
|
+
return this;
|
|
525
|
+
}
|
|
526
|
+
async ready() {
|
|
527
|
+
await this.readyPromise;
|
|
528
|
+
}
|
|
529
|
+
async loadPack(source) {
|
|
530
|
+
try {
|
|
531
|
+
await this.ensureLiveReady();
|
|
532
|
+
const sourceType = classifyPackSource(source);
|
|
533
|
+
this.events.emit("packloadstart", { sourceType });
|
|
534
|
+
const database = this.databaseOrThrow();
|
|
535
|
+
const normalized = await normalizePackSource(source, this.fetcher);
|
|
536
|
+
const embeddedPackBytes = await loadEmbeddedPackBytes();
|
|
537
|
+
database.clear();
|
|
538
|
+
database.loadPackBytes(embeddedPackBytes);
|
|
539
|
+
database.loadPackBytesAppend(normalized.bytes);
|
|
540
|
+
this.distinctMatchDeduper.reset();
|
|
541
|
+
this.matcher?.resetDistinctMatchState?.();
|
|
542
|
+
this.events.emit("packload", {
|
|
543
|
+
sourceType: normalized.sourceType,
|
|
544
|
+
byteLength: normalized.bytes.byteLength
|
|
545
|
+
});
|
|
546
|
+
} catch (error) {
|
|
547
|
+
handleError(this.events, "loadPack", error);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async addEntry(name, afpType, fingerprint) {
|
|
551
|
+
try {
|
|
552
|
+
await this.ensureLiveReady();
|
|
553
|
+
this.databaseOrThrow().addEntry(name, afpType, toUint8ArrayCopy(fingerprint));
|
|
554
|
+
this.distinctMatchDeduper.reset();
|
|
555
|
+
this.matcher?.resetDistinctMatchState?.();
|
|
556
|
+
} catch (error) {
|
|
557
|
+
handleError(this.events, "addEntry", error);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async addOrReplace(name, afpType, fingerprint) {
|
|
561
|
+
try {
|
|
562
|
+
await this.ensureLiveReady();
|
|
563
|
+
this.databaseOrThrow().addOrReplace(name, afpType, toUint8ArrayCopy(fingerprint));
|
|
564
|
+
this.distinctMatchDeduper.reset();
|
|
565
|
+
this.matcher?.resetDistinctMatchState?.();
|
|
566
|
+
} catch (error) {
|
|
567
|
+
handleError(this.events, "addOrReplace", error);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async removeEntry(name) {
|
|
571
|
+
try {
|
|
572
|
+
await this.ensureLiveReady();
|
|
573
|
+
this.databaseOrThrow().removeEntry(name);
|
|
574
|
+
this.distinctMatchDeduper.reset();
|
|
575
|
+
this.matcher?.resetDistinctMatchState?.();
|
|
576
|
+
} catch (error) {
|
|
577
|
+
handleError(this.events, "removeEntry", error);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async clear() {
|
|
581
|
+
try {
|
|
582
|
+
await this.ensureLiveReady();
|
|
583
|
+
this.databaseOrThrow().clear();
|
|
584
|
+
this.distinctMatchDeduper.reset();
|
|
585
|
+
this.matcher?.resetDistinctMatchState?.();
|
|
586
|
+
} catch (error) {
|
|
587
|
+
handleError(this.events, "clear", error);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async match(query, config) {
|
|
591
|
+
try {
|
|
592
|
+
await this.ensureLiveReady();
|
|
593
|
+
const safeQuery = toUint8ArrayCopy(query);
|
|
594
|
+
this.events.emit("querystart", { queryByteLength: safeQuery.byteLength });
|
|
595
|
+
const results = this.matcherOrThrow().findMatches(this.databaseOrThrow(), safeQuery, config);
|
|
596
|
+
this.emitDistinctMatchEvent(results[0] ?? null, safeQuery.byteLength, results);
|
|
597
|
+
return results;
|
|
598
|
+
} catch (error) {
|
|
599
|
+
handleError(this.events, "match", error);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
async bestMatch(query, config) {
|
|
603
|
+
try {
|
|
604
|
+
await this.ensureLiveReady();
|
|
605
|
+
const safeQuery = toUint8ArrayCopy(query);
|
|
606
|
+
this.events.emit("querystart", { queryByteLength: safeQuery.byteLength });
|
|
607
|
+
const best = this.matcherOrThrow().bestMatch(this.databaseOrThrow(), safeQuery, config);
|
|
608
|
+
if (isInternallyDuplicatedBestMatch(best)) {
|
|
609
|
+
return best;
|
|
610
|
+
}
|
|
611
|
+
this.emitDistinctMatchEvent(best, safeQuery.byteLength, best ? [best] : []);
|
|
612
|
+
return best;
|
|
613
|
+
} catch (error) {
|
|
614
|
+
handleError(this.events, "bestMatch", error);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
destroy() {
|
|
618
|
+
if (this.destroyed) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
this.destroyed = true;
|
|
622
|
+
this.matcher?.destroy();
|
|
623
|
+
this.database?.destroy();
|
|
624
|
+
this.matcher = null;
|
|
625
|
+
this.database = null;
|
|
626
|
+
this.bindings = null;
|
|
627
|
+
this.distinctMatchDeduper.reset();
|
|
628
|
+
this.events.emit("destroy", { destroyedAt: Date.now() });
|
|
629
|
+
this.events.clear();
|
|
630
|
+
}
|
|
631
|
+
async bootstrap() {
|
|
632
|
+
let database = null;
|
|
633
|
+
let matcher = null;
|
|
634
|
+
try {
|
|
635
|
+
const bindings = await getBindings(this.initOptions);
|
|
636
|
+
database = bindings.createDatabase();
|
|
637
|
+
matcher = bindings.createMatcher();
|
|
638
|
+
if (this.destroyed) {
|
|
639
|
+
matcher.destroy();
|
|
640
|
+
database.destroy();
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
this.bindings = bindings;
|
|
644
|
+
this.database = database;
|
|
645
|
+
this.matcher = matcher;
|
|
646
|
+
this.events.emit("ready", { createdAt: Date.now() });
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (this.destroyed) {
|
|
649
|
+
matcher?.destroy();
|
|
650
|
+
database?.destroy();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
handleError(this.events, "init", error);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async ensureLiveReady() {
|
|
657
|
+
this.ensureNotDestroyed();
|
|
658
|
+
await this.readyPromise;
|
|
659
|
+
this.ensureNotDestroyed();
|
|
660
|
+
}
|
|
661
|
+
ensureNotDestroyed() {
|
|
662
|
+
if (this.destroyed) {
|
|
663
|
+
throw new Error("MatcherSession has been destroyed");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
matcherOrThrow() {
|
|
667
|
+
if (!this.matcher) {
|
|
668
|
+
throw new Error("Matcher is not initialized");
|
|
669
|
+
}
|
|
670
|
+
return this.matcher;
|
|
671
|
+
}
|
|
672
|
+
databaseOrThrow() {
|
|
673
|
+
if (!this.database) {
|
|
674
|
+
throw new Error("Database is not initialized");
|
|
675
|
+
}
|
|
676
|
+
return this.database;
|
|
677
|
+
}
|
|
678
|
+
emitDistinctMatchEvent(best, queryByteLength, results) {
|
|
679
|
+
const observation = this.distinctMatchDeduper.observe(best);
|
|
680
|
+
if (observation.kind === "match") {
|
|
681
|
+
this.events.emit("match", { results, best: observation.best });
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (observation.kind === "nomatch") {
|
|
685
|
+
this.events.emit("nomatch", { queryByteLength });
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
function __setMatcherWasmModuleForTests(moduleLike) {
|
|
690
|
+
testInjectedWasmModule = moduleLike;
|
|
691
|
+
initializedBindingsPromise = null;
|
|
692
|
+
}
|
|
693
|
+
function __setMatcherEmbeddedPackForTests(bytes) {
|
|
694
|
+
__setEmbeddedPackBytesForTests(bytes);
|
|
695
|
+
}
|
|
696
|
+
function __resetMatcherWasmForTests() {
|
|
697
|
+
testInjectedWasmModule = null;
|
|
698
|
+
__resetEmbeddedPackLoaderForTests();
|
|
699
|
+
initializedBindingsPromise = null;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/types.ts
|
|
703
|
+
var AudioFingerprintType = /* @__PURE__ */ ((AudioFingerprintType2) => {
|
|
704
|
+
AudioFingerprintType2[AudioFingerprintType2["Livestream"] = 1] = "Livestream";
|
|
705
|
+
AudioFingerprintType2[AudioFingerprintType2["ArchiveCf"] = 2] = "ArchiveCf";
|
|
706
|
+
AudioFingerprintType2[AudioFingerprintType2["ArchivePg"] = 4] = "ArchivePg";
|
|
707
|
+
return AudioFingerprintType2;
|
|
708
|
+
})(AudioFingerprintType || {});
|
|
709
|
+
export {
|
|
710
|
+
AudioFingerprintType,
|
|
711
|
+
MatcherSession,
|
|
712
|
+
__resetMatcherWasmForTests,
|
|
713
|
+
__setMatcherEmbeddedPackForTests,
|
|
714
|
+
__setMatcherWasmModuleForTests,
|
|
715
|
+
initMatcher
|
|
716
|
+
};
|