@aigne/afs-sqlite 1.0.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE.md +93 -0
  3. package/README.md +277 -0
  4. package/lib/cjs/actions/built-in.d.ts +5 -0
  5. package/lib/cjs/actions/built-in.js +165 -0
  6. package/lib/cjs/actions/registry.d.ts +49 -0
  7. package/lib/cjs/actions/registry.js +102 -0
  8. package/lib/cjs/actions/types.d.ts +51 -0
  9. package/lib/cjs/actions/types.js +2 -0
  10. package/lib/cjs/config.d.ts +89 -0
  11. package/lib/cjs/config.js +33 -0
  12. package/lib/cjs/index.d.ts +13 -0
  13. package/lib/cjs/index.js +47 -0
  14. package/lib/cjs/node/builder.d.ts +43 -0
  15. package/lib/cjs/node/builder.js +187 -0
  16. package/lib/cjs/operations/crud.d.ts +64 -0
  17. package/lib/cjs/operations/crud.js +225 -0
  18. package/lib/cjs/operations/query-builder.d.ts +37 -0
  19. package/lib/cjs/operations/query-builder.js +102 -0
  20. package/lib/cjs/operations/search.d.ts +75 -0
  21. package/lib/cjs/operations/search.js +172 -0
  22. package/lib/cjs/package.json +3 -0
  23. package/lib/cjs/router/path-router.d.ts +38 -0
  24. package/lib/cjs/router/path-router.js +90 -0
  25. package/lib/cjs/router/types.d.ts +30 -0
  26. package/lib/cjs/router/types.js +2 -0
  27. package/lib/cjs/schema/introspector.d.ts +48 -0
  28. package/lib/cjs/schema/introspector.js +186 -0
  29. package/lib/cjs/schema/types.d.ts +104 -0
  30. package/lib/cjs/schema/types.js +13 -0
  31. package/lib/cjs/sqlite-afs.d.ts +144 -0
  32. package/lib/cjs/sqlite-afs.js +337 -0
  33. package/lib/dts/actions/built-in.d.ts +5 -0
  34. package/lib/dts/actions/registry.d.ts +49 -0
  35. package/lib/dts/actions/types.d.ts +51 -0
  36. package/lib/dts/config.d.ts +89 -0
  37. package/lib/dts/index.d.ts +13 -0
  38. package/lib/dts/node/builder.d.ts +43 -0
  39. package/lib/dts/operations/crud.d.ts +64 -0
  40. package/lib/dts/operations/query-builder.d.ts +37 -0
  41. package/lib/dts/operations/search.d.ts +75 -0
  42. package/lib/dts/router/path-router.d.ts +38 -0
  43. package/lib/dts/router/types.d.ts +30 -0
  44. package/lib/dts/schema/introspector.d.ts +48 -0
  45. package/lib/dts/schema/types.d.ts +104 -0
  46. package/lib/dts/sqlite-afs.d.ts +144 -0
  47. package/lib/esm/actions/built-in.d.ts +5 -0
  48. package/lib/esm/actions/built-in.js +162 -0
  49. package/lib/esm/actions/registry.d.ts +49 -0
  50. package/lib/esm/actions/registry.js +98 -0
  51. package/lib/esm/actions/types.d.ts +51 -0
  52. package/lib/esm/actions/types.js +1 -0
  53. package/lib/esm/config.d.ts +89 -0
  54. package/lib/esm/config.js +30 -0
  55. package/lib/esm/index.d.ts +13 -0
  56. package/lib/esm/index.js +17 -0
  57. package/lib/esm/node/builder.d.ts +43 -0
  58. package/lib/esm/node/builder.js +177 -0
  59. package/lib/esm/operations/crud.d.ts +64 -0
  60. package/lib/esm/operations/crud.js +221 -0
  61. package/lib/esm/operations/query-builder.d.ts +37 -0
  62. package/lib/esm/operations/query-builder.js +92 -0
  63. package/lib/esm/operations/search.d.ts +75 -0
  64. package/lib/esm/operations/search.js +167 -0
  65. package/lib/esm/package.json +3 -0
  66. package/lib/esm/router/path-router.d.ts +38 -0
  67. package/lib/esm/router/path-router.js +83 -0
  68. package/lib/esm/router/types.d.ts +30 -0
  69. package/lib/esm/router/types.js +1 -0
  70. package/lib/esm/schema/introspector.d.ts +48 -0
  71. package/lib/esm/schema/introspector.js +182 -0
  72. package/lib/esm/schema/types.d.ts +104 -0
  73. package/lib/esm/schema/types.js +10 -0
  74. package/lib/esm/sqlite-afs.d.ts +144 -0
  75. package/lib/esm/sqlite-afs.js +333 -0
  76. package/package.json +71 -0
@@ -0,0 +1,75 @@
1
+ import type { AFSSearchOptions, AFSSearchResult } from "@aigne/afs";
2
+ import type { LibSQLDatabase } from "drizzle-orm/libsql";
3
+ import type { TableSchema } from "../schema/types.js";
4
+ /**
5
+ * FTS5 search configuration for a table
6
+ */
7
+ export interface FTSTableConfig {
8
+ /** Columns to include in FTS index */
9
+ columns: string[];
10
+ /** Whether FTS table has been created */
11
+ initialized?: boolean;
12
+ }
13
+ /**
14
+ * FTS5 search configuration
15
+ */
16
+ export interface FTSConfig {
17
+ /** Whether FTS is enabled */
18
+ enabled: boolean;
19
+ /** Per-table FTS configuration */
20
+ tables: Map<string, FTSTableConfig>;
21
+ }
22
+ /**
23
+ * FTS5 Search operations for SQLite AFS
24
+ */
25
+ export declare class FTSSearch {
26
+ private db;
27
+ private schemas;
28
+ private config;
29
+ private basePath;
30
+ constructor(db: LibSQLDatabase, schemas: Map<string, TableSchema>, config: FTSConfig, basePath?: string);
31
+ /**
32
+ * Performs full-text search across configured tables
33
+ */
34
+ search(query: string, options?: AFSSearchOptions & {
35
+ /** Specific tables to search (defaults to all FTS-enabled tables) */
36
+ tables?: string[];
37
+ }): Promise<AFSSearchResult>;
38
+ /**
39
+ * Searches within a specific table
40
+ */
41
+ searchTable(tableName: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
42
+ /**
43
+ * Checks if FTS is configured for a table
44
+ */
45
+ hasFTS(tableName: string): boolean;
46
+ /**
47
+ * Gets FTS configuration for a table
48
+ */
49
+ getFTSConfig(tableName: string): FTSTableConfig | undefined;
50
+ /**
51
+ * Checks if an FTS table exists
52
+ */
53
+ private ftsTableExists;
54
+ /**
55
+ * Prepares a query string for FTS5
56
+ * Handles special characters and case sensitivity
57
+ */
58
+ private prepareFTSQuery;
59
+ /**
60
+ * Updates the schemas map (after refresh)
61
+ */
62
+ setSchemas(schemas: Map<string, TableSchema>): void;
63
+ /**
64
+ * Simple search fallback when FTS is not available
65
+ * Uses LIKE queries on specified columns
66
+ */
67
+ simpleLikeSearch(tableName: string, query: string, columns: string[], options?: AFSSearchOptions): Promise<AFSSearchResult>;
68
+ }
69
+ /**
70
+ * Creates FTS configuration from options
71
+ */
72
+ export declare function createFTSConfig(options?: {
73
+ enabled?: boolean;
74
+ tables?: Record<string, string[]>;
75
+ }): FTSConfig;
@@ -0,0 +1,38 @@
1
+ import { type RadixRouter } from "radix3";
2
+ import type { RouteData, RouteMatch } from "./types.js";
3
+ export type { RouteData };
4
+ /**
5
+ * Creates a radix3 router for SQLite AFS path routing
6
+ *
7
+ * Routes:
8
+ * - / → listTables
9
+ * - /:table → listTable
10
+ * - /:table/new → createRow
11
+ * - /:table/@schema → getSchema
12
+ * - /:table/:pk → readRow
13
+ * - /:table/:pk/@attr → listAttributes
14
+ * - /:table/:pk/@attr/:column → getAttribute
15
+ * - /:table/:pk/@meta → getMeta
16
+ * - /:table/:pk/@actions → listActions
17
+ * - /:table/:pk/@actions/:action → executeAction
18
+ */
19
+ export declare function createPathRouter(): RadixRouter<RouteData>;
20
+ /**
21
+ * Parses a path and returns the matched route with params
22
+ * @param router - The radix3 router instance
23
+ * @param path - The path to match
24
+ * @returns RouteMatch if matched, undefined otherwise
25
+ */
26
+ export declare function matchPath(router: RadixRouter<RouteData>, path: string): RouteMatch | undefined;
27
+ /**
28
+ * Builds a path from components
29
+ */
30
+ export declare function buildPath(table?: string, pk?: string, suffix?: string): string;
31
+ /**
32
+ * Checks if a path segment is a virtual path (@attr, @meta, @actions, @schema)
33
+ */
34
+ export declare function isVirtualPath(segment: string): boolean;
35
+ /**
36
+ * Gets the type of virtual path
37
+ */
38
+ export declare function getVirtualPathType(segment: string): "attr" | "meta" | "actions" | "schema" | null;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Route action types for SQLite AFS
3
+ */
4
+ export type RouteAction = "listTables" | "listTable" | "readRow" | "createRow" | "getSchema" | "listAttributes" | "getAttribute" | "getMeta" | "listActions" | "executeAction";
5
+ /**
6
+ * Route data associated with each path pattern
7
+ */
8
+ export interface RouteData {
9
+ /** The action to perform for this route */
10
+ action: RouteAction;
11
+ }
12
+ /**
13
+ * Route match result with params
14
+ */
15
+ export interface RouteMatch extends RouteData {
16
+ params: RouteParams;
17
+ }
18
+ /**
19
+ * Dynamic route parameters extracted from path
20
+ */
21
+ export interface RouteParams {
22
+ /** Table name */
23
+ table?: string;
24
+ /** Primary key value */
25
+ pk?: string;
26
+ /** Column name for attribute access */
27
+ column?: string;
28
+ /** Action name for @actions */
29
+ action?: string;
30
+ }
@@ -0,0 +1,48 @@
1
+ import type { LibSQLDatabase } from "drizzle-orm/libsql";
2
+ import { type TableSchema } from "./types.js";
3
+ /**
4
+ * Schema introspector that uses SQLite PRAGMA queries to discover database schema
5
+ */
6
+ export declare class SchemaIntrospector {
7
+ /**
8
+ * Introspects all tables in the database
9
+ * @param db - Drizzle database instance
10
+ * @param options - Introspection options
11
+ * @returns Map of table name to TableSchema
12
+ */
13
+ introspect(db: LibSQLDatabase, options?: {
14
+ /** Whitelist of tables to include */
15
+ tables?: string[];
16
+ /** Tables to exclude */
17
+ excludeTables?: string[];
18
+ }): Promise<Map<string, TableSchema>>;
19
+ /**
20
+ * Introspects a single table
21
+ * @param db - Drizzle database instance
22
+ * @param tableName - Name of the table to introspect
23
+ * @returns TableSchema for the specified table
24
+ */
25
+ introspectTable(db: LibSQLDatabase, tableName: string): Promise<TableSchema>;
26
+ /**
27
+ * Gets the primary key column name for a table
28
+ * Returns the first PK column, or 'rowid' if no explicit PK
29
+ */
30
+ getPrimaryKeyColumn(schema: TableSchema): string;
31
+ /**
32
+ * Checks if a table has FTS (Full-Text Search) enabled
33
+ */
34
+ hasFTS(db: LibSQLDatabase, tableName: string): Promise<boolean>;
35
+ /**
36
+ * Creates FTS5 table for full-text search on specified columns
37
+ */
38
+ createFTS(db: LibSQLDatabase, tableName: string, columns: string[], options?: {
39
+ /** Content table (defaults to tableName) */
40
+ contentTable?: string;
41
+ /** Content rowid column (defaults to 'rowid') */
42
+ contentRowid?: string;
43
+ }): Promise<void>;
44
+ /**
45
+ * Rebuilds FTS index for a table (useful after bulk inserts)
46
+ */
47
+ rebuildFTS(db: LibSQLDatabase, tableName: string): Promise<void>;
48
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Column information from SQLite PRAGMA table_info
3
+ */
4
+ export interface ColumnInfo {
5
+ /** Column name */
6
+ name: string;
7
+ /** SQLite type (INTEGER, TEXT, REAL, BLOB, etc.) */
8
+ type: string;
9
+ /** Whether the column has NOT NULL constraint */
10
+ notnull: boolean;
11
+ /** Whether this column is part of the primary key */
12
+ pk: number;
13
+ /** Default value for the column */
14
+ dfltValue: unknown;
15
+ }
16
+ /**
17
+ * Foreign key information from SQLite PRAGMA foreign_key_list
18
+ */
19
+ export interface ForeignKeyInfo {
20
+ /** Foreign key id */
21
+ id: number;
22
+ /** Sequence number for composite foreign keys */
23
+ seq: number;
24
+ /** Referenced table */
25
+ table: string;
26
+ /** Column in this table */
27
+ from: string;
28
+ /** Column in referenced table */
29
+ to: string;
30
+ /** ON UPDATE action */
31
+ onUpdate: string;
32
+ /** ON DELETE action */
33
+ onDelete: string;
34
+ /** MATCH clause */
35
+ match: string;
36
+ }
37
+ /**
38
+ * Index information from SQLite PRAGMA index_list
39
+ */
40
+ export interface IndexInfo {
41
+ /** Index sequence number */
42
+ seq: number;
43
+ /** Index name */
44
+ name: string;
45
+ /** Whether this is a unique index */
46
+ unique: boolean;
47
+ /** Origin of the index (c = CREATE INDEX, u = UNIQUE constraint, pk = PRIMARY KEY) */
48
+ origin: string;
49
+ /** Whether the index is partial */
50
+ partial: boolean;
51
+ }
52
+ /**
53
+ * Complete schema information for a single table
54
+ */
55
+ export interface TableSchema {
56
+ /** Table name */
57
+ name: string;
58
+ /** Column definitions */
59
+ columns: ColumnInfo[];
60
+ /** Primary key column names */
61
+ primaryKey: string[];
62
+ /** Foreign key relationships */
63
+ foreignKeys: ForeignKeyInfo[];
64
+ /** Indexes on this table */
65
+ indexes: IndexInfo[];
66
+ }
67
+ /**
68
+ * Raw PRAGMA table_info result row
69
+ */
70
+ export interface PragmaTableInfoRow {
71
+ cid: number;
72
+ name: string;
73
+ type: string;
74
+ notnull: number;
75
+ dflt_value: unknown;
76
+ pk: number;
77
+ }
78
+ /**
79
+ * Raw PRAGMA foreign_key_list result row
80
+ */
81
+ export interface PragmaForeignKeyRow {
82
+ id: number;
83
+ seq: number;
84
+ table: string;
85
+ from: string;
86
+ to: string;
87
+ on_update: string;
88
+ on_delete: string;
89
+ match: string;
90
+ }
91
+ /**
92
+ * Raw PRAGMA index_list result row
93
+ */
94
+ export interface PragmaIndexListRow {
95
+ seq: number;
96
+ name: string;
97
+ unique: number;
98
+ origin: string;
99
+ partial: number;
100
+ }
101
+ /**
102
+ * System tables that should be excluded from introspection
103
+ */
104
+ export declare const SYSTEM_TABLES: readonly ["sqlite_sequence", "sqlite_stat1", "sqlite_stat2", "sqlite_stat3", "sqlite_stat4"];
@@ -0,0 +1,144 @@
1
+ import type { AFSAccessMode, AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecResult, AFSListOptions, AFSListResult, AFSModule, AFSModuleLoadParams, AFSReadOptions, AFSReadResult, AFSRoot, AFSSearchOptions, AFSSearchResult, AFSWriteEntryPayload, AFSWriteOptions, AFSWriteResult } from "@aigne/afs";
2
+ import type { LibSQLDatabase } from "drizzle-orm/libsql";
3
+ import type { ActionContext } from "./actions/types.js";
4
+ import { type SQLiteAFSOptions } from "./config.js";
5
+ import type { TableSchema } from "./schema/types.js";
6
+ /**
7
+ * SQLite AFS Module
8
+ *
9
+ * Exposes SQLite databases as AFS nodes with full CRUD support,
10
+ * schema introspection, FTS5 search, and virtual paths (@attr, @meta, @actions).
11
+ */
12
+ export declare class SQLiteAFS implements AFSModule {
13
+ private options;
14
+ readonly name: string;
15
+ readonly description: string;
16
+ readonly accessMode: AFSAccessMode;
17
+ private db;
18
+ private schemas;
19
+ private router;
20
+ private crud;
21
+ private ftsSearch;
22
+ private actions;
23
+ private ftsConfig;
24
+ private initialized;
25
+ constructor(options: SQLiteAFSOptions);
26
+ /**
27
+ * Returns the Zod schema for configuration validation
28
+ */
29
+ static schema(): import("zod").ZodObject<{
30
+ url: import("zod").ZodString;
31
+ name: import("zod").ZodOptional<import("zod").ZodString>;
32
+ description: import("zod").ZodOptional<import("zod").ZodString>;
33
+ accessMode: import("zod").ZodOptional<import("zod").ZodEnum<["readonly", "readwrite"]>>;
34
+ tables: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
35
+ excludeTables: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
36
+ fts: import("zod").ZodOptional<import("zod").ZodObject<{
37
+ enabled: import("zod").ZodDefault<import("zod").ZodBoolean>;
38
+ tables: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodArray<import("zod").ZodString, "many">>>;
39
+ }, "strip", import("zod").ZodTypeAny, {
40
+ enabled: boolean;
41
+ tables?: Record<string, string[]> | undefined;
42
+ }, {
43
+ enabled?: boolean | undefined;
44
+ tables?: Record<string, string[]> | undefined;
45
+ }>>;
46
+ wal: import("zod").ZodDefault<import("zod").ZodOptional<import("zod").ZodBoolean>>;
47
+ }, "strip", import("zod").ZodTypeAny, {
48
+ url: string;
49
+ wal: boolean;
50
+ tables?: string[] | undefined;
51
+ name?: string | undefined;
52
+ description?: string | undefined;
53
+ accessMode?: "readonly" | "readwrite" | undefined;
54
+ excludeTables?: string[] | undefined;
55
+ fts?: {
56
+ enabled: boolean;
57
+ tables?: Record<string, string[]> | undefined;
58
+ } | undefined;
59
+ }, {
60
+ url: string;
61
+ tables?: string[] | undefined;
62
+ name?: string | undefined;
63
+ description?: string | undefined;
64
+ accessMode?: "readonly" | "readwrite" | undefined;
65
+ excludeTables?: string[] | undefined;
66
+ fts?: {
67
+ enabled?: boolean | undefined;
68
+ tables?: Record<string, string[]> | undefined;
69
+ } | undefined;
70
+ wal?: boolean | undefined;
71
+ }>;
72
+ /**
73
+ * Loads a module instance from configuration
74
+ */
75
+ static load({ parsed }: AFSModuleLoadParams): Promise<SQLiteAFS>;
76
+ /**
77
+ * Called when the module is mounted to AFS
78
+ */
79
+ onMount(_afs: AFSRoot): Promise<void>;
80
+ /**
81
+ * Initializes the database connection and introspects schema
82
+ */
83
+ private initialize;
84
+ /**
85
+ * Ensures the module is initialized
86
+ */
87
+ private ensureInitialized;
88
+ /**
89
+ * Lists entries at a path
90
+ */
91
+ list(path: string, options?: AFSListOptions): Promise<AFSListResult>;
92
+ /**
93
+ * Reads an entry at a path
94
+ */
95
+ read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
96
+ /**
97
+ * Writes an entry at a path
98
+ */
99
+ write(path: string, content: AFSWriteEntryPayload, _options?: AFSWriteOptions): Promise<AFSWriteResult>;
100
+ /**
101
+ * Deletes an entry at a path
102
+ */
103
+ delete(path: string, _options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
104
+ /**
105
+ * Searches for entries matching a query
106
+ */
107
+ search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
108
+ /**
109
+ * Executes a module operation
110
+ */
111
+ exec(path: string, args: Record<string, unknown>, _options: AFSExecOptions): Promise<AFSExecResult>;
112
+ /**
113
+ * Lists available actions for a row
114
+ */
115
+ private listActions;
116
+ /**
117
+ * Executes an action
118
+ */
119
+ private executeAction;
120
+ /**
121
+ * Refreshes the schema cache
122
+ */
123
+ refreshSchema(): Promise<void>;
124
+ /**
125
+ * Exports table data in specified format
126
+ */
127
+ exportTable(table: string, format: string): Promise<unknown>;
128
+ /**
129
+ * Registers a custom action
130
+ */
131
+ registerAction(name: string, handler: (ctx: ActionContext, params: Record<string, unknown>) => Promise<unknown>, options?: {
132
+ description?: string;
133
+ tableLevel?: boolean;
134
+ rowLevel?: boolean;
135
+ }): void;
136
+ /**
137
+ * Gets table schemas (for external access)
138
+ */
139
+ getSchemas(): Map<string, TableSchema>;
140
+ /**
141
+ * Gets the database instance (for advanced operations)
142
+ */
143
+ getDatabase(): LibSQLDatabase;
144
+ }
@@ -0,0 +1,5 @@
1
+ import type { ActionsRegistry } from "./registry.js";
2
+ /**
3
+ * Registers built-in actions to the registry
4
+ */
5
+ export declare function registerBuiltInActions(registry: ActionsRegistry): void;
@@ -0,0 +1,162 @@
1
+ import { sql } from "@aigne/sqlite";
2
+ /**
3
+ * Executes a raw SQL query and returns all rows
4
+ */
5
+ async function execAll(db, query) {
6
+ return db.all(sql.raw(query)).execute();
7
+ }
8
+ /**
9
+ * Executes a raw SQL query (for INSERT, UPDATE, DELETE)
10
+ */
11
+ async function execRun(db, query) {
12
+ await db.run(sql.raw(query)).execute();
13
+ }
14
+ /**
15
+ * Registers built-in actions to the registry
16
+ */
17
+ export function registerBuiltInActions(registry) {
18
+ // Refresh schema action (table level)
19
+ registry.register({
20
+ name: "refresh",
21
+ description: "Refresh the schema cache for this module",
22
+ tableLevel: true,
23
+ rowLevel: false,
24
+ handler: async (ctx) => {
25
+ await ctx.module.refreshSchema();
26
+ return {
27
+ success: true,
28
+ message: "Schema refreshed successfully",
29
+ };
30
+ },
31
+ });
32
+ // Export table action (table level)
33
+ registry.register({
34
+ name: "export",
35
+ description: "Export table data in specified format (json, csv)",
36
+ tableLevel: true,
37
+ rowLevel: false,
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {
41
+ format: {
42
+ type: "string",
43
+ enum: ["json", "csv"],
44
+ default: "json",
45
+ },
46
+ },
47
+ },
48
+ handler: async (ctx, params) => {
49
+ const format = params.format ?? "json";
50
+ const data = await ctx.module.exportTable(ctx.table, format);
51
+ return {
52
+ success: true,
53
+ data,
54
+ };
55
+ },
56
+ });
57
+ // Count rows action (table level)
58
+ registry.register({
59
+ name: "count",
60
+ description: "Get the total row count for this table",
61
+ tableLevel: true,
62
+ rowLevel: false,
63
+ handler: async (ctx) => {
64
+ const result = await execAll(ctx.db, `SELECT COUNT(*) as count FROM "${ctx.table}"`);
65
+ return {
66
+ success: true,
67
+ data: { count: result[0]?.count ?? 0 },
68
+ };
69
+ },
70
+ });
71
+ // Duplicate row action (row level)
72
+ registry.register({
73
+ name: "duplicate",
74
+ description: "Create a copy of this row",
75
+ tableLevel: false,
76
+ rowLevel: true,
77
+ handler: async (ctx) => {
78
+ if (!ctx.row) {
79
+ return { success: false, message: "Row data not available" };
80
+ }
81
+ const schema = ctx.schemas.get(ctx.table);
82
+ if (!schema) {
83
+ return { success: false, message: `Table '${ctx.table}' not found` };
84
+ }
85
+ // Create a copy without the primary key
86
+ const pkColumn = schema.primaryKey[0] ?? "rowid";
87
+ const rowCopy = { ...ctx.row };
88
+ delete rowCopy[pkColumn];
89
+ delete rowCopy.rowid;
90
+ // Build insert query
91
+ const columns = Object.keys(rowCopy);
92
+ const values = columns.map((col) => formatValueForSQL(rowCopy[col]));
93
+ await execRun(ctx.db, `INSERT INTO "${ctx.table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`);
94
+ // Get the new row's ID
95
+ const lastIdResult = await execAll(ctx.db, "SELECT last_insert_rowid() as id");
96
+ return {
97
+ success: true,
98
+ data: { newId: lastIdResult[0]?.id },
99
+ message: "Row duplicated successfully",
100
+ };
101
+ },
102
+ });
103
+ // Validate row action (row level)
104
+ registry.register({
105
+ name: "validate",
106
+ description: "Validate row data against schema constraints",
107
+ tableLevel: false,
108
+ rowLevel: true,
109
+ handler: async (ctx) => {
110
+ if (!ctx.row) {
111
+ return { success: false, message: "Row data not available" };
112
+ }
113
+ const schema = ctx.schemas.get(ctx.table);
114
+ if (!schema) {
115
+ return { success: false, message: `Table '${ctx.table}' not found` };
116
+ }
117
+ const errors = [];
118
+ // Check NOT NULL constraints
119
+ for (const col of schema.columns) {
120
+ if (col.notnull && (ctx.row[col.name] === null || ctx.row[col.name] === undefined)) {
121
+ errors.push(`Column '${col.name}' cannot be null`);
122
+ }
123
+ }
124
+ // Check foreign key references
125
+ for (const fk of schema.foreignKeys) {
126
+ const value = ctx.row[fk.from];
127
+ if (value !== null && value !== undefined) {
128
+ const refResult = await execAll(ctx.db, `SELECT COUNT(*) as count FROM "${fk.table}" WHERE "${fk.to}" = '${String(value).replace(/'/g, "''")}'`);
129
+ if (refResult[0]?.count === 0) {
130
+ errors.push(`Foreign key violation: ${fk.from} references non-existent ${fk.table}.${fk.to}`);
131
+ }
132
+ }
133
+ }
134
+ return {
135
+ success: errors.length === 0,
136
+ data: { errors, valid: errors.length === 0 },
137
+ message: errors.length > 0 ? `Validation failed: ${errors.join("; ")}` : "Row is valid",
138
+ };
139
+ },
140
+ });
141
+ }
142
+ /**
143
+ * Formats a value for SQL insertion
144
+ */
145
+ function formatValueForSQL(value) {
146
+ if (value === null || value === undefined) {
147
+ return "NULL";
148
+ }
149
+ if (typeof value === "number") {
150
+ return String(value);
151
+ }
152
+ if (typeof value === "boolean") {
153
+ return value ? "1" : "0";
154
+ }
155
+ if (value instanceof Date) {
156
+ return `'${value.toISOString()}'`;
157
+ }
158
+ if (typeof value === "object") {
159
+ return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
160
+ }
161
+ return `'${String(value).replace(/'/g, "''")}'`;
162
+ }
@@ -0,0 +1,49 @@
1
+ import type { ActionContext, ActionDefinition, ActionHandler, ActionResult } from "./types.js";
2
+ /**
3
+ * Registry for managing action handlers
4
+ */
5
+ export declare class ActionsRegistry {
6
+ private handlers;
7
+ /**
8
+ * Registers an action handler
9
+ */
10
+ register(definition: ActionDefinition): void;
11
+ /**
12
+ * Registers a simple action with just name and handler
13
+ */
14
+ registerSimple(name: string, handler: ActionHandler, options?: {
15
+ description?: string;
16
+ tableLevel?: boolean;
17
+ rowLevel?: boolean;
18
+ }): void;
19
+ /**
20
+ * Unregisters an action
21
+ */
22
+ unregister(name: string): boolean;
23
+ /**
24
+ * Checks if an action is registered
25
+ */
26
+ has(name: string): boolean;
27
+ /**
28
+ * Gets an action definition
29
+ */
30
+ get(name: string): ActionDefinition | undefined;
31
+ /**
32
+ * Lists all registered actions
33
+ */
34
+ list(options?: {
35
+ tableLevel?: boolean;
36
+ rowLevel?: boolean;
37
+ }): ActionDefinition[];
38
+ /**
39
+ * Lists action names
40
+ */
41
+ listNames(options?: {
42
+ tableLevel?: boolean;
43
+ rowLevel?: boolean;
44
+ }): string[];
45
+ /**
46
+ * Executes an action
47
+ */
48
+ execute(name: string, ctx: ActionContext, params?: Record<string, unknown>): Promise<ActionResult>;
49
+ }