@aigne/afs-index 1.12.0-beta.5
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/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/backend/d1-index-backend.cjs +656 -0
- package/dist/backend/d1-index-backend.d.cts +149 -0
- package/dist/backend/d1-index-backend.d.cts.map +1 -0
- package/dist/backend/d1-index-backend.d.mts +149 -0
- package/dist/backend/d1-index-backend.d.mts.map +1 -0
- package/dist/backend/d1-index-backend.mjs +657 -0
- package/dist/backend/d1-index-backend.mjs.map +1 -0
- package/dist/backend/d1-vector-backend.cjs +83 -0
- package/dist/backend/d1-vector-backend.d.cts +21 -0
- package/dist/backend/d1-vector-backend.d.cts.map +1 -0
- package/dist/backend/d1-vector-backend.d.mts +21 -0
- package/dist/backend/d1-vector-backend.d.mts.map +1 -0
- package/dist/backend/d1-vector-backend.mjs +84 -0
- package/dist/backend/d1-vector-backend.mjs.map +1 -0
- package/dist/backend/memory-backend.cjs +304 -0
- package/dist/backend/memory-backend.d.cts +66 -0
- package/dist/backend/memory-backend.d.cts.map +1 -0
- package/dist/backend/memory-backend.d.mts +66 -0
- package/dist/backend/memory-backend.d.mts.map +1 -0
- package/dist/backend/memory-backend.mjs +305 -0
- package/dist/backend/memory-backend.mjs.map +1 -0
- package/dist/backend/prefix-range.cjs +25 -0
- package/dist/backend/prefix-range.mjs +25 -0
- package/dist/backend/prefix-range.mjs.map +1 -0
- package/dist/backend/schema.cjs +187 -0
- package/dist/backend/schema.mjs +188 -0
- package/dist/backend/schema.mjs.map +1 -0
- package/dist/backend/sqlite-backend.cjs +889 -0
- package/dist/backend/sqlite-backend.d.cts +96 -0
- package/dist/backend/sqlite-backend.d.cts.map +1 -0
- package/dist/backend/sqlite-backend.d.mts +96 -0
- package/dist/backend/sqlite-backend.d.mts.map +1 -0
- package/dist/backend/sqlite-backend.mjs +890 -0
- package/dist/backend/sqlite-backend.mjs.map +1 -0
- package/dist/backend/types.d.cts +209 -0
- package/dist/backend/types.d.cts.map +1 -0
- package/dist/backend/types.d.mts +209 -0
- package/dist/backend/types.d.mts.map +1 -0
- package/dist/backend/upstash-vector-backend.cjs +98 -0
- package/dist/backend/upstash-vector-backend.d.cts +21 -0
- package/dist/backend/upstash-vector-backend.d.cts.map +1 -0
- package/dist/backend/upstash-vector-backend.d.mts +21 -0
- package/dist/backend/upstash-vector-backend.d.mts.map +1 -0
- package/dist/backend/upstash-vector-backend.mjs +98 -0
- package/dist/backend/upstash-vector-backend.mjs.map +1 -0
- package/dist/backend/vector-backend.d.cts +29 -0
- package/dist/backend/vector-backend.d.cts.map +1 -0
- package/dist/backend/vector-backend.d.mts +29 -0
- package/dist/backend/vector-backend.d.mts.map +1 -0
- package/dist/dirty-queue.d.cts +41 -0
- package/dist/dirty-queue.d.cts.map +1 -0
- package/dist/dirty-queue.d.mts +41 -0
- package/dist/dirty-queue.d.mts.map +1 -0
- package/dist/embed.cjs +45 -0
- package/dist/embed.d.cts +20 -0
- package/dist/embed.d.cts.map +1 -0
- package/dist/embed.d.mts +20 -0
- package/dist/embed.d.mts.map +1 -0
- package/dist/embed.mjs +46 -0
- package/dist/embed.mjs.map +1 -0
- package/dist/extraction/defaults.cjs +242 -0
- package/dist/extraction/defaults.d.cts +14 -0
- package/dist/extraction/defaults.d.cts.map +1 -0
- package/dist/extraction/defaults.d.mts +14 -0
- package/dist/extraction/defaults.d.mts.map +1 -0
- package/dist/extraction/defaults.mjs +242 -0
- package/dist/extraction/defaults.mjs.map +1 -0
- package/dist/extraction/field-map.cjs +162 -0
- package/dist/extraction/field-map.d.cts +70 -0
- package/dist/extraction/field-map.d.cts.map +1 -0
- package/dist/extraction/field-map.d.mts +70 -0
- package/dist/extraction/field-map.d.mts.map +1 -0
- package/dist/extraction/field-map.mjs +157 -0
- package/dist/extraction/field-map.mjs.map +1 -0
- package/dist/extraction/manager.cjs +74 -0
- package/dist/extraction/manager.d.cts +47 -0
- package/dist/extraction/manager.d.cts.map +1 -0
- package/dist/extraction/manager.d.mts +47 -0
- package/dist/extraction/manager.d.mts.map +1 -0
- package/dist/extraction/manager.mjs +74 -0
- package/dist/extraction/manager.mjs.map +1 -0
- package/dist/extraction/path-anchors.cjs +86 -0
- package/dist/extraction/path-anchors.d.cts +72 -0
- package/dist/extraction/path-anchors.d.cts.map +1 -0
- package/dist/extraction/path-anchors.d.mts +72 -0
- package/dist/extraction/path-anchors.d.mts.map +1 -0
- package/dist/extraction/path-anchors.mjs +86 -0
- package/dist/extraction/path-anchors.mjs.map +1 -0
- package/dist/follower-scheduler.cjs +92 -0
- package/dist/follower-scheduler.d.cts +58 -0
- package/dist/follower-scheduler.d.cts.map +1 -0
- package/dist/follower-scheduler.d.mts +58 -0
- package/dist/follower-scheduler.d.mts.map +1 -0
- package/dist/follower-scheduler.mjs +92 -0
- package/dist/follower-scheduler.mjs.map +1 -0
- package/dist/follower.cjs +577 -0
- package/dist/follower.d.cts +241 -0
- package/dist/follower.d.cts.map +1 -0
- package/dist/follower.d.mts +241 -0
- package/dist/follower.d.mts.map +1 -0
- package/dist/follower.mjs +571 -0
- package/dist/follower.mjs.map +1 -0
- package/dist/index-provider.cjs +1166 -0
- package/dist/index-provider.d.cts +175 -0
- package/dist/index-provider.d.cts.map +1 -0
- package/dist/index-provider.d.mts +175 -0
- package/dist/index-provider.d.mts.map +1 -0
- package/dist/index-provider.mjs +1167 -0
- package/dist/index-provider.mjs.map +1 -0
- package/dist/index.cjs +53 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +20 -0
- package/dist/keys/exact.cjs +20 -0
- package/dist/keys/exact.mjs +20 -0
- package/dist/keys/exact.mjs.map +1 -0
- package/dist/keys/location.cjs +13 -0
- package/dist/keys/location.mjs +13 -0
- package/dist/keys/location.mjs.map +1 -0
- package/dist/keys/normalized.cjs +62 -0
- package/dist/keys/normalized.mjs +63 -0
- package/dist/keys/normalized.mjs.map +1 -0
- package/dist/keys/person.cjs +15 -0
- package/dist/keys/person.mjs +15 -0
- package/dist/keys/person.mjs.map +1 -0
- package/dist/keys/registry.cjs +45 -0
- package/dist/keys/registry.d.cts +15 -0
- package/dist/keys/registry.d.cts.map +1 -0
- package/dist/keys/registry.d.mts +15 -0
- package/dist/keys/registry.d.mts.map +1 -0
- package/dist/keys/registry.mjs +46 -0
- package/dist/keys/registry.mjs.map +1 -0
- package/dist/keys/time.cjs +128 -0
- package/dist/keys/time.mjs +128 -0
- package/dist/keys/time.mjs.map +1 -0
- package/dist/keys/topic.cjs +23 -0
- package/dist/keys/topic.mjs +23 -0
- package/dist/keys/topic.mjs.map +1 -0
- package/dist/manifest-index.cjs +29 -0
- package/dist/manifest-index.d.cts +24 -0
- package/dist/manifest-index.d.cts.map +1 -0
- package/dist/manifest-index.d.mts +24 -0
- package/dist/manifest-index.d.mts.map +1 -0
- package/dist/manifest-index.mjs +28 -0
- package/dist/manifest-index.mjs.map +1 -0
- package/dist/offline-rebuild.cjs +48 -0
- package/dist/offline-rebuild.d.cts +57 -0
- package/dist/offline-rebuild.d.cts.map +1 -0
- package/dist/offline-rebuild.d.mts +57 -0
- package/dist/offline-rebuild.d.mts.map +1 -0
- package/dist/offline-rebuild.mjs +49 -0
- package/dist/offline-rebuild.mjs.map +1 -0
- package/dist/query/engine.cjs +433 -0
- package/dist/query/engine.d.cts +34 -0
- package/dist/query/engine.d.cts.map +1 -0
- package/dist/query/engine.d.mts +34 -0
- package/dist/query/engine.d.mts.map +1 -0
- package/dist/query/engine.mjs +433 -0
- package/dist/query/engine.mjs.map +1 -0
- package/dist/query/limits.cjs +12 -0
- package/dist/query/limits.mjs +12 -0
- package/dist/query/limits.mjs.map +1 -0
- package/dist/text/cjk.cjs +30 -0
- package/dist/text/cjk.d.cts +24 -0
- package/dist/text/cjk.d.cts.map +1 -0
- package/dist/text/cjk.d.mts +24 -0
- package/dist/text/cjk.d.mts.map +1 -0
- package/dist/text/cjk.mjs +30 -0
- package/dist/text/cjk.mjs.map +1 -0
- package/dist/text/fts.cjs +244 -0
- package/dist/text/fts.mjs +245 -0
- package/dist/text/fts.mjs.map +1 -0
- package/dist/text/host.cjs +39 -0
- package/dist/text/host.mjs +38 -0
- package/dist/text/host.mjs.map +1 -0
- package/dist/types.cjs +44 -0
- package/dist/types.d.cts +393 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +393 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +42 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
import { KeyGeneratorRegistry } from "../keys/registry.mjs";
|
|
2
|
+
import { segmentCJK } from "../text/cjk.mjs";
|
|
3
|
+
import { entryPathPrefixRange } from "./prefix-range.mjs";
|
|
4
|
+
import { createSchema } from "./schema.mjs";
|
|
5
|
+
import { SQLiteAFS } from "@aigne/afs-sqlite";
|
|
6
|
+
import { sql } from "drizzle-orm";
|
|
7
|
+
import { AFS } from "@aigne/afs";
|
|
8
|
+
import { joinURL } from "ufo";
|
|
9
|
+
|
|
10
|
+
//#region src/backend/sqlite-backend.ts
|
|
11
|
+
/**
|
|
12
|
+
* SQLiteIndexBackend — pluggable SQLite storage backend for the Index Provider.
|
|
13
|
+
*
|
|
14
|
+
* Implements the IndexBackend interface using AFSSQLite through AFS.
|
|
15
|
+
* - CRUD operations use built-in AFS actions (insert, query, update_where, delete_where)
|
|
16
|
+
* - Schema creation uses getDatabase() as a local variable (one-time DDL bootstrap)
|
|
17
|
+
* - FTS / count / anchor-intersection use registered custom actions via afs.exec()
|
|
18
|
+
* - Zero stored db handle — all runtime operations go through AFS
|
|
19
|
+
*/
|
|
20
|
+
const MOUNT_NAME = "db";
|
|
21
|
+
var SQLiteIndexBackend = class {
|
|
22
|
+
afs;
|
|
23
|
+
sqliteProvider;
|
|
24
|
+
dbUrl;
|
|
25
|
+
keyRegistry;
|
|
26
|
+
ftsTokenizer;
|
|
27
|
+
scope;
|
|
28
|
+
embeddingDimension = 0;
|
|
29
|
+
initialized = false;
|
|
30
|
+
constructor(dbUrl = ":memory:", keyRegistry, opts = {}) {
|
|
31
|
+
this.dbUrl = dbUrl;
|
|
32
|
+
this.keyRegistry = keyRegistry ?? new KeyGeneratorRegistry();
|
|
33
|
+
this.ftsTokenizer = opts.ftsTokenizer;
|
|
34
|
+
this.scope = opts.scope ?? "";
|
|
35
|
+
}
|
|
36
|
+
/** See `IndexBackend.setKeyRegistry` — wires an injected backend to the provider's shared registry. */
|
|
37
|
+
setKeyRegistry(registry) {
|
|
38
|
+
this.keyRegistry = registry;
|
|
39
|
+
}
|
|
40
|
+
get mountPath() {
|
|
41
|
+
return joinURL("/modules", MOUNT_NAME);
|
|
42
|
+
}
|
|
43
|
+
tableAction(table, action) {
|
|
44
|
+
return joinURL(this.mountPath, table, ".actions", action);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Idempotent (design.md §4.4 addendum, owner decision 2026-07-05): a
|
|
48
|
+
* caller that needs the backend usable BEFORE any `AFSIndex` action runs
|
|
49
|
+
* (e.g. `initCursor` at follower-registration time — see daemon.ts /
|
|
50
|
+
* cf-session-view.ts) must call this explicitly; `AFSIndex.ensureInitialized()`
|
|
51
|
+
* ALSO calls it unconditionally on the first real action. Without the
|
|
52
|
+
* guard, that second call re-mounted a BRAND NEW `SQLiteAFS`/connection —
|
|
53
|
+
* for a `:memory:` db this silently discarded everything the first
|
|
54
|
+
* connection had written (caught by the Node/CF parity test's in-memory
|
|
55
|
+
* backend); for a file-backed db it "only" leaked an extra connection.
|
|
56
|
+
*/
|
|
57
|
+
async initialize() {
|
|
58
|
+
if (this.initialized) return;
|
|
59
|
+
this.sqliteProvider = new SQLiteAFS({
|
|
60
|
+
url: this.dbUrl,
|
|
61
|
+
name: MOUNT_NAME,
|
|
62
|
+
accessMode: "readwrite"
|
|
63
|
+
});
|
|
64
|
+
this.afs = new AFS();
|
|
65
|
+
await this.afs.mount(this.sqliteProvider);
|
|
66
|
+
await createSchema(await this.sqliteProvider.getDatabase(), { ftsTokenizer: this.ftsTokenizer });
|
|
67
|
+
this.registerCustomActions();
|
|
68
|
+
this.initialized = true;
|
|
69
|
+
}
|
|
70
|
+
registerCustomActions() {
|
|
71
|
+
this.sqliteProvider.registerAction("anchor_intersection", async (ctx, params) => {
|
|
72
|
+
const keyGroups = params.key_groups;
|
|
73
|
+
const tenantScope = params.tenant_scope ?? "";
|
|
74
|
+
const scope = params.scope;
|
|
75
|
+
const pathPrefix = params.path_prefix;
|
|
76
|
+
const minConfidence = params.min_confidence;
|
|
77
|
+
const limit = params.limit;
|
|
78
|
+
const metaFilter = params.meta_filter;
|
|
79
|
+
const domains = params.domains;
|
|
80
|
+
if (!keyGroups || keyGroups.length === 0) return { rows: [] };
|
|
81
|
+
const cteNames = [];
|
|
82
|
+
const cteParts = [];
|
|
83
|
+
for (let i = 0; i < keyGroups.length; i++) {
|
|
84
|
+
const cteName = `g${i}`;
|
|
85
|
+
cteNames.push(cteName);
|
|
86
|
+
const extraConds = [];
|
|
87
|
+
for (const prefix of [scope, pathPrefix]) if (prefix) {
|
|
88
|
+
const [lo, hi] = entryPathPrefixRange(prefix);
|
|
89
|
+
extraConds.push(sql`entry_path >= ${lo} AND entry_path < ${hi}`);
|
|
90
|
+
}
|
|
91
|
+
if (minConfidence != null) extraConds.push(sql`confidence >= ${minConfidence}`);
|
|
92
|
+
const arms = keyGroups[i].values.map((v) => sql`SELECT entry_path, confidence FROM idx_anchor_key WHERE ${sql.join([
|
|
93
|
+
sql`scope = ${tenantScope}`,
|
|
94
|
+
sql`index_key = ${v}`,
|
|
95
|
+
...extraConds
|
|
96
|
+
], sql` AND `)}`);
|
|
97
|
+
cteParts.push(sql`${sql.raw(cteName)} AS (SELECT entry_path, MIN(confidence) as min_conf FROM (${sql.join(arms, sql` UNION ALL `)}) GROUP BY entry_path)`);
|
|
98
|
+
}
|
|
99
|
+
const cteSql = sql`WITH ${sql.join(cteParts, sql`, `)}`;
|
|
100
|
+
let selectSql;
|
|
101
|
+
const cteJoinParts = [];
|
|
102
|
+
let scoreExpr;
|
|
103
|
+
if (cteNames.length === 1) scoreExpr = `${cteNames[0]}.min_conf`;
|
|
104
|
+
else {
|
|
105
|
+
for (let i = 1; i < cteNames.length; i++) cteJoinParts.push(`INNER JOIN ${cteNames[i]} ON ${cteNames[0]}.entry_path = ${cteNames[i]}.entry_path`);
|
|
106
|
+
scoreExpr = `MIN(${cteNames.map((n) => `${n}.min_conf`).join(", ")})`;
|
|
107
|
+
}
|
|
108
|
+
const entryJoinPart = [];
|
|
109
|
+
let entryWhereSql = null;
|
|
110
|
+
if (domains && domains.length > 0 || metaFilter && Object.keys(metaFilter).length > 0) {
|
|
111
|
+
entryJoinPart.push(`INNER JOIN ds_indexed_entry ie ON ${cteNames[0]}.entry_path = ie.entry_path AND ie.scope = '${sqlEscape(tenantScope)}'`);
|
|
112
|
+
const entryConditions = [];
|
|
113
|
+
if (domains && domains.length > 0) {
|
|
114
|
+
const domainPlaceholders = domains.map((d) => sql`${d}`);
|
|
115
|
+
entryConditions.push(sql`ie.domain IN (${sql.join(domainPlaceholders, sql`, `)})`);
|
|
116
|
+
}
|
|
117
|
+
for (const [key, expected] of Object.entries(metaFilter ?? {})) if (Array.isArray(expected)) {
|
|
118
|
+
if (expected.length === 0) return { rows: [] };
|
|
119
|
+
const placeholders = expected.map((v) => sql`${v}`);
|
|
120
|
+
entryConditions.push(sql`json_extract(ie.meta, ${`$.${key}`}) IN (${sql.join(placeholders, sql`, `)})`);
|
|
121
|
+
} else entryConditions.push(sql`json_extract(ie.meta, ${`$.${key}`}) = ${expected}`);
|
|
122
|
+
entryWhereSql = sql.join(entryConditions, sql` AND `);
|
|
123
|
+
}
|
|
124
|
+
const allJoins = [...cteJoinParts, ...entryJoinPart].join(" ");
|
|
125
|
+
if (entryWhereSql) selectSql = sql`${cteSql} SELECT ${sql.raw(cteNames[0])}.entry_path, ${sql.raw(scoreExpr)} as score FROM ${sql.raw(cteNames[0])} ${sql.raw(allJoins)} WHERE ${entryWhereSql}`;
|
|
126
|
+
else selectSql = sql`${cteSql} SELECT ${sql.raw(cteNames[0])}.entry_path, ${sql.raw(scoreExpr)} as score FROM ${sql.raw(cteNames[0])} ${sql.raw(allJoins)}`;
|
|
127
|
+
const orderSql = limit != null ? sql`${selectSql} ORDER BY score DESC, ${sql.raw(cteNames[0])}.entry_path LIMIT ${limit}` : sql`${selectSql} ORDER BY score DESC, ${sql.raw(cteNames[0])}.entry_path`;
|
|
128
|
+
return { rows: await ctx.db.all(orderSql) };
|
|
129
|
+
}, {
|
|
130
|
+
description: "Anchor key intersection with CTE-based AND logic",
|
|
131
|
+
tableLevel: true
|
|
132
|
+
});
|
|
133
|
+
this.sqliteProvider.registerAction("fts_write", async (ctx, params) => {
|
|
134
|
+
const { scope, path, summary } = params;
|
|
135
|
+
await writeFtsRow(ctx.db, scope, path, segmentCJK(summary));
|
|
136
|
+
return { success: true };
|
|
137
|
+
}, {
|
|
138
|
+
description: "Write summary to FTS5 index (insert-or-replace by rowid, not full scan)",
|
|
139
|
+
tableLevel: true
|
|
140
|
+
});
|
|
141
|
+
this.sqliteProvider.registerAction("fts_query", async (ctx, params) => {
|
|
142
|
+
const { text, limit, meta_filter, domain, domains, path_prefix, tenant_scope } = params;
|
|
143
|
+
const tScope = tenant_scope ?? "";
|
|
144
|
+
const needsJoin = meta_filter && Object.keys(meta_filter).length > 0 || typeof domain === "string" && domain.length > 0 || Array.isArray(domains) && domains.length > 0 || typeof path_prefix === "string" && path_prefix.length > 0;
|
|
145
|
+
let query;
|
|
146
|
+
if (needsJoin) {
|
|
147
|
+
const conditions = [sql`idx_fts MATCH ${text}`, sql`idx_fts.scope = ${tScope}`];
|
|
148
|
+
if (domain) conditions.push(sql`ie.domain = ${domain}`);
|
|
149
|
+
if (domains && domains.length > 0) {
|
|
150
|
+
const domainPlaceholders = domains.map((d) => sql`${d}`);
|
|
151
|
+
conditions.push(sql`ie.domain IN (${sql.join(domainPlaceholders, sql`, `)})`);
|
|
152
|
+
}
|
|
153
|
+
if (path_prefix) conditions.push(sql`ie.entry_path LIKE ${`${path_prefix}%`}`);
|
|
154
|
+
for (const [key, expected] of Object.entries(meta_filter ?? {})) if (Array.isArray(expected)) {
|
|
155
|
+
if (expected.length === 0) return { rows: [] };
|
|
156
|
+
const placeholders = expected.map((v) => sql`${v}`);
|
|
157
|
+
conditions.push(sql`json_extract(ie.meta, ${`$.${key}`}) IN (${sql.join(placeholders, sql`, `)})`);
|
|
158
|
+
} else conditions.push(sql`json_extract(ie.meta, ${`$.${key}`}) = ${expected}`);
|
|
159
|
+
const where = sql.join(conditions, sql` AND `);
|
|
160
|
+
query = limit != null ? sql`SELECT idx_fts.entry_path, idx_fts.rank FROM idx_fts INNER JOIN ds_indexed_entry ie ON idx_fts.entry_path = ie.entry_path WHERE ${where} ORDER BY idx_fts.rank LIMIT ${limit}` : sql`SELECT idx_fts.entry_path, idx_fts.rank FROM idx_fts INNER JOIN ds_indexed_entry ie ON idx_fts.entry_path = ie.entry_path WHERE ${where} ORDER BY idx_fts.rank`;
|
|
161
|
+
} else query = limit != null ? sql`SELECT entry_path, rank FROM idx_fts WHERE idx_fts MATCH ${text} AND scope = ${tScope} ORDER BY rank LIMIT ${limit}` : sql`SELECT entry_path, rank FROM idx_fts WHERE idx_fts MATCH ${text} AND scope = ${tScope} ORDER BY rank`;
|
|
162
|
+
return { rows: await ctx.db.all(query) };
|
|
163
|
+
}, {
|
|
164
|
+
description: "FTS5 search with optional domain/scope/metaFilter isolation",
|
|
165
|
+
tableLevel: true
|
|
166
|
+
});
|
|
167
|
+
this.sqliteProvider.registerAction("fts_delete", async (ctx, params) => {
|
|
168
|
+
const { scope, path } = params;
|
|
169
|
+
await deleteFtsRow(ctx.db, scope, path);
|
|
170
|
+
return { success: true };
|
|
171
|
+
}, {
|
|
172
|
+
description: "Remove entry from FTS5 index (delete by rowid, not full scan)",
|
|
173
|
+
tableLevel: true
|
|
174
|
+
});
|
|
175
|
+
this.sqliteProvider.registerAction("fts_get", async (ctx, params) => {
|
|
176
|
+
const { scope, path } = params;
|
|
177
|
+
return { summary: (await ctx.db.all(sql`SELECT summary FROM idx_fts WHERE scope = ${scope} AND entry_path = ${path} LIMIT 1`))[0]?.summary ?? null };
|
|
178
|
+
}, {
|
|
179
|
+
description: "Read the stored FTS summary for a single entry",
|
|
180
|
+
tableLevel: true
|
|
181
|
+
});
|
|
182
|
+
this.sqliteProvider.registerAction("count_entries", async (ctx, params) => {
|
|
183
|
+
const filter = params;
|
|
184
|
+
const conditions = [sql`scope = ${filter.tenant_scope ?? ""}`];
|
|
185
|
+
if (filter.domain) conditions.push(sql`domain = ${filter.domain}`);
|
|
186
|
+
if (filter.state) conditions.push(sql`state = ${filter.state}`);
|
|
187
|
+
if (filter.extractorId) conditions.push(sql`extractor_id = ${filter.extractorId}`);
|
|
188
|
+
if (filter.scope) conditions.push(sql`entry_path LIKE ${`${filter.scope}%`}`);
|
|
189
|
+
if (filter.boundDomains && filter.boundDomains.length > 0) {
|
|
190
|
+
const ps = filter.boundDomains.map((d) => sql`${d}`);
|
|
191
|
+
conditions.push(sql`domain IN (${sql.join(ps, sql`, `)})`);
|
|
192
|
+
}
|
|
193
|
+
const query = sql`SELECT COUNT(*) as count FROM ds_indexed_entry WHERE ${sql.join(conditions, sql` AND `)}`;
|
|
194
|
+
return { count: (await ctx.db.all(query))[0]?.count ?? 0 };
|
|
195
|
+
}, {
|
|
196
|
+
description: "Count indexed entries with optional filters",
|
|
197
|
+
tableLevel: true
|
|
198
|
+
});
|
|
199
|
+
this.sqliteProvider.registerAction("embedding_write", async (ctx, params) => {
|
|
200
|
+
const { scope, path, embedding } = params;
|
|
201
|
+
const blob = embeddingToBlob(embedding);
|
|
202
|
+
await ctx.db.run(sql`INSERT INTO idx_embedding_vector (scope, entry_path, embedding, embedding_blob, dimension) VALUES (${scope}, ${path}, '', ${blob}, ${embedding.length})
|
|
203
|
+
ON CONFLICT(scope, entry_path) DO UPDATE SET
|
|
204
|
+
embedding = excluded.embedding,
|
|
205
|
+
embedding_blob = excluded.embedding_blob,
|
|
206
|
+
dimension = excluded.dimension`);
|
|
207
|
+
return { success: true };
|
|
208
|
+
}, {
|
|
209
|
+
description: "Write embedding vector",
|
|
210
|
+
tableLevel: true
|
|
211
|
+
});
|
|
212
|
+
this.sqliteProvider.registerAction("embedding_query", async (ctx, params) => {
|
|
213
|
+
const filter = params ?? {};
|
|
214
|
+
const conditions = [sql`idx_embedding_vector.scope = ${filter.tenant_scope ?? ""}`];
|
|
215
|
+
if (filter.domain) conditions.push(sql`ds_indexed_entry.domain = ${filter.domain}`);
|
|
216
|
+
if (filter.domains && filter.domains.length > 0) {
|
|
217
|
+
const domainPlaceholders = filter.domains.map((d) => sql`${d}`);
|
|
218
|
+
conditions.push(sql`ds_indexed_entry.domain IN (${sql.join(domainPlaceholders, sql`, `)})`);
|
|
219
|
+
}
|
|
220
|
+
if (filter.path_prefix) conditions.push(sql`ds_indexed_entry.entry_path LIKE ${`${filter.path_prefix}%`}`);
|
|
221
|
+
const where = sql`WHERE ${sql.join(conditions, sql` AND `)}`;
|
|
222
|
+
return { rows: await ctx.db.all(sql`SELECT idx_embedding_vector.entry_path AS entry_path,
|
|
223
|
+
idx_embedding_vector.embedding AS embedding,
|
|
224
|
+
idx_embedding_vector.embedding_blob AS embedding_blob
|
|
225
|
+
FROM idx_embedding_vector
|
|
226
|
+
JOIN ds_indexed_entry
|
|
227
|
+
ON ds_indexed_entry.scope = idx_embedding_vector.scope
|
|
228
|
+
AND ds_indexed_entry.entry_path = idx_embedding_vector.entry_path
|
|
229
|
+
${where}`) };
|
|
230
|
+
}, {
|
|
231
|
+
description: "Fetch embeddings (filtered by domain/scope) for vector search",
|
|
232
|
+
tableLevel: true
|
|
233
|
+
});
|
|
234
|
+
this.sqliteProvider.registerAction("embedding_delete", async (ctx, params) => {
|
|
235
|
+
const { scope, path } = params;
|
|
236
|
+
await ctx.db.run(sql`DELETE FROM idx_embedding_vector WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
237
|
+
return { success: true };
|
|
238
|
+
}, {
|
|
239
|
+
description: "Remove embedding vector",
|
|
240
|
+
tableLevel: true
|
|
241
|
+
});
|
|
242
|
+
this.sqliteProvider.registerAction("upsert_entry", async (ctx, params) => {
|
|
243
|
+
const e = params;
|
|
244
|
+
await ctx.db.run(sql`INSERT OR REPLACE INTO ds_indexed_entry (scope, entry_path, domain, state, indexed_at, updated_at, anchor_count, source_path, source_hash, extractor_id, meta) VALUES (${e.scope}, ${e.entry_path}, ${e.domain}, ${e.state}, ${e.indexed_at}, ${e.updated_at}, ${e.anchor_count}, ${e.source_path ?? null}, ${e.source_hash ?? null}, ${e.extractor_id ?? null}, ${e.meta ?? null})`);
|
|
245
|
+
return { success: true };
|
|
246
|
+
}, {
|
|
247
|
+
description: "Single entry upsert via INSERT OR REPLACE",
|
|
248
|
+
tableLevel: true
|
|
249
|
+
});
|
|
250
|
+
this.sqliteProvider.registerAction("replace_anchors", async (ctx, params) => {
|
|
251
|
+
const { scope, path, dataRows, keyRows } = params;
|
|
252
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_data WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
253
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_key WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
254
|
+
for (const row of dataRows) await ctx.db.run(sql`INSERT INTO idx_anchor_data (scope, entry_path, anchor_type, confidence, raw_anchor) VALUES (${scope}, ${row.entry_path}, ${row.anchor_type}, ${row.confidence}, ${row.raw_anchor})`);
|
|
255
|
+
for (const row of keyRows) await ctx.db.run(sql`INSERT OR REPLACE INTO idx_anchor_key (scope, index_key, entry_path, anchor_type, confidence) VALUES (${scope}, ${row.index_key}, ${row.entry_path}, ${row.anchor_type}, ${row.confidence})`);
|
|
256
|
+
return { success: true };
|
|
257
|
+
}, {
|
|
258
|
+
description: "Replace all anchors for a single entry",
|
|
259
|
+
tableLevel: true
|
|
260
|
+
});
|
|
261
|
+
this.sqliteProvider.registerAction("write_indexed_document", async (ctx, params) => {
|
|
262
|
+
const { entry, path, dataRows, keyRows, summary } = params;
|
|
263
|
+
await ctx.db.run(sql.raw("BEGIN TRANSACTION"));
|
|
264
|
+
try {
|
|
265
|
+
await ctx.db.run(sql`INSERT OR REPLACE INTO ds_indexed_entry (scope, entry_path, domain, state, indexed_at, updated_at, anchor_count, source_path, source_hash, extractor_id, meta) VALUES (${entry.scope}, ${entry.entry_path}, ${entry.domain}, ${entry.state}, ${entry.indexed_at}, ${entry.updated_at}, ${entry.anchor_count}, ${entry.source_path ?? null}, ${entry.source_hash ?? null}, ${entry.extractor_id ?? null}, ${entry.meta ?? null})`);
|
|
266
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_data WHERE scope = ${entry.scope} AND entry_path = ${path}`);
|
|
267
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_key WHERE scope = ${entry.scope} AND entry_path = ${path}`);
|
|
268
|
+
for (const row of dataRows) await ctx.db.run(sql`INSERT INTO idx_anchor_data (scope, entry_path, anchor_type, confidence, raw_anchor) VALUES (${entry.scope}, ${row.entry_path}, ${row.anchor_type}, ${row.confidence}, ${row.raw_anchor})`);
|
|
269
|
+
for (const row of keyRows) await ctx.db.run(sql`INSERT OR REPLACE INTO idx_anchor_key (scope, index_key, entry_path, anchor_type, confidence) VALUES (${entry.scope}, ${row.index_key}, ${row.entry_path}, ${row.anchor_type}, ${row.confidence})`);
|
|
270
|
+
if (summary) await writeFtsRow(ctx.db, entry.scope, path, segmentCJK(summary));
|
|
271
|
+
await ctx.db.run(sql.raw("COMMIT"));
|
|
272
|
+
return { success: true };
|
|
273
|
+
} catch (error) {
|
|
274
|
+
await ctx.db.run(sql.raw("ROLLBACK"));
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}, {
|
|
278
|
+
description: "Write entry metadata, anchors, and FTS summary in one transaction",
|
|
279
|
+
tableLevel: true
|
|
280
|
+
});
|
|
281
|
+
this.sqliteProvider.registerAction("batch_purge", async (ctx, params) => {
|
|
282
|
+
const { scope, paths, skipVector } = params;
|
|
283
|
+
await ctx.db.run(sql.raw("BEGIN TRANSACTION"));
|
|
284
|
+
try {
|
|
285
|
+
for (const path of paths) {
|
|
286
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_data WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
287
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_key WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
288
|
+
await deleteFtsRow(ctx.db, scope, path);
|
|
289
|
+
if (skipVector !== true) await ctx.db.run(sql`DELETE FROM idx_embedding_vector WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
290
|
+
await ctx.db.run(sql`DELETE FROM ds_indexed_entry WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
291
|
+
}
|
|
292
|
+
await ctx.db.run(sql.raw("COMMIT"));
|
|
293
|
+
return { count: paths.length };
|
|
294
|
+
} catch (error) {
|
|
295
|
+
await ctx.db.run(sql.raw("ROLLBACK"));
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
298
|
+
}, {
|
|
299
|
+
description: "Purge entries and all associated data in a single transaction",
|
|
300
|
+
tableLevel: true
|
|
301
|
+
});
|
|
302
|
+
this.sqliteProvider.registerAction("batch_upsert_entries", async (ctx, params) => {
|
|
303
|
+
const entries = params.entries;
|
|
304
|
+
await ctx.db.run(sql.raw("BEGIN TRANSACTION"));
|
|
305
|
+
try {
|
|
306
|
+
for (const e of entries) await ctx.db.run(sql`INSERT OR REPLACE INTO ds_indexed_entry (scope, entry_path, domain, state, indexed_at, updated_at, anchor_count, source_path, source_hash, extractor_id, meta) VALUES (${e.scope}, ${e.entry_path}, ${e.domain}, ${e.state}, ${e.indexed_at}, ${e.updated_at}, ${e.anchor_count}, ${e.source_path ?? null}, ${e.source_hash ?? null}, ${e.extractor_id ?? null}, ${e.meta ?? null})`);
|
|
307
|
+
await ctx.db.run(sql.raw("COMMIT"));
|
|
308
|
+
return { count: entries.length };
|
|
309
|
+
} catch (error) {
|
|
310
|
+
await ctx.db.run(sql.raw("ROLLBACK"));
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}, {
|
|
314
|
+
description: "Batch upsert entries in a single transaction",
|
|
315
|
+
tableLevel: true
|
|
316
|
+
});
|
|
317
|
+
this.sqliteProvider.registerAction("batch_replace_anchors", async (ctx, params) => {
|
|
318
|
+
const { scope, items } = params;
|
|
319
|
+
await ctx.db.run(sql.raw("BEGIN TRANSACTION"));
|
|
320
|
+
try {
|
|
321
|
+
for (const item of items) {
|
|
322
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_data WHERE scope = ${scope} AND entry_path = ${item.path}`);
|
|
323
|
+
await ctx.db.run(sql`DELETE FROM idx_anchor_key WHERE scope = ${scope} AND entry_path = ${item.path}`);
|
|
324
|
+
for (const row of item.dataRows) await ctx.db.run(sql`INSERT INTO idx_anchor_data (scope, entry_path, anchor_type, confidence, raw_anchor) VALUES (${scope}, ${row.entry_path}, ${row.anchor_type}, ${row.confidence}, ${row.raw_anchor})`);
|
|
325
|
+
for (const row of item.keyRows) await ctx.db.run(sql`INSERT OR REPLACE INTO idx_anchor_key (scope, index_key, entry_path, anchor_type, confidence) VALUES (${scope}, ${row.index_key}, ${row.entry_path}, ${row.anchor_type}, ${row.confidence})`);
|
|
326
|
+
}
|
|
327
|
+
await ctx.db.run(sql.raw("COMMIT"));
|
|
328
|
+
return { count: items.length };
|
|
329
|
+
} catch (error) {
|
|
330
|
+
await ctx.db.run(sql.raw("ROLLBACK"));
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}, {
|
|
334
|
+
description: "Batch replace anchors in a single transaction",
|
|
335
|
+
tableLevel: true
|
|
336
|
+
});
|
|
337
|
+
this.sqliteProvider.registerAction("batch_write_fts", async (ctx, params) => {
|
|
338
|
+
const { scope, items } = params;
|
|
339
|
+
await ctx.db.run(sql.raw("BEGIN TRANSACTION"));
|
|
340
|
+
try {
|
|
341
|
+
for (const item of items) await writeFtsRow(ctx.db, scope, item.path, item.summary);
|
|
342
|
+
await ctx.db.run(sql.raw("COMMIT"));
|
|
343
|
+
return { count: items.length };
|
|
344
|
+
} catch (error) {
|
|
345
|
+
await ctx.db.run(sql.raw("ROLLBACK"));
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}, {
|
|
349
|
+
description: "Batch write FTS summaries in a single transaction",
|
|
350
|
+
tableLevel: true
|
|
351
|
+
});
|
|
352
|
+
this.sqliteProvider.registerAction("cursor_read", async (ctx, params) => {
|
|
353
|
+
const { consumer_id } = params;
|
|
354
|
+
const rows = await ctx.db.all(sql`SELECT consumer_id, last_seq, watermarks, updated_at FROM idx_index_cursor WHERE consumer_id = ${consumer_id} LIMIT 1`);
|
|
355
|
+
if (rows.length === 0) return { cursor: null };
|
|
356
|
+
const row = rows[0];
|
|
357
|
+
return { cursor: {
|
|
358
|
+
consumerId: row.consumer_id,
|
|
359
|
+
lastSeq: row.last_seq,
|
|
360
|
+
watermarks: row.watermarks ? JSON.parse(row.watermarks) : void 0,
|
|
361
|
+
updatedAt: row.updated_at
|
|
362
|
+
} };
|
|
363
|
+
}, {
|
|
364
|
+
description: "Read changelog follower cursor",
|
|
365
|
+
tableLevel: true
|
|
366
|
+
});
|
|
367
|
+
this.sqliteProvider.registerAction("cursor_write", async (ctx, params) => {
|
|
368
|
+
const { consumer_id, last_seq, watermarks } = params;
|
|
369
|
+
const now = Date.now();
|
|
370
|
+
await ctx.db.run(sql`INSERT INTO idx_index_cursor (consumer_id, last_seq, watermarks, updated_at, served_at)
|
|
371
|
+
VALUES (${consumer_id}, ${last_seq}, ${watermarks ? JSON.stringify(watermarks) : null}, ${now}, ${now})
|
|
372
|
+
ON CONFLICT(consumer_id) DO UPDATE SET
|
|
373
|
+
last_seq = MAX(last_seq, excluded.last_seq),
|
|
374
|
+
watermarks = excluded.watermarks,
|
|
375
|
+
updated_at = excluded.updated_at,
|
|
376
|
+
served_at = excluded.served_at`);
|
|
377
|
+
return { success: true };
|
|
378
|
+
}, {
|
|
379
|
+
description: "Write index follower cursor (monotonic last_seq; dead column post-cutover)",
|
|
380
|
+
tableLevel: true
|
|
381
|
+
});
|
|
382
|
+
this.sqliteProvider.registerAction("cursor_mark_served", async (ctx, params) => {
|
|
383
|
+
const { consumer_id } = params;
|
|
384
|
+
await ctx.db.run(sql`UPDATE idx_index_cursor SET served_at = ${Date.now()} WHERE consumer_id = ${consumer_id}`);
|
|
385
|
+
return { success: true };
|
|
386
|
+
}, {
|
|
387
|
+
description: "Mark changelog follower cursor as served",
|
|
388
|
+
tableLevel: true
|
|
389
|
+
});
|
|
390
|
+
this.sqliteProvider.registerAction("cursor_init", async (ctx, params) => {
|
|
391
|
+
const { consumer_id, instance_did, domains } = params;
|
|
392
|
+
const now = Date.now();
|
|
393
|
+
await ctx.db.run(sql`INSERT OR IGNORE INTO idx_index_cursor (consumer_id, last_seq, watermarks, updated_at, served_at, instance_did, domains)
|
|
394
|
+
VALUES (${consumer_id}, 0, NULL, ${now}, ${now}, ${instance_did ?? null}, ${domains ?? null})`);
|
|
395
|
+
return { success: true };
|
|
396
|
+
}, {
|
|
397
|
+
description: "Initialize changelog follower cursor (idempotent)",
|
|
398
|
+
tableLevel: true
|
|
399
|
+
});
|
|
400
|
+
this.sqliteProvider.registerAction("cursor_scope_list", async (ctx, params) => {
|
|
401
|
+
const { family_consumer_id } = params;
|
|
402
|
+
return { registrations: (await ctx.db.all(sql`SELECT consumer_id, instance_did, domains, updated_at, served_at
|
|
403
|
+
FROM idx_index_cursor
|
|
404
|
+
WHERE consumer_id LIKE ${`${family_consumer_id}:%`}`)).map((r) => ({
|
|
405
|
+
scope: cursorScopeFromConsumerId(r.consumer_id, family_consumer_id, r.instance_did),
|
|
406
|
+
consumerId: r.consumer_id,
|
|
407
|
+
instanceDid: r.instance_did,
|
|
408
|
+
domainsJson: r.domains,
|
|
409
|
+
updatedAt: r.updated_at,
|
|
410
|
+
servedAt: r.served_at ?? r.updated_at
|
|
411
|
+
})) };
|
|
412
|
+
}, {
|
|
413
|
+
description: "List changelog follower cursor registrations",
|
|
414
|
+
tableLevel: true
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
async readCursor(consumerId) {
|
|
418
|
+
return (await this.afs.exec(this.tableAction("idx_index_cursor", "cursor_read"), { consumer_id: consumerId })).data.cursor;
|
|
419
|
+
}
|
|
420
|
+
async writeCursor(consumerId, lastSeq, watermarks) {
|
|
421
|
+
await this.afs.exec(this.tableAction("idx_index_cursor", "cursor_write"), {
|
|
422
|
+
consumer_id: consumerId,
|
|
423
|
+
last_seq: lastSeq,
|
|
424
|
+
watermarks: watermarks ?? null
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
async markCursorServed(consumerId) {
|
|
428
|
+
await this.afs.exec(this.tableAction("idx_index_cursor", "cursor_mark_served"), { consumer_id: consumerId });
|
|
429
|
+
}
|
|
430
|
+
async initCursor(consumerId, registration) {
|
|
431
|
+
await this.afs.exec(this.tableAction("idx_index_cursor", "cursor_init"), {
|
|
432
|
+
consumer_id: consumerId,
|
|
433
|
+
instance_did: registration?.instanceDid ?? null,
|
|
434
|
+
domains: registration?.domainsJson ?? null
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
async listCursorScopes(consumerId) {
|
|
438
|
+
return (await this.afs.exec(this.tableAction("idx_index_cursor", "cursor_scope_list"), { family_consumer_id: consumerId })).data.registrations;
|
|
439
|
+
}
|
|
440
|
+
async close() {
|
|
441
|
+
this.afs = void 0;
|
|
442
|
+
this.sqliteProvider = void 0;
|
|
443
|
+
}
|
|
444
|
+
async writeEntry(entry) {
|
|
445
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "upsert_entry"), this.entryToRow(entry));
|
|
446
|
+
}
|
|
447
|
+
async writeIndexedDocument(document) {
|
|
448
|
+
const path = document.entry.entryPath;
|
|
449
|
+
const anchors = document.anchors ?? [];
|
|
450
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "write_indexed_document"), {
|
|
451
|
+
entry: this.entryToRow(document.entry),
|
|
452
|
+
path,
|
|
453
|
+
dataRows: anchors.map((anchor) => ({
|
|
454
|
+
entry_path: path,
|
|
455
|
+
anchor_type: anchor.type,
|
|
456
|
+
confidence: anchor.confidence,
|
|
457
|
+
raw_anchor: JSON.stringify(anchor)
|
|
458
|
+
})),
|
|
459
|
+
keyRows: anchors.flatMap((anchor) => this.keyRegistry.generateKeys(anchor).map((key) => ({
|
|
460
|
+
index_key: key,
|
|
461
|
+
entry_path: path,
|
|
462
|
+
anchor_type: anchor.type,
|
|
463
|
+
confidence: anchor.confidence
|
|
464
|
+
}))),
|
|
465
|
+
summary: document.summary ?? null
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
entryToRow(entry) {
|
|
469
|
+
const data = {
|
|
470
|
+
scope: this.scope,
|
|
471
|
+
entry_path: entry.entryPath,
|
|
472
|
+
domain: entry.domain,
|
|
473
|
+
state: entry.state,
|
|
474
|
+
indexed_at: entry.indexedAt,
|
|
475
|
+
updated_at: entry.updatedAt,
|
|
476
|
+
anchor_count: entry.anchorCount
|
|
477
|
+
};
|
|
478
|
+
if (entry.sourcePath != null) data.source_path = entry.sourcePath;
|
|
479
|
+
if (entry.sourceHash != null) data.source_hash = entry.sourceHash;
|
|
480
|
+
if (entry.extractorId != null) data.extractor_id = entry.extractorId;
|
|
481
|
+
if (entry.meta != null) data.meta = JSON.stringify(entry.meta);
|
|
482
|
+
return data;
|
|
483
|
+
}
|
|
484
|
+
async getEntry(path) {
|
|
485
|
+
const data = (await this.afs.exec(this.tableAction("ds_indexed_entry", "query"), {
|
|
486
|
+
where: {
|
|
487
|
+
scope: this.scope,
|
|
488
|
+
entry_path: path
|
|
489
|
+
},
|
|
490
|
+
limit: 1
|
|
491
|
+
})).data;
|
|
492
|
+
if (!data.rows || data.rows.length === 0) return null;
|
|
493
|
+
return rowToEntry(data.rows[0]);
|
|
494
|
+
}
|
|
495
|
+
async removeEntry(path) {
|
|
496
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "delete_where"), { where: {
|
|
497
|
+
scope: this.scope,
|
|
498
|
+
entry_path: path
|
|
499
|
+
} });
|
|
500
|
+
}
|
|
501
|
+
async removeIndexedDocument(path, opts) {
|
|
502
|
+
await this.purgeEntries([path], opts);
|
|
503
|
+
}
|
|
504
|
+
async listEntries(filter) {
|
|
505
|
+
const where = { scope: this.scope };
|
|
506
|
+
if (filter.domain) where.domain = filter.domain;
|
|
507
|
+
if (filter.state) where.state = filter.state;
|
|
508
|
+
if (filter.extractorId) where.extractor_id = filter.extractorId;
|
|
509
|
+
if (filter.scope) where.entry_path = { $like: `${filter.scope}%` };
|
|
510
|
+
if (filter.boundDomains && filter.boundDomains.length > 0) where.domain = { $in: filter.boundDomains };
|
|
511
|
+
return ((await this.afs.exec(this.tableAction("ds_indexed_entry", "query"), { where })).data.rows ?? []).map(rowToEntry);
|
|
512
|
+
}
|
|
513
|
+
async markStale(filter) {
|
|
514
|
+
const entryWhere = {
|
|
515
|
+
scope: this.scope,
|
|
516
|
+
state: "active"
|
|
517
|
+
};
|
|
518
|
+
if (filter.extractorId) entryWhere.extractor_id = filter.extractorId;
|
|
519
|
+
if (filter.domain) entryWhere.domain = filter.domain;
|
|
520
|
+
if (filter.scope) entryWhere.entry_path = { $like: `${filter.scope}%` };
|
|
521
|
+
if (filter.boundDomains && filter.boundDomains.length > 0) entryWhere.domain = { $in: filter.boundDomains };
|
|
522
|
+
if (filter.anchorType) {
|
|
523
|
+
const anchorData = (await this.afs.exec(this.tableAction("idx_anchor_data", "query"), { where: {
|
|
524
|
+
scope: this.scope,
|
|
525
|
+
anchor_type: filter.anchorType
|
|
526
|
+
} })).data;
|
|
527
|
+
const paths = [...new Set(anchorData.rows.map((r) => r.entry_path))];
|
|
528
|
+
if (paths.length === 0) return 0;
|
|
529
|
+
if (typeof entryWhere.entry_path === "object") entryWhere.entry_path = {
|
|
530
|
+
...entryWhere.entry_path,
|
|
531
|
+
$in: paths
|
|
532
|
+
};
|
|
533
|
+
else entryWhere.entry_path = { $in: paths };
|
|
534
|
+
}
|
|
535
|
+
return (await this.afs.exec(this.tableAction("ds_indexed_entry", "update_where"), {
|
|
536
|
+
where: entryWhere,
|
|
537
|
+
data: { state: "stale" }
|
|
538
|
+
})).data.affectedRows;
|
|
539
|
+
}
|
|
540
|
+
async countEntries(filter) {
|
|
541
|
+
return (await this.afs.exec(this.tableAction("ds_indexed_entry", "count_entries"), {
|
|
542
|
+
tenant_scope: this.scope,
|
|
543
|
+
domain: filter.domain,
|
|
544
|
+
state: filter.state,
|
|
545
|
+
extractorId: filter.extractorId,
|
|
546
|
+
scope: filter.scope,
|
|
547
|
+
boundDomains: filter.boundDomains
|
|
548
|
+
})).data.count;
|
|
549
|
+
}
|
|
550
|
+
async writeAnchors(path, anchors) {
|
|
551
|
+
const dataRows = anchors.map((anchor) => ({
|
|
552
|
+
entry_path: path,
|
|
553
|
+
anchor_type: anchor.type,
|
|
554
|
+
confidence: anchor.confidence,
|
|
555
|
+
raw_anchor: JSON.stringify(anchor)
|
|
556
|
+
}));
|
|
557
|
+
const keyRows = anchors.flatMap((anchor) => this.keyRegistry.generateKeys(anchor).map((key) => ({
|
|
558
|
+
index_key: key,
|
|
559
|
+
entry_path: path,
|
|
560
|
+
anchor_type: anchor.type,
|
|
561
|
+
confidence: anchor.confidence
|
|
562
|
+
})));
|
|
563
|
+
await this.afs.exec(this.tableAction("idx_anchor_data", "replace_anchors"), {
|
|
564
|
+
scope: this.scope,
|
|
565
|
+
path,
|
|
566
|
+
dataRows,
|
|
567
|
+
keyRows
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
async queryAnchors(query) {
|
|
571
|
+
if (query.keys.length === 0) return [];
|
|
572
|
+
const params = {
|
|
573
|
+
key_groups: query.keys.map((kg) => {
|
|
574
|
+
const values = /* @__PURE__ */ new Set();
|
|
575
|
+
for (const raw of kg.values) {
|
|
576
|
+
const synthetic = {
|
|
577
|
+
type: kg.type ?? "exact",
|
|
578
|
+
values: [raw],
|
|
579
|
+
confidence: 1
|
|
580
|
+
};
|
|
581
|
+
const generated = this.keyRegistry.generateKeys(synthetic);
|
|
582
|
+
for (const k of generated) values.add(k);
|
|
583
|
+
values.add(raw);
|
|
584
|
+
}
|
|
585
|
+
return { values: [...values] };
|
|
586
|
+
}),
|
|
587
|
+
tenant_scope: this.scope,
|
|
588
|
+
scope: query.scope,
|
|
589
|
+
min_confidence: query.minConfidence,
|
|
590
|
+
limit: query.limit
|
|
591
|
+
};
|
|
592
|
+
if (query.pathPrefix) params.path_prefix = query.pathPrefix;
|
|
593
|
+
if (query.metaFilter && Object.keys(query.metaFilter).length > 0) params.meta_filter = query.metaFilter;
|
|
594
|
+
if (query.domains && query.domains.length > 0) params.domains = query.domains;
|
|
595
|
+
return ((await this.afs.exec(this.tableAction("idx_anchor_key", "anchor_intersection"), params)).data.rows ?? []).map((r) => ({
|
|
596
|
+
entryPath: r.entry_path,
|
|
597
|
+
score: r.score
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
async removeAnchors(path) {
|
|
601
|
+
await this.afs.exec(this.tableAction("idx_anchor_data", "delete_where"), { where: {
|
|
602
|
+
scope: this.scope,
|
|
603
|
+
entry_path: path
|
|
604
|
+
} });
|
|
605
|
+
await this.afs.exec(this.tableAction("idx_anchor_key", "delete_where"), { where: {
|
|
606
|
+
scope: this.scope,
|
|
607
|
+
entry_path: path
|
|
608
|
+
} });
|
|
609
|
+
}
|
|
610
|
+
async getAnchorData(path) {
|
|
611
|
+
return ((await this.afs.exec(this.tableAction("idx_anchor_data", "query"), { where: {
|
|
612
|
+
scope: this.scope,
|
|
613
|
+
entry_path: path
|
|
614
|
+
} })).data.rows ?? []).map((r) => ({
|
|
615
|
+
entryPath: r.entry_path,
|
|
616
|
+
anchorType: r.anchor_type,
|
|
617
|
+
confidence: r.confidence,
|
|
618
|
+
rawAnchor: r.raw_anchor
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
async writeSummary(path, summary) {
|
|
622
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "fts_write"), {
|
|
623
|
+
scope: this.scope,
|
|
624
|
+
path,
|
|
625
|
+
summary
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
async getSummary(path) {
|
|
629
|
+
return (await this.afs.exec(this.tableAction("ds_indexed_entry", "fts_get"), {
|
|
630
|
+
scope: this.scope,
|
|
631
|
+
path
|
|
632
|
+
})).data.summary ?? null;
|
|
633
|
+
}
|
|
634
|
+
async queryFTS(text, limit, metaFilter, opts) {
|
|
635
|
+
try {
|
|
636
|
+
const sanitized = sanitizeFTS5(text);
|
|
637
|
+
if (!sanitized) return [];
|
|
638
|
+
const params = {
|
|
639
|
+
text: sanitized,
|
|
640
|
+
limit,
|
|
641
|
+
tenant_scope: this.scope
|
|
642
|
+
};
|
|
643
|
+
if (metaFilter && Object.keys(metaFilter).length > 0) params.meta_filter = metaFilter;
|
|
644
|
+
if (opts?.domain) params.domain = opts.domain;
|
|
645
|
+
if (opts?.domains && opts.domains.length > 0) params.domains = opts.domains;
|
|
646
|
+
if (opts?.scope) params.scope = opts.scope;
|
|
647
|
+
if (opts?.pathPrefix) params.path_prefix = opts.pathPrefix;
|
|
648
|
+
return ((await this.afs.exec(this.tableAction("ds_indexed_entry", "fts_query"), params)).data.rows ?? []).map((r) => ({
|
|
649
|
+
entryPath: r.entry_path,
|
|
650
|
+
rank: -r.rank
|
|
651
|
+
}));
|
|
652
|
+
} catch {
|
|
653
|
+
return [];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async removeSummary(path) {
|
|
657
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "fts_delete"), {
|
|
658
|
+
scope: this.scope,
|
|
659
|
+
path
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
async writeEmbedding(path, embedding) {
|
|
663
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "embedding_write"), {
|
|
664
|
+
scope: this.scope,
|
|
665
|
+
path,
|
|
666
|
+
embedding
|
|
667
|
+
});
|
|
668
|
+
if (this.embeddingDimension === 0 && embedding.length > 0) this.embeddingDimension = embedding.length;
|
|
669
|
+
}
|
|
670
|
+
async queryVector(embedding, limit, opts) {
|
|
671
|
+
const rows = (await this.afs.exec(this.tableAction("ds_indexed_entry", "embedding_query"), {
|
|
672
|
+
tenant_scope: this.scope,
|
|
673
|
+
...opts?.domain ? { domain: opts.domain } : {},
|
|
674
|
+
...opts?.domains && opts.domains.length > 0 ? { domains: opts.domains } : {},
|
|
675
|
+
...opts?.scope ? { scope: opts.scope } : {},
|
|
676
|
+
...opts?.pathPrefix ? { path_prefix: opts.pathPrefix } : {}
|
|
677
|
+
})).data.rows ?? [];
|
|
678
|
+
const queryVec = Float32Array.from(embedding);
|
|
679
|
+
const results = rows.map((r) => ({
|
|
680
|
+
entryPath: r.entry_path,
|
|
681
|
+
similarity: cosineSimilarityF32(queryVec, decodeStoredEmbedding(r))
|
|
682
|
+
}));
|
|
683
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
684
|
+
if (limit != null) return results.slice(0, limit);
|
|
685
|
+
return results;
|
|
686
|
+
}
|
|
687
|
+
async removeEmbedding(path) {
|
|
688
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "embedding_delete"), {
|
|
689
|
+
scope: this.scope,
|
|
690
|
+
path
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
embeddingCapability() {
|
|
694
|
+
return {
|
|
695
|
+
supported: true,
|
|
696
|
+
dimension: this.embeddingDimension
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
async writeEntriesBatch(entries) {
|
|
700
|
+
if (entries.length === 0) return;
|
|
701
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "batch_upsert_entries"), { entries: entries.map((e) => this.entryToRow(e)) });
|
|
702
|
+
}
|
|
703
|
+
async writeAnchorsBatch(items) {
|
|
704
|
+
if (items.length === 0) return;
|
|
705
|
+
const batchItems = items.map((item) => ({
|
|
706
|
+
path: item.path,
|
|
707
|
+
dataRows: item.anchors.map((anchor) => ({
|
|
708
|
+
entry_path: item.path,
|
|
709
|
+
anchor_type: anchor.type,
|
|
710
|
+
confidence: anchor.confidence,
|
|
711
|
+
raw_anchor: JSON.stringify(anchor)
|
|
712
|
+
})),
|
|
713
|
+
keyRows: item.anchors.flatMap((anchor) => this.keyRegistry.generateKeys(anchor).map((key) => ({
|
|
714
|
+
index_key: key,
|
|
715
|
+
entry_path: item.path,
|
|
716
|
+
anchor_type: anchor.type,
|
|
717
|
+
confidence: anchor.confidence
|
|
718
|
+
})))
|
|
719
|
+
}));
|
|
720
|
+
await this.afs.exec(this.tableAction("idx_anchor_data", "batch_replace_anchors"), {
|
|
721
|
+
scope: this.scope,
|
|
722
|
+
items: batchItems
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
async writeSummariesBatch(items) {
|
|
726
|
+
if (items.length === 0) return;
|
|
727
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "batch_write_fts"), {
|
|
728
|
+
scope: this.scope,
|
|
729
|
+
items
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
async purgeEntries(paths, opts) {
|
|
733
|
+
if (paths.length === 0) return 0;
|
|
734
|
+
await this.afs.exec(this.tableAction("ds_indexed_entry", "batch_purge"), {
|
|
735
|
+
scope: this.scope,
|
|
736
|
+
paths,
|
|
737
|
+
skipVector: opts?.skipVector === true
|
|
738
|
+
});
|
|
739
|
+
return paths.length;
|
|
740
|
+
}
|
|
741
|
+
async queryCombined(keys, text, opts) {
|
|
742
|
+
const anchorResults = keys.length > 0 ? await this.queryAnchors({
|
|
743
|
+
keys,
|
|
744
|
+
scope: opts?.scope,
|
|
745
|
+
minConfidence: opts?.minConfidence,
|
|
746
|
+
limit: opts?.limit
|
|
747
|
+
}) : [];
|
|
748
|
+
const anchorPaths = new Set(anchorResults.map((r) => r.entryPath));
|
|
749
|
+
const ftsResults = text ? await this.queryFTS(text, opts?.limit) : [];
|
|
750
|
+
const results = anchorResults.map((r) => {
|
|
751
|
+
const fts = ftsResults.find((f) => f.entryPath === r.entryPath);
|
|
752
|
+
return {
|
|
753
|
+
entryPath: r.entryPath,
|
|
754
|
+
anchorScore: r.score,
|
|
755
|
+
ftsRank: fts?.rank,
|
|
756
|
+
score: r.score + (fts?.rank ?? 0)
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
for (const f of ftsResults) if (!anchorPaths.has(f.entryPath)) results.push({
|
|
760
|
+
entryPath: f.entryPath,
|
|
761
|
+
ftsRank: f.rank,
|
|
762
|
+
score: f.rank
|
|
763
|
+
});
|
|
764
|
+
results.sort((a, b) => b.score - a.score);
|
|
765
|
+
if (opts?.limit != null) return results.slice(0, opts.limit);
|
|
766
|
+
return results;
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
/** Get-or-create the stable idx_fts rowid for (scope, path) via the mapping table. */
|
|
770
|
+
async function ensureFtsRowid(db, scope, path) {
|
|
771
|
+
await db.run(sql`INSERT INTO idx_fts_rowid (scope, entry_path) VALUES (${scope}, ${path})
|
|
772
|
+
ON CONFLICT(scope, entry_path) DO NOTHING`);
|
|
773
|
+
return (await db.all(sql`SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ${scope} AND entry_path = ${path} LIMIT 1`))[0].fts_rowid;
|
|
774
|
+
}
|
|
775
|
+
/** Atomically insert-or-replace an entry's FTS row at its stable mapped rowid. */
|
|
776
|
+
async function writeFtsRow(db, scope, path, summary) {
|
|
777
|
+
const rowid = await ensureFtsRowid(db, scope, path);
|
|
778
|
+
await db.run(sql`INSERT OR REPLACE INTO idx_fts (rowid, scope, entry_path, summary) VALUES (${rowid}, ${scope}, ${path}, ${summary})`);
|
|
779
|
+
}
|
|
780
|
+
/** Delete an entry's idx_fts row (by mapped rowid, O(1)) and its mapping row. */
|
|
781
|
+
async function deleteFtsRow(db, scope, path) {
|
|
782
|
+
await db.run(sql`DELETE FROM idx_fts WHERE rowid = (SELECT fts_rowid FROM idx_fts_rowid WHERE scope = ${scope} AND entry_path = ${path})`);
|
|
783
|
+
await db.run(sql`DELETE FROM idx_fts_rowid WHERE scope = ${scope} AND entry_path = ${path}`);
|
|
784
|
+
}
|
|
785
|
+
function sqlEscape(value) {
|
|
786
|
+
return value.replace(/'/g, "''");
|
|
787
|
+
}
|
|
788
|
+
function rowToEntry(row) {
|
|
789
|
+
return {
|
|
790
|
+
entryPath: row.entry_path,
|
|
791
|
+
sourcePath: row.source_path ?? void 0,
|
|
792
|
+
sourceHash: row.source_hash ?? void 0,
|
|
793
|
+
domain: row.domain,
|
|
794
|
+
state: row.state,
|
|
795
|
+
extractorId: row.extractor_id ?? void 0,
|
|
796
|
+
indexedAt: row.indexed_at,
|
|
797
|
+
updatedAt: row.updated_at,
|
|
798
|
+
anchorCount: row.anchor_count,
|
|
799
|
+
meta: row.meta ? JSON.parse(row.meta) : void 0
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function cursorScopeFromConsumerId(consumerId, familyConsumerId, instanceDid) {
|
|
803
|
+
const prefix = `${familyConsumerId}:`;
|
|
804
|
+
if (!consumerId.startsWith(prefix)) return "";
|
|
805
|
+
const tail = consumerId.slice(prefix.length);
|
|
806
|
+
if (instanceDid) {
|
|
807
|
+
const suffix = `:${instanceDid}`;
|
|
808
|
+
if (tail.endsWith(suffix)) return tail.slice(0, -suffix.length);
|
|
809
|
+
}
|
|
810
|
+
const firstSeparator = tail.indexOf(":");
|
|
811
|
+
return firstSeparator === -1 ? tail : tail.slice(0, firstSeparator);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Sanitize text for FTS5 MATCH queries.
|
|
815
|
+
* Removes special FTS5 characters to prevent query syntax errors.
|
|
816
|
+
*
|
|
817
|
+
* NOTE: does NOT strip AND/OR/NOT. The query text reaching this backend is
|
|
818
|
+
* always pre-built by `transformFtsQuery` (the single owner of FTS query
|
|
819
|
+
* construction, #933), which strips *bare* boolean keywords from the raw
|
|
820
|
+
* input and then re-emits `OR` only as an intentional infix operator for
|
|
821
|
+
* memory's or-recall mode (#1002). Stripping it again here silently collapsed
|
|
822
|
+
* or-mode back to AND — the #1002 follow-up bug that dropped memory recall for
|
|
823
|
+
* plural/partial-word queries (e.g. "architecture OR use" → "architecture use",
|
|
824
|
+
* which no longer matched "…uses architecture…").
|
|
825
|
+
*/
|
|
826
|
+
function sanitizeFTS5(text) {
|
|
827
|
+
return text.replace(/["*^()\-+{}:]/g, " ").replace(/\s+/g, " ").trim();
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Float32-typed cosine similarity. Used by `queryVector` once we've
|
|
831
|
+
* decoded the stored blob into a Float32Array view — typed-array indexed
|
|
832
|
+
* access is ~2× the speed of `number[]` access in V8 because the JIT
|
|
833
|
+
* doesn't need to deopt-check for hole/undefined slots.
|
|
834
|
+
*/
|
|
835
|
+
function cosineSimilarityF32(a, b) {
|
|
836
|
+
const len = Math.min(a.length, b.length);
|
|
837
|
+
let dot = 0;
|
|
838
|
+
let magA = 0;
|
|
839
|
+
let magB = 0;
|
|
840
|
+
for (let i = 0; i < len; i++) {
|
|
841
|
+
const ai = a[i];
|
|
842
|
+
const bi = b[i];
|
|
843
|
+
dot += ai * bi;
|
|
844
|
+
magA += ai * ai;
|
|
845
|
+
magB += bi * bi;
|
|
846
|
+
}
|
|
847
|
+
const denom = Math.sqrt(magA) * Math.sqrt(magB);
|
|
848
|
+
return denom === 0 ? 0 : dot / denom;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Encode an embedding (number[]) to little-endian Float32 bytes for
|
|
852
|
+
* BLOB storage. Returns a Node `Buffer` — that's the only typed-array
|
|
853
|
+
* shape that drizzle's libsql adapter binds through correctly when
|
|
854
|
+
* the underlying client is file-backed. Both `Uint8Array` and raw
|
|
855
|
+
* `ArrayBuffer` get serialized via `toString()` somewhere in the
|
|
856
|
+
* binding pipeline (yielding "0,32,71,..." or "[object ArrayBuffer]"
|
|
857
|
+
* respectively), which corrupts the BLOB. The in-memory libsql client
|
|
858
|
+
* doesn't exhibit this, which is why unit tests passed — file-backed
|
|
859
|
+
* is the production path. `Buffer` is a `Uint8Array` subclass with the
|
|
860
|
+
* correct `toString()` and is honored as a BLOB by both clients.
|
|
861
|
+
*
|
|
862
|
+
* `Buffer` is Node-only. CF Workers / browser builds of this provider
|
|
863
|
+
* would need a different code path; we don't ship the index there
|
|
864
|
+
* today.
|
|
865
|
+
*/
|
|
866
|
+
function embeddingToBlob(embedding) {
|
|
867
|
+
const f32 = Float32Array.from(embedding);
|
|
868
|
+
return Buffer.from(f32.buffer, f32.byteOffset, f32.byteLength);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Decode a stored embedding row into Float32Array. Prefers the BLOB
|
|
872
|
+
* column; falls back to JSON-parsing the legacy TEXT column for rows
|
|
873
|
+
* indexed before the schema migration. Different SQLite drivers hand
|
|
874
|
+
* back different containers — libsql returns a raw `ArrayBuffer`,
|
|
875
|
+
* better-sqlite3 returns a `Buffer`, others a `Uint8Array`. Normalize.
|
|
876
|
+
*/
|
|
877
|
+
function decodeStoredEmbedding(row) {
|
|
878
|
+
const blob = row.embedding_blob;
|
|
879
|
+
if (blob != null) {
|
|
880
|
+
if (blob instanceof ArrayBuffer) return new Float32Array(blob.slice(0));
|
|
881
|
+
const view = new Float32Array(blob.buffer, blob.byteOffset, blob.byteLength / Float32Array.BYTES_PER_ELEMENT);
|
|
882
|
+
return new Float32Array(view);
|
|
883
|
+
}
|
|
884
|
+
if (row.embedding != null && row.embedding !== "") return Float32Array.from(JSON.parse(row.embedding));
|
|
885
|
+
return new Float32Array(0);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
//#endregion
|
|
889
|
+
export { SQLiteIndexBackend };
|
|
890
|
+
//# sourceMappingURL=sqlite-backend.mjs.map
|