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