@voltro/plugin-search 0.85.0 → 0.86.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/dist/index.js CHANGED
@@ -1,873 +1,29 @@
1
- import { a as e, i as t, o as n, r, t as i } from "./rpc-CggQ77CG.js";
2
- import { Cause as a, Data as o, Duration as s, Effect as c, Exit as l, Option as u, Schedule as d, Stream as f } from "effect";
3
- import { and as p, count as m, eq as h, id as g, integer as _, streamTable as ee, table as v, text as y, timestamp as b } from "@voltro/database";
4
- import { definePlugin as te, pluginInstanceName as ne } from "@voltro/protocol";
5
- import { replicaId as re } from "@voltro/protocol/identity";
6
- //#region src/sanitize.ts
7
- var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
8
- let n = t ? new Set(t) : void 0;
9
- for (let [t, r] of e) {
10
- if (!S(r)) return {
11
- field: r,
12
- where: t,
13
- reason: "not-an-identifier"
14
- };
15
- if (n && !n.has(r)) return {
16
- field: r,
17
- where: t,
18
- reason: "not-queryable"
19
- };
20
- }
21
- }, C = {
22
- typesense: [
23
- "per_page",
24
- "page",
25
- "offset",
26
- "limit",
27
- "sort_by",
28
- "num_typos",
29
- "typo_tokens_threshold",
30
- "drop_tokens_threshold",
31
- "prefix",
32
- "prioritize_exact_match",
33
- "highlight_start_tag",
34
- "highlight_end_tag",
35
- "highlight_affix_num_tokens",
36
- "snippet_threshold",
37
- "max_facet_values"
38
- ],
39
- meilisearch: [
40
- "limit",
41
- "offset",
42
- "page",
43
- "hitsPerPage",
44
- "sort",
45
- "matchingStrategy",
46
- "showRankingScore",
47
- "rankingScoreThreshold",
48
- "distinct",
49
- "attributesToHighlight",
50
- "highlightPreTag",
51
- "highlightPostTag",
52
- "attributesToCrop",
53
- "cropLength",
54
- "cropMarker",
55
- "showMatchesPosition"
56
- ],
57
- algolia: [
58
- "hitsPerPage",
59
- "page",
60
- "offset",
61
- "length",
62
- "typoTolerance",
63
- "minWordSizefor1Typo",
64
- "minWordSizefor2Typos",
65
- "ignorePlurals",
66
- "removeStopWords",
67
- "queryType",
68
- "distinct",
69
- "attributesToHighlight",
70
- "attributesToSnippet",
71
- "snippetEllipsisText",
72
- "highlightPreTag",
73
- "highlightPostTag"
74
- ]
75
- }, w = {
76
- typesense: [
77
- "filter_by",
78
- "q",
79
- "preset",
80
- "pinned_hits",
81
- "hidden_hits",
82
- "enable_overrides",
83
- "override_tags",
84
- "include_fields",
85
- "exclude_fields",
86
- "vector_query"
87
- ],
88
- meilisearch: [
89
- "filter",
90
- "q",
91
- "attributesToSearchOn",
92
- "attributesToRetrieve",
93
- "hybrid",
94
- "vector",
95
- "federation"
96
- ],
97
- algolia: [
98
- "filters",
99
- "facetFilters",
100
- "numericFilters",
101
- "tagFilters",
102
- "optionalFilters",
103
- "query",
104
- "restrictSearchableAttributes",
105
- "attributesToRetrieve",
106
- "enableRules",
107
- "ruleContexts",
108
- "similarQuery",
109
- "aroundLatLng",
110
- "insideBoundingBox"
111
- ]
112
- }, T = (e, t, n) => {
113
- if (!t) return {
114
- params: {},
115
- appAllowed: {},
116
- dropped: []
117
- };
118
- let r = new Set(w[e]), i = new Set(C[e]), a = new Set(n ?? []), o = {}, s = {}, c = [];
119
- for (let [e, n] of Object.entries(t)) r.has(e) ? c.push(e) : i.has(e) ? o[e] = n : a.has(e) ? s[e] = n : c.push(e);
120
- return {
121
- params: o,
122
- appAllowed: s,
123
- dropped: c
124
- };
125
- }, E = (e) => C[e], D = class extends o.TaggedError("SearchBackendError") {}, O = (e) => Array.isArray(e) ? e : [e], k = (e, t, n) => {
126
- if (Math.abs(e.length - t.length) > n) return !1;
127
- if (e === t) return !0;
128
- if (Number.isNaN(n)) return !1;
129
- let r = t.length, i = Math.floor(n), a = Array(r + 1).fill(Infinity), o = Array(r + 1).fill(Infinity);
130
- for (let e = 0; e <= Math.min(r, i); e++) a[e] = e;
131
- for (let s = 1; s <= e.length; s++) {
132
- let c = Math.max(1, s - i), l = Math.min(r, s + i);
133
- o[0] = s, c > 1 && (o[c - 1] = Infinity), l < r && (o[l + 1] = Infinity);
134
- let u = s, d = e.charCodeAt(s - 1);
135
- for (let e = c; e <= l; e++) {
136
- let n = d === t.charCodeAt(e - 1) ? 0 : 1, r = Math.min(a[e] + 1, o[e - 1] + 1, a[e - 1] + n);
137
- o[e] = r, u = Math.min(u, r);
138
- }
139
- if (u > n) return !1;
140
- let f = a;
141
- a = o, o = f;
142
- }
143
- return a[r] <= n;
144
- }, A = (e) => e.length <= 4 ? 1 : 2, j = () => {
145
- let e = /* @__PURE__ */ new Map(), t = (t) => {
146
- let n = e.get(t);
147
- return n || (n = /* @__PURE__ */ new Map(), e.set(t, n)), n;
148
- }, n = (e, t) => {
149
- let n = e[t.field];
150
- switch (t.op) {
151
- case "eq": return String(n) === String(t.value);
152
- case "neq": return String(n) !== String(t.value);
153
- case "in": return O(t.value).some((e) => String(n) === String(e));
154
- case "nin": return O(t.value).every((e) => String(n) !== String(e));
155
- case "gt":
156
- case "gte":
157
- case "lt":
158
- case "lte": {
159
- let [e, r] = [M(n), M(t.value)];
160
- return e === void 0 || r === void 0 ? !1 : t.op === "gt" ? e > r : t.op === "gte" ? e >= r : t.op === "lt" ? e < r : e <= r;
161
- }
162
- }
163
- }, r = (e, t) => !t || t.every((t) => n(e, t)), i = (e) => Object.entries(e).filter(([e]) => e !== "id").map(([e, t]) => [e, String(t)]), a = (e, t, n) => {
164
- if (t.length === 0) return !0;
165
- for (let [, r] of i(e)) {
166
- let e = r.toLowerCase();
167
- if (e.includes(t)) return !0;
168
- if (n !== void 0 && n !== 0) {
169
- let r = n === "auto" ? A(t) : n;
170
- for (let n of e.split(/\s+/)) if (k(n, t, r)) return !0;
171
- }
172
- }
173
- return !1;
174
- }, o = (e, t, n) => {
175
- if (t.length === 0) return {};
176
- let r = n.preTag ?? "<mark>", a = n.postTag ?? "</mark>", o = n.fields && n.fields.length > 0 ? new Set(n.fields) : null, s = {}, c = new RegExp(t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
177
- for (let [n, l] of i(e)) (!o || o.has(n)) && l.toLowerCase().includes(t) && (s[n] = l.replace(c, (e) => `${r}${e}${a}`));
178
- return s;
179
- };
180
- return {
181
- engineKind: "memory",
182
- upsert: async (e, n) => {
183
- let r = t(e);
184
- for (let e of n) r.set(e.id, e);
185
- },
186
- remove: async (e, n) => {
187
- let r = t(e);
188
- for (let e of n) r.delete(e);
189
- },
190
- query: async (e, { q: n, limit: i, offset: s, filters: c, facets: l, highlight: u, fuzziness: d }) => {
191
- let f = n.trim().toLowerCase(), p = [];
192
- for (let n of t(e).values()) r(n, c) && a(n, f, d) && p.push(n);
193
- let m = {};
194
- if (l) for (let e of l) {
195
- let t = {};
196
- for (let n of p) {
197
- let r = n[e];
198
- if (r == null) continue;
199
- let i = String(r);
200
- t[i] = (t[i] ?? 0) + 1;
201
- }
202
- m[e] = t;
203
- }
204
- let h = s ?? 0;
205
- return {
206
- hits: (i === void 0 ? p.slice(h) : p.slice(h, h + i)).map((e) => u ? {
207
- doc: e,
208
- highlights: o(e, f, u)
209
- } : { doc: e }),
210
- facets: m
211
- };
212
- }
213
- };
214
- }, M = (e) => {
215
- if (typeof e == "number") return e;
216
- if (typeof e == "boolean") return +!!e;
217
- if (typeof e == "string") {
218
- let t = Number(e);
219
- if (!Number.isNaN(t) && e.trim() !== "") return t;
220
- let n = Date.parse(e);
221
- return Number.isNaN(n) ? void 0 : n;
222
- }
223
- }, N = async (e, t, n) => {
224
- try {
225
- return await n();
226
- } catch {
227
- throw new D({
228
- engine: t,
229
- op: "query",
230
- message: `the '${t}' search backend requires the '${e}' optional dependency. Install it to enable it (e.g. \`npm i ${e}\`).`,
231
- transient: !1
232
- });
233
- }
234
- }, P = (e, t) => (n) => {
235
- let { params: r, appAllowed: i, dropped: a } = T(e, n, t.allowEngineParams);
236
- return a.length > 0 && t.onWarn?.("search dropped disallowed engineParams", {
237
- engine: e,
238
- keys: a
239
- }), {
240
- before: r,
241
- after: i
242
- };
243
- }, F = (e, t) => {
244
- if (!S(t)) throw new D({
245
- engine: e,
246
- op: "query",
247
- message: `filter field "${t}" is not a plain field path (^[A-Za-z_][A-Za-z0-9_.]*$)`,
248
- transient: !1
249
- });
250
- return t;
251
- }, ae = (e) => {
252
- let t = (e) => JSON.stringify(e), n = F("typesense", e.field);
253
- switch (e.op) {
254
- case "eq": return `${n}:=${t(e.value)}`;
255
- case "neq": return `${n}:!=${t(e.value)}`;
256
- case "gt": return `${n}:>${t(e.value)}`;
257
- case "gte": return `${n}:>=${t(e.value)}`;
258
- case "lt": return `${n}:<${t(e.value)}`;
259
- case "lte": return `${n}:<=${t(e.value)}`;
260
- case "in": return `${n}:[${O(e.value).map(t).join(",")}]`;
261
- case "nin": return `${n}:!=[${O(e.value).map(t).join(",")}]`;
262
- }
263
- }, oe = (e, t = {}) => {
264
- let n = P("typesense", t), r, i = async () => (r ||= N("typesense", "typesense", () => import("typesense")).then((t) => {
265
- let n = t.Client;
266
- return new n({
267
- nodes: [{ url: e.url ?? e.host ?? "http://localhost:8108" }],
268
- apiKey: e.apiKey
269
- });
270
- }), r), a = (e) => e && e.length > 0 ? e.map(ae).join(" && ") : void 0, o = async (e, t) => {
271
- try {
272
- return await t();
273
- } catch (t) {
274
- throw t instanceof D ? t : new D({
275
- engine: "typesense",
276
- op: e,
277
- message: String(t?.message ?? t),
278
- transient: !0
279
- });
280
- }
281
- };
282
- return {
283
- upsert: (e, t) => o("upsert", async () => {
284
- await (await i()).collections(e).documents().import(t, { action: "upsert" });
285
- }),
286
- remove: (e, t) => o("remove", async () => {
287
- await (await i()).collections(e).documents().delete({ filter_by: `id:[${t.map((e) => JSON.stringify(e)).join(",")}]` });
288
- }),
289
- query: (e, { q: t, limit: r, offset: s, filters: c, facets: l, highlight: u, fuzziness: d, engineParams: f }) => o("query", async () => {
290
- let o = await i(), p = n(f), m = {
291
- ...p.before,
292
- q: t || "*",
293
- query_by: "*",
294
- per_page: r ?? 20,
295
- ...s ? { offset: s } : {},
296
- ...a(c) ? { filter_by: a(c) } : {},
297
- ...l && l.length > 0 ? { facet_by: l.join(",") } : {},
298
- ...u ? {
299
- ...u.fields && u.fields.length > 0 ? { highlight_fields: u.fields.join(",") } : { highlight_full_fields: "*" },
300
- ...u.preTag ? { highlight_start_tag: u.preTag } : {},
301
- ...u.postTag ? { highlight_end_tag: u.postTag } : {}
302
- } : {},
303
- ...d !== void 0 && d !== "auto" ? { num_typos: Math.min(2, Math.max(0, d)) } : {},
304
- ...p.after
305
- };
306
- return se(await o.collections(e).documents().search(m));
307
- })
308
- };
309
- }, se = (e) => {
310
- let t = (e.hits ?? []).map((e) => {
311
- let t = {};
312
- for (let n of e.highlights ?? []) {
313
- let e = n.snippet ?? n.value;
314
- e && (t[n.field] = e);
315
- }
316
- return Object.keys(t).length > 0 ? {
317
- doc: e.document,
318
- highlights: t
319
- } : { doc: e.document };
320
- }), n = {};
321
- for (let t of e.facet_counts ?? []) n[t.field_name] = Object.fromEntries(t.counts.map((e) => [e.value, e.count]));
322
- return {
323
- hits: t,
324
- facets: n
325
- };
326
- }, ce = (e) => {
327
- let t = (e) => JSON.stringify(e), n = F("meilisearch", e.field);
328
- switch (e.op) {
329
- case "eq": return `${n} = ${t(e.value)}`;
330
- case "neq": return `${n} != ${t(e.value)}`;
331
- case "gt": return `${n} > ${t(e.value)}`;
332
- case "gte": return `${n} >= ${t(e.value)}`;
333
- case "lt": return `${n} < ${t(e.value)}`;
334
- case "lte": return `${n} <= ${t(e.value)}`;
335
- case "in": return `${n} IN [${O(e.value).map(t).join(", ")}]`;
336
- case "nin": return `${n} NOT IN [${O(e.value).map(t).join(", ")}]`;
337
- }
338
- }, I = (e, t = {}) => {
339
- let n = P("meilisearch", t), r, i = async () => (r ||= N("meilisearch", "meilisearch", () => import("meilisearch")).then((t) => new t.Meilisearch({
340
- host: e.host ?? e.url ?? "http://localhost:7700",
341
- apiKey: e.apiKey
342
- })), r), a = (e) => e && e.length > 0 ? e.map(ce) : void 0, o = async (e, t) => {
343
- try {
344
- return await t();
345
- } catch (t) {
346
- throw t instanceof D ? t : new D({
347
- engine: "meilisearch",
348
- op: e,
349
- message: String(t?.message ?? t),
350
- transient: !0
351
- });
352
- }
353
- }, s = async (e, t) => {
354
- let n = await (await i()).tasks.waitForTask(t, {
355
- timeout: 6e4,
356
- interval: 50
357
- });
358
- if (n.status !== "succeeded") throw new D({
359
- engine: "meilisearch",
360
- op: e,
361
- message: n.error?.message ?? `Meilisearch task ${t.taskUid} ${n.status}`,
362
- transient: n.status === "canceled"
363
- });
364
- };
365
- return {
366
- upsert: (e, t) => o("upsert", async () => {
367
- await s("upsert", await (await i()).index(e).addDocuments([...t]));
368
- }),
369
- remove: (e, t) => o("remove", async () => {
370
- await s("remove", await (await i()).index(e).deleteDocuments([...t]));
371
- }),
372
- query: (e, { q: t, limit: r, offset: s, filters: c, facets: l, highlight: u, fuzziness: d, engineParams: f }) => o("query", async () => {
373
- let o = n(f), p = {
374
- ...o.before,
375
- limit: r ?? 20,
376
- ...s ? { offset: s } : {},
377
- ...a(c) ? { filter: a(c) } : {},
378
- ...l && l.length > 0 ? { facets: [...l] } : {},
379
- ...u ? {
380
- attributesToHighlight: u.fields && u.fields.length > 0 ? [...u.fields] : ["*"],
381
- ...u.preTag ? { highlightPreTag: u.preTag } : {},
382
- ...u.postTag ? { highlightPostTag: u.postTag } : {}
383
- } : {},
384
- ...d === 0 ? {
385
- rankingScoreThreshold: void 0,
386
- typoTolerance: { enabled: !1 }
387
- } : {},
388
- ...o.after
389
- };
390
- return le(await (await i()).index(e).search(t, p), u);
391
- })
392
- };
393
- }, le = (e, t) => ({
394
- hits: e.hits.map((e) => {
395
- let { _formatted: n, ...r } = e;
396
- if (t && n) {
397
- let e = {};
398
- for (let [t, r] of Object.entries(n)) t !== "id" && (e[t] = String(r));
399
- return Object.keys(e).length > 0 ? {
400
- doc: r,
401
- highlights: e
402
- } : { doc: r };
403
- }
404
- return { doc: r };
405
- }),
406
- facets: e.facetDistribution ?? {}
407
- }), ue = (e) => String(e).replace(/^-/, "\\-"), de = (e, t) => {
408
- let n = typeof t == "boolean" ? +!!t : Number(t);
409
- if (!Number.isFinite(n)) throw new D({
410
- engine: "algolia",
411
- op: "query",
412
- message: `range filter on "${e}" needs a numeric value (got ${JSON.stringify(t)})`,
413
- transient: !1
414
- });
415
- return n;
416
- }, fe = (e) => {
417
- let t = [], n = [];
418
- for (let r of e ?? []) {
419
- let e = F("algolia", r.field), i = (e) => ue(e), a = (t) => de(e, t);
420
- switch (r.op) {
421
- case "eq":
422
- t.push(`${e}:${i(r.value)}`);
423
- break;
424
- case "neq":
425
- t.push(`${e}:-${i(r.value)}`);
426
- break;
427
- case "in":
428
- t.push(O(r.value).map((t) => `${e}:${i(t)}`));
429
- break;
430
- case "nin":
431
- for (let n of O(r.value)) t.push(`${e}:-${i(n)}`);
432
- break;
433
- case "gt":
434
- n.push(`${e} > ${a(r.value)}`);
435
- break;
436
- case "gte":
437
- n.push(`${e} >= ${a(r.value)}`);
438
- break;
439
- case "lt":
440
- n.push(`${e} < ${a(r.value)}`);
441
- break;
442
- case "lte": n.push(`${e} <= ${a(r.value)}`);
443
- }
444
- }
445
- return {
446
- facetFilters: t,
447
- numericFilters: n
448
- };
449
- }, L = (e, t = {}) => {
450
- let n = P("algolia", t), r, i = async () => (r ||= N("algoliasearch", "algolia", () => import("algoliasearch")).then((t) => t.algoliasearch(e.appId ?? "", e.apiKey)), r), a = async (e, t) => {
451
- try {
452
- return await t();
453
- } catch (t) {
454
- throw t instanceof D ? t : new D({
455
- engine: "algolia",
456
- op: e,
457
- message: String(t?.message ?? t),
458
- transient: !0
459
- });
460
- }
461
- };
462
- return {
463
- upsert: (e, t) => a("upsert", async () => {
464
- await (await i()).saveObjects({
465
- indexName: e,
466
- objects: t.map((e) => ({
467
- ...e,
468
- objectID: e.id
469
- }))
470
- });
471
- }),
472
- remove: (e, t) => a("remove", async () => {
473
- await (await i()).deleteObjects({
474
- indexName: e,
475
- objectIDs: [...t]
476
- });
477
- }),
478
- query: (e, { q: t, limit: r, offset: o, filters: s, facets: c, highlight: l, fuzziness: u, engineParams: d }) => a("query", async () => {
479
- let { facetFilters: a, numericFilters: f } = fe(s), p = o ? {
480
- offset: o,
481
- length: r ?? 20
482
- } : { hitsPerPage: r ?? 20 }, m = n(d), h = {
483
- ...m.before,
484
- query: t,
485
- ...p,
486
- ...a.length > 0 ? { facetFilters: a } : {},
487
- ...f.length > 0 ? { numericFilters: f } : {},
488
- ...c && c.length > 0 ? { facets: [...c] } : {},
489
- ...l ? {
490
- ...l.fields && l.fields.length > 0 ? { attributesToHighlight: [...l.fields] } : {},
491
- ...l.preTag ? { highlightPreTag: l.preTag } : {},
492
- ...l.postTag ? { highlightPostTag: l.postTag } : {}
493
- } : {},
494
- ...u === 0 ? { typoTolerance: !1 } : {},
495
- ...m.after
496
- };
497
- return pe(await (await i()).searchSingleIndex({
498
- indexName: e,
499
- searchParams: h
500
- }), l);
501
- })
502
- };
503
- }, pe = (e, t) => ({
504
- hits: e.hits.map((e) => {
505
- let { _highlightResult: n, ...r } = e;
506
- if (t && n) {
507
- let e = {};
508
- for (let [t, r] of Object.entries(n)) t !== "id" && r?.value && (e[t] = r.value);
509
- return Object.keys(e).length > 0 ? {
510
- doc: r,
511
- highlights: e
512
- } : { doc: r };
513
- }
514
- return { doc: r };
515
- }),
516
- facets: e.facets ?? {}
517
- }), R = "_voltro_search_stats", z = () => ({
518
- synced: 0,
519
- removed: 0,
520
- dropped: 0,
521
- lastReindexAt: null,
522
- lastReindexCount: 0
523
- }), B = v(R, {
524
- id: g({ prefix: "sstat" }),
525
- indexName: y().maxLength(191),
526
- replicaId: y().maxLength(191),
527
- synced: _().default(0),
528
- removed: _().default(0),
529
- dropped: _().default(0),
530
- scope: y().maxLength(8).default("local"),
531
- lastReindexAt: b().nullable(),
532
- lastReindexCount: _().default(0),
533
- updatedAt: b()
534
- }).unique("bySearchStatsIndexReplica", ["indexName", "replicaId"]).index("bySearchStatsIndex", ["indexName"]), V = () => [B], H = () => {
535
- let e = /* @__PURE__ */ new Map(), t = (t) => e.get(t) ?? z();
536
- return {
537
- bump: async (n, r) => {
538
- let i = t(n);
539
- e.set(n, r === "synced" ? {
540
- ...i,
541
- synced: i.synced + 1
542
- } : r === "removed" ? {
543
- ...i,
544
- removed: i.removed + 1
545
- } : {
546
- ...i,
547
- dropped: i.dropped + 1
548
- });
549
- },
550
- add: async (n, r) => {
551
- let i = t(n);
552
- e.set(n, {
553
- ...i,
554
- synced: i.synced + r.synced,
555
- removed: i.removed + r.removed,
556
- dropped: i.dropped + r.dropped
557
- });
558
- },
559
- recordReindex: async (n, r, i) => {
560
- let a = t(n);
561
- e.set(n, {
562
- ...a,
563
- synced: a.synced + r,
564
- lastReindexAt: i,
565
- lastReindexCount: r
566
- });
567
- },
568
- get: async (e) => t(e)
569
- };
570
- }, U = (e) => ({
571
- table: R,
572
- predicate: e,
573
- order: [],
574
- take: void 0,
575
- skip: void 0,
576
- projection: void 0
577
- }), me = () => `sstat_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, he = () => re(), W = 32, G = (e, t = he()) => {
578
- let n = async (n) => (await e.query(U(p(h("indexName", n), h("replicaId", t)))))[0] ?? null, r = async (e) => {
579
- try {
580
- return await e();
581
- } catch (e) {
582
- throw e instanceof D ? e : new D({
583
- engine: "stats",
584
- op: "upsert",
585
- message: String(e?.message ?? e),
586
- transient: !0
587
- });
588
- }
589
- }, i = (i, a, o, s) => r(async () => {
590
- for (let r = 0; r < W; r++) {
591
- r > 0 && await new Promise((e) => setTimeout(e, r));
592
- let c = await n(i);
593
- if (c) {
594
- let t = Number(c.synced ?? 0), n = Number(c.removed ?? 0), r = Number(c.dropped ?? 0), i = {
595
- synced: t,
596
- removed: n,
597
- dropped: r,
598
- lastReindexAt: c.lastReindexAt ? new Date(c.lastReindexAt).toISOString() : null,
599
- lastReindexCount: Number(c.lastReindexCount ?? 0)
600
- };
601
- if (await e.updateMany("_voltro_search_stats", {
602
- ...o(i),
603
- ...a ? { scope: a } : {},
604
- updatedAt: /* @__PURE__ */ new Date()
605
- }, { where: p(h("id", String(c.id)), h("synced", t), h("removed", n), h("dropped", r)) }) === 1) return;
606
- } else {
607
- let n = me(), r = await e.insertIgnore(R, {
608
- id: n,
609
- indexName: i,
610
- replicaId: t,
611
- synced: 0,
612
- removed: 0,
613
- dropped: 0,
614
- scope: a ?? "local",
615
- lastReindexAt: null,
616
- lastReindexCount: 0,
617
- updatedAt: /* @__PURE__ */ new Date(),
618
- ...s
619
- }, { conflictColumns: ["indexName", "replicaId"] });
620
- if (String(r.id) === n) return;
621
- }
622
- }
623
- throw new D({
624
- engine: "stats",
625
- op: "upsert",
626
- message: `search stats: contention on (${i}, ${t}) exceeded ${W} attempts`,
627
- transient: !0
628
- });
629
- });
630
- return {
631
- bump: (e, t, n) => i(e, n, (e) => t === "synced" ? { synced: e.synced + 1 } : t === "removed" ? { removed: e.removed + 1 } : { dropped: e.dropped + 1 }, t === "synced" ? { synced: 1 } : t === "removed" ? { removed: 1 } : { dropped: 1 }),
632
- add: (e, t, n) => i(e, n, (e) => ({
633
- synced: e.synced + t.synced,
634
- removed: e.removed + t.removed,
635
- dropped: e.dropped + t.dropped
636
- }), {
637
- synced: t.synced,
638
- removed: t.removed,
639
- dropped: t.dropped
640
- }),
641
- recordReindex: (e, t, n) => i(e, void 0, (e) => ({
642
- synced: e.synced + t,
643
- lastReindexAt: new Date(n),
644
- lastReindexCount: t
645
- }), {
646
- synced: t,
647
- lastReindexAt: new Date(n),
648
- lastReindexCount: t
649
- }),
650
- get: (t) => r(async () => {
651
- let n = await e.query(U(h("indexName", t))), r = n.some((e) => e.scope === "fleet"), i = (e) => {
652
- let t = 0;
653
- for (let i of n) {
654
- let n = Number(i[e] ?? 0);
655
- t = r ? Math.max(t, n) : t + n;
656
- }
657
- return t;
658
- }, a = null, o = 0;
659
- for (let e of n) {
660
- let t = e.lastReindexAt ? new Date(e.lastReindexAt).toISOString() : null;
661
- t && (a === null || t > a) && (a = t, o = Number(e.lastReindexCount ?? 0));
662
- }
663
- return {
664
- ...z(),
665
- synced: i("synced"),
666
- removed: i("removed"),
667
- dropped: i("dropped"),
668
- lastReindexAt: a,
669
- lastReindexCount: o
670
- };
671
- })
672
- };
673
- }, K = (e) => {
674
- let t = /* @__PURE__ */ new Map(), n = 0, r = !1, i = async () => {
675
- if (t.size === 0) return;
676
- let r = t;
677
- t = /* @__PURE__ */ new Map(), n = 0;
678
- for (let [t, n] of r) try {
679
- await e.flush(t, {
680
- synced: n.synced,
681
- removed: n.removed,
682
- dropped: n.dropped
683
- }, n.scope);
684
- } catch {}
685
- }, a = setInterval(() => {
686
- i();
687
- }, Math.max(1, e.intervalMs));
688
- return a.unref?.(), {
689
- bump: (a, o, s) => {
690
- if (r) return;
691
- let c = t.get(a);
692
- c === void 0 ? t.set(a, {
693
- synced: +(o === "synced"),
694
- removed: +(o === "removed"),
695
- dropped: +(o === "dropped"),
696
- scope: s
697
- }) : (o === "synced" ? c.synced += 1 : o === "removed" ? c.removed += 1 : c.dropped += 1, c.scope = s), n += 1, n >= e.maxBuffered && i();
698
- },
699
- flushNow: i,
700
- shutdown: async () => {
701
- r || (r = !0, clearInterval(a), await i());
702
- },
703
- pending: () => n
704
- };
705
- }, q = (e) => (t, n, r) => e().add(t, n, r), ge = (e) => e === void 0 || e === "memory" ? "memory" : typeof e.query == "function" ? e.engineKind === "memory" ? "memory" : "custom" : "vendor", _e = class extends Error {
706
- _tag = "SearchBackendNotDurable";
707
- constructor(e) {
708
- super(e), this.name = "SearchBackendNotDurable";
709
- }
710
- }, ve = [
711
- "plugin-search refuses to boot in production on the in-memory backend.",
712
- "",
713
- "The memory index lives in THIS process's heap. Two consequences, both silent:",
714
- " • every replica holds a different index, so a result depends on which replica served you;",
715
- " • the index starts EMPTY after every restart/deploy, and nothing re-seeds it automatically.",
716
- "",
717
- "Configure a durable engine in app.config.ts:",
718
- " searchPlugin({ backend: { engine: 'typesense', url: …, apiKey: … }, indexes })",
719
- " searchPlugin({ backend: { engine: 'meilisearch', url: …, apiKey: … }, indexes })",
720
- " searchPlugin({ backend: { engine: 'algolia', appId: …, apiKey: … }, indexes })",
721
- "or pass your own `SearchBackend` implementation.",
722
- "",
723
- "If this deployment really is ONE process that re-seeds its index at startup",
724
- "(a `*.startup.tsx` calling `backfillIndex`), say so explicitly:",
725
- " searchPlugin({ singleProcessMemoryIndex: true, indexes })",
726
- "That is a claim about your topology, not a mute switch: if a second replica",
727
- "ever announces itself, the plugin logs that the claim was contradicted."
728
- ].join("\n"), ye = [
729
- "plugin-search is running the in-memory backend in production under",
730
- "`singleProcessMemoryIndex: true`. Holding you to the two conditions that makes true:",
731
- "exactly ONE process serves search, and it re-seeds every index at startup",
732
- "(`backfillIndex`) — the index is empty after each restart until it does."
733
- ].join(" "), be = (e) => e.backendKind !== "memory" || !e.production ? { level: "quiet" } : e.singleProcessDeclared ? {
734
- level: "note",
735
- message: ye
736
- } : {
737
- level: "refuse",
738
- message: ve
739
- }, xe = (e, t) => t ? `search: replica "${e}" joined, but this app declared \`singleProcessMemoryIndex: true\`. That declaration is now false: each replica has its own in-memory index, so search results depend on which one serves the request. Configure a durable backend (typesense / meilisearch / algolia) or run one process.` : `search: replica "${e}" joined while search is served from an in-memory index. Each replica indexes only its OWN writes, so results depend on which replica answers. Configure a durable backend (typesense / meilisearch / algolia) before relying on these results.`, J = "_voltro_search_drift", Se = v(J, {
740
- id: g({ prefix: "sdrft" }),
741
- indexName: y().maxLength(191),
742
- sourceTable: y().maxLength(191),
743
- rowId: y().maxLength(191),
744
- docId: y().maxLength(191),
745
- op: y().maxLength(16),
746
- attempts: _().default(0),
747
- lastError: y().maxLength(1e3),
748
- firstFailedAt: b(),
749
- lastFailedAt: b()
750
- }).unique("bySearchDriftIndexRow", ["indexName", "rowId"]).index("bySearchDriftIndex", ["indexName"]), Ce = (e, t) => ({
751
- table: e,
752
- predicate: h("id", t),
753
- order: [],
754
- take: 1,
755
- skip: void 0,
756
- projection: void 0
757
- }), Y = (e) => e ? new Date(e).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString(), we = (e) => ({
758
- indexName: String(e.indexName),
759
- sourceTable: String(e.sourceTable),
760
- rowId: String(e.rowId),
761
- docId: String(e.docId),
762
- op: e.op === "remove" ? "remove" : "upsert",
763
- attempts: Number(e.attempts ?? 0),
764
- lastError: String(e.lastError ?? ""),
765
- firstFailedAt: Y(e.firstFailedAt),
766
- lastFailedAt: Y(e.lastFailedAt)
767
- }), Te = () => {
768
- let e = /* @__PURE__ */ new Map(), t = (e, t) => `${e}\u0000${t}`;
769
- return {
770
- record: async (n) => {
771
- let r = (/* @__PURE__ */ new Date()).toISOString(), i = e.get(t(n.indexName, n.rowId));
772
- e.set(t(n.indexName, n.rowId), {
773
- indexName: n.indexName,
774
- sourceTable: n.sourceTable,
775
- rowId: n.rowId,
776
- docId: n.docId,
777
- op: n.op,
778
- attempts: (i?.attempts ?? 0) + 1,
779
- lastError: n.error,
780
- firstFailedAt: i?.firstFailedAt ?? r,
781
- lastFailedAt: r
782
- });
783
- },
784
- list: async (t) => [...e.values()].sort((e, t) => e.firstFailedAt.localeCompare(t.firstFailedAt)).slice(0, t),
785
- summary: async (t) => {
786
- let n = [...e.values()].filter((e) => e.indexName === t), r = null;
787
- for (let e of n) (r === null || e.lastFailedAt > r) && (r = e.lastFailedAt);
788
- return {
789
- pending: n.length,
790
- lastFailedAt: r
791
- };
792
- },
793
- clear: async (n, r) => {
794
- e.delete(t(n, r));
795
- }
796
- };
797
- }, Ee = () => `sdrft_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`, X = (e, t) => ({
798
- table: J,
799
- predicate: e,
800
- order: [{
801
- column: "firstFailedAt",
802
- direction: "asc"
803
- }],
804
- take: t,
805
- skip: void 0,
806
- projection: void 0
807
- }), De = (e) => ({
808
- record: async (t) => {
809
- let n = /* @__PURE__ */ new Date(), r = Ee(), i = await e.insertIgnore(J, {
810
- id: r,
811
- indexName: t.indexName,
812
- sourceTable: t.sourceTable,
813
- rowId: t.rowId,
814
- docId: t.docId,
815
- op: t.op,
816
- attempts: 1,
817
- lastError: t.error.slice(0, 1e3),
818
- firstFailedAt: n,
819
- lastFailedAt: n
820
- }, { conflictColumns: ["indexName", "rowId"] });
821
- String(i.id) !== r && await e.updateMany(J, {
822
- attempts: Number(i.attempts ?? 0) + 1,
823
- op: t.op,
824
- docId: t.docId,
825
- lastError: t.error.slice(0, 1e3),
826
- lastFailedAt: n
827
- }, { where: h("id", String(i.id)) });
828
- },
829
- list: async (t) => (await e.query(X(void 0, t))).map(we),
830
- summary: async (t) => {
831
- let n = (await e.query({
832
- ...X(h("indexName", t), void 0),
833
- order: [],
834
- aggregations: [m("n")]
835
- }))[0], r = Number(n?.n ?? n?.count ?? 0);
836
- if (r === 0) return {
837
- pending: 0,
838
- lastFailedAt: null
839
- };
840
- let i = await e.query({
841
- ...X(h("indexName", t), 1),
842
- order: [{
843
- column: "lastFailedAt",
844
- direction: "desc"
845
- }]
846
- });
847
- return {
848
- pending: r,
849
- lastFailedAt: i[0] ? Y(i[0].lastFailedAt) : null
850
- };
851
- },
852
- clear: async (t, n) => {
853
- await e.deleteMany(J, { where: p(h("indexName", t), h("rowId", n)) });
854
- }
855
- }), Oe = () => [Se], ke = (e, t) => {
856
- if (e === void 0 || e === "memory") return j();
1
+ import { SearchFieldRejected as e, SearchIndexNotFound as t } from "./errors.js";
2
+ import { BASE_NAME as n, queryDescriptor as r, searchRpcClientImports as i } from "./rpc.js";
3
+ import { FIELD_NAME_PATTERN as a, allowedEngineParams as o, firstRejectedField as s, isSafeFieldName as c, sanitizeEngineParams as l } from "./sanitize.js";
4
+ import { SearchBackendError as u, algoliaBackend as d, meilisearchBackend as f, memoryBackend as p, typesenseBackend as m } from "./backend.js";
5
+ import { SEARCH_STATS_TABLE as h, dataStoreStatsStore as g, memoryStatsStore as _, searchStatsTable as v, searchTables as y } from "./statsStore.js";
6
+ import { makeSearchStatsBuffer as b, statsStoreFlush as x } from "./statsBuffer.js";
7
+ import { SearchBackendNotDurable as S, auditMemoryBackend as C, backendKindOf as w, peerObservedWarning as T } from "./memoryGuard.js";
8
+ import { SEARCH_DRIFT_TABLE as E, dataStoreDriftLedger as D, driftSourceQuery as O, driftTables as k, memoryDriftLedger as A, searchDriftTable as j } from "./drift.js";
9
+ import { Cause as ee, Duration as M, Effect as N, Exit as P, Option as F, Schedule as I, Stream as L } from "effect";
10
+ import { streamTable as te } from "@voltro/database";
11
+ import { definePlugin as ne, pluginInstanceName as re } from "@voltro/protocol";
12
+ //#region src/index.ts
13
+ var ie = (e, t) => {
14
+ if (e === void 0 || e === "memory") return p();
857
15
  if (typeof e.query == "function") return e;
858
16
  let n = e;
859
- return n.engine === "typesense" ? oe(n, t) : n.engine === "meilisearch" ? I(n, t) : n.engine === "algolia" ? L(n, t) : j();
860
- }, Ae = async (e, t, n) => {
17
+ return n.engine === "typesense" ? m(n, t) : n.engine === "meilisearch" ? f(n, t) : n.engine === "algolia" ? d(n, t) : p();
18
+ }, R = async (e, t, n) => {
861
19
  let r = n.map((e) => t.map(e));
862
20
  return r.length > 0 && await e.upsert(t.index, r), r.length;
863
- }, je = async (e, t, n) => {
21
+ }, z = async (e, t, n) => {
864
22
  if (n.op === "delete") {
865
23
  let r = n.old?.id;
866
24
  typeof r == "string" && await e.remove(t.index, [r]);
867
- return;
868
- }
869
- n.new && await e.upsert(t.index, [t.map(n.new)]);
870
- }, Z = {
25
+ } else n.new && await e.upsert(t.index, [t.map(n.new)]);
26
+ }, B = {
871
27
  retries: 5,
872
28
  retryBaseDelayMs: 200,
873
29
  retryMaxDelayMs: 1e4,
@@ -876,21 +32,21 @@ var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
876
32
  reindexBatchSize: 1e3,
877
33
  statsFlushIntervalMs: 5e3,
878
34
  statsFlushMaxBuffered: 1e3
879
- }, Q = (e) => ({
880
- retries: Math.max(0, e?.retries ?? Z.retries),
881
- retryBaseDelayMs: Math.max(1, e?.retryBaseDelayMs ?? Z.retryBaseDelayMs),
882
- retryMaxDelayMs: Math.max(1, e?.retryMaxDelayMs ?? Z.retryMaxDelayMs),
883
- resyncIntervalMs: Math.max(0, e?.resyncIntervalMs ?? Z.resyncIntervalMs),
884
- resyncBatchSize: Math.max(1, e?.resyncBatchSize ?? Z.resyncBatchSize),
885
- reindexBatchSize: Math.max(1, e?.reindexBatchSize ?? Z.reindexBatchSize),
886
- statsFlushIntervalMs: Math.max(0, e?.statsFlushIntervalMs ?? Z.statsFlushIntervalMs),
887
- statsFlushMaxBuffered: Math.max(1, e?.statsFlushMaxBuffered ?? Z.statsFlushMaxBuffered)
888
- }), Me = (e) => e instanceof D ? e : new D({
35
+ }, V = (e) => ({
36
+ retries: Math.max(0, e?.retries ?? B.retries),
37
+ retryBaseDelayMs: Math.max(1, e?.retryBaseDelayMs ?? B.retryBaseDelayMs),
38
+ retryMaxDelayMs: Math.max(1, e?.retryMaxDelayMs ?? B.retryMaxDelayMs),
39
+ resyncIntervalMs: Math.max(0, e?.resyncIntervalMs ?? B.resyncIntervalMs),
40
+ resyncBatchSize: Math.max(1, e?.resyncBatchSize ?? B.resyncBatchSize),
41
+ reindexBatchSize: Math.max(1, e?.reindexBatchSize ?? B.reindexBatchSize),
42
+ statsFlushIntervalMs: Math.max(0, e?.statsFlushIntervalMs ?? B.statsFlushIntervalMs),
43
+ statsFlushMaxBuffered: Math.max(1, e?.statsFlushMaxBuffered ?? B.statsFlushMaxBuffered)
44
+ }), H = (e) => e instanceof u ? e : new u({
889
45
  engine: "unknown",
890
46
  op: "upsert",
891
47
  message: String(e?.message ?? e),
892
48
  transient: !1
893
- }), Ne = (e) => d.max([d.min([d.exponential(s.millis(e.retryBaseDelayMs), 2), d.spaced(s.millis(e.retryMaxDelayMs))]), d.recurs(e.retries)]).pipe(d.while(({ input: e }) => e.transient)), Pe = (e) => {
49
+ }), U = (e) => I.max([I.min([I.exponential(M.millis(e.retryBaseDelayMs), 2), I.spaced(M.millis(e.retryMaxDelayMs))]), I.recurs(e.retries)]).pipe(I.while(({ input: e }) => e.transient)), ae = (e) => {
894
50
  if (!e || e === "memory" || typeof e.query == "function") return;
895
51
  let t = e;
896
52
  try {
@@ -898,80 +54,77 @@ var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
898
54
  } catch {
899
55
  return;
900
56
  }
901
- }, Fe = (e, t, n) => {
57
+ }, W = (e, t, n) => {
902
58
  let r = n ?? [];
903
59
  return e ? t == null ? null : [...r, {
904
60
  field: e,
905
61
  op: "eq",
906
62
  value: t
907
63
  }] : r;
908
- }, Ie = (e) => [
64
+ }, oe = (e) => [
909
65
  ...(e.filters ?? []).map((e) => ["filter", e.field]),
910
66
  ...(e.facets ?? []).map((e) => ["facet", e]),
911
67
  ...(e.highlight?.fields ?? []).map((e) => ["highlight", e])
912
- ], $ = i, Le = (i) => {
913
- let o = ne({
914
- base: $,
915
- alias: i.alias,
916
- instance: i.name
917
- }), s = () => void 0, d = () => void 0, p = Q(i.sync), m = Ne(p), h = ke(i.backend, {
918
- ...i.allowedEngineParams ? { allowEngineParams: i.allowedEngineParams } : {},
919
- onWarn: (e, t) => s(e, t)
920
- }), g = /* @__PURE__ */ new Map();
921
- for (let e of Object.values(i.indexes)) g.set(e.index, e);
922
- let _ = ge(i.backend), v = i.singleProcessMemoryIndex === !0, y = !1, b, re = {
68
+ ], G = n, K = (n) => {
69
+ let a = re({
70
+ base: G,
71
+ alias: n.alias,
72
+ instance: n.name
73
+ }), o = () => void 0, c = () => void 0, l = V(n.sync), u = U(l), d = ie(n.backend, {
74
+ ...n.allowedEngineParams ? { allowEngineParams: n.allowedEngineParams } : {},
75
+ onWarn: (e, t) => o(e, t)
76
+ }), f = /* @__PURE__ */ new Map();
77
+ for (let e of Object.values(n.indexes)) f.set(e.index, e);
78
+ let p = w(n.backend), m = n.singleProcessMemoryIndex === !0, h = !1, v, E = {
923
79
  ...r,
924
80
  description: "Full-text search an index (facets/highlight/fuzzy/filters), auto-scoped to the caller tenant.",
925
- execute: (t, r) => {
926
- let { index: i, q: a, limit: o, offset: s, filters: l, facets: u, highlight: d, fuzziness: f, engineParams: p } = t, m = g.get(i);
927
- if (!m) return c.fail(new n({ index: i }));
928
- let _ = ie(Ie({
81
+ execute: (n, r) => {
82
+ let { index: i, q: a, limit: o, offset: c, filters: l, facets: u, highlight: p, fuzziness: m, engineParams: h } = n, g = f.get(i);
83
+ if (!g) return N.fail(new t({ index: i }));
84
+ let _ = s(oe({
929
85
  ...l ? { filters: l } : {},
930
86
  ...u ? { facets: u } : {},
931
- ...d ? { highlight: d } : {}
932
- }), m.queryableFields);
933
- if (_) return c.fail(new e(_));
934
- let ee = r.request.subject.tenantId ?? null, v = Fe(m.tenantField, ee, l);
935
- if (v === null) return c.succeed({
87
+ ...p ? { highlight: p } : {}
88
+ }), g.queryableFields);
89
+ if (_) return N.fail(new e(_));
90
+ let v = r.request.subject.tenantId ?? null, y = W(g.tenantField, v, l);
91
+ if (y === null) return N.succeed({
936
92
  hits: [],
937
93
  facets: {}
938
94
  });
939
- let y = {
95
+ let b = {
940
96
  q: a,
941
97
  ...o === void 0 ? {} : { limit: o },
942
- ...s === void 0 ? {} : { offset: s },
943
- ...v.length > 0 ? { filters: v } : {},
98
+ ...c === void 0 ? {} : { offset: c },
99
+ ...y.length > 0 ? { filters: y } : {},
944
100
  ...u && u.length > 0 ? { facets: u } : {},
945
- ...d ? { highlight: d } : {},
946
- ...f === void 0 ? {} : { fuzziness: f },
947
- ...p ? { engineParams: p } : {}
101
+ ...p ? { highlight: p } : {},
102
+ ...m === void 0 ? {} : { fuzziness: m },
103
+ ...h ? { engineParams: h } : {}
948
104
  };
949
- return c.promise(() => h.query(i, y));
105
+ return N.promise(() => d.query(i, b));
950
106
  }
951
- }, x = Pe(i.backend), S = [
107
+ }, j = ae(n.backend), M = [
952
108
  "store:changes:read",
953
109
  "store:write",
954
110
  "inspect:read",
955
111
  "inspect:write",
956
- ...x ? [`network:outbound:${x}`] : []
957
- ], C = H(), w = Te(), T, E, D, O, k = (e, t, n) => O === void 0 ? c.tryPromise(() => C.bump(e, t, n)).pipe(c.catch((n) => c.sync(() => s("search stats bump failed", {
112
+ ...j ? [`network:outbound:${j}`] : []
113
+ ], I = _(), B = A(), K, q, J, Y, X = (e, t, n) => Y === void 0 ? N.tryPromise(() => I.bump(e, t, n)).pipe(N.catch((n) => N.sync(() => o("search stats bump failed", {
958
114
  index: e,
959
115
  kind: t,
960
116
  error: String(n)
961
- })))) : c.sync(() => O.bump(e, t, n)), A = async (e) => {
962
- let t = await w.list(e), n = 0, r = 0;
117
+ })))) : N.sync(() => Y.bump(e, t, n)), Z = async (e) => {
118
+ let t = await B.list(e), n = 0, r = 0;
963
119
  for (let e of t) {
964
- let t = g.get(e.indexName);
965
- if (!t) {
966
- await w.clear(e.indexName, e.rowId);
967
- continue;
968
- }
969
- try {
970
- if (!E) throw Error("data store not bound");
971
- let r = (await E.query(Ce(e.sourceTable, e.rowId)))[0];
972
- r ? await h.upsert(t.index, [t.map(r)]) : await h.remove(t.index, [e.docId]), await w.clear(e.indexName, e.rowId), n++;
120
+ let t = f.get(e.indexName);
121
+ if (!t) await B.clear(e.indexName, e.rowId);
122
+ else try {
123
+ if (!q) throw Error("data store not bound");
124
+ let r = (await q.query(O(e.sourceTable, e.rowId)))[0];
125
+ r ? await d.upsert(t.index, [t.map(r)]) : await d.remove(t.index, [e.docId]), await B.clear(e.indexName, e.rowId), n++;
973
126
  } catch (t) {
974
- r++, await w.record({
127
+ r++, await B.record({
975
128
  ...e,
976
129
  error: String(t?.message ?? t)
977
130
  }).catch(() => void 0);
@@ -982,31 +135,31 @@ var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
982
135
  repaired: n,
983
136
  failed: r
984
137
  };
985
- }, j = (e) => ({
138
+ }, Q = (e) => ({
986
139
  kind: "json",
987
140
  data: e
988
- }), M = (e, t) => ({
141
+ }), $ = (e, t) => ({
989
142
  kind: "json",
990
143
  status: e,
991
144
  data: { error: t }
992
145
  });
993
- return te({
994
- name: o,
995
- baseName: $,
146
+ return ne({
147
+ name: a,
148
+ baseName: G,
996
149
  description: "External search index sync (Typesense/Meilisearch/Algolia/memory) via the ChangeEvent tap + a tenant-scoped query route.",
997
- permissions: S,
998
- routes: [re],
999
- rpcClientDescriptors: t,
150
+ permissions: M,
151
+ routes: [E],
152
+ rpcClientDescriptors: i,
1000
153
  inspectEndpoints: [
1001
154
  {
1002
155
  method: "GET",
1003
156
  path: "/indexes",
1004
157
  scope: "process",
1005
158
  description: "Configured indexes + durable, cross-replica sync stats + current index drift.",
1006
- handler: () => c.promise(async () => {
1007
- await O?.flushNow();
1008
- let e = await Promise.all(Object.entries(i.indexes).map(async ([e, t]) => {
1009
- let n = await C.get(t.index), r = await w.summary(t.index);
159
+ handler: () => N.promise(async () => {
160
+ await Y?.flushNow();
161
+ let e = await Promise.all(Object.entries(n.indexes).map(async ([e, t]) => {
162
+ let n = await I.get(t.index), r = await B.summary(t.index);
1010
163
  return {
1011
164
  table: e,
1012
165
  index: t.index,
@@ -1017,15 +170,15 @@ var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
1017
170
  drifted: r.pending > 0
1018
171
  };
1019
172
  }));
1020
- return j({
173
+ return Q({
1021
174
  indexes: e,
1022
- backend: typeof i.backend == "string" ? i.backend : i.backend ? "custom/vendor" : "memory",
1023
- durable: _ !== "memory",
1024
- singleProcessMemoryIndex: v,
175
+ backend: typeof n.backend == "string" ? n.backend : n.backend ? "custom/vendor" : "memory",
176
+ durable: p !== "memory",
177
+ singleProcessMemoryIndex: m,
1025
178
  pendingDrift: e.reduce((e, t) => e + t.pendingDrift, 0),
1026
179
  sync: {
1027
- ...p,
1028
- resyncArmed: T !== void 0
180
+ ...l,
181
+ resyncArmed: K !== void 0
1029
182
  }
1030
183
  });
1031
184
  })
@@ -1035,34 +188,34 @@ var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
1035
188
  path: "/drift",
1036
189
  scope: "process",
1037
190
  description: "The changes that did NOT reach the engine, oldest first (the repair queue).",
1038
- handler: (e) => c.promise(async () => {
1039
- let t = Number(new URL(e.url, "http://localhost").searchParams.get("limit") ?? 100), n = await w.list(Number.isFinite(t) && t > 0 ? Math.min(t, 1e3) : 100);
1040
- return j({ entries: n });
191
+ handler: (e) => N.promise(async () => {
192
+ let t = Number(new URL(e.url, "http://localhost").searchParams.get("limit") ?? 100), n = await B.list(Number.isFinite(t) && t > 0 ? Math.min(t, 1e3) : 100);
193
+ return Q({ entries: n });
1041
194
  })
1042
195
  },
1043
196
  {
1044
197
  method: "POST",
1045
198
  path: "/resync",
1046
199
  description: "Repair drifted rows now — re-reads each from the DB and re-applies it.",
1047
- handler: (e) => c.promise(async () => {
200
+ handler: (e) => N.promise(async () => {
1048
201
  let t;
1049
202
  try {
1050
203
  t = JSON.parse(e.body || "{}");
1051
204
  } catch {
1052
- return M(400, "invalid JSON body");
205
+ return $(400, "invalid JSON body");
1053
206
  }
1054
- if (typeof t != "object" || !t || Array.isArray(t)) return M(400, "expected a JSON object");
1055
- let n = "limit" in t ? t.limit : p.resyncBatchSize;
1056
- if (typeof n != "number" || !Number.isSafeInteger(n) || n < 1) return M(400, "limit must be a positive integer");
1057
- if (!E) return M(503, "data store not bound yet");
207
+ if (typeof t != "object" || !t || Array.isArray(t)) return $(400, "expected a JSON object");
208
+ let n = "limit" in t ? t.limit : l.resyncBatchSize;
209
+ if (typeof n != "number" || !Number.isSafeInteger(n) || n < 1) return $(400, "limit must be a positive integer");
210
+ if (!q) return $(503, "data store not bound yet");
1058
211
  try {
1059
- let e = await A(Math.min(n, 1e3));
1060
- return j({
212
+ let e = await Z(Math.min(n, 1e3));
213
+ return Q({
1061
214
  ok: !0,
1062
215
  ...e
1063
216
  });
1064
217
  } catch (e) {
1065
- return M(500, e.message);
218
+ return $(500, e.message);
1066
219
  }
1067
220
  })
1068
221
  },
@@ -1070,128 +223,128 @@ var x = /^[A-Za-z_][A-Za-z0-9_.]*$/, S = (e) => x.test(e), ie = (e, t) => {
1070
223
  method: "POST",
1071
224
  path: "/reindex",
1072
225
  description: "Re-seed an index from the table's current rows (streamed in bounded batches).",
1073
- handler: (e) => c.promise(async () => {
226
+ handler: (e) => N.promise(async () => {
1074
227
  let t;
1075
228
  try {
1076
229
  t = JSON.parse(e.body || "{}");
1077
230
  } catch {
1078
- return M(400, "invalid JSON body");
231
+ return $(400, "invalid JSON body");
1079
232
  }
1080
- if (typeof t != "object" || !t || Array.isArray(t)) return M(400, "expected a JSON object");
1081
- let n = "table" in t ? t.table : void 0;
1082
- if (typeof n != "string" || n.trim() === "") return M(400, "table must be a non-empty string");
1083
- let r = Object.hasOwn(i.indexes, n) ? i.indexes[n] : void 0;
1084
- if (!r) return M(404, `no index configured for table "${n}"`);
1085
- if (!D) return M(503, "data store not bound yet");
1086
- let o = 0, s = await c.runPromiseExit(ee(D, {
1087
- table: n,
1088
- chunkSize: p.reindexBatchSize
1089
- }).pipe(f.chunks, f.runForEach((e) => c.tryPromise(async () => {
1090
- o += await Ae(h, r, e);
233
+ if (typeof t != "object" || !t || Array.isArray(t)) return $(400, "expected a JSON object");
234
+ let r = "table" in t ? t.table : void 0;
235
+ if (typeof r != "string" || r.trim() === "") return $(400, "table must be a non-empty string");
236
+ let i = Object.hasOwn(n.indexes, r) ? n.indexes[r] : void 0;
237
+ if (!i) return $(404, `no index configured for table "${r}"`);
238
+ if (!J) return $(503, "data store not bound yet");
239
+ let a = 0, o = await N.runPromiseExit(te(J, {
240
+ table: r,
241
+ chunkSize: l.reindexBatchSize
242
+ }).pipe(L.chunks, L.runForEach((e) => N.tryPromise(async () => {
243
+ a += await R(d, i, e);
1091
244
  }))));
1092
- if (l.isFailure(s)) {
1093
- let e = a.findErrorOption(s.cause), t = u.isSome(e) ? e.value : s.cause, n = t.cause ?? t.error ?? t;
1094
- return M(500, String(n?.message ?? n));
245
+ if (P.isFailure(o)) {
246
+ let e = ee.findErrorOption(o.cause), t = F.isSome(e) ? e.value : o.cause, n = t.cause ?? t.error ?? t;
247
+ return $(500, String(n?.message ?? n));
1095
248
  }
1096
249
  try {
1097
- await C.recordReindex(r.index, o, (/* @__PURE__ */ new Date()).toISOString());
250
+ await I.recordReindex(i.index, a, (/* @__PURE__ */ new Date()).toISOString());
1098
251
  } catch (e) {
1099
- return M(500, e.message);
252
+ return $(500, e.message);
1100
253
  }
1101
- return j({
254
+ return Q({
1102
255
  ok: !0,
1103
- table: n,
1104
- index: r.index,
1105
- count: o
256
+ table: r,
257
+ index: i.index,
258
+ count: a
1106
259
  });
1107
260
  })
1108
261
  }
1109
262
  ],
1110
- extendSchema: { tables: [...V(), ...Oe()] },
263
+ extendSchema: { tables: [...y(), ...k()] },
1111
264
  bindDataStore: (e, t) => {
1112
- E = e, D = e, C = G(e, ...t?.instanceId === void 0 ? [] : [t.instanceId]), w = De(e), _ === "memory" && t?.membership !== void 0 && (b = t.membership.onChange((e) => {
1113
- e.kind === "left" || y || (t.instanceId === void 0 || e.instanceId !== t.instanceId) && (y = !0, s(xe(e.instanceId, v), {
265
+ q = e, J = e, I = g(e, ...t?.instanceId === void 0 ? [] : [t.instanceId]), B = D(e), p === "memory" && t?.membership !== void 0 && (v = t.membership.onChange((e) => {
266
+ e.kind === "left" || h || (t.instanceId === void 0 || e.instanceId !== t.instanceId) && (h = !0, o(T(e.instanceId, m), {
1114
267
  instanceId: t.instanceId ?? null,
1115
268
  peer: e.instanceId,
1116
- indexes: Object.keys(i.indexes)
269
+ indexes: Object.keys(n.indexes)
1117
270
  }));
1118
- })), !(p.resyncIntervalMs <= 0 || t?.scheduleCoordinated === void 0) && (T = t.scheduleCoordinated("search.resync", p.resyncIntervalMs, async () => {
271
+ })), !(l.resyncIntervalMs <= 0 || t?.scheduleCoordinated === void 0) && (K = t.scheduleCoordinated("search.resync", l.resyncIntervalMs, async () => {
1119
272
  try {
1120
- let e = await A(p.resyncBatchSize);
1121
- return (e.repaired > 0 || e.failed > 0) && d("search: drift repair pass", e), { idle: e.scanned === 0 };
273
+ let e = await Z(l.resyncBatchSize);
274
+ return (e.repaired > 0 || e.failed > 0) && c("search: drift repair pass", e), { idle: e.scanned === 0 };
1122
275
  } catch (e) {
1123
- return s("search: drift repair pass failed", { error: String(e?.message ?? e) }), { idle: !1 };
276
+ return o("search: drift repair pass failed", { error: String(e?.message ?? e) }), { idle: !1 };
1124
277
  }
1125
278
  }));
1126
279
  },
1127
280
  onChangeEvent: (e) => {
1128
- let t = i.indexes[e.table];
1129
- if (!t) return c.void;
1130
- let n = e.changeScope, r = e.changeScope === "fleet" || e.origin !== "injected", a = e.op === "delete" ? e.old : e.new, o = typeof a?.id == "string" ? a.id : "", l = e.op === "delete" ? "remove" : "upsert", u = () => {
1131
- if (e.op === "delete" || !e.new) return o;
281
+ let t = n.indexes[e.table];
282
+ if (!t) return N.void;
283
+ let r = e.changeScope, i = e.changeScope === "fleet" || e.origin !== "injected", a = e.op === "delete" ? e.old : e.new, s = typeof a?.id == "string" ? a.id : "", c = e.op === "delete" ? "remove" : "upsert", l = () => {
284
+ if (e.op === "delete" || !e.new) return s;
1132
285
  try {
1133
- return String(t.map(e.new).id ?? o);
286
+ return String(t.map(e.new).id ?? s);
1134
287
  } catch {
1135
- return o;
288
+ return s;
1136
289
  }
1137
290
  };
1138
- return c.tryPromise({
1139
- try: () => je(h, t, e),
1140
- catch: Me
1141
- }).pipe(c.retry(m)).pipe(c.matchEffect({
1142
- onSuccess: () => r ? k(t.index, e.op === "delete" ? "removed" : "synced", n) : c.void,
1143
- onFailure: (r) => {
291
+ return N.tryPromise({
292
+ try: () => z(d, t, e),
293
+ catch: H
294
+ }).pipe(N.retry(u)).pipe(N.matchEffect({
295
+ onSuccess: () => i ? X(t.index, e.op === "delete" ? "removed" : "synced", r) : N.void,
296
+ onFailure: (n) => {
1144
297
  let i = {
1145
298
  table: e.table,
1146
299
  index: t.index,
1147
300
  op: e.op,
1148
- rowId: o,
1149
- transient: r.transient,
1150
- error: r.message
301
+ rowId: s,
302
+ transient: n.transient,
303
+ error: n.message
1151
304
  };
1152
- return o === "" ? c.sync(() => s("search: change not indexed and NOT recoverable (row has no id)", i)).pipe(c.andThen(c.fail(r))) : c.tryPromise(() => w.record({
305
+ return s === "" ? N.sync(() => o("search: change not indexed and NOT recoverable (row has no id)", i)).pipe(N.andThen(N.fail(n))) : N.tryPromise(() => B.record({
1153
306
  indexName: t.index,
1154
307
  sourceTable: e.table,
1155
- rowId: o,
1156
- docId: u(),
1157
- op: l,
1158
- error: r.message
1159
- })).pipe(c.matchEffect({
1160
- onSuccess: () => k(t.index, "dropped", n).pipe(c.andThen(c.sync(() => s("search: index drift recorded — will be repaired from the database", i)))),
1161
- onFailure: (e) => c.sync(() => s("search: change LOST — engine failed and the drift ledger could not record it", {
308
+ rowId: s,
309
+ docId: l(),
310
+ op: c,
311
+ error: n.message
312
+ })).pipe(N.matchEffect({
313
+ onSuccess: () => X(t.index, "dropped", r).pipe(N.andThen(N.sync(() => o("search: index drift recorded — will be repaired from the database", i)))),
314
+ onFailure: (e) => N.sync(() => o("search: change LOST — engine failed and the drift ledger could not record it", {
1162
315
  ...i,
1163
316
  ledgerError: String(e)
1164
- })).pipe(c.andThen(c.fail(r)))
317
+ })).pipe(N.andThen(N.fail(n)))
1165
318
  }));
1166
319
  }
1167
320
  }));
1168
321
  },
1169
- onActivate: (e) => c.suspend(() => {
1170
- s = (t, n) => e.logger.warn(t, n), d = (t, n) => e.logger.info(t, n);
1171
- let t = be({
1172
- backendKind: _,
322
+ onActivate: (e) => N.suspend(() => {
323
+ o = (t, n) => e.logger.warn(t, n), c = (t, n) => e.logger.info(t, n);
324
+ let t = C({
325
+ backendKind: p,
1173
326
  production: (e.env?.NODE_ENV ?? process.env.NODE_ENV) === "production",
1174
- singleProcessDeclared: v
327
+ singleProcessDeclared: m
1175
328
  });
1176
- return t.level === "refuse" ? c.fail(new _e(t.message)) : (t.level === "note" && e.logger.info(t.message, { indexes: Object.keys(i.indexes) }), p.statsFlushIntervalMs > 0 && O === void 0 && (O = K({
1177
- flush: q(() => C),
1178
- intervalMs: p.statsFlushIntervalMs,
1179
- maxBuffered: p.statsFlushMaxBuffered
1180
- })), c.sync(() => {
329
+ return t.level === "refuse" ? N.fail(new S(t.message)) : (t.level === "note" && e.logger.info(t.message, { indexes: Object.keys(n.indexes) }), l.statsFlushIntervalMs > 0 && Y === void 0 && (Y = b({
330
+ flush: x(() => I),
331
+ intervalMs: l.statsFlushIntervalMs,
332
+ maxBuffered: l.statsFlushMaxBuffered
333
+ })), N.sync(() => {
1181
334
  e.logger.info("search active", {
1182
- indexes: Object.keys(i.indexes),
1183
- backend: typeof i.backend == "string" ? i.backend : "custom/vendor",
1184
- durable: _ !== "memory",
1185
- sync: p
335
+ indexes: Object.keys(n.indexes),
336
+ backend: typeof n.backend == "string" ? n.backend : "custom/vendor",
337
+ durable: p !== "memory",
338
+ sync: l
1186
339
  });
1187
340
  }));
1188
341
  }),
1189
- onDeactivate: () => c.promise(async () => {
1190
- T?.stop(), T = void 0, b?.(), b = void 0, y = !1;
1191
- let e = O;
1192
- O = void 0, await e?.shutdown();
342
+ onDeactivate: () => N.promise(async () => {
343
+ K?.stop(), K = void 0, v?.(), v = void 0, h = !1;
344
+ let e = Y;
345
+ Y = void 0, await e?.shutdown();
1193
346
  })
1194
347
  });
1195
348
  };
1196
349
  //#endregion
1197
- export { x as FIELD_NAME_PATTERN, J as SEARCH_DRIFT_TABLE, R as SEARCH_STATS_TABLE, Z as SYNC_DEFAULTS, D as SearchBackendError, _e as SearchBackendNotDurable, e as SearchFieldRejected, n as SearchIndexNotFound, L as algoliaBackend, E as allowedEngineParams, je as applyChange, Me as asBackendError, be as auditMemoryBackend, ge as backendKindOf, Ae as backfillIndex, De as dataStoreDriftLedger, G as dataStoreStatsStore, Ce as driftSourceQuery, Oe as driftTables, S as isSafeFieldName, K as makeSearchStatsBuffer, I as meilisearchBackend, j as memoryBackend, Te as memoryDriftLedger, H as memoryStatsStore, xe as peerObservedWarning, Q as resolveSyncOptions, T as sanitizeEngineParams, Fe as scopeSearchFilters, Se as searchDriftTable, Le as searchPlugin, B as searchStatsTable, V as searchTables, q as statsStoreFlush, Ne as syncRetrySchedule, oe as typesenseBackend };
350
+ export { a as FIELD_NAME_PATTERN, E as SEARCH_DRIFT_TABLE, h as SEARCH_STATS_TABLE, B as SYNC_DEFAULTS, u as SearchBackendError, S as SearchBackendNotDurable, e as SearchFieldRejected, t as SearchIndexNotFound, d as algoliaBackend, o as allowedEngineParams, z as applyChange, H as asBackendError, C as auditMemoryBackend, w as backendKindOf, R as backfillIndex, D as dataStoreDriftLedger, g as dataStoreStatsStore, O as driftSourceQuery, k as driftTables, c as isSafeFieldName, b as makeSearchStatsBuffer, f as meilisearchBackend, p as memoryBackend, A as memoryDriftLedger, _ as memoryStatsStore, T as peerObservedWarning, V as resolveSyncOptions, l as sanitizeEngineParams, W as scopeSearchFilters, j as searchDriftTable, K as searchPlugin, v as searchStatsTable, y as searchTables, x as statsStoreFlush, U as syncRetrySchedule, m as typesenseBackend };