@undefineds.co/xpod 0.3.25 → 0.3.27

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.
Files changed (89) hide show
  1. package/config/cloud.json +6 -5
  2. package/config/main.json +0 -3
  3. package/config/xpod.base.json +0 -32
  4. package/dist/api/container/index.js +3 -0
  5. package/dist/api/container/index.js.map +1 -1
  6. package/dist/api/container/routes.js +2 -0
  7. package/dist/api/container/routes.js.map +1 -1
  8. package/dist/api/container/types.d.ts +5 -0
  9. package/dist/api/container/types.js.map +1 -1
  10. package/dist/authorization/AuthMode.d.ts +8 -0
  11. package/dist/authorization/AuthMode.js +51 -0
  12. package/dist/authorization/AuthMode.js.map +1 -0
  13. package/dist/authorization/PodAuthorizationResources.d.ts +18 -0
  14. package/dist/authorization/PodAuthorizationResources.js +108 -0
  15. package/dist/authorization/PodAuthorizationResources.js.map +1 -0
  16. package/dist/cli/commands/start.js +11 -2
  17. package/dist/cli/commands/start.js.map +1 -1
  18. package/dist/components/components.jsonld +3 -2
  19. package/dist/components/context.jsonld +115 -14
  20. package/dist/index.d.ts +5 -3
  21. package/dist/index.js +6 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/main.js +11 -2
  24. package/dist/main.js.map +1 -1
  25. package/dist/provision/LocalPodProvisioningService.d.ts +6 -2
  26. package/dist/provision/LocalPodProvisioningService.js +36 -33
  27. package/dist/provision/LocalPodProvisioningService.js.map +1 -1
  28. package/dist/provision/LocalPodProvisioningService.jsonld +65 -8
  29. package/dist/runtime/XpodRuntime.js +0 -1
  30. package/dist/runtime/XpodRuntime.js.map +1 -1
  31. package/dist/runtime/bootstrap.d.ts +4 -2
  32. package/dist/runtime/bootstrap.js +43 -11
  33. package/dist/runtime/bootstrap.js.map +1 -1
  34. package/dist/runtime/css-process.d.ts +6 -1
  35. package/dist/runtime/css-process.js +18 -6
  36. package/dist/runtime/css-process.js.map +1 -1
  37. package/dist/runtime/lifecycle.d.ts +2 -3
  38. package/dist/runtime/lifecycle.js +2 -2
  39. package/dist/runtime/lifecycle.js.map +1 -1
  40. package/dist/runtime/runtime-types.d.ts +2 -1
  41. package/dist/runtime/runtime-types.js.map +1 -1
  42. package/dist/storage/accessors/SolidRdfDataAccessor.d.ts +2 -3
  43. package/dist/storage/accessors/SolidRdfDataAccessor.js +48 -42
  44. package/dist/storage/accessors/SolidRdfDataAccessor.js.map +1 -1
  45. package/dist/storage/accessors/SolidRdfDataAccessor.jsonld +1 -1
  46. package/dist/storage/keyvalue/BaseKeyValueStorage.d.ts +33 -0
  47. package/dist/storage/keyvalue/BaseKeyValueStorage.js +106 -0
  48. package/dist/storage/keyvalue/BaseKeyValueStorage.js.map +1 -0
  49. package/dist/storage/keyvalue/BaseKeyValueStorage.jsonld +177 -0
  50. package/dist/storage/keyvalue/PostgresKeyValueStorage.d.ts +9 -18
  51. package/dist/storage/keyvalue/PostgresKeyValueStorage.js +24 -96
  52. package/dist/storage/keyvalue/PostgresKeyValueStorage.js.map +1 -1
  53. package/dist/storage/keyvalue/PostgresKeyValueStorage.jsonld +15 -58
  54. package/dist/storage/keyvalue/SqliteKeyValueStorage.d.ts +9 -15
  55. package/dist/storage/keyvalue/SqliteKeyValueStorage.js +36 -104
  56. package/dist/storage/keyvalue/SqliteKeyValueStorage.js.map +1 -1
  57. package/dist/storage/keyvalue/SqliteKeyValueStorage.jsonld +21 -52
  58. package/dist/storage/quint/BaseQuintStore.d.ts +4 -1
  59. package/dist/storage/quint/BaseQuintStore.js +41 -52
  60. package/dist/storage/quint/BaseQuintStore.js.map +1 -1
  61. package/dist/storage/quint/PgQuintStore.d.ts +4 -3
  62. package/dist/storage/quint/PgQuintStore.js.map +1 -1
  63. package/dist/storage/quint/SqliteQuintStore.d.ts +43 -54
  64. package/dist/storage/quint/SqliteQuintStore.js +197 -520
  65. package/dist/storage/quint/SqliteQuintStore.js.map +1 -1
  66. package/dist/storage/quint/SqliteQuintStore.jsonld +38 -86
  67. package/dist/storage/rdf/PostgresRdfEngine.d.ts +118 -0
  68. package/dist/storage/rdf/PostgresRdfEngine.js +2609 -0
  69. package/dist/storage/rdf/PostgresRdfEngine.js.map +1 -0
  70. package/dist/storage/rdf/PostgresRdfEngine.jsonld +657 -0
  71. package/dist/storage/rdf/SolidRdfEngine.d.ts +2 -2
  72. package/dist/storage/rdf/SolidRdfEngine.js.map +1 -1
  73. package/dist/storage/rdf/SolidRdfEngine.jsonld +3 -0
  74. package/dist/storage/rdf/SolidRdfSparqlEngine.d.ts +3 -3
  75. package/dist/storage/rdf/SolidRdfSparqlEngine.js +20 -20
  76. package/dist/storage/rdf/SolidRdfSparqlEngine.js.map +1 -1
  77. package/dist/storage/rdf/SolidRdfSparqlEngine.jsonld +1 -1
  78. package/dist/storage/rdf/index.d.ts +2 -1
  79. package/dist/storage/rdf/index.js +3 -1
  80. package/dist/storage/rdf/index.js.map +1 -1
  81. package/dist/storage/rdf/types.d.ts +19 -0
  82. package/dist/storage/rdf/types.js.map +1 -1
  83. package/dist/storage/rdf/types.jsonld +115 -0
  84. package/package.json +2 -2
  85. package/config/runtime-open.json +0 -22
  86. package/dist/authorization/AuthModeSelector.d.ts +0 -10
  87. package/dist/authorization/AuthModeSelector.js +0 -27
  88. package/dist/authorization/AuthModeSelector.js.map +0 -1
  89. package/dist/authorization/AuthModeSelector.jsonld +0 -81
@@ -7,363 +7,206 @@ exports.SqliteQuintStore = void 0;
7
7
  const node_crypto_1 = require("node:crypto");
8
8
  const node_fs_1 = require("node:fs");
9
9
  const node_path_1 = require("node:path");
10
- const drizzle_orm_1 = require("drizzle-orm");
11
- const asynciterator_1 = require("asynciterator");
12
- const n3_1 = require("n3");
13
- const schema_1 = require("./schema");
10
+ const BaseQuintStore_1 = require("./BaseQuintStore");
14
11
  const serialization_1 = require("./serialization");
15
12
  const SqliteRuntime_1 = require("../SqliteRuntime");
16
- const types_1 = require("./types");
17
13
  const value_types_1 = require("./value-types");
18
14
  const SQLITE_UNBOUNDED_LIMIT = Number.MAX_SAFE_INTEGER;
19
15
  function digestObject(value) {
20
16
  return (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
21
17
  }
22
- class SqliteQuintStore {
18
+ class SqliteExecutor {
19
+ constructor(db) {
20
+ this.db = db;
21
+ }
22
+ async query(sqlText, params) {
23
+ return this.db.prepare(sqlText).all(...(params ?? []));
24
+ }
25
+ async execute(sqlText, params) {
26
+ return this.db.prepare(sqlText).run(...(params ?? [])).changes;
27
+ }
28
+ async executeInTransaction(statements) {
29
+ this.db.transaction(() => {
30
+ for (const statement of statements) {
31
+ this.db.prepare(statement.sql).run(...(statement.params ?? []));
32
+ }
33
+ })();
34
+ }
35
+ async exec(sqlText) {
36
+ this.db.exec(sqlText);
37
+ }
38
+ }
39
+ class SqliteQuintStore extends BaseQuintStore_1.BaseQuintStore {
23
40
  constructor(options) {
41
+ const path = options.path.startsWith('sqlite:') ? options.path.slice(7) : options.path;
42
+ super(options);
24
43
  this.sqlite = null;
25
- this.db = null;
26
44
  this.sqliteRuntime = (0, SqliteRuntime_1.getSqliteRuntime)();
27
- // Handle sqlite: prefix
28
- let path = options.path;
29
- if (path.startsWith('sqlite:')) {
30
- path = path.slice(7);
31
- }
32
- this.options = { ...options, path };
33
- }
34
- // ============================================
35
- // Lifecycle
36
- // ============================================
37
- async open() {
38
- // Idempotent: if already open, do nothing
39
- if (this.sqlite) {
40
- return;
41
- }
42
- const dbPath = this.options.path;
43
- // Ensure directory exists (unless it's in-memory)
45
+ this.path = path;
46
+ }
47
+ async createExecutor() {
48
+ const dbPath = this.path;
44
49
  if (dbPath !== ':memory:') {
45
50
  const dir = (0, node_path_1.dirname)(dbPath);
46
- if (!(0, node_fs_1.existsSync)(dir)) {
51
+ if (dir && !(0, node_fs_1.existsSync)(dir)) {
47
52
  (0, node_fs_1.mkdirSync)(dir, { recursive: true });
48
53
  }
49
54
  }
50
55
  this.sqlite = this.sqliteRuntime.openDatabase(dbPath);
51
- this.db = this.sqliteRuntime.createDrizzleDatabase(this.sqlite);
52
- // Create table and indexes
53
- this.sqlite.exec(`
54
- CREATE TABLE IF NOT EXISTS quints (
55
- object_kind TEXT,
56
- object_key TEXT,
57
- object_text TEXT,
58
- object_digest TEXT,
59
- graph TEXT NOT NULL,
60
- subject TEXT NOT NULL,
61
- predicate TEXT NOT NULL,
62
- object TEXT NOT NULL,
63
- vector TEXT
64
- );
65
- `);
66
- this.ensureTypedObjectSchema();
67
- this.sqlite.exec(`
68
- CREATE INDEX IF NOT EXISTS idx_quints_graph ON quints (graph);
69
- CREATE INDEX IF NOT EXISTS idx_quints_subject ON quints (subject);
70
- CREATE INDEX IF NOT EXISTS idx_quints_predicate ON quints (predicate);
71
- CREATE INDEX IF NOT EXISTS idx_quints_object_key ON quints (object_kind, object_key);
72
- CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_key ON quints (predicate, object_kind, object_key);
73
- CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_digest ON quints (predicate, object_kind, object_digest);
74
- CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_key
75
- ON quints (graph, subject, predicate, object_kind, object_key)
76
- WHERE object_key IS NOT NULL;
77
- CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_digest
78
- ON quints (graph, subject, predicate, object_kind, object_digest)
79
- WHERE object_digest IS NOT NULL;
80
- CREATE INDEX IF NOT EXISTS idx_quints_gsp ON quints (graph, subject, predicate);
81
- CREATE INDEX IF NOT EXISTS idx_quints_sp ON quints (subject, predicate);
82
- CREATE INDEX IF NOT EXISTS idx_quints_gp ON quints (graph, predicate);
83
- `);
56
+ return new SqliteExecutor(this.sqlite);
84
57
  }
85
- async close() {
58
+ async closeExecutor() {
86
59
  if (this.sqlite) {
87
60
  this.sqlite.close();
88
61
  this.sqlite = null;
89
- this.db = null;
90
62
  }
91
63
  }
92
- // ============================================
93
- // Query Operations
94
- // ============================================
95
- async get(pattern, options) {
96
- this.ensureOpen();
97
- const { sql: query, params } = this.buildSelectQuery(pattern, options);
98
- const rows = this.sqlite.prepare(query).all(...params);
99
- return rows.map((row) => this.rowToQuint(row));
100
- }
101
- match(subject, predicate, object, graph) {
102
- const pattern = {};
103
- if (subject)
104
- pattern.subject = subject;
105
- if (predicate)
106
- pattern.predicate = predicate;
107
- if (object)
108
- pattern.object = object;
109
- if (graph)
110
- pattern.graph = graph;
111
- return (0, asynciterator_1.wrap)(this.get(pattern));
112
- }
113
- async getByGraphPrefix(prefix, options) {
114
- return this.get({ graph: { $startsWith: prefix } }, options);
115
- }
116
- async count(pattern) {
117
- this.ensureOpen();
118
- const { whereClause, params } = this.buildWhereClause(pattern);
119
- const result = this.sqlite.prepare(`SELECT COUNT(*) AS count FROM quints${whereClause}`).get(...params);
120
- return Number(result?.count ?? 0);
121
- }
122
- /**
123
- * Compound query - multiple patterns JOINed by a common field
124
- * This executes a single SQL query with JOINs, letting SQLite optimize the execution plan
125
- */
126
- async getCompound(compound, options) {
127
- this.ensureOpen();
128
- const { patterns, joinOn, select } = compound;
129
- if (patterns.length === 0) {
130
- return [];
131
- }
132
- if (patterns.length === 1) {
133
- // Single pattern, fall back to regular get
134
- const quads = await this.get(patterns[0], options);
135
- return quads.map(q => ({
136
- joinValue: (0, serialization_1.termToId)(q[joinOn]),
137
- bindings: {},
138
- quads: [q],
139
- }));
140
- }
141
- // Build JOIN SQL
142
- const { sql: sqlQuery, params } = this.buildCompoundSQL(compound, options);
143
- if (this.options.debug) {
144
- console.log('[SqliteQuintStore] Compound SQL:', sqlQuery);
145
- console.log('[SqliteQuintStore] Params:', params);
146
- }
147
- // Execute raw SQL
148
- const stmt = this.sqlite.prepare(sqlQuery);
149
- const rows = stmt.all(...params);
150
- // Convert rows to CompoundResult
151
- return rows.map(row => {
152
- const bindings = {};
153
- // Extract bindings based on select config or default naming
154
- if (select) {
155
- for (const s of select) {
156
- bindings[s.alias] = row[s.alias];
157
- }
158
- }
159
- else {
160
- // Default: include all fields from all patterns
161
- for (const key of Object.keys(row)) {
162
- if (key !== 'join_value') {
163
- bindings[key] = row[key];
164
- }
165
- }
166
- }
167
- return {
168
- joinValue: row.join_value,
169
- bindings,
170
- };
171
- });
172
- }
173
- /**
174
- * 批量获取多个 subject 的多个属性
175
- *
176
- * 用于优化 OPTIONAL 查询:避免每个 OPTIONAL 变成一次 LEFT JOIN
177
- *
178
- * SQL: SELECT subject, predicate, object FROM quints
179
- * WHERE subject IN (...) AND predicate IN (...)
180
- */
181
- async getAttributes(subjects, predicates, graph) {
182
- this.ensureOpen();
183
- if (subjects.length === 0 || predicates.length === 0) {
184
- return new Map();
185
- }
186
- // Build SQL with IN clauses
187
- const params = [];
188
- let sql = `SELECT subject, predicate, object FROM quints WHERE subject IN (${subjects.map(() => '?').join(', ')}) AND predicate IN (${predicates.map(() => '?').join(', ')})`;
189
- params.push(...subjects);
190
- params.push(...predicates);
191
- // Add graph filter if specified
192
- if (graph && graph.termType !== 'DefaultGraph') {
193
- sql += ` AND graph = ?`;
194
- params.push((0, serialization_1.termToId)(graph));
195
- }
196
- if (this.options.debug) {
197
- console.log('[SqliteQuintStore] getAttributes SQL:', sql);
198
- console.log('[SqliteQuintStore] Params:', params.length, 'subjects:', subjects.length, 'predicates:', predicates.length);
199
- }
200
- const stmt = this.sqlite.prepare(sql);
201
- const rows = stmt.all(...params);
202
- // Build result map: subject -> predicate -> object[]
203
- const result = new Map();
204
- for (const row of rows) {
205
- if (!result.has(row.subject)) {
206
- result.set(row.subject, new Map());
207
- }
208
- const predicateMap = result.get(row.subject);
209
- if (!predicateMap.has(row.predicate)) {
210
- predicateMap.set(row.predicate, []);
64
+ async openOnce() {
65
+ const executor = await this.createExecutor();
66
+ this.executor = executor;
67
+ try {
68
+ await executor.exec(`
69
+ CREATE TABLE IF NOT EXISTS quints (
70
+ object_kind TEXT,
71
+ object_key TEXT,
72
+ object_text TEXT,
73
+ object_digest TEXT,
74
+ graph TEXT NOT NULL,
75
+ subject TEXT NOT NULL,
76
+ predicate TEXT NOT NULL,
77
+ object TEXT NOT NULL,
78
+ vector TEXT
79
+ );
80
+ `);
81
+ await this.ensureTypedObjectSchema();
82
+ await this.createTypedObjectIndexes();
83
+ this.opened = true;
84
+ }
85
+ catch (error) {
86
+ await this.closeExecutor().catch(() => { });
87
+ if (this.executor === executor) {
88
+ this.executor = null;
211
89
  }
212
- // Deserialize object back to Term
213
- const objectTerm = (0, serialization_1.deserializeObject)(row.object);
214
- predicateMap.get(row.predicate).push(objectTerm);
215
- }
216
- if (this.options.debug) {
217
- console.log('[SqliteQuintStore] getAttributes returned', result.size, 'subjects');
90
+ this.opened = false;
91
+ throw error;
218
92
  }
219
- return result;
220
93
  }
221
- /**
222
- * Build SQL for compound query with JOINs
223
- */
224
- buildCompoundSQL(compound, options) {
225
- const { patterns, joinOn, select } = compound;
226
- const params = [];
227
- // Map joinOn to column name
228
- const joinColumn = joinOn; // 'subject' | 'predicate' | 'object' | 'graph'
229
- // Build SELECT clause
230
- let selectClause = `q0.${joinColumn} as join_value`;
231
- if (select) {
232
- for (const s of select) {
233
- selectClause += `, q${s.pattern}.${s.field} as ${s.alias}`;
234
- }
235
- }
236
- else {
237
- // Default: select object from each pattern as p0_object, p1_object, etc.
238
- for (let i = 0; i < patterns.length; i++) {
239
- selectClause += `, q${i}.object as p${i}_object`;
240
- selectClause += `, q${i}.predicate as p${i}_predicate`;
94
+ buildSelectQuery(pattern, options) {
95
+ const { whereClause, params } = this.buildWhereClause(pattern);
96
+ let sqlText = `SELECT * FROM quints${whereClause}`;
97
+ if (options?.order && options.order.length > 0) {
98
+ const orderCols = options.order.map(field => {
99
+ if (field === 'object') {
100
+ const objectType = this.resolveObjectDataTypeForPattern(pattern);
101
+ if (objectType === 'longText') {
102
+ throw new Error('ORDER BY object is not supported for longText predicates');
103
+ }
104
+ return 'object_key';
105
+ }
106
+ return field;
107
+ }).join(', ');
108
+ sqlText += ` ORDER BY ${orderCols}`;
109
+ if (options.reverse) {
110
+ sqlText += ' DESC';
241
111
  }
242
112
  }
243
- // Build FROM clause with JOINs
244
- let fromClause = 'quints q0';
245
- for (let i = 1; i < patterns.length; i++) {
246
- fromClause += ` JOIN quints q${i} ON q0.${joinColumn} = q${i}.${joinColumn}`;
247
- // Also join on graph to ensure same graph
248
- fromClause += ` AND q0.graph = q${i}.graph`;
249
- }
250
- // Build WHERE clause
251
- const whereParts = [];
252
- for (let i = 0; i < patterns.length; i++) {
253
- const pattern = patterns[i];
254
- const alias = `q${i}`;
255
- const conditions = this.buildConditionsForAlias(pattern, alias, params);
256
- whereParts.push(...conditions);
257
- }
258
- let sql = `SELECT ${selectClause} FROM ${fromClause}`;
259
- if (whereParts.length > 0) {
260
- sql += ` WHERE ${whereParts.join(' AND ')}`;
261
- }
262
- // Add LIMIT/OFFSET
263
- if (options?.limit) {
264
- sql += ` LIMIT ?`;
113
+ if (options?.limit !== undefined) {
114
+ sqlText += ` LIMIT ?`;
265
115
  params.push(options.limit);
266
116
  }
267
- if (options?.offset) {
268
- if (!options?.limit) {
269
- sql += ` LIMIT -1`;
117
+ if (options?.offset !== undefined) {
118
+ if (options.limit === undefined) {
119
+ sqlText += ` LIMIT ?`;
120
+ params.push(SQLITE_UNBOUNDED_LIMIT);
270
121
  }
271
- sql += ` OFFSET ?`;
122
+ sqlText += ` OFFSET ?`;
272
123
  params.push(options.offset);
273
124
  }
274
- return { sql, params };
125
+ return { sql: sqlText, params };
275
126
  }
276
- /**
277
- * Build WHERE conditions for a specific table alias
278
- */
279
- buildConditionsForAlias(pattern, alias, params) {
280
- const conditions = [];
281
- this.addTermConditions(conditions, params, `${alias}.graph`, pattern.graph, false);
282
- this.addTermConditions(conditions, params, `${alias}.subject`, pattern.subject, false);
283
- this.addTermConditions(conditions, params, `${alias}.predicate`, pattern.predicate, false);
284
- if (pattern.object) {
285
- this.addObjectConditions(conditions, params, alias, pattern.object, this.extractExactPredicate(pattern.predicate));
286
- }
287
- return conditions;
127
+ async stats() {
128
+ const stats = await super.stats();
129
+ return {
130
+ totalCount: Number(stats.totalCount),
131
+ vectorCount: Number(stats.vectorCount),
132
+ graphCount: Number(stats.graphCount),
133
+ ...this.sqliteSpaceStats(),
134
+ };
288
135
  }
289
- // ============================================
290
- // Write Operations
291
- // ============================================
292
- async put(quint) {
136
+ async clear() {
293
137
  this.ensureOpen();
294
- const row = this.quintToRow(quint);
295
- const statement = this.writeStatementForRow(row);
296
- this.sqlite.prepare(statement.sql).run(...statement.params);
138
+ await this.executor.execute('DELETE FROM quints');
297
139
  }
298
- async multiPut(quintList) {
299
- this.ensureOpen();
300
- if (quintList.length === 0)
301
- return;
302
- const rows = quintList.map(q => this.writeStatementForRow(this.quintToRow(q)));
303
- // Use transaction for batch insert
304
- this.sqlite.transaction(() => {
305
- for (const row of rows) {
306
- this.sqlite.prepare(row.sql).run(...row.params);
307
- }
308
- })();
140
+ async ensureTypedObjectSchema() {
141
+ await this.addColumnIfMissing('object_kind', 'TEXT');
142
+ await this.addColumnIfMissing('object_key', 'TEXT');
143
+ await this.addColumnIfMissing('object_text', 'TEXT');
144
+ await this.addColumnIfMissing('object_digest', 'TEXT');
145
+ for (const indexName of ['idx_spog', 'idx_ogsp', 'idx_gspo', 'idx_sopg', 'idx_pogs', 'idx_gpos']) {
146
+ await this.executor.exec(`DROP INDEX IF EXISTS ${indexName}`);
147
+ }
148
+ await this.backfillMissingObjectIndexFields();
149
+ await this.executor.exec(`
150
+ UPDATE quints
151
+ SET object_kind = 'text'
152
+ WHERE object_kind = 'shortText'
153
+ `);
309
154
  }
310
- async updateEmbedding(pattern, embedding) {
311
- this.ensureOpen();
312
- const { whereClause, params } = this.buildWhereClause(pattern);
313
- const vectorJson = JSON.stringify(embedding);
314
- const result = this.sqlite.prepare(`UPDATE quints SET vector = ?${whereClause}`).run(vectorJson, ...params);
315
- return result.changes;
316
- }
317
- // ============================================
318
- // Delete Operations
319
- // ============================================
320
- async del(pattern) {
321
- this.ensureOpen();
322
- const { whereClause, params } = this.buildWhereClause(pattern);
323
- const result = this.sqlite.prepare(`DELETE FROM quints${whereClause}`).run(...params);
324
- return result.changes;
155
+ async addColumnIfMissing(name, definition) {
156
+ const columns = new Set(this.sqlite.prepare('PRAGMA table_info(quints)').all().map(row => row.name));
157
+ if (!columns.has(name)) {
158
+ await this.executor.exec(`ALTER TABLE quints ADD COLUMN ${name} ${definition}`);
159
+ }
325
160
  }
326
- async multiDel(quintList) {
327
- this.ensureOpen();
328
- if (quintList.length === 0)
329
- return;
330
- this.sqlite.transaction(() => {
331
- for (const quint of quintList) {
332
- const g = (0, serialization_1.termToId)(quint.graph);
333
- const s = (0, serialization_1.termToId)(quint.subject);
334
- const p = (0, serialization_1.termToId)(quint.predicate);
335
- const o = (0, serialization_1.serializeObject)(quint.object);
336
- this.sqlite.prepare(`
337
- DELETE FROM quints
338
- WHERE graph = ?
339
- AND subject = ?
340
- AND predicate = ?
341
- AND object = ?
342
- `).run(g, s, p, o);
343
- }
344
- })();
161
+ async createTypedObjectIndexes() {
162
+ await this.executor.exec(`
163
+ CREATE INDEX IF NOT EXISTS idx_quints_graph ON quints (graph);
164
+ CREATE INDEX IF NOT EXISTS idx_quints_subject ON quints (subject);
165
+ CREATE INDEX IF NOT EXISTS idx_quints_predicate ON quints (predicate);
166
+ CREATE INDEX IF NOT EXISTS idx_quints_object_key ON quints (object_kind, object_key);
167
+ CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_key ON quints (predicate, object_kind, object_key);
168
+ CREATE INDEX IF NOT EXISTS idx_quints_predicate_object_digest ON quints (predicate, object_kind, object_digest);
169
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_key
170
+ ON quints (graph, subject, predicate, object_kind, object_key)
171
+ WHERE object_key IS NOT NULL;
172
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_quints_gspo_digest
173
+ ON quints (graph, subject, predicate, object_kind, object_digest)
174
+ WHERE object_digest IS NOT NULL;
175
+ CREATE INDEX IF NOT EXISTS idx_quints_gsp ON quints (graph, subject, predicate);
176
+ CREATE INDEX IF NOT EXISTS idx_quints_sp ON quints (subject, predicate);
177
+ CREATE INDEX IF NOT EXISTS idx_quints_gp ON quints (graph, predicate);
178
+ `);
345
179
  }
346
- // ============================================
347
- // Management
348
- // ============================================
349
- async stats() {
350
- this.ensureOpen();
351
- const totalResult = await this.db
352
- .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
353
- .from(schema_1.quints);
354
- const vectorResult = await this.db
355
- .select({ count: (0, drizzle_orm_1.sql) `count(*)` })
356
- .from(schema_1.quints)
357
- .where((0, drizzle_orm_1.sql) `${schema_1.quints.vector} IS NOT NULL`);
358
- const graphResult = await this.db
359
- .select({ count: (0, drizzle_orm_1.sql) `COUNT(DISTINCT ${schema_1.quints.graph})` })
360
- .from(schema_1.quints);
361
- return {
362
- totalCount: totalResult[0]?.count ?? 0,
363
- vectorCount: vectorResult[0]?.count ?? 0,
364
- graphCount: graphResult[0]?.count ?? 0,
365
- ...this.sqliteSpaceStats(),
366
- };
180
+ async backfillMissingObjectIndexFields() {
181
+ const rows = await this.executor.query(`
182
+ SELECT graph, subject, predicate, object
183
+ FROM quints
184
+ WHERE object_kind IS NULL
185
+ OR (object_key IS NULL AND object_digest IS NULL)
186
+ `);
187
+ for (const row of rows) {
188
+ const objectIndex = this.objectIndexForSerialized(row.predicate, row.object);
189
+ await this.executor.execute(`
190
+ UPDATE quints
191
+ SET object_kind = ?,
192
+ object_key = ?,
193
+ object_text = ?,
194
+ object_digest = ?
195
+ WHERE graph = ?
196
+ AND subject = ?
197
+ AND predicate = ?
198
+ AND object = ?
199
+ `, [
200
+ objectIndex.objectKind,
201
+ objectIndex.objectKey,
202
+ objectIndex.objectText,
203
+ this.objectDigestForIndex(row.object, objectIndex),
204
+ row.graph,
205
+ row.subject,
206
+ row.predicate,
207
+ row.object,
208
+ ]);
209
+ }
367
210
  }
368
211
  sqliteSpaceStats() {
369
212
  const spaceObjects = this.collectSpaceObjects();
@@ -447,94 +290,25 @@ class SqliteQuintStore {
447
290
  return 4096;
448
291
  }
449
292
  }
450
- async clear() {
451
- this.ensureOpen();
452
- await this.db.delete(schema_1.quints);
453
- }
454
- // ============================================
455
- // Private Helpers
456
- // ============================================
457
- ensureOpen() {
458
- if (!this.db) {
459
- throw new Error('Store not open. Call open() first.');
460
- }
461
- }
462
- ensureTypedObjectSchema() {
463
- this.addColumnIfMissing('object_kind', 'TEXT');
464
- this.addColumnIfMissing('object_key', 'TEXT');
465
- this.addColumnIfMissing('object_text', 'TEXT');
466
- this.addColumnIfMissing('object_digest', 'TEXT');
467
- for (const indexName of ['idx_spog', 'idx_ogsp', 'idx_gspo', 'idx_sopg', 'idx_pogs', 'idx_gpos']) {
468
- this.sqlite.exec(`DROP INDEX IF EXISTS ${indexName}`);
469
- }
470
- this.backfillMissingObjectIndexFields();
471
- this.sqlite.prepare(`
472
- UPDATE quints
473
- SET object_kind = 'text'
474
- WHERE object_kind = 'shortText'
475
- `).run();
476
- }
477
- addColumnIfMissing(name, definition) {
478
- const columns = new Set(this.sqlite.prepare('PRAGMA table_info(quints)').all().map(row => row.name));
479
- if (!columns.has(name)) {
480
- this.sqlite.exec(`ALTER TABLE quints ADD COLUMN ${name} ${definition}`);
481
- }
293
+ objectIndexForSerialized(predicate, object) {
294
+ return (0, value_types_1.objectIndexFieldsFromSerialized)(object, {
295
+ predicate,
296
+ predicateObjectDataTypes: this.options.predicateObjectDataTypes,
297
+ textMaxBytes: this.options.textMaxBytes,
298
+ });
482
299
  }
483
- backfillMissingObjectIndexFields() {
484
- const rows = this.sqlite.prepare(`
485
- SELECT graph, subject, predicate, object
486
- FROM quints
487
- WHERE object_kind IS NULL
488
- OR (object_key IS NULL AND object_digest IS NULL)
489
- `).all();
490
- const update = this.sqlite.prepare(`
491
- UPDATE quints
492
- SET object_kind = ?,
493
- object_key = ?,
494
- object_text = ?,
495
- object_digest = ?
496
- WHERE graph = ?
497
- AND subject = ?
498
- AND predicate = ?
499
- AND object = ?
500
- `);
501
- for (const row of rows) {
502
- const objectIndex = this.objectIndexForSerialized(row.predicate, row.object);
503
- update.run(objectIndex.objectKind, objectIndex.objectKey, objectIndex.objectText, this.objectDigestForIndex(row.object, objectIndex), row.graph, row.subject, row.predicate, row.object);
504
- }
300
+ objectDigestForIndex(serialized, fields) {
301
+ return fields.objectKey === null ? digestObject(serialized) : null;
505
302
  }
506
- buildSelectQuery(pattern, options) {
507
- const { whereClause, params } = this.buildWhereClause(pattern);
508
- let query = `SELECT * FROM quints${whereClause}`;
509
- if (options?.order && options.order.length > 0) {
510
- const orderCols = options.order.map(field => {
511
- if (field === 'object') {
512
- const objectType = this.resolveObjectDataTypeForPattern(pattern);
513
- if (objectType === 'longText') {
514
- throw new Error('ORDER BY object is not supported for longText predicates');
515
- }
516
- return 'object_key';
517
- }
518
- return field;
519
- }).join(', ');
520
- query += ` ORDER BY ${orderCols}`;
521
- if (options.reverse) {
522
- query += ' DESC';
523
- }
524
- }
525
- if (options?.limit !== undefined) {
526
- query += ' LIMIT ?';
527
- params.push(options.limit);
303
+ resolveObjectDataTypeForPattern(pattern) {
304
+ const predicate = this.extractExactPredicate(pattern.predicate);
305
+ if (predicate) {
306
+ return (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
528
307
  }
529
- if (options?.offset !== undefined) {
530
- if (options.limit === undefined) {
531
- query += ' LIMIT ?';
532
- params.push(SQLITE_UNBOUNDED_LIMIT);
533
- }
534
- query += ' OFFSET ?';
535
- params.push(options.offset);
308
+ if (pattern.object && typeof pattern.object === 'object' && 'termType' in pattern.object) {
309
+ return this.objectIndexForTerm(predicate, pattern.object).objectKind;
536
310
  }
537
- return { sql: query, params };
311
+ return undefined;
538
312
  }
539
313
  buildWhereClause(pattern) {
540
314
  const conditions = [];
@@ -552,93 +326,15 @@ class SqliteQuintStore {
552
326
  };
553
327
  }
554
328
  addTermConditions(conditions, params, column, match, isObject) {
555
- if (!match)
556
- return;
557
- if ((0, types_1.isTerm)(match)) {
558
- conditions.push(`${column} = ?`);
559
- params.push(isObject ? (0, serialization_1.serializeObject)(match) : (0, serialization_1.termToId)(match));
560
- return;
561
- }
562
- const ops = match;
563
- if (ops.$eq !== undefined) {
564
- conditions.push(`${column} = ?`);
565
- params.push(this.serializeOpValue(ops.$eq, isObject, '$eq'));
566
- }
567
- if (ops.$ne !== undefined) {
568
- conditions.push(`${column} != ?`);
569
- params.push(this.serializeOpValue(ops.$ne, isObject, '$ne'));
570
- }
571
- if (ops.$gt !== undefined) {
572
- conditions.push(`${column} > ?`);
573
- params.push(this.serializeOpValue(ops.$gt, isObject, '$gt'));
574
- }
575
- if (ops.$gte !== undefined) {
576
- conditions.push(`${column} >= ?`);
577
- params.push(this.serializeOpValue(ops.$gte, isObject, '$gte'));
578
- }
579
- if (ops.$lt !== undefined) {
580
- conditions.push(`${column} < ?`);
581
- params.push(this.serializeOpValue(ops.$lt, isObject, '$lt'));
582
- }
583
- if (ops.$lte !== undefined) {
584
- conditions.push(`${column} <= ?`);
585
- params.push(this.serializeOpValue(ops.$lte, isObject, '$lte'));
586
- }
587
- if (ops.$in !== undefined && ops.$in.length > 0) {
588
- const placeholders = ops.$in.map(() => '?').join(', ');
589
- conditions.push(`${column} IN (${placeholders})`);
590
- params.push(...ops.$in.map(v => this.serializeOpValue(v, isObject, '$in')));
591
- }
592
- if (ops.$notIn !== undefined && ops.$notIn.length > 0) {
593
- const placeholders = ops.$notIn.map(() => '?').join(', ');
594
- conditions.push(`${column} NOT IN (${placeholders})`);
595
- params.push(...ops.$notIn.map(v => this.serializeOpValue(v, isObject, '$notIn')));
596
- }
597
- if (ops.$startsWith !== undefined) {
598
- conditions.push(`${column} >= ? AND ${column} < ?`);
599
- params.push(ops.$startsWith, ops.$startsWith + '\uffff');
600
- }
601
- if (ops.$endsWith !== undefined) {
602
- conditions.push(`${column} LIKE ?`);
603
- params.push(`%${ops.$endsWith}`);
604
- }
605
- if (ops.$contains !== undefined) {
606
- conditions.push(`${column} LIKE ?`);
607
- params.push(`%${ops.$contains}%`);
608
- }
609
- if (ops.$regex !== undefined) {
610
- conditions.push(`${column} GLOB ?`);
611
- params.push(ops.$regex.replace(/\.\*/g, '*').replace(/\./g, '?'));
612
- }
613
- if (ops.$isNull === true) {
614
- conditions.push(`${column} IS NULL`);
615
- }
616
- if (ops.$isNull === false) {
617
- conditions.push(`${column} IS NOT NULL`);
618
- }
329
+ this.addConditions(conditions, params, column, match, isObject);
619
330
  }
620
- serializeOpValue(value, isObject, filterOp) {
621
- if (typeof value === 'object' && 'termType' in value) {
622
- return isObject ? (0, serialization_1.serializeObject)(value) : (0, serialization_1.termToId)(value);
623
- }
624
- if (typeof value === 'number') {
625
- if (isObject) {
626
- if (filterOp === '$eq' || filterOp === '$ne' || filterOp === '$in' || filterOp === '$notIn') {
627
- const lit = n3_1.DataFactory.literal(String(value), n3_1.DataFactory.namedNode('http://www.w3.org/2001/XMLSchema#integer'));
628
- return (0, serialization_1.serializeObject)(lit);
629
- }
630
- const fpValue = `N${serialization_1.SEP}${(0, serialization_1.fpEncode)(value)}`;
631
- if (filterOp === '$gt' || filterOp === '$lte') {
632
- return fpValue + serialization_1.SEP + '\uffff';
633
- }
634
- return fpValue;
635
- }
636
- return value;
637
- }
638
- if (isObject && !(0, serialization_1.isSerializedObjectValue)(value)) {
639
- return `"${value}"`;
331
+ addAliasedConditions(conditions, params, alias, pattern) {
332
+ this.addTermConditions(conditions, params, `${alias}.graph`, pattern.graph, false);
333
+ this.addTermConditions(conditions, params, `${alias}.subject`, pattern.subject, false);
334
+ this.addTermConditions(conditions, params, `${alias}.predicate`, pattern.predicate, false);
335
+ if (pattern.object) {
336
+ this.addObjectConditions(conditions, params, alias, pattern.object, this.extractExactPredicate(pattern.predicate));
640
337
  }
641
- return value;
642
338
  }
643
339
  addObjectConditions(conditions, params, alias, match, predicate) {
644
340
  const column = (name) => alias ? `${alias}.${name}` : name;
@@ -843,7 +539,7 @@ class SqliteQuintStore {
843
539
  return { objectKind: 'dateTime', objectKey: serialized, objectText: null };
844
540
  }
845
541
  }
846
- return this.objectIndexForSerialized(predicate, serialized);
542
+ return this.objectIndexForSerialized(predicate ?? '', serialized);
847
543
  }
848
544
  objectIndexForTerm(predicate, object) {
849
545
  return (0, value_types_1.objectIndexFieldsFromTerm)(object, {
@@ -852,13 +548,6 @@ class SqliteQuintStore {
852
548
  textMaxBytes: this.options.textMaxBytes,
853
549
  });
854
550
  }
855
- objectIndexForSerialized(predicate, object) {
856
- return (0, value_types_1.objectIndexFieldsFromSerialized)(object, {
857
- predicate,
858
- predicateObjectDataTypes: this.options.predicateObjectDataTypes,
859
- textMaxBytes: this.options.textMaxBytes,
860
- });
861
- }
862
551
  objectFieldsForPrefix(prefix, predicate) {
863
552
  const declaredType = (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
864
553
  if (declaredType) {
@@ -881,40 +570,28 @@ class SqliteQuintStore {
881
570
  }
882
571
  return { objectKind: 'iri', objectKey: prefix, objectText: null };
883
572
  }
884
- objectDigestForIndex(serialized, fields) {
885
- return fields.objectKey === null ? digestObject(serialized) : null;
886
- }
887
573
  assertComparableObject(fields, op) {
888
574
  if (fields.objectKey !== null && fields.objectKind !== 'longText') {
889
575
  return;
890
576
  }
891
577
  throw new Error(`Object ${op} is not supported for ${fields.objectKind}; declare/use a comparable data type instead of longText`);
892
578
  }
893
- extractExactPredicate(match) {
894
- if (!match)
895
- return undefined;
896
- if (typeof match === 'object' && 'termType' in match) {
897
- return (0, serialization_1.termToId)(match);
898
- }
899
- const ops = match;
900
- if (ops.$eq !== undefined) {
901
- return String(this.serializeOpValue(ops.$eq, false, '$eq'));
902
- }
903
- return undefined;
904
- }
905
- resolveObjectDataTypeForPattern(pattern) {
906
- const predicate = this.extractExactPredicate(pattern.predicate);
907
- if (predicate) {
908
- return (0, value_types_1.getPredicateObjectDataType)(predicate, this.options.predicateObjectDataTypes);
909
- }
910
- if (pattern.object && typeof pattern.object === 'object' && 'termType' in pattern.object) {
911
- return this.objectIndexForTerm(predicate, pattern.object).objectKind;
912
- }
913
- return undefined;
914
- }
915
579
  predicateForIndex(predicate) {
916
580
  return predicate;
917
581
  }
582
+ async put(quint) {
583
+ this.ensureOpen();
584
+ const row = this.quintToRow(quint);
585
+ const statement = this.writeStatementForRow(row);
586
+ await this.executor.execute(statement.sql, statement.params);
587
+ }
588
+ async multiPut(quintList) {
589
+ this.ensureOpen();
590
+ if (quintList.length === 0)
591
+ return;
592
+ const statements = quintList.map(quint => this.writeStatementForRow(this.quintToRow(quint)));
593
+ await this.executor.executeInTransaction(statements);
594
+ }
918
595
  writeStatementForRow(row) {
919
596
  const params = [
920
597
  row.objectKind,