@aigne/afs-sqlite 1.1.0-beta.1 → 1.11.0-beta

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 (82) hide show
  1. package/README.md +51 -36
  2. package/dist/index.cjs +1324 -0
  3. package/dist/index.d.cts +758 -0
  4. package/dist/index.d.cts.map +1 -0
  5. package/dist/index.d.mts +758 -0
  6. package/dist/index.d.mts.map +1 -0
  7. package/dist/index.mjs +1299 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +31 -44
  10. package/CHANGELOG.md +0 -61
  11. package/lib/cjs/actions/built-in.d.ts +0 -5
  12. package/lib/cjs/actions/built-in.js +0 -165
  13. package/lib/cjs/actions/registry.d.ts +0 -49
  14. package/lib/cjs/actions/registry.js +0 -102
  15. package/lib/cjs/actions/types.d.ts +0 -51
  16. package/lib/cjs/actions/types.js +0 -2
  17. package/lib/cjs/config.d.ts +0 -89
  18. package/lib/cjs/config.js +0 -33
  19. package/lib/cjs/index.d.ts +0 -13
  20. package/lib/cjs/index.js +0 -47
  21. package/lib/cjs/node/builder.d.ts +0 -43
  22. package/lib/cjs/node/builder.js +0 -187
  23. package/lib/cjs/operations/crud.d.ts +0 -64
  24. package/lib/cjs/operations/crud.js +0 -225
  25. package/lib/cjs/operations/query-builder.d.ts +0 -37
  26. package/lib/cjs/operations/query-builder.js +0 -102
  27. package/lib/cjs/operations/search.d.ts +0 -75
  28. package/lib/cjs/operations/search.js +0 -172
  29. package/lib/cjs/package.json +0 -3
  30. package/lib/cjs/router/path-router.d.ts +0 -38
  31. package/lib/cjs/router/path-router.js +0 -90
  32. package/lib/cjs/router/types.d.ts +0 -30
  33. package/lib/cjs/router/types.js +0 -2
  34. package/lib/cjs/schema/introspector.d.ts +0 -48
  35. package/lib/cjs/schema/introspector.js +0 -186
  36. package/lib/cjs/schema/types.d.ts +0 -104
  37. package/lib/cjs/schema/types.js +0 -13
  38. package/lib/cjs/sqlite-afs.d.ts +0 -144
  39. package/lib/cjs/sqlite-afs.js +0 -337
  40. package/lib/dts/actions/built-in.d.ts +0 -5
  41. package/lib/dts/actions/registry.d.ts +0 -49
  42. package/lib/dts/actions/types.d.ts +0 -51
  43. package/lib/dts/config.d.ts +0 -89
  44. package/lib/dts/index.d.ts +0 -13
  45. package/lib/dts/node/builder.d.ts +0 -43
  46. package/lib/dts/operations/crud.d.ts +0 -64
  47. package/lib/dts/operations/query-builder.d.ts +0 -37
  48. package/lib/dts/operations/search.d.ts +0 -75
  49. package/lib/dts/router/path-router.d.ts +0 -38
  50. package/lib/dts/router/types.d.ts +0 -30
  51. package/lib/dts/schema/introspector.d.ts +0 -48
  52. package/lib/dts/schema/types.d.ts +0 -104
  53. package/lib/dts/sqlite-afs.d.ts +0 -144
  54. package/lib/esm/actions/built-in.d.ts +0 -5
  55. package/lib/esm/actions/built-in.js +0 -162
  56. package/lib/esm/actions/registry.d.ts +0 -49
  57. package/lib/esm/actions/registry.js +0 -98
  58. package/lib/esm/actions/types.d.ts +0 -51
  59. package/lib/esm/actions/types.js +0 -1
  60. package/lib/esm/config.d.ts +0 -89
  61. package/lib/esm/config.js +0 -30
  62. package/lib/esm/index.d.ts +0 -13
  63. package/lib/esm/index.js +0 -17
  64. package/lib/esm/node/builder.d.ts +0 -43
  65. package/lib/esm/node/builder.js +0 -177
  66. package/lib/esm/operations/crud.d.ts +0 -64
  67. package/lib/esm/operations/crud.js +0 -221
  68. package/lib/esm/operations/query-builder.d.ts +0 -37
  69. package/lib/esm/operations/query-builder.js +0 -92
  70. package/lib/esm/operations/search.d.ts +0 -75
  71. package/lib/esm/operations/search.js +0 -167
  72. package/lib/esm/package.json +0 -3
  73. package/lib/esm/router/path-router.d.ts +0 -38
  74. package/lib/esm/router/path-router.js +0 -83
  75. package/lib/esm/router/types.d.ts +0 -30
  76. package/lib/esm/router/types.js +0 -1
  77. package/lib/esm/schema/introspector.d.ts +0 -48
  78. package/lib/esm/schema/introspector.js +0 -182
  79. package/lib/esm/schema/types.d.ts +0 -104
  80. package/lib/esm/schema/types.js +0 -10
  81. package/lib/esm/sqlite-afs.d.ts +0 -144
  82. package/lib/esm/sqlite-afs.js +0 -333
package/dist/index.cjs ADDED
@@ -0,0 +1,1324 @@
1
+ let _aigne_sqlite = require("@aigne/sqlite");
2
+ let _aigne_afs = require("@aigne/afs");
3
+ let zod = require("zod");
4
+ let radix3 = require("radix3");
5
+
6
+ //#region src/actions/built-in.ts
7
+ /**
8
+ * Executes a raw SQL query and returns all rows
9
+ */
10
+ async function execAll$3(db, query) {
11
+ return db.all(_aigne_sqlite.sql.raw(query)).execute();
12
+ }
13
+ /**
14
+ * Executes a raw SQL query (for INSERT, UPDATE, DELETE)
15
+ */
16
+ async function execRun$2(db, query) {
17
+ await db.run(_aigne_sqlite.sql.raw(query)).execute();
18
+ }
19
+ /**
20
+ * Registers built-in actions to the registry
21
+ */
22
+ function registerBuiltInActions(registry) {
23
+ registry.register({
24
+ name: "refresh",
25
+ description: "Refresh the schema cache for this module",
26
+ tableLevel: true,
27
+ rowLevel: false,
28
+ handler: async (ctx) => {
29
+ await ctx.module.refreshSchema();
30
+ return {
31
+ success: true,
32
+ message: "Schema refreshed successfully"
33
+ };
34
+ }
35
+ });
36
+ registry.register({
37
+ name: "export",
38
+ description: "Export table data in specified format (json, csv)",
39
+ tableLevel: true,
40
+ rowLevel: false,
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: { format: {
44
+ type: "string",
45
+ enum: ["json", "csv"],
46
+ default: "json"
47
+ } }
48
+ },
49
+ handler: async (ctx, params) => {
50
+ const format = params.format ?? "json";
51
+ return {
52
+ success: true,
53
+ data: await ctx.module.exportTable(ctx.table, format)
54
+ };
55
+ }
56
+ });
57
+ registry.register({
58
+ name: "count",
59
+ description: "Get the total row count for this table",
60
+ tableLevel: true,
61
+ rowLevel: false,
62
+ handler: async (ctx) => {
63
+ return {
64
+ success: true,
65
+ data: { count: (await execAll$3(ctx.db, `SELECT COUNT(*) as count FROM "${ctx.table}"`))[0]?.count ?? 0 }
66
+ };
67
+ }
68
+ });
69
+ registry.register({
70
+ name: "duplicate",
71
+ description: "Create a copy of this row",
72
+ tableLevel: false,
73
+ rowLevel: true,
74
+ handler: async (ctx) => {
75
+ if (!ctx.row) return {
76
+ success: false,
77
+ message: "Row data not available"
78
+ };
79
+ const schema = ctx.schemas.get(ctx.table);
80
+ if (!schema) return {
81
+ success: false,
82
+ message: `Table '${ctx.table}' not found`
83
+ };
84
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
85
+ const rowCopy = { ...ctx.row };
86
+ delete rowCopy[pkColumn];
87
+ delete rowCopy.rowid;
88
+ const columns = Object.keys(rowCopy);
89
+ const values = columns.map((col) => formatValueForSQL(rowCopy[col]));
90
+ await execRun$2(ctx.db, `INSERT INTO "${ctx.table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`);
91
+ return {
92
+ success: true,
93
+ data: { newId: (await execAll$3(ctx.db, "SELECT last_insert_rowid() as id"))[0]?.id },
94
+ message: "Row duplicated successfully"
95
+ };
96
+ }
97
+ });
98
+ registry.register({
99
+ name: "validate",
100
+ description: "Validate row data against schema constraints",
101
+ tableLevel: false,
102
+ rowLevel: true,
103
+ handler: async (ctx) => {
104
+ if (!ctx.row) return {
105
+ success: false,
106
+ message: "Row data not available"
107
+ };
108
+ const schema = ctx.schemas.get(ctx.table);
109
+ if (!schema) return {
110
+ success: false,
111
+ message: `Table '${ctx.table}' not found`
112
+ };
113
+ const errors = [];
114
+ for (const col of schema.columns) if (col.notnull && (ctx.row[col.name] === null || ctx.row[col.name] === void 0)) errors.push(`Column '${col.name}' cannot be null`);
115
+ for (const fk of schema.foreignKeys) {
116
+ const value = ctx.row[fk.from];
117
+ if (value !== null && value !== void 0) {
118
+ if ((await execAll$3(ctx.db, `SELECT COUNT(*) as count FROM "${fk.table}" WHERE "${fk.to}" = '${String(value).replace(/'/g, "''")}'`))[0]?.count === 0) errors.push(`Foreign key violation: ${fk.from} references non-existent ${fk.table}.${fk.to}`);
119
+ }
120
+ }
121
+ return {
122
+ success: errors.length === 0,
123
+ data: {
124
+ errors,
125
+ valid: errors.length === 0
126
+ },
127
+ message: errors.length > 0 ? `Validation failed: ${errors.join("; ")}` : "Row is valid"
128
+ };
129
+ }
130
+ });
131
+ }
132
+ /**
133
+ * Formats a value for SQL insertion
134
+ */
135
+ function formatValueForSQL(value) {
136
+ if (value === null || value === void 0) return "NULL";
137
+ if (typeof value === "number") return String(value);
138
+ if (typeof value === "boolean") return value ? "1" : "0";
139
+ if (value instanceof Date) return `'${value.toISOString()}'`;
140
+ if (typeof value === "object") return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
141
+ return `'${String(value).replace(/'/g, "''")}'`;
142
+ }
143
+
144
+ //#endregion
145
+ //#region src/actions/registry.ts
146
+ /**
147
+ * Registry for managing action handlers
148
+ */
149
+ var ActionsRegistry = class {
150
+ handlers = /* @__PURE__ */ new Map();
151
+ /**
152
+ * Registers an action handler
153
+ */
154
+ register(definition) {
155
+ this.handlers.set(definition.name, definition);
156
+ }
157
+ /**
158
+ * Registers a simple action with just name and handler
159
+ */
160
+ registerSimple(name, handler, options) {
161
+ this.register({
162
+ name,
163
+ handler,
164
+ description: options?.description,
165
+ tableLevel: options?.tableLevel ?? false,
166
+ rowLevel: options?.rowLevel ?? true
167
+ });
168
+ }
169
+ /**
170
+ * Unregisters an action
171
+ */
172
+ unregister(name) {
173
+ return this.handlers.delete(name);
174
+ }
175
+ /**
176
+ * Checks if an action is registered
177
+ */
178
+ has(name) {
179
+ return this.handlers.has(name);
180
+ }
181
+ /**
182
+ * Gets an action definition
183
+ */
184
+ get(name) {
185
+ return this.handlers.get(name);
186
+ }
187
+ /**
188
+ * Lists all registered actions
189
+ */
190
+ list(options) {
191
+ const actions = Array.from(this.handlers.values());
192
+ if (options?.tableLevel !== void 0 || options?.rowLevel !== void 0) return actions.filter((a) => {
193
+ if (options.tableLevel && !a.tableLevel) return false;
194
+ if (options.rowLevel && !a.rowLevel) return false;
195
+ return true;
196
+ });
197
+ return actions;
198
+ }
199
+ /**
200
+ * Lists action names
201
+ */
202
+ listNames(options) {
203
+ return this.list(options).map((a) => a.name);
204
+ }
205
+ /**
206
+ * Executes an action
207
+ */
208
+ async execute(name, ctx, params = {}) {
209
+ const definition = this.handlers.get(name);
210
+ if (!definition) return {
211
+ success: false,
212
+ message: `Unknown action: ${name}`
213
+ };
214
+ if (ctx.pk && !definition.rowLevel) return {
215
+ success: false,
216
+ message: `Action '${name}' is not available at row level`
217
+ };
218
+ if (!ctx.pk && !definition.tableLevel) return {
219
+ success: false,
220
+ message: `Action '${name}' is not available at table level`
221
+ };
222
+ try {
223
+ return await definition.handler(ctx, params);
224
+ } catch (error) {
225
+ return {
226
+ success: false,
227
+ message: error instanceof Error ? error.message : String(error)
228
+ };
229
+ }
230
+ }
231
+ };
232
+
233
+ //#endregion
234
+ //#region src/config.ts
235
+ /**
236
+ * FTS (Full-Text Search) configuration schema
237
+ */
238
+ const ftsConfigSchema = zod.z.object({
239
+ enabled: zod.z.boolean().default(true).describe("Whether FTS is enabled"),
240
+ tables: zod.z.record(zod.z.array(zod.z.string())).optional().describe("Map of table name to columns to index for FTS")
241
+ }).optional();
242
+ /**
243
+ * SQLite AFS module configuration schema
244
+ */
245
+ const sqliteAFSConfigSchema = zod.z.object({
246
+ url: zod.z.string().describe("SQLite database URL (file:./path or :memory:)"),
247
+ name: zod.z.string().optional().describe("Module name, defaults to 'sqlite-afs'"),
248
+ description: zod.z.string().optional().describe("Description of this module"),
249
+ accessMode: _aigne_afs.accessModeSchema,
250
+ tables: zod.z.array(zod.z.string()).optional().describe("Whitelist of tables to expose (if not specified, all tables are exposed)"),
251
+ excludeTables: zod.z.array(zod.z.string()).optional().describe("Tables to exclude from exposure"),
252
+ fts: ftsConfigSchema,
253
+ wal: zod.z.boolean().optional().default(true).describe("Enable WAL mode for better concurrency")
254
+ });
255
+
256
+ //#endregion
257
+ //#region src/node/builder.ts
258
+ /**
259
+ * Builds an AFSEntry from a database row
260
+ */
261
+ function buildRowEntry(table, schema, row, options) {
262
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
263
+ const pk = String(row[pkColumn] ?? row.rowid);
264
+ const basePath = options?.basePath ?? "";
265
+ return {
266
+ id: `${table}:${pk}`,
267
+ path: `${basePath}/${table}/${pk}`,
268
+ content: row,
269
+ metadata: {
270
+ table,
271
+ primaryKey: pkColumn,
272
+ primaryKeyValue: pk
273
+ },
274
+ createdAt: parseDate(row.created_at ?? row.createdAt),
275
+ updatedAt: parseDate(row.updated_at ?? row.updatedAt)
276
+ };
277
+ }
278
+ /**
279
+ * Builds an AFSEntry for a table listing
280
+ */
281
+ function buildTableEntry(table, schema, options) {
282
+ return {
283
+ id: table,
284
+ path: `${options?.basePath ?? ""}/${table}`,
285
+ description: `Table: ${table} (${schema.columns.length} columns)`,
286
+ metadata: {
287
+ table,
288
+ columnCount: schema.columns.length,
289
+ primaryKey: schema.primaryKey,
290
+ childrenCount: options?.rowCount
291
+ }
292
+ };
293
+ }
294
+ /**
295
+ * Builds an AFSEntry for table schema
296
+ */
297
+ function buildSchemaEntry(table, schema, options) {
298
+ const basePath = options?.basePath ?? "";
299
+ return {
300
+ id: `${table}:@schema`,
301
+ path: `${basePath}/${table}/@schema`,
302
+ description: `Schema for table: ${table}`,
303
+ content: {
304
+ name: schema.name,
305
+ columns: schema.columns.map((col) => ({
306
+ name: col.name,
307
+ type: col.type,
308
+ nullable: !col.notnull,
309
+ primaryKey: col.pk > 0,
310
+ defaultValue: col.dfltValue
311
+ })),
312
+ primaryKey: schema.primaryKey,
313
+ foreignKeys: schema.foreignKeys.map((fk) => ({
314
+ column: fk.from,
315
+ references: {
316
+ table: fk.table,
317
+ column: fk.to
318
+ },
319
+ onUpdate: fk.onUpdate,
320
+ onDelete: fk.onDelete
321
+ })),
322
+ indexes: schema.indexes.map((idx) => ({
323
+ name: idx.name,
324
+ unique: idx.unique,
325
+ origin: idx.origin
326
+ }))
327
+ },
328
+ metadata: {
329
+ table,
330
+ type: "schema"
331
+ }
332
+ };
333
+ }
334
+ /**
335
+ * Builds an AFSEntry for an attribute (single column value)
336
+ */
337
+ function buildAttributeEntry(table, pk, column, value, options) {
338
+ const basePath = options?.basePath ?? "";
339
+ return {
340
+ id: `${table}:${pk}:@attr:${column}`,
341
+ path: `${basePath}/${table}/${pk}/@attr/${column}`,
342
+ content: value,
343
+ metadata: {
344
+ table,
345
+ primaryKeyValue: pk,
346
+ column,
347
+ type: "attribute"
348
+ }
349
+ };
350
+ }
351
+ /**
352
+ * Builds an AFSEntry listing all attributes for a row
353
+ */
354
+ function buildAttributeListEntry(table, schema, pk, row, options) {
355
+ const basePath = options?.basePath ?? "";
356
+ return schema.columns.map((col) => ({
357
+ id: `${table}:${pk}:@attr:${col.name}`,
358
+ path: `${basePath}/${table}/${pk}/@attr/${col.name}`,
359
+ summary: col.name,
360
+ description: `${col.type}${col.notnull ? " NOT NULL" : ""}`,
361
+ content: row[col.name],
362
+ metadata: {
363
+ column: col.name,
364
+ type: col.type
365
+ }
366
+ }));
367
+ }
368
+ /**
369
+ * Builds an AFSEntry for row metadata
370
+ */
371
+ function buildMetaEntry(table, schema, pk, row, options) {
372
+ const basePath = options?.basePath ?? "";
373
+ return {
374
+ id: `${table}:${pk}:@meta`,
375
+ path: `${basePath}/${table}/${pk}/@meta`,
376
+ content: {
377
+ table,
378
+ primaryKey: schema.primaryKey[0] ?? "rowid",
379
+ primaryKeyValue: pk,
380
+ schema: {
381
+ columns: schema.columns.map((c) => c.name),
382
+ types: Object.fromEntries(schema.columns.map((c) => [c.name, c.type]))
383
+ },
384
+ foreignKeys: schema.foreignKeys.filter((fk) => Object.keys(row).includes(fk.from)),
385
+ rowid: row.rowid
386
+ },
387
+ metadata: {
388
+ table,
389
+ type: "meta"
390
+ }
391
+ };
392
+ }
393
+ /**
394
+ * Builds AFSEntry for actions list
395
+ */
396
+ function buildActionsListEntry(table, pk, actions, options) {
397
+ const basePath = options?.basePath ?? "";
398
+ return actions.map((action) => ({
399
+ id: `${table}:${pk}:@actions:${action}`,
400
+ path: `${basePath}/${table}/${pk}/@actions/${action}`,
401
+ summary: action,
402
+ metadata: { execute: {
403
+ name: action,
404
+ description: `Execute ${action} action on ${table}:${pk}`
405
+ } }
406
+ }));
407
+ }
408
+ /**
409
+ * Builds a search result entry with highlights
410
+ */
411
+ function buildSearchEntry(table, schema, row, snippet, options) {
412
+ const entry = buildRowEntry(table, schema, row, options);
413
+ if (snippet) entry.summary = snippet;
414
+ return entry;
415
+ }
416
+ /**
417
+ * Parses a date from various formats
418
+ */
419
+ function parseDate(value) {
420
+ if (!value) return void 0;
421
+ if (value instanceof Date) return value;
422
+ if (typeof value === "string") return new Date(value);
423
+ if (typeof value === "number") return new Date(value);
424
+ }
425
+
426
+ //#endregion
427
+ //#region src/operations/query-builder.ts
428
+ /**
429
+ * Builds a SELECT query string for a single row by primary key
430
+ */
431
+ function buildSelectByPK(tableName, schema, pk) {
432
+ return `SELECT * FROM "${tableName}" WHERE "${schema.primaryKey[0] ?? "rowid"}" = '${escapeSQLString(pk)}'`;
433
+ }
434
+ /**
435
+ * Builds a SELECT query string for listing rows with optional limit and offset
436
+ */
437
+ function buildSelectAll(tableName, options) {
438
+ let query = `SELECT * FROM "${tableName}"`;
439
+ if (options?.orderBy?.length) {
440
+ const orderClauses = options.orderBy.map(([col, dir]) => `"${col}" ${dir.toUpperCase()}`);
441
+ query += ` ORDER BY ${orderClauses.join(", ")}`;
442
+ }
443
+ if (options?.limit !== void 0) query += ` LIMIT ${options.limit}`;
444
+ if (options?.offset !== void 0) query += ` OFFSET ${options.offset}`;
445
+ return query;
446
+ }
447
+ /**
448
+ * Builds an INSERT query string from content object
449
+ */
450
+ function buildInsert(tableName, schema, content) {
451
+ const validColumns = new Set(schema.columns.map((c) => c.name));
452
+ const entries = Object.entries(content).filter(([key]) => validColumns.has(key));
453
+ if (entries.length === 0) throw new Error(`No valid columns provided for INSERT into ${tableName}`);
454
+ return `INSERT INTO "${tableName}" (${entries.map(([key]) => `"${key}"`).join(", ")}) VALUES (${entries.map(([, value]) => formatValue(value)).join(", ")})`;
455
+ }
456
+ /**
457
+ * Builds an UPDATE query string from content object
458
+ */
459
+ function buildUpdate(tableName, schema, pk, content) {
460
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
461
+ const validColumns = new Set(schema.columns.map((c) => c.name));
462
+ const entries = Object.entries(content).filter(([key]) => validColumns.has(key) && key !== pkColumn);
463
+ if (entries.length === 0) throw new Error(`No valid columns provided for UPDATE on ${tableName}`);
464
+ return `UPDATE "${tableName}" SET ${entries.map(([key, value]) => `"${key}" = ${formatValue(value)}`).join(", ")} WHERE "${pkColumn}" = '${escapeSQLString(pk)}'`;
465
+ }
466
+ /**
467
+ * Builds a DELETE query string by primary key
468
+ */
469
+ function buildDelete(tableName, schema, pk) {
470
+ return `DELETE FROM "${tableName}" WHERE "${schema.primaryKey[0] ?? "rowid"}" = '${escapeSQLString(pk)}'`;
471
+ }
472
+ /**
473
+ * Formats a value for SQL insertion
474
+ */
475
+ function formatValue(value) {
476
+ if (value === null || value === void 0) return "NULL";
477
+ if (typeof value === "number") return String(value);
478
+ if (typeof value === "boolean") return value ? "1" : "0";
479
+ if (value instanceof Date) return `'${value.toISOString()}'`;
480
+ if (typeof value === "object") return `'${escapeSQLString(JSON.stringify(value))}'`;
481
+ return `'${escapeSQLString(String(value))}'`;
482
+ }
483
+ /**
484
+ * Escapes a string for safe SQL insertion
485
+ */
486
+ function escapeSQLString(str) {
487
+ return str.replace(/'/g, "''");
488
+ }
489
+ /**
490
+ * Gets the last inserted rowid query string
491
+ */
492
+ function buildGetLastRowId() {
493
+ return "SELECT last_insert_rowid() as id";
494
+ }
495
+
496
+ //#endregion
497
+ //#region src/operations/crud.ts
498
+ /**
499
+ * Executes a raw SQL query and returns all rows
500
+ */
501
+ async function execAll$2(db, query) {
502
+ return db.all(_aigne_sqlite.sql.raw(query)).execute();
503
+ }
504
+ /**
505
+ * Executes a raw SQL query (for INSERT, UPDATE, DELETE)
506
+ */
507
+ async function execRun$1(db, query) {
508
+ await db.run(_aigne_sqlite.sql.raw(query)).execute();
509
+ }
510
+ /**
511
+ * CRUD operations for SQLite AFS
512
+ */
513
+ var CRUDOperations = class {
514
+ constructor(db, schemas, basePath = "") {
515
+ this.db = db;
516
+ this.schemas = schemas;
517
+ this.basePath = basePath;
518
+ }
519
+ /**
520
+ * Lists all tables
521
+ */
522
+ async listTables() {
523
+ const entries = [];
524
+ const buildOptions = { basePath: this.basePath };
525
+ for (const [name, schema] of this.schemas) {
526
+ const rowCount = (await execAll$2(this.db, `SELECT COUNT(*) as count FROM "${name}"`))[0]?.count ?? 0;
527
+ entries.push(buildTableEntry(name, schema, {
528
+ ...buildOptions,
529
+ rowCount
530
+ }));
531
+ }
532
+ return { data: entries };
533
+ }
534
+ /**
535
+ * Lists rows in a table
536
+ */
537
+ async listTable(table, options) {
538
+ const schema = this.schemas.get(table);
539
+ if (!schema) return {
540
+ data: [],
541
+ message: `Table '${table}' not found`
542
+ };
543
+ const buildOptions = { basePath: this.basePath };
544
+ const queryStr = buildSelectAll(table, {
545
+ limit: options?.limit ?? 100,
546
+ orderBy: options?.orderBy
547
+ });
548
+ return { data: (await execAll$2(this.db, queryStr)).map((row) => buildRowEntry(table, schema, row, buildOptions)) };
549
+ }
550
+ /**
551
+ * Reads a single row by primary key
552
+ */
553
+ async readRow(table, pk) {
554
+ const schema = this.schemas.get(table);
555
+ if (!schema) return { message: `Table '${table}' not found` };
556
+ const buildOptions = { basePath: this.basePath };
557
+ const row = (await execAll$2(this.db, buildSelectByPK(table, schema, pk)))[0];
558
+ if (!row) return { message: `Row with pk '${pk}' not found in table '${table}'` };
559
+ return { data: buildRowEntry(table, schema, row, buildOptions) };
560
+ }
561
+ /**
562
+ * Gets table schema
563
+ */
564
+ getSchema(table) {
565
+ const schema = this.schemas.get(table);
566
+ if (!schema) return { message: `Table '${table}' not found` };
567
+ return { data: buildSchemaEntry(table, schema, { basePath: this.basePath }) };
568
+ }
569
+ /**
570
+ * Lists attributes (columns) for a row
571
+ */
572
+ async listAttributes(table, pk) {
573
+ const schema = this.schemas.get(table);
574
+ if (!schema) return {
575
+ data: [],
576
+ message: `Table '${table}' not found`
577
+ };
578
+ const buildOptions = { basePath: this.basePath };
579
+ const row = (await execAll$2(this.db, buildSelectByPK(table, schema, pk)))[0];
580
+ if (!row) return {
581
+ data: [],
582
+ message: `Row with pk '${pk}' not found`
583
+ };
584
+ return { data: buildAttributeListEntry(table, schema, pk, row, buildOptions) };
585
+ }
586
+ /**
587
+ * Gets a single attribute (column value) for a row
588
+ */
589
+ async getAttribute(table, pk, column) {
590
+ const schema = this.schemas.get(table);
591
+ if (!schema) return { message: `Table '${table}' not found` };
592
+ if (!schema.columns.find((c) => c.name === column)) return { message: `Column '${column}' not found in table '${table}'` };
593
+ const buildOptions = { basePath: this.basePath };
594
+ const rows = await execAll$2(this.db, buildSelectByPK(table, schema, pk));
595
+ if (rows.length === 0) return { message: `Row with pk '${pk}' not found` };
596
+ return { data: buildAttributeEntry(table, pk, column, rows[0]?.[column], buildOptions) };
597
+ }
598
+ /**
599
+ * Gets row metadata
600
+ */
601
+ async getMeta(table, pk) {
602
+ const schema = this.schemas.get(table);
603
+ if (!schema) return { message: `Table '${table}' not found` };
604
+ const buildOptions = { basePath: this.basePath };
605
+ const row = (await execAll$2(this.db, buildSelectByPK(table, schema, pk)))[0];
606
+ if (!row) return { message: `Row with pk '${pk}' not found` };
607
+ return { data: buildMetaEntry(table, schema, pk, row, buildOptions) };
608
+ }
609
+ /**
610
+ * Creates a new row in a table
611
+ */
612
+ async createRow(table, content) {
613
+ const schema = this.schemas.get(table);
614
+ if (!schema) throw new Error(`Table '${table}' not found`);
615
+ const buildOptions = { basePath: this.basePath };
616
+ await execRun$1(this.db, buildInsert(table, schema, content));
617
+ const lastId = (await execAll$2(this.db, buildGetLastRowId()))[0]?.id;
618
+ if (lastId === void 0) throw new Error("Failed to get last inserted row ID");
619
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
620
+ const pk = content[pkColumn] !== void 0 ? String(content[pkColumn]) : String(lastId);
621
+ const row = (await execAll$2(this.db, buildSelectByPK(table, schema, pk)))[0];
622
+ if (!row) throw new Error("Failed to fetch inserted row");
623
+ return { data: buildRowEntry(table, schema, row, buildOptions) };
624
+ }
625
+ /**
626
+ * Updates an existing row
627
+ */
628
+ async updateRow(table, pk, content) {
629
+ const schema = this.schemas.get(table);
630
+ if (!schema) throw new Error(`Table '${table}' not found`);
631
+ const buildOptions = { basePath: this.basePath };
632
+ await execRun$1(this.db, buildUpdate(table, schema, pk, content));
633
+ const row = (await execAll$2(this.db, buildSelectByPK(table, schema, pk)))[0];
634
+ if (!row) throw new Error(`Row with pk '${pk}' not found after update`);
635
+ return { data: buildRowEntry(table, schema, row, buildOptions) };
636
+ }
637
+ /**
638
+ * Deletes a row by primary key
639
+ */
640
+ async deleteRow(table, pk) {
641
+ const schema = this.schemas.get(table);
642
+ if (!schema) throw new Error(`Table '${table}' not found`);
643
+ if ((await execAll$2(this.db, buildSelectByPK(table, schema, pk))).length === 0) return { message: `Row with pk '${pk}' not found in table '${table}'` };
644
+ await execRun$1(this.db, buildDelete(table, schema, pk));
645
+ return { message: `Deleted row '${pk}' from table '${table}'` };
646
+ }
647
+ /**
648
+ * Checks if a table exists
649
+ */
650
+ hasTable(table) {
651
+ return this.schemas.has(table);
652
+ }
653
+ /**
654
+ * Gets the schema for a table
655
+ */
656
+ getTableSchema(table) {
657
+ return this.schemas.get(table);
658
+ }
659
+ /**
660
+ * Updates the schemas map (after refresh)
661
+ */
662
+ setSchemas(schemas) {
663
+ this.schemas = schemas;
664
+ }
665
+ };
666
+
667
+ //#endregion
668
+ //#region src/operations/search.ts
669
+ /**
670
+ * Executes a raw SQL query and returns all rows
671
+ */
672
+ async function execAll$1(db, query) {
673
+ return db.all(_aigne_sqlite.sql.raw(query)).execute();
674
+ }
675
+ /**
676
+ * FTS5 Search operations for SQLite AFS
677
+ */
678
+ var FTSSearch = class {
679
+ constructor(db, schemas, config, basePath = "") {
680
+ this.db = db;
681
+ this.schemas = schemas;
682
+ this.config = config;
683
+ this.basePath = basePath;
684
+ }
685
+ /**
686
+ * Performs full-text search across configured tables
687
+ */
688
+ async search(query, options) {
689
+ if (!this.config.enabled) return {
690
+ data: [],
691
+ message: "Full-text search is not enabled"
692
+ };
693
+ const results = [];
694
+ const limit = options?.limit ?? 50;
695
+ const buildOptions = { basePath: this.basePath };
696
+ const tablesToSearch = options?.tables ? options.tables.filter((t) => this.config.tables.has(t)) : Array.from(this.config.tables.keys());
697
+ const ftsQuery = this.prepareFTSQuery(query, options?.caseSensitive);
698
+ for (const tableName of tablesToSearch) {
699
+ const tableConfig = this.config.tables.get(tableName);
700
+ const schema = this.schemas.get(tableName);
701
+ if (!tableConfig || !schema) continue;
702
+ const ftsTableName = `${tableName}_fts`;
703
+ try {
704
+ if (!await this.ftsTableExists(ftsTableName)) continue;
705
+ const highlightColumn = tableConfig.columns[0] ?? "";
706
+ const highlightIndex = highlightColumn ? tableConfig.columns.indexOf(highlightColumn) : 0;
707
+ const rows = await execAll$1(this.db, `
708
+ SELECT t.*, highlight("${ftsTableName}", ${highlightIndex}, '<mark>', '</mark>') as snippet
709
+ FROM "${ftsTableName}" fts
710
+ JOIN "${tableName}" t ON fts.rowid = t.rowid
711
+ WHERE "${ftsTableName}" MATCH '${ftsQuery}'
712
+ LIMIT ${Math.ceil(limit / tablesToSearch.length)}
713
+ `);
714
+ for (const row of rows) {
715
+ const { snippet, ...rowData } = row;
716
+ results.push(buildSearchEntry(tableName, schema, rowData, snippet, buildOptions));
717
+ }
718
+ } catch (error) {
719
+ console.warn(`FTS search failed for table ${tableName}:`, error);
720
+ }
721
+ if (results.length >= limit) break;
722
+ }
723
+ return {
724
+ data: results.slice(0, limit),
725
+ message: results.length === 0 ? `No results found for "${query}"` : void 0
726
+ };
727
+ }
728
+ /**
729
+ * Searches within a specific table
730
+ */
731
+ async searchTable(tableName, query, options) {
732
+ return this.search(query, {
733
+ ...options,
734
+ tables: [tableName]
735
+ });
736
+ }
737
+ /**
738
+ * Checks if FTS is configured for a table
739
+ */
740
+ hasFTS(tableName) {
741
+ return this.config.enabled && this.config.tables.has(tableName);
742
+ }
743
+ /**
744
+ * Gets FTS configuration for a table
745
+ */
746
+ getFTSConfig(tableName) {
747
+ return this.config.tables.get(tableName);
748
+ }
749
+ /**
750
+ * Checks if an FTS table exists
751
+ */
752
+ async ftsTableExists(ftsTableName) {
753
+ return (await execAll$1(this.db, `SELECT name FROM sqlite_master WHERE type = 'table' AND name = '${ftsTableName}'`)).length > 0;
754
+ }
755
+ /**
756
+ * Prepares a query string for FTS5
757
+ * Handles special characters and case sensitivity
758
+ */
759
+ prepareFTSQuery(query, _caseSensitive) {
760
+ let prepared = query.replace(/"/g, "\"\"").replace(/'/g, "''");
761
+ if (prepared.includes(" ") && !prepared.startsWith("\"")) prepared = `"${prepared}"`;
762
+ return prepared;
763
+ }
764
+ /**
765
+ * Updates the schemas map (after refresh)
766
+ */
767
+ setSchemas(schemas) {
768
+ this.schemas = schemas;
769
+ }
770
+ /**
771
+ * Simple search fallback when FTS is not available
772
+ * Uses LIKE queries on specified columns
773
+ */
774
+ async simpleLikeSearch(tableName, query, columns, options) {
775
+ const schema = this.schemas.get(tableName);
776
+ if (!schema) return {
777
+ data: [],
778
+ message: `Table '${tableName}' not found`
779
+ };
780
+ const buildOptions = { basePath: this.basePath };
781
+ const limit = options?.limit ?? 50;
782
+ const escapedQuery = query.replace(/'/g, "''");
783
+ const conditions = columns.filter((col) => schema.columns.some((c) => c.name === col)).map((col) => `"${col}" LIKE '%${escapedQuery}%'`).join(" OR ");
784
+ if (!conditions) return {
785
+ data: [],
786
+ message: "No valid columns to search"
787
+ };
788
+ return { data: (await execAll$1(this.db, `SELECT * FROM "${tableName}" WHERE ${conditions} LIMIT ${limit}`)).map((row) => buildSearchEntry(tableName, schema, row, void 0, buildOptions)) };
789
+ }
790
+ };
791
+ /**
792
+ * Creates FTS configuration from options
793
+ */
794
+ function createFTSConfig(options) {
795
+ const config = {
796
+ enabled: options?.enabled ?? false,
797
+ tables: /* @__PURE__ */ new Map()
798
+ };
799
+ if (options?.tables) for (const [table, columns] of Object.entries(options.tables)) config.tables.set(table, { columns });
800
+ return config;
801
+ }
802
+
803
+ //#endregion
804
+ //#region src/router/path-router.ts
805
+ /**
806
+ * Creates a radix3 router for SQLite AFS path routing
807
+ *
808
+ * Routes:
809
+ * - / → listTables
810
+ * - /:table → listTable
811
+ * - /:table/new → createRow
812
+ * - /:table/@schema → getSchema
813
+ * - /:table/:pk → readRow
814
+ * - /:table/:pk/@attr → listAttributes
815
+ * - /:table/:pk/@attr/:column → getAttribute
816
+ * - /:table/:pk/@meta → getMeta
817
+ * - /:table/:pk/@actions → listActions
818
+ * - /:table/:pk/@actions/:action → executeAction
819
+ */
820
+ function createPathRouter() {
821
+ return (0, radix3.createRouter)({ routes: {
822
+ "/": { action: "listTables" },
823
+ "/:table": { action: "listTable" },
824
+ "/:table/new": { action: "createRow" },
825
+ "/:table/@schema": { action: "getSchema" },
826
+ "/:table/:pk": { action: "readRow" },
827
+ "/:table/:pk/@attr": { action: "listAttributes" },
828
+ "/:table/:pk/@attr/:column": { action: "getAttribute" },
829
+ "/:table/:pk/@meta": { action: "getMeta" },
830
+ "/:table/:pk/@actions": { action: "listActions" },
831
+ "/:table/:pk/@actions/:action": { action: "executeAction" }
832
+ } });
833
+ }
834
+ /**
835
+ * Parses a path and returns the matched route with params
836
+ * @param router - The radix3 router instance
837
+ * @param path - The path to match
838
+ * @returns RouteMatch if matched, undefined otherwise
839
+ */
840
+ function matchPath(router, path) {
841
+ const result = router.lookup(path);
842
+ if (!result) return void 0;
843
+ return {
844
+ action: result.action,
845
+ params: result.params ?? {}
846
+ };
847
+ }
848
+ /**
849
+ * Builds a path from components
850
+ */
851
+ function buildPath(table, pk, suffix) {
852
+ const parts = ["/"];
853
+ if (table) parts.push(table);
854
+ if (pk) parts.push(pk);
855
+ if (suffix) parts.push(suffix);
856
+ return parts.join("/").replace(/\/+/g, "/");
857
+ }
858
+ /**
859
+ * Checks if a path segment is a virtual path (@attr, @meta, @actions, @schema)
860
+ */
861
+ function isVirtualPath(segment) {
862
+ return segment.startsWith("@");
863
+ }
864
+ /**
865
+ * Gets the type of virtual path
866
+ */
867
+ function getVirtualPathType(segment) {
868
+ if (segment === "@attr") return "attr";
869
+ if (segment === "@meta") return "meta";
870
+ if (segment === "@actions") return "actions";
871
+ if (segment === "@schema") return "schema";
872
+ return null;
873
+ }
874
+
875
+ //#endregion
876
+ //#region src/schema/types.ts
877
+ /**
878
+ * System tables that should be excluded from introspection
879
+ */
880
+ const SYSTEM_TABLES = [
881
+ "sqlite_sequence",
882
+ "sqlite_stat1",
883
+ "sqlite_stat2",
884
+ "sqlite_stat3",
885
+ "sqlite_stat4"
886
+ ];
887
+
888
+ //#endregion
889
+ //#region src/schema/introspector.ts
890
+ /**
891
+ * Executes a raw SQL query and returns all rows
892
+ */
893
+ async function execAll(db, query) {
894
+ return db.all(_aigne_sqlite.sql.raw(query)).execute();
895
+ }
896
+ /**
897
+ * Executes a raw SQL query (for INSERT, UPDATE, DELETE)
898
+ */
899
+ async function execRun(db, query) {
900
+ await db.run(_aigne_sqlite.sql.raw(query)).execute();
901
+ }
902
+ /**
903
+ * Maps raw PRAGMA table_info row to ColumnInfo
904
+ */
905
+ function mapColumn(row) {
906
+ return {
907
+ name: row.name,
908
+ type: row.type,
909
+ notnull: row.notnull === 1,
910
+ pk: row.pk,
911
+ dfltValue: row.dflt_value
912
+ };
913
+ }
914
+ /**
915
+ * Maps raw PRAGMA foreign_key_list row to ForeignKeyInfo
916
+ */
917
+ function mapForeignKey(row) {
918
+ return {
919
+ id: row.id,
920
+ seq: row.seq,
921
+ table: row.table,
922
+ from: row.from,
923
+ to: row.to,
924
+ onUpdate: row.on_update,
925
+ onDelete: row.on_delete,
926
+ match: row.match
927
+ };
928
+ }
929
+ /**
930
+ * Maps raw PRAGMA index_list row to IndexInfo
931
+ */
932
+ function mapIndex(row) {
933
+ return {
934
+ seq: row.seq,
935
+ name: row.name,
936
+ unique: row.unique === 1,
937
+ origin: row.origin,
938
+ partial: row.partial === 1
939
+ };
940
+ }
941
+ /**
942
+ * Schema introspector that uses SQLite PRAGMA queries to discover database schema
943
+ */
944
+ var SchemaIntrospector = class {
945
+ /**
946
+ * Introspects all tables in the database
947
+ * @param db - Drizzle database instance
948
+ * @param options - Introspection options
949
+ * @returns Map of table name to TableSchema
950
+ */
951
+ async introspect(db, options) {
952
+ const schemas = /* @__PURE__ */ new Map();
953
+ const tablesResult = await execAll(db, `
954
+ SELECT name FROM sqlite_master
955
+ WHERE type = 'table'
956
+ AND name NOT LIKE 'sqlite_%'
957
+ AND name NOT LIKE '%_fts%'
958
+ ORDER BY name
959
+ `);
960
+ for (const { name } of tablesResult) {
961
+ if (SYSTEM_TABLES.includes(name)) continue;
962
+ if (options?.tables && !options.tables.includes(name)) continue;
963
+ if (options?.excludeTables?.includes(name)) continue;
964
+ const schema = await this.introspectTable(db, name);
965
+ schemas.set(name, schema);
966
+ }
967
+ return schemas;
968
+ }
969
+ /**
970
+ * Introspects a single table
971
+ * @param db - Drizzle database instance
972
+ * @param tableName - Name of the table to introspect
973
+ * @returns TableSchema for the specified table
974
+ */
975
+ async introspectTable(db, tableName) {
976
+ const columns = (await execAll(db, `PRAGMA table_info("${tableName}")`)).map(mapColumn);
977
+ return {
978
+ name: tableName,
979
+ columns,
980
+ primaryKey: columns.filter((c) => c.pk > 0).map((c) => c.name),
981
+ foreignKeys: (await execAll(db, `PRAGMA foreign_key_list("${tableName}")`)).map(mapForeignKey),
982
+ indexes: (await execAll(db, `PRAGMA index_list("${tableName}")`)).map(mapIndex)
983
+ };
984
+ }
985
+ /**
986
+ * Gets the primary key column name for a table
987
+ * Returns the first PK column, or 'rowid' if no explicit PK
988
+ */
989
+ getPrimaryKeyColumn(schema) {
990
+ if (schema.primaryKey.length > 0 && schema.primaryKey[0]) return schema.primaryKey[0];
991
+ return "rowid";
992
+ }
993
+ /**
994
+ * Checks if a table has FTS (Full-Text Search) enabled
995
+ */
996
+ async hasFTS(db, tableName) {
997
+ return (await execAll(db, `
998
+ SELECT name FROM sqlite_master
999
+ WHERE type = 'table'
1000
+ AND name = '${`${tableName}_fts`}'
1001
+ `)).length > 0;
1002
+ }
1003
+ /**
1004
+ * Creates FTS5 table for full-text search on specified columns
1005
+ */
1006
+ async createFTS(db, tableName, columns, options) {
1007
+ const ftsTableName = `${tableName}_fts`;
1008
+ const contentTable = options?.contentTable ?? tableName;
1009
+ const contentRowid = options?.contentRowid ?? "rowid";
1010
+ const columnList = columns.join(", ");
1011
+ await execRun(db, `
1012
+ CREATE VIRTUAL TABLE IF NOT EXISTS "${ftsTableName}" USING fts5(
1013
+ ${columnList},
1014
+ content="${contentTable}",
1015
+ content_rowid="${contentRowid}"
1016
+ )
1017
+ `);
1018
+ await execRun(db, `
1019
+ CREATE TRIGGER IF NOT EXISTS "${tableName}_ai" AFTER INSERT ON "${tableName}" BEGIN
1020
+ INSERT INTO "${ftsTableName}"(rowid, ${columnList}) VALUES (new.${contentRowid}, ${columns.map((c) => `new."${c}"`).join(", ")});
1021
+ END
1022
+ `);
1023
+ await execRun(db, `
1024
+ CREATE TRIGGER IF NOT EXISTS "${tableName}_ad" AFTER DELETE ON "${tableName}" BEGIN
1025
+ INSERT INTO "${ftsTableName}"("${ftsTableName}", rowid, ${columnList}) VALUES ('delete', old.${contentRowid}, ${columns.map((c) => `old."${c}"`).join(", ")});
1026
+ END
1027
+ `);
1028
+ await execRun(db, `
1029
+ CREATE TRIGGER IF NOT EXISTS "${tableName}_au" AFTER UPDATE ON "${tableName}" BEGIN
1030
+ INSERT INTO "${ftsTableName}"("${ftsTableName}", rowid, ${columnList}) VALUES ('delete', old.${contentRowid}, ${columns.map((c) => `old."${c}"`).join(", ")});
1031
+ INSERT INTO "${ftsTableName}"(rowid, ${columnList}) VALUES (new.${contentRowid}, ${columns.map((c) => `new."${c}"`).join(", ")});
1032
+ END
1033
+ `);
1034
+ }
1035
+ /**
1036
+ * Rebuilds FTS index for a table (useful after bulk inserts)
1037
+ */
1038
+ async rebuildFTS(db, tableName) {
1039
+ const ftsTableName = `${tableName}_fts`;
1040
+ await execRun(db, `INSERT INTO "${ftsTableName}"("${ftsTableName}") VALUES ('rebuild')`);
1041
+ }
1042
+ };
1043
+
1044
+ //#endregion
1045
+ //#region src/sqlite-afs.ts
1046
+ /**
1047
+ * SQLite AFS Module
1048
+ *
1049
+ * Exposes SQLite databases as AFS nodes with full CRUD support,
1050
+ * schema introspection, FTS5 search, and virtual paths (@attr, @meta, @actions).
1051
+ */
1052
+ var SQLiteAFS = class SQLiteAFS {
1053
+ name;
1054
+ description;
1055
+ accessMode;
1056
+ db;
1057
+ schemas = /* @__PURE__ */ new Map();
1058
+ router;
1059
+ crud;
1060
+ ftsSearch;
1061
+ actions;
1062
+ ftsConfig;
1063
+ initialized = false;
1064
+ constructor(options) {
1065
+ this.options = options;
1066
+ this.name = options.name ?? "sqlite-afs";
1067
+ this.description = options.description ?? `SQLite database: ${options.url}`;
1068
+ this.accessMode = options.accessMode ?? "readwrite";
1069
+ this.ftsConfig = createFTSConfig(options.fts);
1070
+ this.actions = new ActionsRegistry();
1071
+ registerBuiltInActions(this.actions);
1072
+ }
1073
+ /**
1074
+ * Returns the Zod schema for configuration validation
1075
+ */
1076
+ static schema() {
1077
+ return sqliteAFSConfigSchema;
1078
+ }
1079
+ /**
1080
+ * Loads a module instance from configuration
1081
+ */
1082
+ static async load({ parsed }) {
1083
+ return new SQLiteAFS(sqliteAFSConfigSchema.parse(parsed));
1084
+ }
1085
+ /**
1086
+ * Called when the module is mounted to AFS
1087
+ */
1088
+ async onMount(_afs) {
1089
+ await this.initialize();
1090
+ }
1091
+ /**
1092
+ * Initializes the database connection and introspects schema
1093
+ */
1094
+ async initialize() {
1095
+ if (this.initialized) return;
1096
+ this.db = await (0, _aigne_sqlite.initDatabase)({
1097
+ url: this.options.url,
1098
+ wal: this.options.wal ?? true
1099
+ });
1100
+ const db = this.db;
1101
+ this.schemas = await new SchemaIntrospector().introspect(db, {
1102
+ tables: this.options.tables,
1103
+ excludeTables: this.options.excludeTables
1104
+ });
1105
+ this.router = createPathRouter();
1106
+ this.crud = new CRUDOperations(db, this.schemas, "");
1107
+ this.ftsSearch = new FTSSearch(db, this.schemas, this.ftsConfig, "");
1108
+ this.initialized = true;
1109
+ }
1110
+ /**
1111
+ * Ensures the module is initialized
1112
+ */
1113
+ async ensureInitialized() {
1114
+ if (!this.initialized) await this.initialize();
1115
+ }
1116
+ /**
1117
+ * Lists entries at a path
1118
+ */
1119
+ async list(path, options) {
1120
+ await this.ensureInitialized();
1121
+ const match = matchPath(this.router, path);
1122
+ if (!match) return { data: [] };
1123
+ switch (match.action) {
1124
+ case "listTables": return this.crud.listTables();
1125
+ case "listTable":
1126
+ if (!match.params.table) return { data: [] };
1127
+ return this.crud.listTable(match.params.table, options);
1128
+ case "listAttributes":
1129
+ if (!match.params.table || !match.params.pk) return { data: [] };
1130
+ return this.crud.listAttributes(match.params.table, match.params.pk);
1131
+ case "listActions":
1132
+ if (!match.params.table || !match.params.pk) return { data: [] };
1133
+ return this.listActions(match.params.table, match.params.pk);
1134
+ default: return { data: [] };
1135
+ }
1136
+ }
1137
+ /**
1138
+ * Reads an entry at a path
1139
+ */
1140
+ async read(path, _options) {
1141
+ await this.ensureInitialized();
1142
+ const match = matchPath(this.router, path);
1143
+ if (!match) return {};
1144
+ switch (match.action) {
1145
+ case "readRow":
1146
+ if (!match.params.table || !match.params.pk) return {};
1147
+ return this.crud.readRow(match.params.table, match.params.pk);
1148
+ case "getSchema":
1149
+ if (!match.params.table) return {};
1150
+ return this.crud.getSchema(match.params.table);
1151
+ case "getAttribute":
1152
+ if (!match.params.table || !match.params.pk || !match.params.column) return {};
1153
+ return this.crud.getAttribute(match.params.table, match.params.pk, match.params.column);
1154
+ case "getMeta":
1155
+ if (!match.params.table || !match.params.pk) return {};
1156
+ return this.crud.getMeta(match.params.table, match.params.pk);
1157
+ default: return {};
1158
+ }
1159
+ }
1160
+ /**
1161
+ * Writes an entry at a path
1162
+ */
1163
+ async write(path, content, _options) {
1164
+ await this.ensureInitialized();
1165
+ if (this.accessMode === "readonly") throw new Error("Module is in readonly mode");
1166
+ const match = matchPath(this.router, path);
1167
+ if (!match) throw new Error(`Invalid path: ${path}`);
1168
+ switch (match.action) {
1169
+ case "createRow":
1170
+ if (!match.params.table) throw new Error("Table name required for create");
1171
+ return this.crud.createRow(match.params.table, content.content ?? content);
1172
+ case "readRow":
1173
+ if (!match.params.table || !match.params.pk) throw new Error("Table and primary key required for update");
1174
+ return this.crud.updateRow(match.params.table, match.params.pk, content.content ?? content);
1175
+ case "executeAction":
1176
+ if (!match.params.table || !match.params.action) throw new Error("Table and action name required");
1177
+ return this.executeAction(match.params.table, match.params.pk, match.params.action, content.content ?? content);
1178
+ default: throw new Error(`Write not supported for path: ${path}`);
1179
+ }
1180
+ }
1181
+ /**
1182
+ * Deletes an entry at a path
1183
+ */
1184
+ async delete(path, _options) {
1185
+ await this.ensureInitialized();
1186
+ if (this.accessMode === "readonly") throw new Error("Module is in readonly mode");
1187
+ const match = matchPath(this.router, path);
1188
+ if (!match || match.action !== "readRow") throw new Error(`Delete not supported for path: ${path}`);
1189
+ if (!match.params.table || !match.params.pk) throw new Error("Table and primary key required for delete");
1190
+ return this.crud.deleteRow(match.params.table, match.params.pk);
1191
+ }
1192
+ /**
1193
+ * Searches for entries matching a query
1194
+ */
1195
+ async search(path, query, options) {
1196
+ await this.ensureInitialized();
1197
+ const match = matchPath(this.router, path);
1198
+ if (match?.params.table) return this.ftsSearch.searchTable(match.params.table, query, options);
1199
+ return this.ftsSearch.search(query, options);
1200
+ }
1201
+ /**
1202
+ * Executes a module operation
1203
+ */
1204
+ async exec(path, args, _options) {
1205
+ await this.ensureInitialized();
1206
+ const match = matchPath(this.router, path);
1207
+ if (match?.action === "executeAction" && match.params.table && match.params.action) return { data: (await this.executeAction(match.params.table, match.params.pk, match.params.action, args)).data };
1208
+ throw new Error(`Exec not supported for path: ${path}`);
1209
+ }
1210
+ /**
1211
+ * Lists available actions for a row
1212
+ */
1213
+ listActions(table, pk) {
1214
+ return { data: buildActionsListEntry(table, pk, this.actions.listNames({ rowLevel: true }), { basePath: "" }) };
1215
+ }
1216
+ /**
1217
+ * Executes an action
1218
+ */
1219
+ async executeAction(table, pk, actionName, params) {
1220
+ if (!this.schemas.get(table)) throw new Error(`Table '${table}' not found`);
1221
+ let row;
1222
+ if (pk) row = (await this.crud.readRow(table, pk)).data?.content;
1223
+ const ctx = {
1224
+ db: this.db,
1225
+ schemas: this.schemas,
1226
+ table,
1227
+ pk,
1228
+ row,
1229
+ module: {
1230
+ refreshSchema: () => this.refreshSchema(),
1231
+ exportTable: (t, f) => this.exportTable(t, f)
1232
+ }
1233
+ };
1234
+ const result = await this.actions.execute(actionName, ctx, params);
1235
+ if (!result.success) throw new Error(result.message ?? "Action failed");
1236
+ return { data: {
1237
+ id: `${table}:${pk ?? ""}:@actions:${actionName}`,
1238
+ path: pk ? `/${table}/${pk}/@actions/${actionName}` : `/${table}/@actions/${actionName}`,
1239
+ content: result.data
1240
+ } };
1241
+ }
1242
+ /**
1243
+ * Refreshes the schema cache
1244
+ */
1245
+ async refreshSchema() {
1246
+ const db = this.db;
1247
+ this.schemas = await new SchemaIntrospector().introspect(db, {
1248
+ tables: this.options.tables,
1249
+ excludeTables: this.options.excludeTables
1250
+ });
1251
+ this.crud.setSchemas(this.schemas);
1252
+ this.ftsSearch.setSchemas(this.schemas);
1253
+ }
1254
+ /**
1255
+ * Exports table data in specified format
1256
+ */
1257
+ async exportTable(table, format) {
1258
+ const listResult = await this.crud.listTable(table, { limit: 1e4 });
1259
+ if (format === "csv") {
1260
+ const schema = this.schemas.get(table);
1261
+ if (!schema) throw new Error(`Table '${table}' not found`);
1262
+ return `${schema.columns.map((c) => c.name).join(",")}\n${listResult.data.map((entry) => {
1263
+ const content = entry.content;
1264
+ return schema.columns.map((c) => {
1265
+ const val = content[c.name];
1266
+ if (val === null || val === void 0) return "";
1267
+ if (typeof val === "string" && (val.includes(",") || val.includes("\""))) return `"${val.replace(/"/g, "\"\"")}"`;
1268
+ return String(val);
1269
+ }).join(",");
1270
+ }).join("\n")}`;
1271
+ }
1272
+ return listResult.data.map((entry) => entry.content);
1273
+ }
1274
+ /**
1275
+ * Registers a custom action
1276
+ */
1277
+ registerAction(name, handler, options) {
1278
+ this.actions.registerSimple(name, async (ctx, params) => ({
1279
+ success: true,
1280
+ data: await handler(ctx, params)
1281
+ }), options);
1282
+ }
1283
+ /**
1284
+ * Gets table schemas (for external access)
1285
+ */
1286
+ getSchemas() {
1287
+ return this.schemas;
1288
+ }
1289
+ /**
1290
+ * Gets the database instance (for advanced operations)
1291
+ */
1292
+ getDatabase() {
1293
+ return this.db;
1294
+ }
1295
+ };
1296
+
1297
+ //#endregion
1298
+ exports.ActionsRegistry = ActionsRegistry;
1299
+ exports.CRUDOperations = CRUDOperations;
1300
+ exports.FTSSearch = FTSSearch;
1301
+ exports.SQLiteAFS = SQLiteAFS;
1302
+ exports.SchemaIntrospector = SchemaIntrospector;
1303
+ exports.buildActionsListEntry = buildActionsListEntry;
1304
+ exports.buildAttributeEntry = buildAttributeEntry;
1305
+ exports.buildAttributeListEntry = buildAttributeListEntry;
1306
+ exports.buildDelete = buildDelete;
1307
+ exports.buildGetLastRowId = buildGetLastRowId;
1308
+ exports.buildInsert = buildInsert;
1309
+ exports.buildMetaEntry = buildMetaEntry;
1310
+ exports.buildPath = buildPath;
1311
+ exports.buildRowEntry = buildRowEntry;
1312
+ exports.buildSchemaEntry = buildSchemaEntry;
1313
+ exports.buildSearchEntry = buildSearchEntry;
1314
+ exports.buildSelectAll = buildSelectAll;
1315
+ exports.buildSelectByPK = buildSelectByPK;
1316
+ exports.buildTableEntry = buildTableEntry;
1317
+ exports.buildUpdate = buildUpdate;
1318
+ exports.createFTSConfig = createFTSConfig;
1319
+ exports.createPathRouter = createPathRouter;
1320
+ exports.getVirtualPathType = getVirtualPathType;
1321
+ exports.isVirtualPath = isVirtualPath;
1322
+ exports.matchPath = matchPath;
1323
+ exports.registerBuiltInActions = registerBuiltInActions;
1324
+ exports.sqliteAFSConfigSchema = sqliteAFSConfigSchema;