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

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 (93) hide show
  1. package/README.md +3 -3
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/_virtual/rolldown_runtime.mjs +7 -0
  5. package/dist/actions/built-in.cjs +1262 -15
  6. package/dist/actions/built-in.d.cts.map +1 -1
  7. package/dist/actions/built-in.d.mts.map +1 -1
  8. package/dist/actions/built-in.mjs +1262 -15
  9. package/dist/actions/built-in.mjs.map +1 -1
  10. package/dist/actions/operators.cjs +156 -0
  11. package/dist/actions/operators.mjs +157 -0
  12. package/dist/actions/operators.mjs.map +1 -0
  13. package/dist/actions/registry.cjs +35 -3
  14. package/dist/actions/registry.d.cts +36 -17
  15. package/dist/actions/registry.d.cts.map +1 -1
  16. package/dist/actions/registry.d.mts +36 -17
  17. package/dist/actions/registry.d.mts.map +1 -1
  18. package/dist/actions/registry.mjs +35 -3
  19. package/dist/actions/registry.mjs.map +1 -1
  20. package/dist/actions/types.cjs +33 -0
  21. package/dist/actions/types.d.cts +46 -5
  22. package/dist/actions/types.d.cts.map +1 -1
  23. package/dist/actions/types.d.mts +46 -5
  24. package/dist/actions/types.d.mts.map +1 -1
  25. package/dist/actions/types.mjs +32 -0
  26. package/dist/actions/types.mjs.map +1 -0
  27. package/dist/config.cjs +1 -1
  28. package/dist/config.d.cts +9 -36
  29. package/dist/config.d.cts.map +1 -1
  30. package/dist/config.d.mts +9 -36
  31. package/dist/config.d.mts.map +1 -1
  32. package/dist/config.mjs +1 -1
  33. package/dist/config.mjs.map +1 -1
  34. package/dist/index.cjs +4 -3
  35. package/dist/index.d.cts +4 -3
  36. package/dist/index.d.mts +4 -3
  37. package/dist/index.mjs +3 -2
  38. package/dist/node/builder.cjs +74 -91
  39. package/dist/node/builder.d.cts +11 -15
  40. package/dist/node/builder.d.cts.map +1 -1
  41. package/dist/node/builder.d.mts +11 -15
  42. package/dist/node/builder.d.mts.map +1 -1
  43. package/dist/node/builder.mjs +72 -89
  44. package/dist/node/builder.mjs.map +1 -1
  45. package/dist/operations/crud.cjs +24 -68
  46. package/dist/operations/crud.d.cts +23 -38
  47. package/dist/operations/crud.d.cts.map +1 -1
  48. package/dist/operations/crud.d.mts +23 -38
  49. package/dist/operations/crud.d.mts.map +1 -1
  50. package/dist/operations/crud.mjs +25 -69
  51. package/dist/operations/crud.mjs.map +1 -1
  52. package/dist/operations/query-builder.cjs +2 -2
  53. package/dist/operations/query-builder.d.cts.map +1 -1
  54. package/dist/operations/query-builder.d.mts.map +1 -1
  55. package/dist/operations/query-builder.mjs +2 -2
  56. package/dist/operations/query-builder.mjs.map +1 -1
  57. package/dist/operations/search.cjs +5 -11
  58. package/dist/operations/search.d.cts +19 -23
  59. package/dist/operations/search.d.cts.map +1 -1
  60. package/dist/operations/search.d.mts +19 -23
  61. package/dist/operations/search.d.mts.map +1 -1
  62. package/dist/operations/search.mjs +5 -11
  63. package/dist/operations/search.mjs.map +1 -1
  64. package/dist/router/path-router.cjs +7 -15
  65. package/dist/router/path-router.d.cts +4 -7
  66. package/dist/router/path-router.d.cts.map +1 -1
  67. package/dist/router/path-router.d.mts +4 -7
  68. package/dist/router/path-router.d.mts.map +1 -1
  69. package/dist/router/path-router.mjs +7 -15
  70. package/dist/router/path-router.mjs.map +1 -1
  71. package/dist/router/types.d.cts.map +1 -1
  72. package/dist/router/types.d.mts.map +1 -1
  73. package/dist/schema/introspector.d.cts +19 -19
  74. package/dist/schema/introspector.d.cts.map +1 -1
  75. package/dist/schema/introspector.d.mts +19 -19
  76. package/dist/schema/introspector.d.mts.map +1 -1
  77. package/dist/schema/service.cjs +86 -0
  78. package/dist/schema/service.d.cts +52 -0
  79. package/dist/schema/service.d.cts.map +1 -0
  80. package/dist/schema/service.d.mts +52 -0
  81. package/dist/schema/service.d.mts.map +1 -0
  82. package/dist/schema/service.mjs +87 -0
  83. package/dist/schema/service.mjs.map +1 -0
  84. package/dist/schema/types.d.cts.map +1 -1
  85. package/dist/schema/types.d.mts.map +1 -1
  86. package/dist/sqlite-afs.cjs +693 -121
  87. package/dist/sqlite-afs.d.cts +279 -101
  88. package/dist/sqlite-afs.d.cts.map +1 -1
  89. package/dist/sqlite-afs.d.mts +279 -101
  90. package/dist/sqlite-afs.d.mts.map +1 -1
  91. package/dist/sqlite-afs.mjs +696 -123
  92. package/dist/sqlite-afs.mjs.map +1 -1
  93. package/package.json +5 -4
@@ -1,34 +1,48 @@
1
+ import { __require } from "./_virtual/rolldown_runtime.mjs";
1
2
  import { registerBuiltInActions } from "./actions/built-in.mjs";
2
3
  import { ActionsRegistry } from "./actions/registry.mjs";
3
4
  import { sqliteAFSConfigSchema } from "./config.mjs";
4
- import { buildActionsListEntry } from "./node/builder.mjs";
5
+ import { buildActionsListEntry, buildRootActionsListEntry, buildRootEntry, buildTableActionsListEntry, buildTableEntry } from "./node/builder.mjs";
5
6
  import { CRUDOperations } from "./operations/crud.mjs";
6
7
  import { FTSSearch, createFTSConfig } from "./operations/search.mjs";
7
- import { createPathRouter, matchPath } from "./router/path-router.mjs";
8
- import { SchemaIntrospector } from "./schema/introspector.mjs";
9
- import { initDatabase } from "@aigne/sqlite";
8
+ import { SchemaService } from "./schema/service.mjs";
9
+ import { __decorate } from "./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs";
10
+ import { initDatabase, sql } from "@aigne/sqlite";
11
+ import { AFSNotFoundError } from "@aigne/afs";
12
+ import { z } from "zod";
13
+ import { AFSBaseProvider, Actions, Delete, Explain, List, Meta, Read, Search, Stat, Write } from "@aigne/afs/provider";
10
14
 
11
15
  //#region src/sqlite-afs.ts
12
16
  /**
13
17
  * SQLite AFS Module
14
18
  *
15
19
  * Exposes SQLite databases as AFS nodes with full CRUD support,
16
- * schema introspection, FTS5 search, and virtual paths (@attr, @meta, @actions).
20
+ * schema introspection, FTS5 search, and virtual paths (.meta, .actions).
17
21
  */
18
- var SQLiteAFS = class SQLiteAFS {
22
+ var SQLiteAFS = class SQLiteAFS extends AFSBaseProvider {
19
23
  name;
20
24
  description;
21
25
  accessMode;
22
26
  db;
23
- schemas = /* @__PURE__ */ new Map();
24
- router;
27
+ schemaService;
25
28
  crud;
26
29
  ftsSearch;
27
30
  actions;
28
31
  ftsConfig;
29
32
  initialized = false;
30
33
  constructor(options) {
34
+ super();
31
35
  this.options = options;
36
+ if (options.localPath && !options.url) options.url = `file:${options.localPath}`;
37
+ if (options.url.startsWith("file:")) {
38
+ const { existsSync, mkdirSync, writeFileSync } = __require("node:fs");
39
+ const { dirname } = __require("node:path");
40
+ const dbPath = options.url.slice(5);
41
+ if (!existsSync(dbPath)) {
42
+ mkdirSync(dirname(dbPath), { recursive: true });
43
+ writeFileSync(dbPath, "");
44
+ }
45
+ }
32
46
  this.name = options.name ?? "sqlite-afs";
33
47
  this.description = options.description ?? `SQLite database: ${options.url}`;
34
48
  this.accessMode = options.accessMode ?? "readwrite";
@@ -42,20 +56,24 @@ var SQLiteAFS = class SQLiteAFS {
42
56
  static schema() {
43
57
  return sqliteAFSConfigSchema;
44
58
  }
45
- /**
46
- * Loads a module instance from configuration
47
- */
48
- static async load({ parsed }) {
49
- return new SQLiteAFS(sqliteAFSConfigSchema.parse(parsed));
59
+ static manifest() {
60
+ return {
61
+ name: "sqlite",
62
+ description: "SQLite database tables as directories, rows as nodes.\n- Browse tables and rows, full-text search (FTS5), schema introspection\n- Exec actions: `insert`, `update`, `delete` at table/row level, custom SQL\n- Path structure: `/{table}/{primary-key}`",
63
+ uriTemplate: "sqlite://{localPath+}",
64
+ category: "database",
65
+ schema: z.object({ localPath: z.string() }),
66
+ tags: ["sqlite", "database"]
67
+ };
50
68
  }
51
69
  /**
52
- * Called when the module is mounted to AFS
70
+ * Loads a module instance from configuration
53
71
  */
54
- async onMount(_afs) {
55
- await this.initialize();
72
+ static async load({ config } = {}) {
73
+ return new SQLiteAFS(sqliteAFSConfigSchema.parse(config));
56
74
  }
57
75
  /**
58
- * Initializes the database connection and introspects schema
76
+ * Initializes the database connection and schema service
59
77
  */
60
78
  async initialize() {
61
79
  if (this.initialized) return;
@@ -64,158 +82,672 @@ var SQLiteAFS = class SQLiteAFS {
64
82
  wal: this.options.wal ?? true
65
83
  });
66
84
  const db = this.db;
67
- this.schemas = await new SchemaIntrospector().introspect(db, {
85
+ this.schemaService = new SchemaService(db, {
68
86
  tables: this.options.tables,
69
87
  excludeTables: this.options.excludeTables
70
88
  });
71
- this.router = createPathRouter();
72
- this.crud = new CRUDOperations(db, this.schemas, "");
73
- this.ftsSearch = new FTSSearch(db, this.schemas, this.ftsConfig, "");
89
+ this.crud = new CRUDOperations(db, this.schemaService, "");
90
+ this.ftsSearch = new FTSSearch(db, this.schemaService, this.ftsConfig, "");
74
91
  this.initialized = true;
75
92
  }
76
93
  /**
77
- * Ensures the module is initialized
94
+ * Ensures the module is initialized.
95
+ * This is called automatically by handlers, but can also be called
96
+ * manually to trigger initialization (e.g., in tests).
78
97
  */
79
98
  async ensureInitialized() {
80
99
  if (!this.initialized) await this.initialize();
81
100
  }
82
101
  /**
83
- * Lists entries at a path
84
- */
85
- async list(path, options) {
86
- await this.ensureInitialized();
87
- const match = matchPath(this.router, path);
88
- if (!match) return { data: [] };
89
- switch (match.action) {
90
- case "listTables": return this.crud.listTables();
91
- case "listTable":
92
- if (!match.params.table) return { data: [] };
93
- return this.crud.listTable(match.params.table, options);
94
- case "listAttributes":
95
- if (!match.params.table || !match.params.pk) return { data: [] };
96
- return this.crud.listAttributes(match.params.table, match.params.pk);
97
- case "listActions":
98
- if (!match.params.table || !match.params.pk) return { data: [] };
99
- return this.listActions(match.params.table, match.params.pk);
100
- default: return { data: [] };
101
- }
102
+ * List all tables
103
+ * Note: list() returns only children, never the path itself (per new semantics)
104
+ */
105
+ async listTablesHandler(_ctx) {
106
+ await this.ensureInitialized();
107
+ return { data: (await this.crud.listTables()).data };
108
+ }
109
+ /**
110
+ * List rows in a table
111
+ * Note: list() returns only children (rows), never the table itself (per new semantics)
112
+ */
113
+ async listTableHandler(ctx) {
114
+ await this.ensureInitialized();
115
+ if (!await this.schemaService.getSchema(ctx.params.table)) throw new AFSNotFoundError(`/${ctx.params.table}`);
116
+ return { data: (await this.crud.listTable(ctx.params.table, ctx.options)).data };
117
+ }
118
+ /**
119
+ * List a row - rows are leaf nodes with no children
120
+ * Note: list() returns only children, never the path itself (per new semantics)
121
+ */
122
+ async listRowHandler(ctx) {
123
+ await this.ensureInitialized();
124
+ if (!(await this.crud.readRow(ctx.params.table, ctx.params.pk)).data) throw new AFSNotFoundError(`/${ctx.params.table}/${ctx.params.pk}`);
125
+ return { data: [] };
126
+ }
127
+ /**
128
+ * List actions for a row
129
+ */
130
+ async listActionsHandler(ctx) {
131
+ await this.ensureInitialized();
132
+ const schema = await this.schemaService.getSchema(ctx.params.table);
133
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
134
+ const actions = this.actions.listWithInfo({ rowLevel: true }, {
135
+ tableSchema: schema,
136
+ tableName: ctx.params.table,
137
+ schemaService: this.schemaService
138
+ });
139
+ return { data: buildActionsListEntry(ctx.params.table, ctx.params.pk, actions, { basePath: "" }) };
140
+ }
141
+ /**
142
+ * List actions for a table (table-level actions)
143
+ */
144
+ async listTableActionsHandler(ctx) {
145
+ await this.ensureInitialized();
146
+ const schema = await this.schemaService.getSchema(ctx.params.table);
147
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
148
+ const actions = this.actions.listWithInfo({ tableLevel: true }, {
149
+ tableSchema: schema,
150
+ tableName: ctx.params.table,
151
+ schemaService: this.schemaService
152
+ });
153
+ return { data: buildTableActionsListEntry(ctx.params.table, actions, { basePath: "" }) };
154
+ }
155
+ /**
156
+ * List actions at root level (database-level actions)
157
+ */
158
+ async listRootActionsHandler(_ctx) {
159
+ await this.ensureInitialized();
160
+ return { data: buildRootActionsListEntry(this.actions.listWithInfo({ rootLevel: true }, { schemaService: this.schemaService }), { basePath: "" }) };
161
+ }
162
+ /**
163
+ * Read root (database) entry
164
+ */
165
+ async readRootHandler(_ctx) {
166
+ await this.ensureInitialized();
167
+ return buildRootEntry(await this.schemaService.getAllSchemas(), { basePath: "" });
168
+ }
169
+ /**
170
+ * Read root metadata (database-level schema information)
171
+ */
172
+ async readRootMetaHandler(_ctx) {
173
+ await this.ensureInitialized();
174
+ const schemas = await this.schemaService.getAllSchemas();
175
+ const tables = Array.from(schemas.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([name, schema]) => ({
176
+ name,
177
+ description: `Table with ${schema.columns.length} columns`,
178
+ columnCount: schema.columns.length,
179
+ primaryKey: schema.primaryKey
180
+ }));
181
+ return {
182
+ id: "root:.meta",
183
+ path: "/.meta",
184
+ content: {
185
+ type: "sqlite",
186
+ tableCount: schemas.size,
187
+ tables
188
+ },
189
+ meta: {
190
+ childrenCount: tables.length,
191
+ tables: tables.map((t) => t.name)
192
+ }
193
+ };
194
+ }
195
+ /**
196
+ * Read capabilities manifest
197
+ * Returns information about available actions at different levels
198
+ */
199
+ async readCapabilitiesHandler(_ctx) {
200
+ await this.ensureInitialized();
201
+ const actionCatalogs = [];
202
+ const rootActions = this.actions.listWithInfo({ rootLevel: true }, { schemaService: this.schemaService });
203
+ if (rootActions.length > 0) actionCatalogs.push({
204
+ kind: "sqlite:root",
205
+ description: "Database-level operations",
206
+ catalog: rootActions.map((a) => ({
207
+ name: a.name,
208
+ description: a.description,
209
+ inputSchema: a.inputSchema
210
+ })),
211
+ discovery: {
212
+ pathTemplate: "/.actions",
213
+ note: "Available at database root"
214
+ }
215
+ });
216
+ const tableActions = this.actions.listWithInfo({ tableLevel: true }, { schemaService: this.schemaService });
217
+ if (tableActions.length > 0) actionCatalogs.push({
218
+ kind: "sqlite:table",
219
+ description: "Table-level operations",
220
+ catalog: tableActions.map((a) => ({
221
+ name: a.name,
222
+ description: a.description,
223
+ inputSchema: a.inputSchema
224
+ })),
225
+ discovery: {
226
+ pathTemplate: "/:table/.actions",
227
+ note: "Replace :table with actual table name"
228
+ }
229
+ });
230
+ const rowActions = this.actions.listWithInfo({ rowLevel: true }, { schemaService: this.schemaService });
231
+ if (rowActions.length > 0) actionCatalogs.push({
232
+ kind: "sqlite:row",
233
+ description: "Row-level operations",
234
+ catalog: rowActions.map((a) => ({
235
+ name: a.name,
236
+ description: a.description,
237
+ inputSchema: a.inputSchema
238
+ })),
239
+ discovery: {
240
+ pathTemplate: "/:table/:pk/.actions",
241
+ note: "Replace :table with table name, :pk with primary key value"
242
+ }
243
+ });
244
+ return {
245
+ id: "/.meta/.capabilities",
246
+ path: "/.meta/.capabilities",
247
+ content: {
248
+ schemaVersion: 1,
249
+ provider: this.name,
250
+ version: "1.0.0",
251
+ description: this.description,
252
+ tools: [],
253
+ actions: actionCatalogs,
254
+ operations: this.getOperationsDeclaration()
255
+ },
256
+ meta: { kind: "afs:capabilities" }
257
+ };
258
+ }
259
+ /**
260
+ * Read table (directory) entry
261
+ */
262
+ async readTableHandler(ctx) {
263
+ await this.ensureInitialized();
264
+ const schema = await this.schemaService.getSchema(ctx.params.table);
265
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
266
+ const rowCount = (await this.db.all(sql.raw(`SELECT COUNT(*) as count FROM "${ctx.params.table}"`)))[0]?.count ?? 0;
267
+ return buildTableEntry(ctx.params.table, schema, {
268
+ basePath: "",
269
+ rowCount
270
+ });
271
+ }
272
+ /**
273
+ * Read table metadata (table-level schema information)
274
+ */
275
+ async readTableMetaHandler(ctx) {
276
+ await this.ensureInitialized();
277
+ const schema = await this.schemaService.getSchema(ctx.params.table);
278
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
279
+ const rowCount = (await this.db.all(sql.raw(`SELECT COUNT(*) as count FROM "${ctx.params.table}"`)))[0]?.count ?? 0;
280
+ const columns = schema.columns.map((col) => ({
281
+ name: col.name,
282
+ type: col.type,
283
+ nullable: !col.notnull,
284
+ primaryKey: col.pk > 0,
285
+ defaultValue: col.dfltValue
286
+ }));
287
+ return {
288
+ id: `${ctx.params.table}:.meta`,
289
+ path: `/${ctx.params.table}/.meta`,
290
+ content: {
291
+ table: ctx.params.table,
292
+ columns,
293
+ primaryKey: schema.primaryKey,
294
+ foreignKeys: schema.foreignKeys.map((fk) => ({
295
+ column: fk.from,
296
+ referencesTable: fk.table,
297
+ referencesColumn: fk.to,
298
+ onUpdate: fk.onUpdate,
299
+ onDelete: fk.onDelete
300
+ })),
301
+ indexes: schema.indexes.map((idx) => ({
302
+ name: idx.name,
303
+ unique: idx.unique,
304
+ origin: idx.origin
305
+ })),
306
+ rowCount
307
+ },
308
+ meta: {
309
+ table: ctx.params.table,
310
+ description: `Table "${ctx.params.table}" with ${columns.length} columns`,
311
+ childrenCount: rowCount,
312
+ columnCount: columns.length,
313
+ columns: columns.map((c) => c.name),
314
+ primaryKey: schema.primaryKey
315
+ }
316
+ };
317
+ }
318
+ /**
319
+ * Read a row
320
+ */
321
+ async readRowHandler(ctx) {
322
+ await this.ensureInitialized();
323
+ return (await this.crud.readRow(ctx.params.table, ctx.params.pk)).data;
324
+ }
325
+ /**
326
+ * Get row metadata (@meta suffix - SQLite-specific)
327
+ */
328
+ async getMetaHandler(ctx) {
329
+ await this.ensureInitialized();
330
+ return (await this.crud.getMeta(ctx.params.table, ctx.params.pk)).data;
331
+ }
332
+ /**
333
+ * Get row metadata (.meta suffix - row-level schema information)
334
+ */
335
+ async getRowDotMetaHandler(ctx) {
336
+ await this.ensureInitialized();
337
+ const schema = await this.schemaService.getSchema(ctx.params.table);
338
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
339
+ if (!(await this.crud.readRow(ctx.params.table, ctx.params.pk)).data) throw new AFSNotFoundError(`/${ctx.params.table}/${ctx.params.pk}`);
340
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
341
+ return {
342
+ id: `${ctx.params.table}:${ctx.params.pk}:.meta`,
343
+ path: `/${ctx.params.table}/${ctx.params.pk}/.meta`,
344
+ content: {
345
+ table: ctx.params.table,
346
+ primaryKey: pkColumn,
347
+ primaryKeyValue: ctx.params.pk,
348
+ columns: schema.columns.map((col) => ({
349
+ name: col.name,
350
+ type: col.type,
351
+ nullable: !col.notnull,
352
+ primaryKey: col.pk > 0
353
+ })),
354
+ foreignKeys: schema.foreignKeys.map((fk) => ({
355
+ column: fk.from,
356
+ referencesTable: fk.table,
357
+ referencesColumn: fk.to
358
+ }))
359
+ },
360
+ meta: {
361
+ table: ctx.params.table,
362
+ primaryKeyValue: ctx.params.pk,
363
+ columns: schema.columns.map((col) => col.name)
364
+ }
365
+ };
366
+ }
367
+ /**
368
+ * Create a new row
369
+ */
370
+ async createRowHandler(ctx, content) {
371
+ await this.ensureInitialized();
372
+ return this.crud.createRow(ctx.params.table, content.content ?? content);
373
+ }
374
+ /**
375
+ * Update an existing row
376
+ */
377
+ async updateRowHandler(ctx, content) {
378
+ await this.ensureInitialized();
379
+ return this.crud.updateRow(ctx.params.table, ctx.params.pk, content.content ?? content);
380
+ }
381
+ /**
382
+ * Execute action via write (for triggering row-level actions)
383
+ */
384
+ async executeActionWriteHandler(ctx, content) {
385
+ await this.ensureInitialized();
386
+ return this.executeAction(ctx.params.table, ctx.params.pk, ctx.params.action, content.content ?? content);
387
+ }
388
+ /**
389
+ * Execute action via write (for triggering table-level actions)
390
+ */
391
+ async executeTableActionWriteHandler(ctx, content) {
392
+ await this.ensureInitialized();
393
+ return this.executeAction(ctx.params.table, void 0, ctx.params.action, content.content ?? content);
394
+ }
395
+ /**
396
+ * Execute action via write (for triggering root-level actions)
397
+ */
398
+ async executeRootActionWriteHandler(ctx, content) {
399
+ await this.ensureInitialized();
400
+ return this.executeRootAction(ctx.params.action, content.content ?? content);
401
+ }
402
+ /**
403
+ * Delete a table entry (not supported - always throws)
404
+ */
405
+ async deleteTableHandler(ctx) {
406
+ await this.ensureInitialized();
407
+ if (!await this.schemaService.hasTable(ctx.params.table)) throw new AFSNotFoundError(`/${ctx.params.table}`);
408
+ throw new Error(`Cannot delete table '${ctx.params.table}'. Use SQL directly to drop tables.`);
409
+ }
410
+ /**
411
+ * Delete a row
412
+ */
413
+ async deleteRowHandler(ctx) {
414
+ await this.ensureInitialized();
415
+ return this.crud.deleteRow(ctx.params.table, ctx.params.pk);
416
+ }
417
+ /**
418
+ * Search all tables
419
+ */
420
+ async searchAllHandler(_ctx, query, options) {
421
+ await this.ensureInitialized();
422
+ return this.ftsSearch.search(query, options);
423
+ }
424
+ /**
425
+ * Search a specific table
426
+ */
427
+ async searchTableHandler(ctx, query, options) {
428
+ await this.ensureInitialized();
429
+ return this.ftsSearch.searchTable(ctx.params.table, query, options);
430
+ }
431
+ /**
432
+ * Get stat for root (database level)
433
+ */
434
+ async statRootHandler(_ctx) {
435
+ await this.ensureInitialized();
436
+ const schemas = await this.schemaService.getAllSchemas();
437
+ const actions = this.actions.listWithInfo({ rootLevel: true }, { schemaService: this.schemaService });
438
+ return { data: {
439
+ id: "/",
440
+ path: "/",
441
+ meta: {
442
+ kind: "sqlite:database",
443
+ kinds: ["sqlite:database", "afs:node"],
444
+ tableCount: schemas.size,
445
+ childrenCount: schemas.size
446
+ },
447
+ actions: actions.length > 0 ? actions.map((a) => ({
448
+ name: a.name,
449
+ description: a.description
450
+ })) : void 0
451
+ } };
102
452
  }
103
453
  /**
104
- * Reads an entry at a path
105
- */
106
- async read(path, _options) {
107
- await this.ensureInitialized();
108
- const match = matchPath(this.router, path);
109
- if (!match) return {};
110
- switch (match.action) {
111
- case "readRow":
112
- if (!match.params.table || !match.params.pk) return {};
113
- return this.crud.readRow(match.params.table, match.params.pk);
114
- case "getSchema":
115
- if (!match.params.table) return {};
116
- return this.crud.getSchema(match.params.table);
117
- case "getAttribute":
118
- if (!match.params.table || !match.params.pk || !match.params.column) return {};
119
- return this.crud.getAttribute(match.params.table, match.params.pk, match.params.column);
120
- case "getMeta":
121
- if (!match.params.table || !match.params.pk) return {};
122
- return this.crud.getMeta(match.params.table, match.params.pk);
123
- default: return {};
454
+ * Get stat for a table
455
+ */
456
+ async statTableHandler(ctx) {
457
+ await this.ensureInitialized();
458
+ const schema = await this.schemaService.getSchema(ctx.params.table);
459
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
460
+ const rowCount = (await this.db.all(sql.raw(`SELECT COUNT(*) as count FROM "${ctx.params.table}"`)))[0]?.count ?? 0;
461
+ const actions = this.actions.listWithInfo({ tableLevel: true }, {
462
+ tableSchema: schema,
463
+ tableName: ctx.params.table,
464
+ schemaService: this.schemaService
465
+ });
466
+ const columns = schema.columns.map((col) => ({
467
+ name: col.name,
468
+ type: col.type,
469
+ nullable: !col.notnull,
470
+ primaryKey: col.pk > 0
471
+ }));
472
+ return { data: {
473
+ id: ctx.params.table,
474
+ path: `/${ctx.params.table}`,
475
+ meta: {
476
+ kind: "sqlite:table",
477
+ kinds: ["sqlite:table", "afs:node"],
478
+ table: ctx.params.table,
479
+ columnCount: schema.columns.length,
480
+ columns,
481
+ primaryKey: schema.primaryKey[0],
482
+ childrenCount: rowCount
483
+ },
484
+ actions: actions.length > 0 ? actions.map((a) => ({
485
+ name: a.name,
486
+ description: a.description
487
+ })) : void 0
488
+ } };
489
+ }
490
+ /**
491
+ * Get stat for a row
492
+ */
493
+ async statRowHandler(ctx) {
494
+ await this.ensureInitialized();
495
+ const schema = await this.schemaService.getSchema(ctx.params.table);
496
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
497
+ if (!(await this.crud.readRow(ctx.params.table, ctx.params.pk)).data) throw new AFSNotFoundError(`/${ctx.params.table}/${ctx.params.pk}`);
498
+ const actions = this.actions.listWithInfo({ rowLevel: true }, {
499
+ tableSchema: schema,
500
+ tableName: ctx.params.table,
501
+ schemaService: this.schemaService
502
+ });
503
+ const columns = schema.columns.map((col) => ({
504
+ name: col.name,
505
+ type: col.type,
506
+ nullable: !col.notnull,
507
+ primaryKey: col.pk > 0
508
+ }));
509
+ return { data: {
510
+ id: ctx.params.pk,
511
+ path: `/${ctx.params.table}/${ctx.params.pk}`,
512
+ meta: {
513
+ kind: "sqlite:row",
514
+ kinds: ["sqlite:row", "afs:node"],
515
+ table: ctx.params.table,
516
+ primaryKey: ctx.params.pk,
517
+ columnCount: schema.columns.length,
518
+ columns,
519
+ childrenCount: 0
520
+ },
521
+ actions: actions.length > 0 ? actions.map((a) => ({
522
+ name: a.name,
523
+ description: a.description
524
+ })) : void 0
525
+ } };
526
+ }
527
+ /**
528
+ * Explain root (database level)
529
+ */
530
+ async explainRootHandler(ctx) {
531
+ await this.ensureInitialized();
532
+ const format = ctx.options?.format || "markdown";
533
+ const schemas = await this.schemaService.getAllSchemas();
534
+ const tables = Array.from(schemas.values());
535
+ const lines = [];
536
+ if (format === "markdown") {
537
+ lines.push(`# ${this.name}`);
538
+ lines.push("");
539
+ lines.push(`**Type:** SQLite Database`);
540
+ lines.push(`**Tables:** ${tables.length}`);
541
+ lines.push("");
542
+ if (tables.length > 0) {
543
+ lines.push("## Tables");
544
+ lines.push("");
545
+ lines.push("| Table | Columns | Primary Key |");
546
+ lines.push("|-------|---------|-------------|");
547
+ for (const table of tables) {
548
+ const pk = table.primaryKey.join(", ") || "rowid";
549
+ lines.push(`| ${table.name} | ${table.columns.length} | ${pk} |`);
550
+ }
551
+ }
552
+ } else {
553
+ lines.push(`${this.name} (SQLite Database)`);
554
+ lines.push(`Tables: ${tables.length}`);
555
+ for (const table of tables) lines.push(` - ${table.name} (${table.columns.length} columns)`);
124
556
  }
557
+ return {
558
+ content: lines.join("\n"),
559
+ format
560
+ };
125
561
  }
126
562
  /**
127
- * Writes an entry at a path
128
- */
129
- async write(path, content, _options) {
130
- await this.ensureInitialized();
131
- if (this.accessMode === "readonly") throw new Error("Module is in readonly mode");
132
- const match = matchPath(this.router, path);
133
- if (!match) throw new Error(`Invalid path: ${path}`);
134
- switch (match.action) {
135
- case "createRow":
136
- if (!match.params.table) throw new Error("Table name required for create");
137
- return this.crud.createRow(match.params.table, content.content ?? content);
138
- case "readRow":
139
- if (!match.params.table || !match.params.pk) throw new Error("Table and primary key required for update");
140
- return this.crud.updateRow(match.params.table, match.params.pk, content.content ?? content);
141
- case "executeAction":
142
- if (!match.params.table || !match.params.action) throw new Error("Table and action name required");
143
- return this.executeAction(match.params.table, match.params.pk, match.params.action, content.content ?? content);
144
- default: throw new Error(`Write not supported for path: ${path}`);
563
+ * Explain a table
564
+ */
565
+ async explainTableHandler(ctx) {
566
+ await this.ensureInitialized();
567
+ const format = ctx.options?.format || "markdown";
568
+ const schema = await this.schemaService.getSchema(ctx.params.table);
569
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
570
+ const rowCount = (await this.db.all(sql.raw(`SELECT COUNT(*) as count FROM "${ctx.params.table}"`)))[0]?.count ?? 0;
571
+ const lines = [];
572
+ if (format === "markdown") {
573
+ lines.push(`# ${ctx.params.table}`);
574
+ lines.push("");
575
+ lines.push(`**Type:** SQLite Table`);
576
+ lines.push(`**Rows:** ${rowCount}`);
577
+ lines.push(`**Primary Key:** ${schema.primaryKey.join(", ") || "rowid"}`);
578
+ lines.push("");
579
+ lines.push("## Columns");
580
+ lines.push("");
581
+ lines.push("| Column | Type | Nullable | Primary Key | Default |");
582
+ lines.push("|--------|------|----------|-------------|---------|");
583
+ for (const col of schema.columns) {
584
+ const nullable = col.notnull ? "NO" : "YES";
585
+ const pk = col.pk > 0 ? "YES" : "";
586
+ const dflt = col.dfltValue !== null && col.dfltValue !== void 0 ? String(col.dfltValue) : "";
587
+ lines.push(`| ${col.name} | ${col.type} | ${nullable} | ${pk} | ${dflt} |`);
588
+ }
589
+ if (schema.indexes.length > 0) {
590
+ lines.push("");
591
+ lines.push("## Indexes");
592
+ lines.push("");
593
+ for (const idx of schema.indexes) {
594
+ const uniqueStr = idx.unique ? " (UNIQUE)" : "";
595
+ lines.push(`- **${idx.name}**${uniqueStr}`);
596
+ }
597
+ }
598
+ if (schema.foreignKeys.length > 0) {
599
+ lines.push("");
600
+ lines.push("## Foreign Keys");
601
+ lines.push("");
602
+ for (const fk of schema.foreignKeys) lines.push(`- \`${fk.from}\` → \`${fk.table}\`(\`${fk.to}\`) ON DELETE ${fk.onDelete}`);
603
+ }
604
+ } else {
605
+ lines.push(`${ctx.params.table} (SQLite Table)`);
606
+ lines.push(`Rows: ${rowCount}`);
607
+ lines.push(`Primary Key: ${schema.primaryKey.join(", ") || "rowid"}`);
608
+ lines.push(`Columns: ${schema.columns.map((c) => `${c.name} (${c.type})`).join(", ")}`);
609
+ if (schema.indexes.length > 0) lines.push(`Indexes: ${schema.indexes.map((i) => i.name).join(", ")}`);
610
+ if (schema.foreignKeys.length > 0) lines.push(`Foreign Keys: ${schema.foreignKeys.map((fk) => `${fk.from} → ${fk.table}(${fk.to})`).join(", ")}`);
145
611
  }
612
+ return {
613
+ content: lines.join("\n"),
614
+ format
615
+ };
146
616
  }
147
617
  /**
148
- * Deletes an entry at a path
618
+ * Explain a row
149
619
  */
150
- async delete(path, _options) {
620
+ async explainRowHandler(ctx) {
151
621
  await this.ensureInitialized();
152
- if (this.accessMode === "readonly") throw new Error("Module is in readonly mode");
153
- const match = matchPath(this.router, path);
154
- if (!match || match.action !== "readRow") throw new Error(`Delete not supported for path: ${path}`);
155
- if (!match.params.table || !match.params.pk) throw new Error("Table and primary key required for delete");
156
- return this.crud.deleteRow(match.params.table, match.params.pk);
622
+ const format = ctx.options?.format || "markdown";
623
+ const schema = await this.schemaService.getSchema(ctx.params.table);
624
+ if (!schema) throw new AFSNotFoundError(`/${ctx.params.table}`);
625
+ const result = await this.crud.readRow(ctx.params.table, ctx.params.pk);
626
+ if (!result.data) throw new AFSNotFoundError(`/${ctx.params.table}/${ctx.params.pk}`);
627
+ const rowContent = result.data.content;
628
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
629
+ const lines = [];
630
+ if (format === "markdown") {
631
+ lines.push(`# ${ctx.params.table}/${ctx.params.pk}`);
632
+ lines.push("");
633
+ lines.push(`**Table:** ${ctx.params.table}`);
634
+ lines.push(`**Primary Key:** ${pkColumn} = ${ctx.params.pk}`);
635
+ lines.push("");
636
+ if (rowContent) {
637
+ lines.push("## Values");
638
+ lines.push("");
639
+ lines.push("| Column | Value |");
640
+ lines.push("|--------|-------|");
641
+ for (const col of schema.columns) {
642
+ const val = rowContent[col.name];
643
+ const displayVal = val === null || val === void 0 ? "*null*" : truncateValue(String(val), 100);
644
+ lines.push(`| ${col.name} | ${displayVal} |`);
645
+ }
646
+ }
647
+ } else {
648
+ lines.push(`${ctx.params.table}/${ctx.params.pk} (SQLite Row)`);
649
+ lines.push(`Table: ${ctx.params.table}, ${pkColumn} = ${ctx.params.pk}`);
650
+ if (rowContent) for (const col of schema.columns) {
651
+ const val = rowContent[col.name];
652
+ lines.push(` ${col.name}: ${val === null || val === void 0 ? "null" : truncateValue(String(val), 100)}`);
653
+ }
654
+ }
655
+ return {
656
+ content: lines.join("\n"),
657
+ format
658
+ };
157
659
  }
158
660
  /**
159
- * Searches for entries matching a query
661
+ * Execute action via exec (row-level)
160
662
  */
161
- async search(path, query, options) {
663
+ async handleRowActionExec(ctx, args) {
162
664
  await this.ensureInitialized();
163
- const match = matchPath(this.router, path);
164
- if (match?.params.table) return this.ftsSearch.searchTable(match.params.table, query, options);
165
- return this.ftsSearch.search(query, options);
665
+ return this.executeActionRaw(ctx.params.table, ctx.params.pk, ctx.params.action, args);
166
666
  }
167
667
  /**
168
- * Executes a module operation
668
+ * Execute action via exec (table-level)
169
669
  */
170
- async exec(path, args, _options) {
670
+ async handleTableActionExec(ctx, args) {
171
671
  await this.ensureInitialized();
172
- const match = matchPath(this.router, path);
173
- 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 };
174
- throw new Error(`Exec not supported for path: ${path}`);
672
+ return this.executeActionRaw(ctx.params.table, void 0, ctx.params.action, args);
175
673
  }
176
674
  /**
177
- * Lists available actions for a row
675
+ * Execute action via exec (root-level)
178
676
  */
179
- listActions(table, pk) {
180
- return { data: buildActionsListEntry(table, pk, this.actions.listNames({ rowLevel: true }), { basePath: "" }) };
677
+ async handleRootActionExec(ctx, args) {
678
+ await this.ensureInitialized();
679
+ return this.executeRootActionRaw(ctx.params.action, args);
181
680
  }
182
681
  /**
183
- * Executes an action
682
+ * Executes an action and returns raw result (for exec handlers)
683
+ * Returns AFSExecResult structure: { success: boolean, data?: Record<string, unknown> }
184
684
  */
185
- async executeAction(table, pk, actionName, params) {
186
- if (!this.schemas.get(table)) throw new Error(`Table '${table}' not found`);
685
+ async executeActionRaw(table, pk, actionName, params) {
686
+ if (!await this.schemaService.getSchema(table)) throw new AFSNotFoundError(`/${table}`);
187
687
  let row;
188
688
  if (pk) row = (await this.crud.readRow(table, pk)).data?.content;
189
689
  const ctx = {
190
690
  db: this.db,
191
- schemas: this.schemas,
691
+ schemaService: this.schemaService,
192
692
  table,
193
693
  pk,
194
694
  row,
195
- module: {
196
- refreshSchema: () => this.refreshSchema(),
197
- exportTable: (t, f) => this.exportTable(t, f)
198
- }
695
+ module: { exportTable: (t, f) => this.exportTable(t, f) }
199
696
  };
200
697
  const result = await this.actions.execute(actionName, ctx, params);
201
698
  if (!result.success) throw new Error(result.message ?? "Action failed");
699
+ if (Array.isArray(result.data)) return {
700
+ success: true,
701
+ data: { data: result.data }
702
+ };
703
+ return {
704
+ success: true,
705
+ data: result.data
706
+ };
707
+ }
708
+ /**
709
+ * Executes a root-level action and returns raw result (for exec handlers)
710
+ * Returns AFSExecResult structure: { success: boolean, data?: Record<string, unknown> }
711
+ */
712
+ async executeRootActionRaw(actionName, params) {
713
+ const ctx = {
714
+ db: this.db,
715
+ schemaService: this.schemaService,
716
+ table: "",
717
+ module: { exportTable: (t, f) => this.exportTable(t, f) }
718
+ };
719
+ const result = await this.actions.execute(actionName, ctx, params);
720
+ if (!result.success) throw new Error(result.message ?? "Action failed");
721
+ if (Array.isArray(result.data)) return {
722
+ success: true,
723
+ data: { data: result.data }
724
+ };
725
+ return {
726
+ success: true,
727
+ data: result.data
728
+ };
729
+ }
730
+ /**
731
+ * Executes an action (for write handlers - wraps in AFSEntry)
732
+ */
733
+ async executeAction(table, pk, actionName, params) {
734
+ const result = await this.executeActionRaw(table, pk, actionName, params);
202
735
  return { data: {
203
- id: `${table}:${pk ?? ""}:@actions:${actionName}`,
204
- path: pk ? `/${table}/${pk}/@actions/${actionName}` : `/${table}/@actions/${actionName}`,
205
- content: result.data
736
+ id: `${table}:${pk ?? ""}:.actions:${actionName}`,
737
+ path: pk ? `/${table}/${pk}/.actions/${actionName}` : `/${table}/.actions/${actionName}`,
738
+ content: result
206
739
  } };
207
740
  }
208
741
  /**
209
- * Refreshes the schema cache
742
+ * Executes a root-level action (for write handlers - wraps in AFSEntry)
210
743
  */
211
- async refreshSchema() {
212
- const db = this.db;
213
- this.schemas = await new SchemaIntrospector().introspect(db, {
214
- tables: this.options.tables,
215
- excludeTables: this.options.excludeTables
216
- });
217
- this.crud.setSchemas(this.schemas);
218
- this.ftsSearch.setSchemas(this.schemas);
744
+ async executeRootAction(actionName, params) {
745
+ const result = await this.executeRootActionRaw(actionName, params);
746
+ return { data: {
747
+ id: `:.actions:${actionName}`,
748
+ path: `/.actions/${actionName}`,
749
+ content: result
750
+ } };
219
751
  }
220
752
  /**
221
753
  * Exports table data in specified format
@@ -223,8 +755,8 @@ var SQLiteAFS = class SQLiteAFS {
223
755
  async exportTable(table, format) {
224
756
  const listResult = await this.crud.listTable(table, { limit: 1e4 });
225
757
  if (format === "csv") {
226
- const schema = this.schemas.get(table);
227
- if (!schema) throw new Error(`Table '${table}' not found`);
758
+ const schema = await this.schemaService.getSchema(table);
759
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
228
760
  return `${schema.columns.map((c) => c.name).join(",")}\n${listResult.data.map((entry) => {
229
761
  const content = entry.content;
230
762
  return schema.columns.map((c) => {
@@ -248,9 +780,11 @@ var SQLiteAFS = class SQLiteAFS {
248
780
  }
249
781
  /**
250
782
  * Gets table schemas (for external access)
783
+ * Note: This queries the database on-demand
251
784
  */
252
- getSchemas() {
253
- return this.schemas;
785
+ async getSchemas() {
786
+ await this.ensureInitialized();
787
+ return this.schemaService.getAllSchemas();
254
788
  }
255
789
  /**
256
790
  * Gets the database instance (for advanced operations)
@@ -259,6 +793,45 @@ var SQLiteAFS = class SQLiteAFS {
259
793
  return this.db;
260
794
  }
261
795
  };
796
+ __decorate([List("/")], SQLiteAFS.prototype, "listTablesHandler", null);
797
+ __decorate([List("/:table")], SQLiteAFS.prototype, "listTableHandler", null);
798
+ __decorate([List("/:table/:pk")], SQLiteAFS.prototype, "listRowHandler", null);
799
+ __decorate([Actions("/:table/:pk")], SQLiteAFS.prototype, "listActionsHandler", null);
800
+ __decorate([Actions("/:table")], SQLiteAFS.prototype, "listTableActionsHandler", null);
801
+ __decorate([Actions("/")], SQLiteAFS.prototype, "listRootActionsHandler", null);
802
+ __decorate([Read("/")], SQLiteAFS.prototype, "readRootHandler", null);
803
+ __decorate([Meta("/")], SQLiteAFS.prototype, "readRootMetaHandler", null);
804
+ __decorate([Read("/.meta/.capabilities")], SQLiteAFS.prototype, "readCapabilitiesHandler", null);
805
+ __decorate([Read("/:table")], SQLiteAFS.prototype, "readTableHandler", null);
806
+ __decorate([Meta("/:table")], SQLiteAFS.prototype, "readTableMetaHandler", null);
807
+ __decorate([Read("/:table/:pk")], SQLiteAFS.prototype, "readRowHandler", null);
808
+ __decorate([Read("/:table/:pk/@meta")], SQLiteAFS.prototype, "getMetaHandler", null);
809
+ __decorate([Meta("/:table/:pk")], SQLiteAFS.prototype, "getRowDotMetaHandler", null);
810
+ __decorate([Write("/:table/new")], SQLiteAFS.prototype, "createRowHandler", null);
811
+ __decorate([Write("/:table/:pk")], SQLiteAFS.prototype, "updateRowHandler", null);
812
+ __decorate([Write("/:table/:pk/.actions/:action")], SQLiteAFS.prototype, "executeActionWriteHandler", null);
813
+ __decorate([Write("/:table/.actions/:action")], SQLiteAFS.prototype, "executeTableActionWriteHandler", null);
814
+ __decorate([Write("/.actions/:action")], SQLiteAFS.prototype, "executeRootActionWriteHandler", null);
815
+ __decorate([Delete("/:table")], SQLiteAFS.prototype, "deleteTableHandler", null);
816
+ __decorate([Delete("/:table/:pk")], SQLiteAFS.prototype, "deleteRowHandler", null);
817
+ __decorate([Search("/")], SQLiteAFS.prototype, "searchAllHandler", null);
818
+ __decorate([Search("/:table")], SQLiteAFS.prototype, "searchTableHandler", null);
819
+ __decorate([Stat("/")], SQLiteAFS.prototype, "statRootHandler", null);
820
+ __decorate([Stat("/:table")], SQLiteAFS.prototype, "statTableHandler", null);
821
+ __decorate([Stat("/:table/:pk")], SQLiteAFS.prototype, "statRowHandler", null);
822
+ __decorate([Explain("/")], SQLiteAFS.prototype, "explainRootHandler", null);
823
+ __decorate([Explain("/:table")], SQLiteAFS.prototype, "explainTableHandler", null);
824
+ __decorate([Explain("/:table/:pk")], SQLiteAFS.prototype, "explainRowHandler", null);
825
+ __decorate([Actions.Exec("/:table/:pk")], SQLiteAFS.prototype, "handleRowActionExec", null);
826
+ __decorate([Actions.Exec("/:table")], SQLiteAFS.prototype, "handleTableActionExec", null);
827
+ __decorate([Actions.Exec("/")], SQLiteAFS.prototype, "handleRootActionExec", null);
828
+ /**
829
+ * Truncate a string value for display purposes.
830
+ */
831
+ function truncateValue(value, maxLength) {
832
+ if (value.length <= maxLength) return value;
833
+ return `${value.slice(0, maxLength)}...`;
834
+ }
262
835
 
263
836
  //#endregion
264
837
  export { SQLiteAFS };