@aigne/afs-sqlite 1.11.0-beta → 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 (111) hide show
  1. package/LICENSE.md +17 -84
  2. package/README.md +3 -6
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  5. package/dist/_virtual/rolldown_runtime.mjs +7 -0
  6. package/dist/actions/built-in.cjs +1389 -0
  7. package/dist/actions/built-in.d.cts +10 -0
  8. package/dist/actions/built-in.d.cts.map +1 -0
  9. package/dist/actions/built-in.d.mts +10 -0
  10. package/dist/actions/built-in.d.mts.map +1 -0
  11. package/dist/actions/built-in.mjs +1390 -0
  12. package/dist/actions/built-in.mjs.map +1 -0
  13. package/dist/actions/operators.cjs +156 -0
  14. package/dist/actions/operators.mjs +157 -0
  15. package/dist/actions/operators.mjs.map +1 -0
  16. package/dist/actions/registry.cjs +123 -0
  17. package/dist/actions/registry.d.cts +73 -0
  18. package/dist/actions/registry.d.cts.map +1 -0
  19. package/dist/actions/registry.d.mts +73 -0
  20. package/dist/actions/registry.d.mts.map +1 -0
  21. package/dist/actions/registry.mjs +123 -0
  22. package/dist/actions/registry.mjs.map +1 -0
  23. package/dist/actions/types.cjs +33 -0
  24. package/dist/actions/types.d.cts +97 -0
  25. package/dist/actions/types.d.cts.map +1 -0
  26. package/dist/actions/types.d.mts +97 -0
  27. package/dist/actions/types.d.mts.map +1 -0
  28. package/dist/actions/types.mjs +32 -0
  29. package/dist/actions/types.mjs.map +1 -0
  30. package/dist/config.cjs +27 -0
  31. package/dist/config.d.cts +54 -0
  32. package/dist/config.d.cts.map +1 -0
  33. package/dist/config.d.mts +54 -0
  34. package/dist/config.d.mts.map +1 -0
  35. package/dist/config.mjs +28 -0
  36. package/dist/config.mjs.map +1 -0
  37. package/dist/index.cjs +39 -1324
  38. package/dist/index.d.cts +15 -758
  39. package/dist/index.d.mts +15 -758
  40. package/dist/index.mjs +13 -1299
  41. package/dist/node/builder.cjs +162 -0
  42. package/dist/node/builder.d.cts +44 -0
  43. package/dist/node/builder.d.cts.map +1 -0
  44. package/dist/node/builder.d.mts +44 -0
  45. package/dist/node/builder.d.mts.map +1 -0
  46. package/dist/node/builder.mjs +155 -0
  47. package/dist/node/builder.mjs.map +1 -0
  48. package/dist/operations/crud.cjs +132 -0
  49. package/dist/operations/crud.d.cts +54 -0
  50. package/dist/operations/crud.d.cts.map +1 -0
  51. package/dist/operations/crud.d.mts +54 -0
  52. package/dist/operations/crud.d.mts.map +1 -0
  53. package/dist/operations/crud.mjs +133 -0
  54. package/dist/operations/crud.mjs.map +1 -0
  55. package/dist/operations/query-builder.cjs +77 -0
  56. package/dist/operations/query-builder.d.cts +34 -0
  57. package/dist/operations/query-builder.d.cts.map +1 -0
  58. package/dist/operations/query-builder.d.mts +34 -0
  59. package/dist/operations/query-builder.d.mts.map +1 -0
  60. package/dist/operations/query-builder.mjs +72 -0
  61. package/dist/operations/query-builder.mjs.map +1 -0
  62. package/dist/operations/search.cjs +135 -0
  63. package/dist/operations/search.d.cts +75 -0
  64. package/dist/operations/search.d.cts.map +1 -0
  65. package/dist/operations/search.d.mts +75 -0
  66. package/dist/operations/search.d.mts.map +1 -0
  67. package/dist/operations/search.mjs +135 -0
  68. package/dist/operations/search.mjs.map +1 -0
  69. package/dist/router/path-router.cjs +71 -0
  70. package/dist/router/path-router.d.cts +39 -0
  71. package/dist/router/path-router.d.cts.map +1 -0
  72. package/dist/router/path-router.d.mts +39 -0
  73. package/dist/router/path-router.d.mts.map +1 -0
  74. package/dist/router/path-router.mjs +68 -0
  75. package/dist/router/path-router.mjs.map +1 -0
  76. package/dist/router/types.d.cts +34 -0
  77. package/dist/router/types.d.cts.map +1 -0
  78. package/dist/router/types.d.mts +34 -0
  79. package/dist/router/types.d.mts.map +1 -0
  80. package/dist/schema/introspector.cjs +160 -0
  81. package/dist/schema/introspector.d.cts +49 -0
  82. package/dist/schema/introspector.d.cts.map +1 -0
  83. package/dist/schema/introspector.d.mts +49 -0
  84. package/dist/schema/introspector.d.mts.map +1 -0
  85. package/dist/schema/introspector.mjs +161 -0
  86. package/dist/schema/introspector.mjs.map +1 -0
  87. package/dist/schema/service.cjs +86 -0
  88. package/dist/schema/service.d.cts +52 -0
  89. package/dist/schema/service.d.cts.map +1 -0
  90. package/dist/schema/service.d.mts +52 -0
  91. package/dist/schema/service.d.mts.map +1 -0
  92. package/dist/schema/service.mjs +87 -0
  93. package/dist/schema/service.mjs.map +1 -0
  94. package/dist/schema/types.cjs +15 -0
  95. package/dist/schema/types.d.cts +104 -0
  96. package/dist/schema/types.d.cts.map +1 -0
  97. package/dist/schema/types.d.mts +104 -0
  98. package/dist/schema/types.d.mts.map +1 -0
  99. package/dist/schema/types.mjs +15 -0
  100. package/dist/schema/types.mjs.map +1 -0
  101. package/dist/sqlite-afs.cjs +836 -0
  102. package/dist/sqlite-afs.d.cts +330 -0
  103. package/dist/sqlite-afs.d.cts.map +1 -0
  104. package/dist/sqlite-afs.d.mts +330 -0
  105. package/dist/sqlite-afs.d.mts.map +1 -0
  106. package/dist/sqlite-afs.mjs +838 -0
  107. package/dist/sqlite-afs.mjs.map +1 -0
  108. package/package.json +6 -5
  109. package/dist/index.d.cts.map +0 -1
  110. package/dist/index.d.mts.map +0 -1
  111. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,1389 @@
1
+ const require_operators = require('./operators.cjs');
2
+ const require_types = require('./types.cjs');
3
+ let _aigne_sqlite = require("@aigne/sqlite");
4
+
5
+ //#region src/actions/built-in.ts
6
+ /**
7
+ * Executes a raw SQL query and returns all rows
8
+ */
9
+ async function execAll(db, query) {
10
+ return db.all(_aigne_sqlite.sql.raw(query)).execute();
11
+ }
12
+ /**
13
+ * Executes a raw SQL query (for INSERT, UPDATE, DELETE)
14
+ */
15
+ async function execRun(db, query) {
16
+ await db.run(_aigne_sqlite.sql.raw(query)).execute();
17
+ }
18
+ /**
19
+ * Executes a parameterized SQL query and returns all rows
20
+ */
21
+ async function execSql(db, sqlQuery) {
22
+ return db.all(sqlQuery).execute();
23
+ }
24
+ /**
25
+ * Executes a parameterized SQL statement (for INSERT, UPDATE, DELETE)
26
+ */
27
+ async function runSql(db, sqlQuery) {
28
+ return { changes: (await db.run(sqlQuery).execute()).rowsAffected };
29
+ }
30
+ /**
31
+ * Registers built-in actions to the registry
32
+ */
33
+ function registerBuiltInActions(registry) {
34
+ registry.register({
35
+ name: "export",
36
+ description: "Export table data in specified format (json, csv)",
37
+ tableLevel: true,
38
+ rowLevel: false,
39
+ inputSchema: {
40
+ type: "object",
41
+ properties: { format: {
42
+ type: "string",
43
+ enum: ["json", "csv"],
44
+ default: "json"
45
+ } }
46
+ },
47
+ handler: async (ctx, params) => {
48
+ const format = params.format ?? "json";
49
+ return {
50
+ success: true,
51
+ data: await ctx.module.exportTable(ctx.table, format)
52
+ };
53
+ }
54
+ });
55
+ registry.register({
56
+ name: "count",
57
+ description: "Get the total row count for this table",
58
+ tableLevel: true,
59
+ rowLevel: false,
60
+ handler: async (ctx) => {
61
+ return {
62
+ success: true,
63
+ data: { count: (await execAll(ctx.db, `SELECT COUNT(*) as count FROM "${ctx.table}"`))[0]?.count ?? 0 }
64
+ };
65
+ }
66
+ });
67
+ registry.register({
68
+ name: "duplicate",
69
+ description: "Create a copy of this row",
70
+ tableLevel: false,
71
+ rowLevel: true,
72
+ inputSchemaGenerator: (schemaCtx) => {
73
+ if (!schemaCtx.tableSchema) return {
74
+ type: "object",
75
+ properties: {},
76
+ additionalProperties: false
77
+ };
78
+ return generateDuplicateSchema(schemaCtx.tableSchema);
79
+ },
80
+ handler: async (ctx) => {
81
+ if (!ctx.row) return {
82
+ success: false,
83
+ message: "Row data not available"
84
+ };
85
+ const schema = await ctx.schemaService.getSchema(ctx.table);
86
+ if (!schema) return {
87
+ success: false,
88
+ message: `Table '${ctx.table}' not found`
89
+ };
90
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
91
+ const rowCopy = { ...ctx.row };
92
+ delete rowCopy[pkColumn];
93
+ delete rowCopy.rowid;
94
+ const columns = Object.keys(rowCopy);
95
+ const values = columns.map((col) => formatValueForSQL(rowCopy[col]));
96
+ await execRun(ctx.db, `INSERT INTO "${ctx.table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`);
97
+ return {
98
+ success: true,
99
+ data: { newId: (await execAll(ctx.db, "SELECT last_insert_rowid() as id"))[0]?.id },
100
+ message: "Row duplicated successfully"
101
+ };
102
+ }
103
+ });
104
+ registry.register({
105
+ name: "validate",
106
+ description: "Validate row data against schema constraints",
107
+ tableLevel: false,
108
+ rowLevel: true,
109
+ inputSchemaGenerator: (schemaCtx) => {
110
+ if (!schemaCtx.tableSchema) return {
111
+ type: "object",
112
+ properties: {},
113
+ additionalProperties: false
114
+ };
115
+ return generateValidateSchema(schemaCtx.tableSchema);
116
+ },
117
+ handler: async (ctx) => {
118
+ if (!ctx.row) return {
119
+ success: false,
120
+ message: "Row data not available"
121
+ };
122
+ const schema = await ctx.schemaService.getSchema(ctx.table);
123
+ if (!schema) return {
124
+ success: false,
125
+ message: `Table '${ctx.table}' not found`
126
+ };
127
+ const errors = [];
128
+ 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`);
129
+ for (const fk of schema.foreignKeys) {
130
+ const value = ctx.row[fk.from];
131
+ if (value !== null && value !== void 0) {
132
+ if ((await execAll(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}`);
133
+ }
134
+ }
135
+ return {
136
+ success: errors.length === 0,
137
+ data: {
138
+ errors,
139
+ valid: errors.length === 0
140
+ },
141
+ message: errors.length > 0 ? `Validation failed: ${errors.join("; ")}` : "Row is valid"
142
+ };
143
+ }
144
+ });
145
+ registry.register({
146
+ name: "insert",
147
+ description: "Insert a new row into the table",
148
+ rootLevel: false,
149
+ tableLevel: true,
150
+ rowLevel: false,
151
+ inputSchemaGenerator: (schemaCtx) => {
152
+ if (!schemaCtx.tableSchema) return {
153
+ type: "object",
154
+ properties: { data: {
155
+ type: "object",
156
+ description: "Row data to insert (column names as keys)",
157
+ additionalProperties: true
158
+ } },
159
+ required: ["data"]
160
+ };
161
+ return generateInsertSchema(schemaCtx.tableSchema);
162
+ },
163
+ handler: async (ctx, params) => {
164
+ const data = params.data;
165
+ if (!data || Object.keys(data).length === 0) throw new Error("Insert data is required");
166
+ if (!await ctx.schemaService.getSchema(ctx.table)) throw new Error(`Table '${ctx.table}' not found`);
167
+ const columns = Object.keys(data);
168
+ const values = columns.map((col) => formatValueForSQL(data[col]));
169
+ const insertSQL = `INSERT INTO "${ctx.table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`;
170
+ try {
171
+ await execRun(ctx.db, insertSQL);
172
+ } catch (error) {
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ throw new Error(`Insert failed: ${message}`);
175
+ }
176
+ const newId = (await execAll(ctx.db, "SELECT last_insert_rowid() as id"))[0]?.id;
177
+ return {
178
+ success: true,
179
+ data: {
180
+ id: newId,
181
+ ...data
182
+ },
183
+ message: `Row inserted successfully with id ${newId}`
184
+ };
185
+ }
186
+ });
187
+ registry.register({
188
+ name: "create_table",
189
+ description: "Create a new table in the database",
190
+ rootLevel: true,
191
+ tableLevel: false,
192
+ rowLevel: false,
193
+ inputSchema: {
194
+ type: "object",
195
+ description: "Create a new SQLite table with specified columns",
196
+ properties: {
197
+ name: {
198
+ type: "string",
199
+ description: "Table name (alphanumeric and underscores, must start with letter)",
200
+ pattern: "^[a-zA-Z][a-zA-Z0-9_]*$",
201
+ minLength: 1,
202
+ maxLength: 128
203
+ },
204
+ columns: {
205
+ type: "array",
206
+ description: "Column definitions (at least one required)",
207
+ minItems: 1,
208
+ items: {
209
+ type: "object",
210
+ description: "Column definition",
211
+ properties: {
212
+ name: {
213
+ type: "string",
214
+ description: "Column name (alphanumeric and underscores)",
215
+ pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$",
216
+ minLength: 1,
217
+ maxLength: 128
218
+ },
219
+ type: {
220
+ type: "string",
221
+ description: "SQLite column type",
222
+ enum: [
223
+ "INTEGER",
224
+ "TEXT",
225
+ "REAL",
226
+ "BLOB",
227
+ "NUMERIC",
228
+ "INT",
229
+ "TINYINT",
230
+ "SMALLINT",
231
+ "MEDIUMINT",
232
+ "BIGINT",
233
+ "UNSIGNED BIG INT",
234
+ "INT2",
235
+ "INT8",
236
+ "CHARACTER",
237
+ "VARCHAR",
238
+ "VARYING CHARACTER",
239
+ "NCHAR",
240
+ "NATIVE CHARACTER",
241
+ "NVARCHAR",
242
+ "CLOB",
243
+ "DOUBLE",
244
+ "DOUBLE PRECISION",
245
+ "FLOAT",
246
+ "DECIMAL",
247
+ "BOOLEAN",
248
+ "DATE",
249
+ "DATETIME",
250
+ "TIMESTAMP"
251
+ ],
252
+ "x-affinity-mapping": {
253
+ INTEGER: [
254
+ "INTEGER",
255
+ "INT",
256
+ "TINYINT",
257
+ "SMALLINT",
258
+ "MEDIUMINT",
259
+ "BIGINT",
260
+ "UNSIGNED BIG INT",
261
+ "INT2",
262
+ "INT8"
263
+ ],
264
+ TEXT: [
265
+ "TEXT",
266
+ "CHARACTER",
267
+ "VARCHAR",
268
+ "VARYING CHARACTER",
269
+ "NCHAR",
270
+ "NATIVE CHARACTER",
271
+ "NVARCHAR",
272
+ "CLOB"
273
+ ],
274
+ REAL: [
275
+ "REAL",
276
+ "DOUBLE",
277
+ "DOUBLE PRECISION",
278
+ "FLOAT"
279
+ ],
280
+ NUMERIC: [
281
+ "NUMERIC",
282
+ "DECIMAL",
283
+ "BOOLEAN",
284
+ "DATE",
285
+ "DATETIME",
286
+ "TIMESTAMP"
287
+ ],
288
+ BLOB: ["BLOB"]
289
+ }
290
+ },
291
+ nullable: {
292
+ type: "boolean",
293
+ description: "Whether the column allows NULL values (default: true)",
294
+ default: true
295
+ },
296
+ primaryKey: {
297
+ type: "boolean",
298
+ description: "Whether this column is the primary key (default: false). INTEGER PRIMARY KEY will auto-increment.",
299
+ default: false
300
+ },
301
+ unique: {
302
+ type: "boolean",
303
+ description: "Whether this column must have unique values (default: false)",
304
+ default: false
305
+ },
306
+ defaultValue: {
307
+ oneOf: [
308
+ { type: "string" },
309
+ { type: "number" },
310
+ { type: "boolean" },
311
+ { type: "null" }
312
+ ],
313
+ description: "Default value for the column. Use string 'CURRENT_TIMESTAMP' for auto timestamp."
314
+ },
315
+ references: {
316
+ type: "object",
317
+ description: "Foreign key reference",
318
+ properties: {
319
+ table: {
320
+ type: "string",
321
+ description: "Referenced table name"
322
+ },
323
+ column: {
324
+ type: "string",
325
+ description: "Referenced column name"
326
+ },
327
+ onDelete: {
328
+ type: "string",
329
+ enum: [
330
+ "CASCADE",
331
+ "SET NULL",
332
+ "SET DEFAULT",
333
+ "RESTRICT",
334
+ "NO ACTION"
335
+ ],
336
+ description: "Action on delete (default: NO ACTION)"
337
+ },
338
+ onUpdate: {
339
+ type: "string",
340
+ enum: [
341
+ "CASCADE",
342
+ "SET NULL",
343
+ "SET DEFAULT",
344
+ "RESTRICT",
345
+ "NO ACTION"
346
+ ],
347
+ description: "Action on update (default: NO ACTION)"
348
+ }
349
+ },
350
+ required: ["table", "column"]
351
+ }
352
+ },
353
+ required: ["name", "type"],
354
+ additionalProperties: false
355
+ }
356
+ },
357
+ ifNotExists: {
358
+ type: "boolean",
359
+ description: "If true, do not throw error if table already exists (default: false)",
360
+ default: false
361
+ }
362
+ },
363
+ required: ["name", "columns"],
364
+ additionalProperties: false
365
+ },
366
+ handler: async (ctx, params) => {
367
+ const tableName = params.name;
368
+ const columns = params.columns;
369
+ const ifNotExists = params.ifNotExists;
370
+ if (!tableName) throw new Error("Table name is required");
371
+ if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(tableName)) throw new Error("Table name must start with a letter and contain only alphanumeric characters and underscores");
372
+ if (!columns || columns.length === 0) throw new Error("At least one column is required");
373
+ for (const col of columns) if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(col.name)) throw new Error(`Invalid column name: ${col.name}. Must start with a letter or underscore and contain only alphanumeric characters and underscores`);
374
+ const validTypes = [
375
+ "INTEGER",
376
+ "TEXT",
377
+ "REAL",
378
+ "BLOB",
379
+ "NUMERIC",
380
+ "INT",
381
+ "TINYINT",
382
+ "SMALLINT",
383
+ "MEDIUMINT",
384
+ "BIGINT",
385
+ "UNSIGNED BIG INT",
386
+ "INT2",
387
+ "INT8",
388
+ "CHARACTER",
389
+ "VARCHAR",
390
+ "VARYING CHARACTER",
391
+ "NCHAR",
392
+ "NATIVE CHARACTER",
393
+ "NVARCHAR",
394
+ "CLOB",
395
+ "DOUBLE",
396
+ "DOUBLE PRECISION",
397
+ "FLOAT",
398
+ "DECIMAL",
399
+ "BOOLEAN",
400
+ "DATE",
401
+ "DATETIME",
402
+ "TIMESTAMP"
403
+ ];
404
+ for (const col of columns) {
405
+ const upperType = col.type.toUpperCase();
406
+ if (!validTypes.includes(upperType)) throw new Error(`Invalid column type: ${col.type}. Valid types are: ${validTypes.join(", ")}`);
407
+ }
408
+ if (!ifNotExists) {
409
+ if (await ctx.schemaService.hasTable(tableName)) throw new Error(`Table '${tableName}' already exists`);
410
+ }
411
+ const columnDefs = [];
412
+ const foreignKeys = [];
413
+ for (const col of columns) {
414
+ const parts = [`"${col.name}"`, col.type.toUpperCase()];
415
+ if (col.primaryKey) {
416
+ parts.push("PRIMARY KEY");
417
+ if (col.type.toUpperCase() === "INTEGER" || col.type.toUpperCase() === "INT") parts.push("AUTOINCREMENT");
418
+ }
419
+ if (col.nullable === false && !col.primaryKey) parts.push("NOT NULL");
420
+ if (col.unique && !col.primaryKey) parts.push("UNIQUE");
421
+ if (col.defaultValue !== void 0) if (col.defaultValue === "CURRENT_TIMESTAMP" || col.defaultValue === "CURRENT_DATE" || col.defaultValue === "CURRENT_TIME") parts.push(`DEFAULT ${col.defaultValue}`);
422
+ else parts.push(`DEFAULT ${formatValueForSQL(col.defaultValue)}`);
423
+ columnDefs.push(parts.join(" "));
424
+ if (col.references) {
425
+ let fkDef = `FOREIGN KEY ("${col.name}") REFERENCES "${col.references.table}"("${col.references.column}")`;
426
+ if (col.references.onDelete) fkDef += ` ON DELETE ${col.references.onDelete}`;
427
+ if (col.references.onUpdate) fkDef += ` ON UPDATE ${col.references.onUpdate}`;
428
+ foreignKeys.push(fkDef);
429
+ }
430
+ }
431
+ const allDefs = [...columnDefs, ...foreignKeys];
432
+ const createSQL = `CREATE TABLE ${ifNotExists ? "IF NOT EXISTS " : ""}"${tableName}" (${allDefs.join(", ")})`;
433
+ await execRun(ctx.db, createSQL);
434
+ return {
435
+ success: true,
436
+ data: {
437
+ tableName,
438
+ columnCount: columns.length,
439
+ sql: createSQL
440
+ },
441
+ message: `Table '${tableName}' created successfully`
442
+ };
443
+ }
444
+ });
445
+ registry.register({
446
+ name: "drop_table",
447
+ description: "Drop a table from the database",
448
+ rootLevel: true,
449
+ tableLevel: false,
450
+ rowLevel: false,
451
+ inputSchema: {
452
+ type: "object",
453
+ properties: {
454
+ name: {
455
+ type: "string",
456
+ description: "Table name to drop"
457
+ },
458
+ ifExists: {
459
+ type: "boolean",
460
+ default: false,
461
+ description: "Don't error if table doesn't exist"
462
+ }
463
+ },
464
+ required: ["name"]
465
+ },
466
+ handler: async (ctx, params) => {
467
+ const tableName = params.name;
468
+ const ifExists = params.ifExists;
469
+ if (!tableName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Table name is required");
470
+ const exists = await ctx.schemaService.hasTable(tableName);
471
+ if (!exists && !ifExists) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${tableName}' does not exist`);
472
+ if (exists) {
473
+ const ifExistsClause = ifExists ? "IF EXISTS " : "";
474
+ await execRun(ctx.db, `DROP TABLE ${ifExistsClause}"${tableName}"`);
475
+ }
476
+ return {
477
+ success: true,
478
+ data: { tableName },
479
+ message: `Table '${tableName}' dropped successfully`
480
+ };
481
+ }
482
+ });
483
+ registry.register({
484
+ name: "rename_table",
485
+ description: "Rename a table in the database",
486
+ rootLevel: true,
487
+ tableLevel: false,
488
+ rowLevel: false,
489
+ inputSchema: {
490
+ type: "object",
491
+ properties: {
492
+ oldName: {
493
+ type: "string",
494
+ description: "Current table name"
495
+ },
496
+ newName: {
497
+ type: "string",
498
+ description: "New table name"
499
+ }
500
+ },
501
+ required: ["oldName", "newName"]
502
+ },
503
+ handler: async (ctx, params) => {
504
+ const oldName = params.oldName;
505
+ const newName = params.newName;
506
+ if (!oldName || !newName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Both oldName and newName are required");
507
+ if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(newName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "New table name must start with a letter and contain only alphanumeric characters and underscores");
508
+ if (!await ctx.schemaService.hasTable(oldName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${oldName}' does not exist`);
509
+ if (await ctx.schemaService.hasTable(newName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Table '${newName}' already exists`);
510
+ await execRun(ctx.db, `ALTER TABLE "${oldName}" RENAME TO "${newName}"`);
511
+ return {
512
+ success: true,
513
+ data: {
514
+ oldName,
515
+ newName
516
+ },
517
+ message: `Table renamed from '${oldName}' to '${newName}'`
518
+ };
519
+ }
520
+ });
521
+ registry.register({
522
+ name: "add_column",
523
+ description: "Add a new column to the table",
524
+ rootLevel: false,
525
+ tableLevel: true,
526
+ rowLevel: false,
527
+ inputSchema: {
528
+ type: "object",
529
+ properties: {
530
+ name: {
531
+ type: "string",
532
+ description: "Column name"
533
+ },
534
+ type: {
535
+ type: "string",
536
+ enum: [
537
+ "INTEGER",
538
+ "TEXT",
539
+ "REAL",
540
+ "BLOB",
541
+ "NUMERIC"
542
+ ],
543
+ description: "Column type"
544
+ },
545
+ nullable: {
546
+ type: "boolean",
547
+ default: true,
548
+ description: "Whether the column allows NULL values"
549
+ },
550
+ defaultValue: {
551
+ oneOf: [
552
+ { type: "string" },
553
+ { type: "number" },
554
+ { type: "boolean" },
555
+ { type: "null" }
556
+ ],
557
+ description: "Default value for the column"
558
+ }
559
+ },
560
+ required: ["name", "type"]
561
+ },
562
+ handler: async (ctx, params) => {
563
+ const colName = params.name;
564
+ const colType = params.type;
565
+ const nullable = params.nullable !== false;
566
+ const defaultValue = params.defaultValue;
567
+ if (!colName || !colType) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Column name and type are required");
568
+ let columnDef = `"${colName}" ${colType.toUpperCase()}`;
569
+ if (!nullable) columnDef += " NOT NULL";
570
+ if (defaultValue !== void 0) columnDef += ` DEFAULT ${formatValueForSQL(defaultValue)}`;
571
+ try {
572
+ await execRun(ctx.db, `ALTER TABLE "${ctx.table}" ADD COLUMN ${columnDef}`);
573
+ } catch (error) {
574
+ if ((error instanceof Error ? error.message : String(error)).includes("duplicate column name")) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Column '${colName}' already exists`);
575
+ throw error;
576
+ }
577
+ return {
578
+ success: true,
579
+ data: {
580
+ table: ctx.table,
581
+ column: colName,
582
+ type: colType
583
+ },
584
+ message: `Column '${colName}' added to table '${ctx.table}'`
585
+ };
586
+ }
587
+ });
588
+ registry.register({
589
+ name: "rename_column",
590
+ description: "Rename a column in the table",
591
+ rootLevel: false,
592
+ tableLevel: true,
593
+ rowLevel: false,
594
+ inputSchema: {
595
+ type: "object",
596
+ properties: {
597
+ oldName: {
598
+ type: "string",
599
+ description: "Current column name"
600
+ },
601
+ newName: {
602
+ type: "string",
603
+ description: "New column name"
604
+ }
605
+ },
606
+ required: ["oldName", "newName"]
607
+ },
608
+ handler: async (ctx, params) => {
609
+ const oldName = params.oldName;
610
+ const newName = params.newName;
611
+ if (!oldName || !newName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Both oldName and newName are required");
612
+ const schema = await ctx.schemaService.getSchema(ctx.table);
613
+ if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
614
+ if (!schema.columns.some((c) => c.name === oldName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Column '${oldName}' not found in table '${ctx.table}'`);
615
+ await execRun(ctx.db, `ALTER TABLE "${ctx.table}" RENAME COLUMN "${oldName}" TO "${newName}"`);
616
+ return {
617
+ success: true,
618
+ data: {
619
+ table: ctx.table,
620
+ oldName,
621
+ newName
622
+ },
623
+ message: `Column renamed from '${oldName}' to '${newName}'`
624
+ };
625
+ }
626
+ });
627
+ registry.register({
628
+ name: "drop_column",
629
+ description: "Drop a column from the table",
630
+ rootLevel: false,
631
+ tableLevel: true,
632
+ rowLevel: false,
633
+ inputSchema: {
634
+ type: "object",
635
+ properties: { name: {
636
+ type: "string",
637
+ description: "Column name to drop"
638
+ } },
639
+ required: ["name"]
640
+ },
641
+ handler: async (ctx, params) => {
642
+ const colName = params.name;
643
+ if (!colName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Column name is required");
644
+ const schema = await ctx.schemaService.getSchema(ctx.table);
645
+ if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
646
+ if (!schema.columns.some((c) => c.name === colName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Column '${colName}' not found in table '${ctx.table}'`);
647
+ await execRun(ctx.db, `ALTER TABLE "${ctx.table}" DROP COLUMN "${colName}"`);
648
+ return {
649
+ success: true,
650
+ data: {
651
+ table: ctx.table,
652
+ column: colName
653
+ },
654
+ message: `Column '${colName}' dropped from table '${ctx.table}'`
655
+ };
656
+ }
657
+ });
658
+ registry.register({
659
+ name: "create_index",
660
+ description: "Create an index on the table",
661
+ rootLevel: false,
662
+ tableLevel: true,
663
+ rowLevel: false,
664
+ inputSchema: {
665
+ type: "object",
666
+ properties: {
667
+ name: {
668
+ type: "string",
669
+ description: "Index name"
670
+ },
671
+ columns: {
672
+ type: "array",
673
+ items: { type: "string" },
674
+ minItems: 1,
675
+ description: "Columns to index (supports compound indexes)"
676
+ },
677
+ unique: {
678
+ type: "boolean",
679
+ default: false,
680
+ description: "Whether the index is unique"
681
+ },
682
+ ifNotExists: {
683
+ type: "boolean",
684
+ default: false,
685
+ description: "Don't error if index already exists"
686
+ }
687
+ },
688
+ required: ["name", "columns"]
689
+ },
690
+ handler: async (ctx, params) => {
691
+ const indexName = params.name;
692
+ const columns = params.columns;
693
+ const unique = params.unique;
694
+ const ifNotExists = params.ifNotExists;
695
+ if (!indexName || !columns || columns.length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Index name and columns are required");
696
+ const uniqueClause = unique ? "UNIQUE " : "";
697
+ const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
698
+ const columnsClause = columns.map((c) => `"${c}"`).join(", ");
699
+ await execRun(ctx.db, `CREATE ${uniqueClause}INDEX ${ifNotExistsClause}"${indexName}" ON "${ctx.table}" (${columnsClause})`);
700
+ return {
701
+ success: true,
702
+ data: {
703
+ indexName,
704
+ table: ctx.table,
705
+ columns,
706
+ unique: !!unique
707
+ },
708
+ message: `Index '${indexName}' created on table '${ctx.table}'`
709
+ };
710
+ }
711
+ });
712
+ registry.register({
713
+ name: "drop_index",
714
+ description: "Drop an index from the table",
715
+ rootLevel: false,
716
+ tableLevel: true,
717
+ rowLevel: false,
718
+ inputSchema: {
719
+ type: "object",
720
+ properties: {
721
+ name: {
722
+ type: "string",
723
+ description: "Index name to drop"
724
+ },
725
+ ifExists: {
726
+ type: "boolean",
727
+ default: false,
728
+ description: "Don't error if index doesn't exist"
729
+ }
730
+ },
731
+ required: ["name"]
732
+ },
733
+ handler: async (ctx, params) => {
734
+ const indexName = params.name;
735
+ const ifExists = params.ifExists;
736
+ if (!indexName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Index name is required");
737
+ const ifExistsClause = ifExists ? "IF EXISTS " : "";
738
+ await execRun(ctx.db, `DROP INDEX ${ifExistsClause}"${indexName}"`);
739
+ return {
740
+ success: true,
741
+ data: { indexName },
742
+ message: `Index '${indexName}' dropped`
743
+ };
744
+ }
745
+ });
746
+ registry.register({
747
+ name: "update",
748
+ description: "Update this row",
749
+ rootLevel: false,
750
+ tableLevel: false,
751
+ rowLevel: true,
752
+ inputSchemaGenerator: (schemaCtx) => {
753
+ if (!schemaCtx.tableSchema) return {
754
+ type: "object",
755
+ properties: { data: {
756
+ type: "object",
757
+ description: "Fields to update",
758
+ additionalProperties: true
759
+ } },
760
+ required: ["data"]
761
+ };
762
+ return generateUpdateSchema(schemaCtx.tableSchema);
763
+ },
764
+ handler: async (ctx, params) => {
765
+ const data = params.data;
766
+ if (!data || Object.keys(data).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Update data is required");
767
+ if (!ctx.pk) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Primary key is required for row-level update");
768
+ const schema = await ctx.schemaService.getSchema(ctx.table);
769
+ if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
770
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
771
+ const setClauses = Object.entries(data).map(([col, val]) => `"${col}" = ${formatValueForSQL(val)}`).join(", ");
772
+ const updateSQL = `UPDATE "${ctx.table}" SET ${setClauses} WHERE "${pkColumn}" = ${formatValueForSQL(ctx.pk)}`;
773
+ try {
774
+ await execRun(ctx.db, updateSQL);
775
+ } catch (error) {
776
+ const message = error instanceof Error ? error.message : String(error);
777
+ if (message.includes("UNIQUE constraint failed")) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Unique constraint violation: ${message}`);
778
+ throw error;
779
+ }
780
+ return {
781
+ success: true,
782
+ data: (await execAll(ctx.db, `SELECT * FROM "${ctx.table}" WHERE "${pkColumn}" = ${formatValueForSQL(ctx.pk)}`))[0],
783
+ message: "Row updated successfully"
784
+ };
785
+ }
786
+ });
787
+ registry.register({
788
+ name: "delete",
789
+ description: "Delete this row",
790
+ rootLevel: false,
791
+ tableLevel: false,
792
+ rowLevel: true,
793
+ inputSchema: {
794
+ type: "object",
795
+ properties: {},
796
+ description: "Delete the current row"
797
+ },
798
+ handler: async (ctx) => {
799
+ if (!ctx.pk) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Primary key is required for row-level delete");
800
+ const schema = await ctx.schemaService.getSchema(ctx.table);
801
+ if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
802
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
803
+ await execRun(ctx.db, `DELETE FROM "${ctx.table}" WHERE "${pkColumn}" = ${formatValueForSQL(ctx.pk)}`);
804
+ return {
805
+ success: true,
806
+ data: { pk: ctx.pk },
807
+ message: "Row deleted successfully"
808
+ };
809
+ }
810
+ });
811
+ registry.register({
812
+ name: "query",
813
+ description: "Query rows with conditions, ordering, and pagination",
814
+ rootLevel: false,
815
+ tableLevel: true,
816
+ rowLevel: false,
817
+ inputSchema: {
818
+ type: "object",
819
+ properties: {
820
+ where: {
821
+ type: "object",
822
+ description: "Query conditions (MongoDB-style operators)"
823
+ },
824
+ orderBy: {
825
+ type: "array",
826
+ items: {
827
+ type: "object",
828
+ properties: {
829
+ column: { type: "string" },
830
+ direction: {
831
+ type: "string",
832
+ enum: ["asc", "desc"],
833
+ default: "asc"
834
+ }
835
+ },
836
+ required: ["column"]
837
+ },
838
+ description: "Sort order"
839
+ },
840
+ limit: {
841
+ type: "integer",
842
+ minimum: 1,
843
+ description: "Maximum rows to return"
844
+ },
845
+ offset: {
846
+ type: "integer",
847
+ minimum: 0,
848
+ description: "Number of rows to skip"
849
+ }
850
+ }
851
+ },
852
+ handler: async (ctx, params) => {
853
+ const where = params.where;
854
+ const orderBy = params.orderBy;
855
+ const limit = params.limit;
856
+ const offset = params.offset;
857
+ let querySQL = _aigne_sqlite.sql`SELECT * FROM ${_aigne_sqlite.sql.identifier(ctx.table)}`;
858
+ querySQL = _aigne_sqlite.sql`${querySQL}${require_operators.buildWhereClause(where)}`;
859
+ if (orderBy && orderBy.length > 0) {
860
+ const orderClauses = orderBy.map((o) => {
861
+ const dir = o.direction?.toUpperCase() === "DESC" ? _aigne_sqlite.sql` DESC` : _aigne_sqlite.sql` ASC`;
862
+ return _aigne_sqlite.sql`${_aigne_sqlite.sql.identifier(o.column)}${dir}`;
863
+ });
864
+ querySQL = _aigne_sqlite.sql`${querySQL} ORDER BY ${_aigne_sqlite.sql.join(orderClauses, _aigne_sqlite.sql`, `)}`;
865
+ }
866
+ if (limit !== void 0) querySQL = _aigne_sqlite.sql`${querySQL} LIMIT ${limit}`;
867
+ if (offset !== void 0) querySQL = _aigne_sqlite.sql`${querySQL} OFFSET ${offset}`;
868
+ const rows = await execSql(ctx.db, querySQL);
869
+ return {
870
+ success: true,
871
+ data: {
872
+ rows,
873
+ count: rows.length
874
+ }
875
+ };
876
+ }
877
+ });
878
+ registry.register({
879
+ name: "update_where",
880
+ description: "Update rows matching conditions",
881
+ rootLevel: false,
882
+ tableLevel: true,
883
+ rowLevel: false,
884
+ inputSchema: {
885
+ type: "object",
886
+ properties: {
887
+ where: {
888
+ type: "object",
889
+ description: "Update conditions (MongoDB-style operators)"
890
+ },
891
+ data: {
892
+ type: "object",
893
+ description: "Fields to update"
894
+ }
895
+ },
896
+ required: ["where", "data"]
897
+ },
898
+ handler: async (ctx, params) => {
899
+ const where = params.where;
900
+ const data = params.data;
901
+ if (!where || Object.keys(where).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Where clause is required for update_where");
902
+ if (!data || Object.keys(data).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Update data is required");
903
+ const setClauses = Object.entries(data).map(([col, val]) => _aigne_sqlite.sql`${_aigne_sqlite.sql.identifier(col)} = ${val}`);
904
+ const updateSQL = _aigne_sqlite.sql`UPDATE ${_aigne_sqlite.sql.identifier(ctx.table)} SET ${_aigne_sqlite.sql.join(setClauses, _aigne_sqlite.sql`, `)}${require_operators.buildWhereClause(where)}`;
905
+ const result = await runSql(ctx.db, updateSQL);
906
+ return {
907
+ success: true,
908
+ data: { affectedRows: result.changes },
909
+ message: `${result.changes} row(s) updated`
910
+ };
911
+ }
912
+ });
913
+ registry.register({
914
+ name: "delete_where",
915
+ description: "Delete rows matching conditions",
916
+ rootLevel: false,
917
+ tableLevel: true,
918
+ rowLevel: false,
919
+ inputSchema: {
920
+ type: "object",
921
+ properties: { where: {
922
+ type: "object",
923
+ description: "Delete conditions (MongoDB-style operators)"
924
+ } },
925
+ required: ["where"]
926
+ },
927
+ handler: async (ctx, params) => {
928
+ const where = params.where;
929
+ if (!where || Object.keys(where).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Where clause is required for delete_where");
930
+ const deleteSQL = _aigne_sqlite.sql`DELETE FROM ${_aigne_sqlite.sql.identifier(ctx.table)}${require_operators.buildWhereClause(where)}`;
931
+ const result = await runSql(ctx.db, deleteSQL);
932
+ return {
933
+ success: true,
934
+ data: { affectedRows: result.changes },
935
+ message: `${result.changes} row(s) deleted`
936
+ };
937
+ }
938
+ });
939
+ registry.register({
940
+ name: "bulk_insert",
941
+ description: "Insert multiple rows at once",
942
+ rootLevel: false,
943
+ tableLevel: true,
944
+ rowLevel: false,
945
+ inputSchema: {
946
+ type: "object",
947
+ properties: { rows: {
948
+ type: "array",
949
+ items: { type: "object" },
950
+ minItems: 1,
951
+ description: "Array of row data to insert"
952
+ } },
953
+ required: ["rows"]
954
+ },
955
+ handler: async (ctx, params) => {
956
+ const rows = params.rows;
957
+ if (!rows || rows.length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "At least one row is required");
958
+ const columns = Object.keys(rows[0]);
959
+ const columnsList = columns.map((c) => `"${c}"`).join(", ");
960
+ const insertedIds = [];
961
+ for (const row of rows) {
962
+ const values = columns.map((col) => formatValueForSQL(row[col]));
963
+ const insertSQL = `INSERT INTO "${ctx.table}" (${columnsList}) VALUES (${values.join(", ")})`;
964
+ try {
965
+ await execRun(ctx.db, insertSQL);
966
+ const lastIdResult = await execAll(ctx.db, "SELECT last_insert_rowid() as id");
967
+ insertedIds.push(lastIdResult[0]?.id ?? 0);
968
+ } catch (error) {
969
+ const message = error instanceof Error ? error.message : String(error);
970
+ return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Insert failed at row ${insertedIds.length}: ${message}`, {
971
+ insertedCount: insertedIds.length,
972
+ insertedIds
973
+ });
974
+ }
975
+ }
976
+ return {
977
+ success: true,
978
+ data: {
979
+ insertedCount: rows.length,
980
+ insertedIds
981
+ },
982
+ message: `${rows.length} row(s) inserted`
983
+ };
984
+ }
985
+ });
986
+ registry.register({
987
+ name: "pragma",
988
+ description: "Execute a PRAGMA command (whitelist-restricted)",
989
+ rootLevel: true,
990
+ tableLevel: false,
991
+ rowLevel: false,
992
+ inputSchema: {
993
+ type: "object",
994
+ properties: {
995
+ command: {
996
+ type: "string",
997
+ enum: [
998
+ "table_info",
999
+ "table_list",
1000
+ "index_list",
1001
+ "index_info",
1002
+ "foreign_key_list",
1003
+ "database_list",
1004
+ "collation_list",
1005
+ "journal_mode",
1006
+ "synchronous",
1007
+ "cache_size",
1008
+ "page_size",
1009
+ "encoding",
1010
+ "auto_vacuum",
1011
+ "integrity_check",
1012
+ "quick_check"
1013
+ ],
1014
+ description: "PRAGMA command to execute"
1015
+ },
1016
+ argument: {
1017
+ type: "string",
1018
+ description: "Argument for the command (e.g., table name)"
1019
+ },
1020
+ value: {
1021
+ oneOf: [{ type: "string" }, { type: "number" }],
1022
+ description: "Value for writable PRAGMAs"
1023
+ }
1024
+ },
1025
+ required: ["command"]
1026
+ },
1027
+ handler: async (ctx, params) => {
1028
+ const command = params.command;
1029
+ const argument = params.argument;
1030
+ const value = params.value;
1031
+ const allowedCommands = [
1032
+ "table_info",
1033
+ "table_list",
1034
+ "index_list",
1035
+ "index_info",
1036
+ "foreign_key_list",
1037
+ "database_list",
1038
+ "collation_list",
1039
+ "journal_mode",
1040
+ "synchronous",
1041
+ "cache_size",
1042
+ "page_size",
1043
+ "encoding",
1044
+ "auto_vacuum",
1045
+ "integrity_check",
1046
+ "quick_check"
1047
+ ];
1048
+ if (!allowedCommands.includes(command)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `PRAGMA command '${command}' is not allowed. Allowed: ${allowedCommands.join(", ")}`);
1049
+ let pragmaSQL = `PRAGMA ${command}`;
1050
+ if (argument) pragmaSQL += `("${argument}")`;
1051
+ if (value !== void 0) pragmaSQL += ` = ${typeof value === "string" ? `'${value}'` : value}`;
1052
+ const result = await execAll(ctx.db, pragmaSQL);
1053
+ return {
1054
+ success: true,
1055
+ data: result.length === 1 && Object.keys(result[0]).length === 1 ? Object.values(result[0])[0] : result
1056
+ };
1057
+ }
1058
+ });
1059
+ registry.register({
1060
+ name: "import",
1061
+ description: "Import data into table from JSON or CSV format",
1062
+ tableLevel: true,
1063
+ rowLevel: false,
1064
+ inputSchema: {
1065
+ type: "object",
1066
+ properties: {
1067
+ format: {
1068
+ type: "string",
1069
+ enum: ["json", "csv"],
1070
+ description: "Data format (json or csv)"
1071
+ },
1072
+ data: {
1073
+ type: "string",
1074
+ description: "The data to import (JSON array string or CSV string)"
1075
+ },
1076
+ onConflict: {
1077
+ type: "string",
1078
+ enum: [
1079
+ "abort",
1080
+ "ignore",
1081
+ "replace"
1082
+ ],
1083
+ default: "abort",
1084
+ description: "Conflict resolution strategy"
1085
+ },
1086
+ delimiter: {
1087
+ type: "string",
1088
+ default: ",",
1089
+ description: "CSV delimiter character"
1090
+ },
1091
+ header: {
1092
+ type: "boolean",
1093
+ default: true,
1094
+ description: "Whether CSV has a header row"
1095
+ }
1096
+ },
1097
+ required: ["format", "data"]
1098
+ },
1099
+ handler: async (ctx, params) => {
1100
+ const format = params.format;
1101
+ const data = params.data;
1102
+ const onConflict = params.onConflict ?? "abort";
1103
+ const delimiter = params.delimiter ?? ",";
1104
+ const hasHeader = params.header !== false;
1105
+ if (!["json", "csv"].includes(format)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Unsupported import format: ${format}. Supported: json, csv`);
1106
+ const schema = await ctx.schemaService.getSchema(ctx.table);
1107
+ if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table not found: ${ctx.table}`);
1108
+ let rows;
1109
+ try {
1110
+ if (format === "json") {
1111
+ const parsed = JSON.parse(data);
1112
+ if (!Array.isArray(parsed)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "JSON data must be an array of objects");
1113
+ rows = parsed;
1114
+ } else rows = parseCSV(data, delimiter, hasHeader, schema);
1115
+ } catch (error) {
1116
+ return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Failed to parse ${format} data: ${error instanceof Error ? error.message : "unknown error"}`);
1117
+ }
1118
+ if (rows.length === 0) return {
1119
+ success: true,
1120
+ data: {
1121
+ imported: 0,
1122
+ skipped: 0
1123
+ }
1124
+ };
1125
+ let conflictClause = "";
1126
+ if (onConflict === "ignore") conflictClause = " OR IGNORE";
1127
+ else if (onConflict === "replace") conflictClause = " OR REPLACE";
1128
+ const columnNames = schema.columns.map((c) => c.name);
1129
+ let imported = 0;
1130
+ let skipped = 0;
1131
+ try {
1132
+ await execRun(ctx.db, "BEGIN TRANSACTION");
1133
+ for (const row of rows) {
1134
+ const usedColumns = [];
1135
+ const values = [];
1136
+ for (const colName of columnNames) if (colName in row) {
1137
+ usedColumns.push(colName);
1138
+ values.push(formatValueForSQL(row[colName]));
1139
+ }
1140
+ if (usedColumns.length === 0) continue;
1141
+ const insertSQL = `INSERT${conflictClause} INTO "${ctx.table}" (${usedColumns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`;
1142
+ try {
1143
+ await execAll(ctx.db, insertSQL);
1144
+ if (((await execAll(ctx.db, "SELECT changes() as changes"))[0]?.changes ?? 0) > 0) imported++;
1145
+ else skipped++;
1146
+ } catch (error) {
1147
+ if (onConflict === "abort") {
1148
+ await execRun(ctx.db, "ROLLBACK");
1149
+ return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Import aborted: ${error instanceof Error ? error.message : "constraint violation"}`);
1150
+ }
1151
+ skipped++;
1152
+ }
1153
+ }
1154
+ await execRun(ctx.db, "COMMIT");
1155
+ return {
1156
+ success: true,
1157
+ data: {
1158
+ imported,
1159
+ skipped
1160
+ }
1161
+ };
1162
+ } catch (error) {
1163
+ try {
1164
+ await execRun(ctx.db, "ROLLBACK");
1165
+ } catch {}
1166
+ return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Import failed: ${error instanceof Error ? error.message : "unknown error"}`);
1167
+ }
1168
+ }
1169
+ });
1170
+ registry.register({
1171
+ name: "vacuum",
1172
+ description: "Compact the database file and reclaim unused space",
1173
+ rootLevel: true,
1174
+ tableLevel: false,
1175
+ rowLevel: false,
1176
+ inputSchema: {
1177
+ type: "object",
1178
+ properties: {},
1179
+ additionalProperties: false
1180
+ },
1181
+ handler: async (ctx) => {
1182
+ const pageCountBefore = await execAll(ctx.db, "PRAGMA page_count");
1183
+ const pageSize = (await execAll(ctx.db, "PRAGMA page_size"))[0]?.page_size ?? 4096;
1184
+ const sizeBefore = (pageCountBefore[0]?.page_count ?? 0) * pageSize;
1185
+ try {
1186
+ await execRun(ctx.db, "VACUUM");
1187
+ } catch (error) {
1188
+ return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Vacuum failed: ${error instanceof Error ? error.message : "unknown error"}`);
1189
+ }
1190
+ const sizeAfter = ((await execAll(ctx.db, "PRAGMA page_count"))[0]?.page_count ?? 0) * pageSize;
1191
+ return {
1192
+ success: true,
1193
+ data: {
1194
+ sizeBefore,
1195
+ sizeAfter,
1196
+ freedBytes: sizeBefore - sizeAfter
1197
+ }
1198
+ };
1199
+ }
1200
+ });
1201
+ }
1202
+ /**
1203
+ * Formats a value for SQL insertion
1204
+ */
1205
+ function formatValueForSQL(value) {
1206
+ if (value === null || value === void 0) return "NULL";
1207
+ if (typeof value === "number") return String(value);
1208
+ if (typeof value === "boolean") return value ? "1" : "0";
1209
+ if (value instanceof Date) return `'${value.toISOString()}'`;
1210
+ if (typeof value === "object") return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
1211
+ return `'${String(value).replace(/'/g, "''")}'`;
1212
+ }
1213
+ /**
1214
+ * Parse a CSV string into an array of row objects.
1215
+ */
1216
+ function parseCSV(data, delimiter, hasHeader, schema) {
1217
+ const lines = data.split("\n").filter((line) => line.trim() !== "");
1218
+ if (lines.length === 0) return [];
1219
+ let headers;
1220
+ let startIndex;
1221
+ if (hasHeader) {
1222
+ if (lines.length < 1) return [];
1223
+ headers = parseCsvLine(lines[0], delimiter);
1224
+ startIndex = 1;
1225
+ } else {
1226
+ headers = schema.columns.map((c) => c.name);
1227
+ startIndex = 0;
1228
+ }
1229
+ const rows = [];
1230
+ for (let i = startIndex; i < lines.length; i++) {
1231
+ const values = parseCsvLine(lines[i], delimiter);
1232
+ const row = {};
1233
+ for (let j = 0; j < headers.length && j < values.length; j++) row[headers[j]] = values[j];
1234
+ rows.push(row);
1235
+ }
1236
+ return rows;
1237
+ }
1238
+ /**
1239
+ * Parse a single CSV line, handling quoted fields.
1240
+ */
1241
+ function parseCsvLine(line, delimiter) {
1242
+ const fields = [];
1243
+ let current = "";
1244
+ let inQuotes = false;
1245
+ let i = 0;
1246
+ while (i < line.length) {
1247
+ const char = line[i];
1248
+ if (inQuotes) if (char === "\"") if (i + 1 < line.length && line[i + 1] === "\"") {
1249
+ current += "\"";
1250
+ i += 2;
1251
+ } else {
1252
+ inQuotes = false;
1253
+ i++;
1254
+ }
1255
+ else {
1256
+ current += char;
1257
+ i++;
1258
+ }
1259
+ else if (char === "\"") {
1260
+ inQuotes = true;
1261
+ i++;
1262
+ } else if (line.substring(i, i + delimiter.length) === delimiter) {
1263
+ fields.push(current);
1264
+ current = "";
1265
+ i += delimiter.length;
1266
+ } else {
1267
+ current += char;
1268
+ i++;
1269
+ }
1270
+ }
1271
+ fields.push(current);
1272
+ return fields;
1273
+ }
1274
+ /**
1275
+ * Maps SQLite column type to JSON Schema type
1276
+ */
1277
+ function sqliteTypeToJsonSchema(sqliteType) {
1278
+ const upperType = sqliteType.toUpperCase();
1279
+ if (upperType.includes("INT")) return { type: "integer" };
1280
+ if (upperType.includes("REAL") || upperType.includes("FLOAT") || upperType.includes("DOUBLE")) return { type: "number" };
1281
+ if (upperType.includes("BOOL")) return {
1282
+ type: "boolean",
1283
+ description: "Stored as INTEGER (0/1)"
1284
+ };
1285
+ if (upperType.includes("DATE") || upperType.includes("TIME")) return {
1286
+ type: "string",
1287
+ format: "date-time"
1288
+ };
1289
+ if (upperType.includes("BLOB")) return {
1290
+ type: "string",
1291
+ format: "binary",
1292
+ description: "Base64 encoded binary data"
1293
+ };
1294
+ return { type: "string" };
1295
+ }
1296
+ /**
1297
+ * Generates JSON Schema for insert action based on table schema
1298
+ */
1299
+ function generateInsertSchema(tableSchema) {
1300
+ const properties = {};
1301
+ const required = [];
1302
+ for (const col of tableSchema.columns) {
1303
+ if (col.pk > 0 && col.type.toUpperCase().includes("INTEGER")) continue;
1304
+ const propSchema = { ...sqliteTypeToJsonSchema(col.type) };
1305
+ if (col.dfltValue !== null) {
1306
+ propSchema.default = col.dfltValue;
1307
+ propSchema.description = `${propSchema.description ?? ""}${propSchema.description ? ". " : ""}Default: ${col.dfltValue}`.trim();
1308
+ }
1309
+ if (col.notnull && col.dfltValue === null && col.pk === 0) required.push(col.name);
1310
+ properties[col.name] = propSchema;
1311
+ }
1312
+ return {
1313
+ type: "object",
1314
+ description: `Insert a new row into "${tableSchema.name}" table`,
1315
+ properties: { data: {
1316
+ type: "object",
1317
+ description: "Row data to insert",
1318
+ properties,
1319
+ required: required.length > 0 ? required : void 0,
1320
+ additionalProperties: false
1321
+ } },
1322
+ required: ["data"]
1323
+ };
1324
+ }
1325
+ /**
1326
+ * Generates JSON Schema for validate action based on table schema
1327
+ */
1328
+ function generateValidateSchema(tableSchema) {
1329
+ const columnInfo = tableSchema.columns.map((col) => ({
1330
+ name: col.name,
1331
+ type: col.type,
1332
+ nullable: !col.notnull,
1333
+ primaryKey: col.pk > 0
1334
+ }));
1335
+ const foreignKeyInfo = tableSchema.foreignKeys.map((fk) => ({
1336
+ column: fk.from,
1337
+ references: `${fk.table}.${fk.to}`
1338
+ }));
1339
+ return {
1340
+ type: "object",
1341
+ description: `Validate row data against "${tableSchema.name}" table constraints`,
1342
+ properties: {},
1343
+ additionalProperties: false,
1344
+ "x-table-schema": {
1345
+ columns: columnInfo,
1346
+ foreignKeys: foreignKeyInfo
1347
+ }
1348
+ };
1349
+ }
1350
+ /**
1351
+ * Generates JSON Schema for duplicate action based on table schema
1352
+ */
1353
+ function generateDuplicateSchema(tableSchema) {
1354
+ const pkColumn = tableSchema.primaryKey[0] ?? "rowid";
1355
+ const copiedColumns = tableSchema.columns.filter((col) => col.name !== pkColumn).map((col) => col.name);
1356
+ return {
1357
+ type: "object",
1358
+ description: `Create a copy of the row (excluding primary key "${pkColumn}")`,
1359
+ properties: {},
1360
+ additionalProperties: false,
1361
+ "x-copied-columns": copiedColumns
1362
+ };
1363
+ }
1364
+ /**
1365
+ * Generates JSON Schema for update action based on table schema
1366
+ */
1367
+ function generateUpdateSchema(tableSchema) {
1368
+ const properties = {};
1369
+ const pkColumn = tableSchema.primaryKey[0] ?? "rowid";
1370
+ for (const col of tableSchema.columns) {
1371
+ if (col.name === pkColumn) continue;
1372
+ const typeInfo = sqliteTypeToJsonSchema(col.type);
1373
+ properties[col.name] = typeInfo;
1374
+ }
1375
+ return {
1376
+ type: "object",
1377
+ description: `Update fields in "${tableSchema.name}" table`,
1378
+ properties: { data: {
1379
+ type: "object",
1380
+ description: "Fields to update",
1381
+ properties,
1382
+ additionalProperties: false
1383
+ } },
1384
+ required: ["data"]
1385
+ };
1386
+ }
1387
+
1388
+ //#endregion
1389
+ exports.registerBuiltInActions = registerBuiltInActions;