@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.
- package/CHANGELOG.md +29 -0
- package/LICENSE.md +93 -0
- package/README.md +277 -0
- package/lib/cjs/actions/built-in.d.ts +5 -0
- package/lib/cjs/actions/built-in.js +165 -0
- package/lib/cjs/actions/registry.d.ts +49 -0
- package/lib/cjs/actions/registry.js +102 -0
- package/lib/cjs/actions/types.d.ts +51 -0
- package/lib/cjs/actions/types.js +2 -0
- package/lib/cjs/config.d.ts +89 -0
- package/lib/cjs/config.js +33 -0
- package/lib/cjs/index.d.ts +13 -0
- package/lib/cjs/index.js +47 -0
- package/lib/cjs/node/builder.d.ts +43 -0
- package/lib/cjs/node/builder.js +187 -0
- package/lib/cjs/operations/crud.d.ts +64 -0
- package/lib/cjs/operations/crud.js +225 -0
- package/lib/cjs/operations/query-builder.d.ts +37 -0
- package/lib/cjs/operations/query-builder.js +102 -0
- package/lib/cjs/operations/search.d.ts +75 -0
- package/lib/cjs/operations/search.js +172 -0
- package/lib/cjs/package.json +3 -0
- package/lib/cjs/router/path-router.d.ts +38 -0
- package/lib/cjs/router/path-router.js +90 -0
- package/lib/cjs/router/types.d.ts +30 -0
- package/lib/cjs/router/types.js +2 -0
- package/lib/cjs/schema/introspector.d.ts +48 -0
- package/lib/cjs/schema/introspector.js +186 -0
- package/lib/cjs/schema/types.d.ts +104 -0
- package/lib/cjs/schema/types.js +13 -0
- package/lib/cjs/sqlite-afs.d.ts +144 -0
- package/lib/cjs/sqlite-afs.js +337 -0
- package/lib/dts/actions/built-in.d.ts +5 -0
- package/lib/dts/actions/registry.d.ts +49 -0
- package/lib/dts/actions/types.d.ts +51 -0
- package/lib/dts/config.d.ts +89 -0
- package/lib/dts/index.d.ts +13 -0
- package/lib/dts/node/builder.d.ts +43 -0
- package/lib/dts/operations/crud.d.ts +64 -0
- package/lib/dts/operations/query-builder.d.ts +37 -0
- package/lib/dts/operations/search.d.ts +75 -0
- package/lib/dts/router/path-router.d.ts +38 -0
- package/lib/dts/router/types.d.ts +30 -0
- package/lib/dts/schema/introspector.d.ts +48 -0
- package/lib/dts/schema/types.d.ts +104 -0
- package/lib/dts/sqlite-afs.d.ts +144 -0
- package/lib/esm/actions/built-in.d.ts +5 -0
- package/lib/esm/actions/built-in.js +162 -0
- package/lib/esm/actions/registry.d.ts +49 -0
- package/lib/esm/actions/registry.js +98 -0
- package/lib/esm/actions/types.d.ts +51 -0
- package/lib/esm/actions/types.js +1 -0
- package/lib/esm/config.d.ts +89 -0
- package/lib/esm/config.js +30 -0
- package/lib/esm/index.d.ts +13 -0
- package/lib/esm/index.js +17 -0
- package/lib/esm/node/builder.d.ts +43 -0
- package/lib/esm/node/builder.js +177 -0
- package/lib/esm/operations/crud.d.ts +64 -0
- package/lib/esm/operations/crud.js +221 -0
- package/lib/esm/operations/query-builder.d.ts +37 -0
- package/lib/esm/operations/query-builder.js +92 -0
- package/lib/esm/operations/search.d.ts +75 -0
- package/lib/esm/operations/search.js +167 -0
- package/lib/esm/package.json +3 -0
- package/lib/esm/router/path-router.d.ts +38 -0
- package/lib/esm/router/path-router.js +83 -0
- package/lib/esm/router/types.d.ts +30 -0
- package/lib/esm/router/types.js +1 -0
- package/lib/esm/schema/introspector.d.ts +48 -0
- package/lib/esm/schema/introspector.js +182 -0
- package/lib/esm/schema/types.d.ts +104 -0
- package/lib/esm/schema/types.js +10 -0
- package/lib/esm/sqlite-afs.d.ts +144 -0
- package/lib/esm/sqlite-afs.js +333 -0
- 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,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
|
+
}
|