@aigne/afs-sqlite 1.11.0-beta.6 → 1.11.0-beta.7

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 (40) hide show
  1. package/dist/actions/built-in.cjs +870 -16
  2. package/dist/actions/built-in.d.cts.map +1 -1
  3. package/dist/actions/built-in.d.mts.map +1 -1
  4. package/dist/actions/built-in.mjs +870 -16
  5. package/dist/actions/built-in.mjs.map +1 -1
  6. package/dist/actions/operators.cjs +156 -0
  7. package/dist/actions/operators.mjs +157 -0
  8. package/dist/actions/operators.mjs.map +1 -0
  9. package/dist/actions/types.cjs +33 -0
  10. package/dist/actions/types.d.cts +22 -0
  11. package/dist/actions/types.d.cts.map +1 -1
  12. package/dist/actions/types.d.mts +22 -0
  13. package/dist/actions/types.d.mts.map +1 -1
  14. package/dist/actions/types.mjs +32 -0
  15. package/dist/actions/types.mjs.map +1 -0
  16. package/dist/index.cjs +2 -0
  17. package/dist/index.d.cts +1 -1
  18. package/dist/index.d.mts +1 -1
  19. package/dist/index.mjs +1 -1
  20. package/dist/node/builder.cjs +11 -8
  21. package/dist/node/builder.d.cts.map +1 -1
  22. package/dist/node/builder.d.mts.map +1 -1
  23. package/dist/node/builder.mjs +11 -8
  24. package/dist/node/builder.mjs.map +1 -1
  25. package/dist/operations/query-builder.cjs +2 -2
  26. package/dist/operations/query-builder.d.cts.map +1 -1
  27. package/dist/operations/query-builder.d.mts.map +1 -1
  28. package/dist/operations/query-builder.mjs +2 -2
  29. package/dist/operations/query-builder.mjs.map +1 -1
  30. package/dist/operations/search.cjs +1 -1
  31. package/dist/operations/search.mjs +1 -1
  32. package/dist/operations/search.mjs.map +1 -1
  33. package/dist/sqlite-afs.cjs +247 -26
  34. package/dist/sqlite-afs.d.cts +29 -4
  35. package/dist/sqlite-afs.d.cts.map +1 -1
  36. package/dist/sqlite-afs.d.mts +29 -4
  37. package/dist/sqlite-afs.d.mts.map +1 -1
  38. package/dist/sqlite-afs.mjs +248 -27
  39. package/dist/sqlite-afs.mjs.map +1 -1
  40. package/package.json +4 -3
@@ -1,3 +1,5 @@
1
+ const require_operators = require('./operators.cjs');
2
+ const require_types = require('./types.cjs');
1
3
  let _aigne_sqlite = require("@aigne/sqlite");
2
4
 
3
5
  //#region src/actions/built-in.ts
@@ -14,6 +16,18 @@ async function execRun(db, query) {
14
16
  await db.run(_aigne_sqlite.sql.raw(query)).execute();
15
17
  }
16
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
+ /**
17
31
  * Registers built-in actions to the registry
18
32
  */
19
33
  function registerBuiltInActions(registry) {
@@ -129,7 +143,49 @@ function registerBuiltInActions(registry) {
129
143
  }
130
144
  });
131
145
  registry.register({
132
- name: "create-table",
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",
133
189
  description: "Create a new table in the database",
134
190
  rootLevel: true,
135
191
  tableLevel: false,
@@ -387,44 +443,758 @@ function registerBuiltInActions(registry) {
387
443
  }
388
444
  });
389
445
  registry.register({
390
- name: "insert",
391
- description: "Insert a new row into the table",
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",
392
630
  rootLevel: false,
393
631
  tableLevel: true,
394
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,
395
752
  inputSchemaGenerator: (schemaCtx) => {
396
753
  if (!schemaCtx.tableSchema) return {
397
754
  type: "object",
398
755
  properties: { data: {
399
756
  type: "object",
400
- description: "Row data to insert (column names as keys)",
757
+ description: "Fields to update",
401
758
  additionalProperties: true
402
759
  } },
403
760
  required: ["data"]
404
761
  };
405
- return generateInsertSchema(schemaCtx.tableSchema);
762
+ return generateUpdateSchema(schemaCtx.tableSchema);
406
763
  },
407
764
  handler: async (ctx, params) => {
408
765
  const data = params.data;
409
- if (!data || Object.keys(data).length === 0) throw new Error("Insert data is required");
410
- if (!await ctx.schemaService.getSchema(ctx.table)) throw new Error(`Table '${ctx.table}' not found`);
411
- const columns = Object.keys(data);
412
- const values = columns.map((col) => formatValueForSQL(data[col]));
413
- const insertSQL = `INSERT INTO "${ctx.table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`;
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)}`;
414
773
  try {
415
- await execRun(ctx.db, insertSQL);
774
+ await execRun(ctx.db, updateSQL);
416
775
  } catch (error) {
417
776
  const message = error instanceof Error ? error.message : String(error);
418
- throw new Error(`Insert failed: ${message}`);
777
+ if (message.includes("UNIQUE constraint failed")) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Unique constraint violation: ${message}`);
778
+ throw error;
419
779
  }
420
- const newId = (await execAll(ctx.db, "SELECT last_insert_rowid() as id"))[0]?.id;
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);
421
869
  return {
422
870
  success: true,
423
871
  data: {
424
- id: newId,
425
- ...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)"
426
890
  },
427
- message: `Row inserted successfully with id ${newId}`
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
+ }
428
1198
  };
429
1199
  }
430
1200
  });
@@ -441,6 +1211,67 @@ function formatValueForSQL(value) {
441
1211
  return `'${String(value).replace(/'/g, "''")}'`;
442
1212
  }
443
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
+ /**
444
1275
  * Maps SQLite column type to JSON Schema type
445
1276
  */
446
1277
  function sqliteTypeToJsonSchema(sqliteType) {
@@ -530,6 +1361,29 @@ function generateDuplicateSchema(tableSchema) {
530
1361
  "x-copied-columns": copiedColumns
531
1362
  };
532
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
+ }
533
1387
 
534
1388
  //#endregion
535
1389
  exports.registerBuiltInActions = registerBuiltInActions;