@aigne/afs-sqlite 1.11.0-beta.5 → 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.
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/actions/built-in.cjs +1262 -15
- package/dist/actions/built-in.d.cts.map +1 -1
- package/dist/actions/built-in.d.mts.map +1 -1
- package/dist/actions/built-in.mjs +1262 -15
- package/dist/actions/built-in.mjs.map +1 -1
- package/dist/actions/operators.cjs +156 -0
- package/dist/actions/operators.mjs +157 -0
- package/dist/actions/operators.mjs.map +1 -0
- package/dist/actions/registry.cjs +35 -3
- package/dist/actions/registry.d.cts +36 -17
- package/dist/actions/registry.d.cts.map +1 -1
- package/dist/actions/registry.d.mts +36 -17
- package/dist/actions/registry.d.mts.map +1 -1
- package/dist/actions/registry.mjs +35 -3
- package/dist/actions/registry.mjs.map +1 -1
- package/dist/actions/types.cjs +33 -0
- package/dist/actions/types.d.cts +46 -5
- package/dist/actions/types.d.cts.map +1 -1
- package/dist/actions/types.d.mts +46 -5
- package/dist/actions/types.d.mts.map +1 -1
- package/dist/actions/types.mjs +32 -0
- package/dist/actions/types.mjs.map +1 -0
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/index.cjs +4 -3
- package/dist/index.d.cts +4 -3
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +3 -2
- package/dist/node/builder.cjs +74 -91
- package/dist/node/builder.d.cts +11 -15
- package/dist/node/builder.d.cts.map +1 -1
- package/dist/node/builder.d.mts +11 -15
- package/dist/node/builder.d.mts.map +1 -1
- package/dist/node/builder.mjs +72 -89
- package/dist/node/builder.mjs.map +1 -1
- package/dist/operations/crud.cjs +24 -68
- package/dist/operations/crud.d.cts +23 -38
- package/dist/operations/crud.d.cts.map +1 -1
- package/dist/operations/crud.d.mts +23 -38
- package/dist/operations/crud.d.mts.map +1 -1
- package/dist/operations/crud.mjs +25 -69
- package/dist/operations/crud.mjs.map +1 -1
- package/dist/operations/query-builder.cjs +2 -2
- package/dist/operations/query-builder.d.cts.map +1 -1
- package/dist/operations/query-builder.d.mts.map +1 -1
- package/dist/operations/query-builder.mjs +2 -2
- package/dist/operations/query-builder.mjs.map +1 -1
- package/dist/operations/search.cjs +5 -11
- package/dist/operations/search.d.cts +19 -23
- package/dist/operations/search.d.cts.map +1 -1
- package/dist/operations/search.d.mts +19 -23
- package/dist/operations/search.d.mts.map +1 -1
- package/dist/operations/search.mjs +5 -11
- package/dist/operations/search.mjs.map +1 -1
- package/dist/router/path-router.cjs +7 -15
- package/dist/router/path-router.d.cts +4 -7
- package/dist/router/path-router.d.cts.map +1 -1
- package/dist/router/path-router.d.mts +4 -7
- package/dist/router/path-router.d.mts.map +1 -1
- package/dist/router/path-router.mjs +7 -15
- package/dist/router/path-router.mjs.map +1 -1
- package/dist/router/types.d.cts.map +1 -1
- package/dist/router/types.d.mts.map +1 -1
- package/dist/schema/introspector.d.cts +19 -19
- package/dist/schema/introspector.d.cts.map +1 -1
- package/dist/schema/introspector.d.mts +19 -19
- package/dist/schema/introspector.d.mts.map +1 -1
- package/dist/schema/service.cjs +86 -0
- package/dist/schema/service.d.cts +52 -0
- package/dist/schema/service.d.cts.map +1 -0
- package/dist/schema/service.d.mts +52 -0
- package/dist/schema/service.d.mts.map +1 -0
- package/dist/schema/service.mjs +87 -0
- package/dist/schema/service.mjs.map +1 -0
- package/dist/schema/types.d.cts.map +1 -1
- package/dist/schema/types.d.mts.map +1 -1
- package/dist/sqlite-afs.cjs +672 -121
- package/dist/sqlite-afs.d.cts +257 -56
- package/dist/sqlite-afs.d.cts.map +1 -1
- package/dist/sqlite-afs.d.mts +257 -56
- package/dist/sqlite-afs.d.mts.map +1 -1
- package/dist/sqlite-afs.mjs +674 -123
- package/dist/sqlite-afs.mjs.map +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-builder.mjs","names":[],"sources":["../../src/operations/query-builder.ts"],"sourcesContent":["import type { TableSchema } from \"../schema/types.js\";\n\n/**\n * Builds a SELECT query string for a single row by primary key\n */\nexport function buildSelectByPK(tableName: string, schema: TableSchema, pk: string): string {\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n return `SELECT * FROM \"${tableName}\" WHERE \"${pkColumn}\" = '${escapeSQLString(pk)}'`;\n}\n\n/**\n * Builds a SELECT query string for listing rows with optional limit and offset\n */\nexport function buildSelectAll(\n tableName: string,\n options?: {\n limit?: number;\n offset?: number;\n orderBy?: [string, \"asc\" | \"desc\"][];\n },\n): string {\n let query = `SELECT * FROM \"${tableName}\"`;\n\n if (options?.orderBy?.length) {\n const orderClauses = options.orderBy.map(([col, dir]) => `\"${col}\" ${dir.toUpperCase()}`);\n query += ` ORDER BY ${orderClauses.join(\", \")}`;\n }\n\n if (options?.limit !== undefined) {\n query += ` LIMIT ${options.limit}`;\n }\n\n if (options?.offset !== undefined) {\n query += ` OFFSET ${options.offset}`;\n }\n\n return query;\n}\n\n/**\n * Builds an INSERT query string from content object\n */\nexport function buildInsert(\n tableName: string,\n schema: TableSchema,\n content: Record<string, unknown>,\n): string {\n // Filter to only valid columns\n const validColumns = new Set(schema.columns.map((c) => c.name));\n const entries = Object.entries(content).filter(([key]) => validColumns.has(key));\n\n if (entries.length === 0) {\n throw new Error(`No valid columns provided for INSERT into ${tableName}`);\n }\n\n const columns = entries.map(([key]) => `\"${key}\"`).join(\", \");\n const values = entries.map(([, value]) => formatValue(value)).join(\", \");\n\n return `INSERT INTO \"${tableName}\" (${columns}) VALUES (${values})`;\n}\n\n/**\n * Builds an UPDATE query string from content object\n */\nexport function buildUpdate(\n tableName: string,\n schema: TableSchema,\n pk: string,\n content: Record<string, unknown>,\n): string {\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n\n // Filter to only valid columns, excluding PK\n const validColumns = new Set(schema.columns.map((c) => c.name));\n const entries = Object.entries(content).filter(\n ([key]) => validColumns.has(key) && key !== pkColumn,\n );\n\n if (entries.length === 0) {\n throw new Error(`No valid columns provided for UPDATE on ${tableName}`);\n }\n\n const setClauses = entries.map(([key, value]) => `\"${key}\" = ${formatValue(value)}`).join(\", \");\n\n return `UPDATE \"${tableName}\" SET ${setClauses} WHERE \"${pkColumn}\" = '${escapeSQLString(pk)}'`;\n}\n\n/**\n * Builds a DELETE query string by primary key\n */\nexport function buildDelete(tableName: string, schema: TableSchema, pk: string): string {\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n return `DELETE FROM \"${tableName}\" WHERE \"${pkColumn}\" = '${escapeSQLString(pk)}'`;\n}\n\n/**\n * Formats a value for SQL insertion\n */\nexport function formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"NULL\";\n }\n\n if (typeof value === \"number\") {\n return String(value);\n }\n\n if (typeof value === \"boolean\") {\n return value ? \"1\" : \"0\";\n }\n\n if (value instanceof Date) {\n return `'${value.toISOString()}'`;\n }\n\n if (typeof value === \"object\") {\n return `'${escapeSQLString(JSON.stringify(value))}'`;\n }\n\n return `'${escapeSQLString(String(value))}'`;\n}\n\n/**\n * Escapes a string for safe SQL insertion\n */\nexport function escapeSQLString(str: string): string {\n return str.replace(/'/g, \"''\");\n}\n\n/**\n * Gets the last inserted rowid query string\n */\nexport function buildGetLastRowId(): string {\n return \"SELECT last_insert_rowid() as id\";\n}\n"],"mappings":";;;;AAKA,SAAgB,gBAAgB,WAAmB,QAAqB,IAAoB;
|
|
1
|
+
{"version":3,"file":"query-builder.mjs","names":[],"sources":["../../src/operations/query-builder.ts"],"sourcesContent":["import type { TableSchema } from \"../schema/types.js\";\n\n/**\n * Builds a SELECT query string for a single row by primary key\n */\nexport function buildSelectByPK(tableName: string, schema: TableSchema, pk: string): string {\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n // Include rowid explicitly for tables without explicit primary key\n return `SELECT rowid, * FROM \"${tableName}\" WHERE \"${pkColumn}\" = '${escapeSQLString(pk)}'`;\n}\n\n/**\n * Builds a SELECT query string for listing rows with optional limit and offset\n */\nexport function buildSelectAll(\n tableName: string,\n options?: {\n limit?: number;\n offset?: number;\n orderBy?: [string, \"asc\" | \"desc\"][];\n },\n): string {\n // Include rowid explicitly for tables without explicit primary key\n let query = `SELECT rowid, * FROM \"${tableName}\"`;\n\n if (options?.orderBy?.length) {\n const orderClauses = options.orderBy.map(([col, dir]) => `\"${col}\" ${dir.toUpperCase()}`);\n query += ` ORDER BY ${orderClauses.join(\", \")}`;\n }\n\n if (options?.limit !== undefined) {\n query += ` LIMIT ${options.limit}`;\n }\n\n if (options?.offset !== undefined) {\n query += ` OFFSET ${options.offset}`;\n }\n\n return query;\n}\n\n/**\n * Builds an INSERT query string from content object\n */\nexport function buildInsert(\n tableName: string,\n schema: TableSchema,\n content: Record<string, unknown>,\n): string {\n // Filter to only valid columns\n const validColumns = new Set(schema.columns.map((c) => c.name));\n const entries = Object.entries(content).filter(([key]) => validColumns.has(key));\n\n if (entries.length === 0) {\n throw new Error(`No valid columns provided for INSERT into ${tableName}`);\n }\n\n const columns = entries.map(([key]) => `\"${key}\"`).join(\", \");\n const values = entries.map(([, value]) => formatValue(value)).join(\", \");\n\n return `INSERT INTO \"${tableName}\" (${columns}) VALUES (${values})`;\n}\n\n/**\n * Builds an UPDATE query string from content object\n */\nexport function buildUpdate(\n tableName: string,\n schema: TableSchema,\n pk: string,\n content: Record<string, unknown>,\n): string {\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n\n // Filter to only valid columns, excluding PK\n const validColumns = new Set(schema.columns.map((c) => c.name));\n const entries = Object.entries(content).filter(\n ([key]) => validColumns.has(key) && key !== pkColumn,\n );\n\n if (entries.length === 0) {\n throw new Error(`No valid columns provided for UPDATE on ${tableName}`);\n }\n\n const setClauses = entries.map(([key, value]) => `\"${key}\" = ${formatValue(value)}`).join(\", \");\n\n return `UPDATE \"${tableName}\" SET ${setClauses} WHERE \"${pkColumn}\" = '${escapeSQLString(pk)}'`;\n}\n\n/**\n * Builds a DELETE query string by primary key\n */\nexport function buildDelete(tableName: string, schema: TableSchema, pk: string): string {\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n return `DELETE FROM \"${tableName}\" WHERE \"${pkColumn}\" = '${escapeSQLString(pk)}'`;\n}\n\n/**\n * Formats a value for SQL insertion\n */\nexport function formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return \"NULL\";\n }\n\n if (typeof value === \"number\") {\n return String(value);\n }\n\n if (typeof value === \"boolean\") {\n return value ? \"1\" : \"0\";\n }\n\n if (value instanceof Date) {\n return `'${value.toISOString()}'`;\n }\n\n if (typeof value === \"object\") {\n return `'${escapeSQLString(JSON.stringify(value))}'`;\n }\n\n return `'${escapeSQLString(String(value))}'`;\n}\n\n/**\n * Escapes a string for safe SQL insertion\n */\nexport function escapeSQLString(str: string): string {\n return str.replace(/'/g, \"''\");\n}\n\n/**\n * Gets the last inserted rowid query string\n */\nexport function buildGetLastRowId(): string {\n return \"SELECT last_insert_rowid() as id\";\n}\n"],"mappings":";;;;AAKA,SAAgB,gBAAgB,WAAmB,QAAqB,IAAoB;AAG1F,QAAO,yBAAyB,UAAU,WAFzB,OAAO,WAAW,MAAM,QAEqB,OAAO,gBAAgB,GAAG,CAAC;;;;;AAM3F,SAAgB,eACd,WACA,SAKQ;CAER,IAAI,QAAQ,yBAAyB,UAAU;AAE/C,KAAI,SAAS,SAAS,QAAQ;EAC5B,MAAM,eAAe,QAAQ,QAAQ,KAAK,CAAC,KAAK,SAAS,IAAI,IAAI,IAAI,IAAI,aAAa,GAAG;AACzF,WAAS,aAAa,aAAa,KAAK,KAAK;;AAG/C,KAAI,SAAS,UAAU,OACrB,UAAS,UAAU,QAAQ;AAG7B,KAAI,SAAS,WAAW,OACtB,UAAS,WAAW,QAAQ;AAG9B,QAAO;;;;;AAMT,SAAgB,YACd,WACA,QACA,SACQ;CAER,MAAM,eAAe,IAAI,IAAI,OAAO,QAAQ,KAAK,MAAM,EAAE,KAAK,CAAC;CAC/D,MAAM,UAAU,OAAO,QAAQ,QAAQ,CAAC,QAAQ,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC;AAEhF,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,6CAA6C,YAAY;AAM3E,QAAO,gBAAgB,UAAU,KAHjB,QAAQ,KAAK,CAAC,SAAS,IAAI,IAAI,GAAG,CAAC,KAAK,KAAK,CAGf,YAF/B,QAAQ,KAAK,GAAG,WAAW,YAAY,MAAM,CAAC,CAAC,KAAK,KAAK,CAEP;;;;;AAMnE,SAAgB,YACd,WACA,QACA,IACA,SACQ;CACR,MAAM,WAAW,OAAO,WAAW,MAAM;CAGzC,MAAM,eAAe,IAAI,IAAI,OAAO,QAAQ,KAAK,MAAM,EAAE,KAAK,CAAC;CAC/D,MAAM,UAAU,OAAO,QAAQ,QAAQ,CAAC,QACrC,CAAC,SAAS,aAAa,IAAI,IAAI,IAAI,QAAQ,SAC7C;AAED,KAAI,QAAQ,WAAW,EACrB,OAAM,IAAI,MAAM,2CAA2C,YAAY;AAKzE,QAAO,WAAW,UAAU,QAFT,QAAQ,KAAK,CAAC,KAAK,WAAW,IAAI,IAAI,MAAM,YAAY,MAAM,GAAG,CAAC,KAAK,KAAK,CAEhD,UAAU,SAAS,OAAO,gBAAgB,GAAG,CAAC;;;;;AAM/F,SAAgB,YAAY,WAAmB,QAAqB,IAAoB;AAEtF,QAAO,gBAAgB,UAAU,WADhB,OAAO,WAAW,MAAM,QACY,OAAO,gBAAgB,GAAG,CAAC;;;;;AAMlF,SAAgB,YAAY,OAAwB;AAClD,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAGT,KAAI,OAAO,UAAU,SACnB,QAAO,OAAO,MAAM;AAGtB,KAAI,OAAO,UAAU,UACnB,QAAO,QAAQ,MAAM;AAGvB,KAAI,iBAAiB,KACnB,QAAO,IAAI,MAAM,aAAa,CAAC;AAGjC,KAAI,OAAO,UAAU,SACnB,QAAO,IAAI,gBAAgB,KAAK,UAAU,MAAM,CAAC,CAAC;AAGpD,QAAO,IAAI,gBAAgB,OAAO,MAAM,CAAC,CAAC;;;;;AAM5C,SAAgB,gBAAgB,KAAqB;AACnD,QAAO,IAAI,QAAQ,MAAM,KAAK;;;;;AAMhC,SAAgB,oBAA4B;AAC1C,QAAO"}
|
|
@@ -12,9 +12,9 @@ async function execAll(db, query) {
|
|
|
12
12
|
* FTS5 Search operations for SQLite AFS
|
|
13
13
|
*/
|
|
14
14
|
var FTSSearch = class {
|
|
15
|
-
constructor(db,
|
|
15
|
+
constructor(db, schemaService, config, basePath = "") {
|
|
16
16
|
this.db = db;
|
|
17
|
-
this.
|
|
17
|
+
this.schemaService = schemaService;
|
|
18
18
|
this.config = config;
|
|
19
19
|
this.basePath = basePath;
|
|
20
20
|
}
|
|
@@ -33,7 +33,7 @@ var FTSSearch = class {
|
|
|
33
33
|
const ftsQuery = this.prepareFTSQuery(query, options?.caseSensitive);
|
|
34
34
|
for (const tableName of tablesToSearch) {
|
|
35
35
|
const tableConfig = this.config.tables.get(tableName);
|
|
36
|
-
const schema = this.
|
|
36
|
+
const schema = await this.schemaService.getSchema(tableName);
|
|
37
37
|
if (!tableConfig || !schema) continue;
|
|
38
38
|
const ftsTableName = `${tableName}_fts`;
|
|
39
39
|
try {
|
|
@@ -98,17 +98,11 @@ var FTSSearch = class {
|
|
|
98
98
|
return prepared;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
|
-
* Updates the schemas map (after refresh)
|
|
102
|
-
*/
|
|
103
|
-
setSchemas(schemas) {
|
|
104
|
-
this.schemas = schemas;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
101
|
* Simple search fallback when FTS is not available
|
|
108
102
|
* Uses LIKE queries on specified columns
|
|
109
103
|
*/
|
|
110
104
|
async simpleLikeSearch(tableName, query, columns, options) {
|
|
111
|
-
const schema = this.
|
|
105
|
+
const schema = await this.schemaService.getSchema(tableName);
|
|
112
106
|
if (!schema) return {
|
|
113
107
|
data: [],
|
|
114
108
|
message: `Table '${tableName}' not found`
|
|
@@ -121,7 +115,7 @@ var FTSSearch = class {
|
|
|
121
115
|
data: [],
|
|
122
116
|
message: "No valid columns to search"
|
|
123
117
|
};
|
|
124
|
-
return { data: (await execAll(this.db, `SELECT * FROM "${tableName}" WHERE ${conditions} LIMIT ${limit}`)).map((row) => require_builder.buildSearchEntry(tableName, schema, row, void 0, buildOptions)) };
|
|
118
|
+
return { data: (await execAll(this.db, `SELECT rowid, * FROM "${tableName}" WHERE ${conditions} LIMIT ${limit}`)).map((row) => require_builder.buildSearchEntry(tableName, schema, row, void 0, buildOptions)) };
|
|
125
119
|
}
|
|
126
120
|
};
|
|
127
121
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SchemaService } from "../schema/service.cjs";
|
|
2
2
|
import { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
3
3
|
import { AFSSearchOptions, AFSSearchResult } from "@aigne/afs";
|
|
4
4
|
|
|
@@ -26,45 +26,41 @@ interface FTSConfig {
|
|
|
26
26
|
*/
|
|
27
27
|
declare class FTSSearch {
|
|
28
28
|
private db;
|
|
29
|
-
private
|
|
29
|
+
private schemaService;
|
|
30
30
|
private config;
|
|
31
31
|
private basePath;
|
|
32
|
-
constructor(db: LibSQLDatabase,
|
|
32
|
+
constructor(db: LibSQLDatabase, schemaService: SchemaService, config: FTSConfig, basePath?: string);
|
|
33
33
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
* Performs full-text search across configured tables
|
|
35
|
+
*/
|
|
36
36
|
search(query: string, options?: AFSSearchOptions & {
|
|
37
37
|
/** Specific tables to search (defaults to all FTS-enabled tables) */tables?: string[];
|
|
38
38
|
}): Promise<AFSSearchResult>;
|
|
39
39
|
/**
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
* Searches within a specific table
|
|
41
|
+
*/
|
|
42
42
|
searchTable(tableName: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
43
43
|
/**
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
* Checks if FTS is configured for a table
|
|
45
|
+
*/
|
|
46
46
|
hasFTS(tableName: string): boolean;
|
|
47
47
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
* Gets FTS configuration for a table
|
|
49
|
+
*/
|
|
50
50
|
getFTSConfig(tableName: string): FTSTableConfig | undefined;
|
|
51
51
|
/**
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
* Checks if an FTS table exists
|
|
53
|
+
*/
|
|
54
54
|
private ftsTableExists;
|
|
55
55
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
* Prepares a query string for FTS5
|
|
57
|
+
* Handles special characters and case sensitivity
|
|
58
|
+
*/
|
|
59
59
|
private prepareFTSQuery;
|
|
60
60
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Simple search fallback when FTS is not available
|
|
66
|
-
* Uses LIKE queries on specified columns
|
|
67
|
-
*/
|
|
61
|
+
* Simple search fallback when FTS is not available
|
|
62
|
+
* Uses LIKE queries on specified columns
|
|
63
|
+
*/
|
|
68
64
|
simpleLikeSearch(tableName: string, query: string, columns: string[], options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
69
65
|
}
|
|
70
66
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.cts","names":[],"sources":["../../src/operations/search.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.d.cts","names":[],"sources":["../../src/operations/search.ts"],"mappings":";;;;;;;AAgBA;UAAiB,cAAA;;EAEf,OAAA;EAEW;EAAX,WAAA;AAAA;;;;UAMe,SAAA;EAIP;EAFR,OAAA;EAEkC;EAAlC,MAAA,EAAQ,GAAA,SAAY,cAAA;AAAA;;;;cAMT,SAAA;EAAA,QAED,EAAA;EAAA,QACA,aAAA;EAAA,QACA,MAAA;EAAA,QACA,QAAA;cAHA,EAAA,EAAI,cAAA,EACJ,aAAA,EAAe,aAAA,EACf,MAAA,EAAQ,SAAA,EACR,QAAA;EA4FC;;;EAtFL,MAAA,CACJ,KAAA,UACA,OAAA,GAAU,gBAAA;IA+ID,qEA7IP,MAAA;EAAA,IAED,OAAA,CAAQ,eAAA;EA2ID;;;EA/DJ,WAAA,CACJ,SAAA,UACA,KAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;EA5FD;;;EAmGV,MAAA,CAAO,SAAA;EArGkB;;;EA4GzB,YAAA,CAAa,SAAA,WAAoB,cAAA;EA1GvB;;;EAAA,QAiHI,cAAA;EAvGV;;;;EAAA,QAmHI,eAAA;EApCN;;;;EAyDI,gBAAA,CACJ,SAAA,UACA,KAAA,UACA,OAAA,YACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;AAAA;;;;iBAkCG,eAAA,CAAgB,OAAA;EAC9B,OAAA;EACA,MAAA,GAAS,MAAA;AAAA,IACP,SAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SchemaService } from "../schema/service.mjs";
|
|
2
2
|
import { AFSSearchOptions, AFSSearchResult } from "@aigne/afs";
|
|
3
3
|
import { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
4
4
|
|
|
@@ -26,45 +26,41 @@ interface FTSConfig {
|
|
|
26
26
|
*/
|
|
27
27
|
declare class FTSSearch {
|
|
28
28
|
private db;
|
|
29
|
-
private
|
|
29
|
+
private schemaService;
|
|
30
30
|
private config;
|
|
31
31
|
private basePath;
|
|
32
|
-
constructor(db: LibSQLDatabase,
|
|
32
|
+
constructor(db: LibSQLDatabase, schemaService: SchemaService, config: FTSConfig, basePath?: string);
|
|
33
33
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
* Performs full-text search across configured tables
|
|
35
|
+
*/
|
|
36
36
|
search(query: string, options?: AFSSearchOptions & {
|
|
37
37
|
/** Specific tables to search (defaults to all FTS-enabled tables) */tables?: string[];
|
|
38
38
|
}): Promise<AFSSearchResult>;
|
|
39
39
|
/**
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
* Searches within a specific table
|
|
41
|
+
*/
|
|
42
42
|
searchTable(tableName: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
43
43
|
/**
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
* Checks if FTS is configured for a table
|
|
45
|
+
*/
|
|
46
46
|
hasFTS(tableName: string): boolean;
|
|
47
47
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
* Gets FTS configuration for a table
|
|
49
|
+
*/
|
|
50
50
|
getFTSConfig(tableName: string): FTSTableConfig | undefined;
|
|
51
51
|
/**
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
* Checks if an FTS table exists
|
|
53
|
+
*/
|
|
54
54
|
private ftsTableExists;
|
|
55
55
|
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
* Prepares a query string for FTS5
|
|
57
|
+
* Handles special characters and case sensitivity
|
|
58
|
+
*/
|
|
59
59
|
private prepareFTSQuery;
|
|
60
60
|
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Simple search fallback when FTS is not available
|
|
66
|
-
* Uses LIKE queries on specified columns
|
|
67
|
-
*/
|
|
61
|
+
* Simple search fallback when FTS is not available
|
|
62
|
+
* Uses LIKE queries on specified columns
|
|
63
|
+
*/
|
|
68
64
|
simpleLikeSearch(tableName: string, query: string, columns: string[], options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
69
65
|
}
|
|
70
66
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.mts","names":[],"sources":["../../src/operations/search.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.d.mts","names":[],"sources":["../../src/operations/search.ts"],"mappings":";;;;;;;AAgBA;UAAiB,cAAA;;EAEf,OAAA;EAEW;EAAX,WAAA;AAAA;;;;UAMe,SAAA;EAIP;EAFR,OAAA;EAEkC;EAAlC,MAAA,EAAQ,GAAA,SAAY,cAAA;AAAA;;;;cAMT,SAAA;EAAA,QAED,EAAA;EAAA,QACA,aAAA;EAAA,QACA,MAAA;EAAA,QACA,QAAA;cAHA,EAAA,EAAI,cAAA,EACJ,aAAA,EAAe,aAAA,EACf,MAAA,EAAQ,SAAA,EACR,QAAA;EA4FC;;;EAtFL,MAAA,CACJ,KAAA,UACA,OAAA,GAAU,gBAAA;IA+ID,qEA7IP,MAAA;EAAA,IAED,OAAA,CAAQ,eAAA;EA2ID;;;EA/DJ,WAAA,CACJ,SAAA,UACA,KAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;EA5FD;;;EAmGV,MAAA,CAAO,SAAA;EArGkB;;;EA4GzB,YAAA,CAAa,SAAA,WAAoB,cAAA;EA1GvB;;;EAAA,QAiHI,cAAA;EAvGV;;;;EAAA,QAmHI,eAAA;EApCN;;;;EAyDI,gBAAA,CACJ,SAAA,UACA,KAAA,UACA,OAAA,YACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,eAAA;AAAA;;;;iBAkCG,eAAA,CAAgB,OAAA;EAC9B,OAAA;EACA,MAAA,GAAS,MAAA;AAAA,IACP,SAAA"}
|
|
@@ -12,9 +12,9 @@ async function execAll(db, query) {
|
|
|
12
12
|
* FTS5 Search operations for SQLite AFS
|
|
13
13
|
*/
|
|
14
14
|
var FTSSearch = class {
|
|
15
|
-
constructor(db,
|
|
15
|
+
constructor(db, schemaService, config, basePath = "") {
|
|
16
16
|
this.db = db;
|
|
17
|
-
this.
|
|
17
|
+
this.schemaService = schemaService;
|
|
18
18
|
this.config = config;
|
|
19
19
|
this.basePath = basePath;
|
|
20
20
|
}
|
|
@@ -33,7 +33,7 @@ var FTSSearch = class {
|
|
|
33
33
|
const ftsQuery = this.prepareFTSQuery(query, options?.caseSensitive);
|
|
34
34
|
for (const tableName of tablesToSearch) {
|
|
35
35
|
const tableConfig = this.config.tables.get(tableName);
|
|
36
|
-
const schema = this.
|
|
36
|
+
const schema = await this.schemaService.getSchema(tableName);
|
|
37
37
|
if (!tableConfig || !schema) continue;
|
|
38
38
|
const ftsTableName = `${tableName}_fts`;
|
|
39
39
|
try {
|
|
@@ -98,17 +98,11 @@ var FTSSearch = class {
|
|
|
98
98
|
return prepared;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
|
-
* Updates the schemas map (after refresh)
|
|
102
|
-
*/
|
|
103
|
-
setSchemas(schemas) {
|
|
104
|
-
this.schemas = schemas;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
101
|
* Simple search fallback when FTS is not available
|
|
108
102
|
* Uses LIKE queries on specified columns
|
|
109
103
|
*/
|
|
110
104
|
async simpleLikeSearch(tableName, query, columns, options) {
|
|
111
|
-
const schema = this.
|
|
105
|
+
const schema = await this.schemaService.getSchema(tableName);
|
|
112
106
|
if (!schema) return {
|
|
113
107
|
data: [],
|
|
114
108
|
message: `Table '${tableName}' not found`
|
|
@@ -121,7 +115,7 @@ var FTSSearch = class {
|
|
|
121
115
|
data: [],
|
|
122
116
|
message: "No valid columns to search"
|
|
123
117
|
};
|
|
124
|
-
return { data: (await execAll(this.db, `SELECT * FROM "${tableName}" WHERE ${conditions} LIMIT ${limit}`)).map((row) => buildSearchEntry(tableName, schema, row, void 0, buildOptions)) };
|
|
118
|
+
return { data: (await execAll(this.db, `SELECT rowid, * FROM "${tableName}" WHERE ${conditions} LIMIT ${limit}`)).map((row) => buildSearchEntry(tableName, schema, row, void 0, buildOptions)) };
|
|
125
119
|
}
|
|
126
120
|
};
|
|
127
121
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.mjs","names":[],"sources":["../../src/operations/search.ts"],"sourcesContent":["import type { AFSEntry, AFSSearchOptions, AFSSearchResult } from \"@aigne/afs\";\nimport { sql } from \"@aigne/sqlite\";\nimport type { LibSQLDatabase } from \"drizzle-orm/libsql\";\nimport { type BuildEntryOptions, buildSearchEntry } from \"../node/builder.js\";\nimport type { TableSchema } from \"../schema/types.js\";\n\n/**\n * Executes a raw SQL query and returns all rows\n */\nasync function execAll<T>(db: LibSQLDatabase, query: string): Promise<T[]> {\n return db.all<T>(sql.raw(query)).execute();\n}\n\n/**\n * FTS5 search configuration for a table\n */\nexport interface FTSTableConfig {\n /** Columns to include in FTS index */\n columns: string[];\n /** Whether FTS table has been created */\n initialized?: boolean;\n}\n\n/**\n * FTS5 search configuration\n */\nexport interface FTSConfig {\n /** Whether FTS is enabled */\n enabled: boolean;\n /** Per-table FTS configuration */\n tables: Map<string, FTSTableConfig>;\n}\n\n/**\n * FTS5 Search operations for SQLite AFS\n */\nexport class FTSSearch {\n constructor(\n private db: LibSQLDatabase,\n private schemas: Map<string, TableSchema>,\n private config: FTSConfig,\n private basePath: string = \"\",\n ) {}\n\n /**\n * Performs full-text search across configured tables\n */\n async search(\n query: string,\n options?: AFSSearchOptions & {\n /** Specific tables to search (defaults to all FTS-enabled tables) */\n tables?: string[];\n },\n ): Promise<AFSSearchResult> {\n if (!this.config.enabled) {\n return { data: [], message: \"Full-text search is not enabled\" };\n }\n\n const results: AFSEntry[] = [];\n const limit = options?.limit ?? 50;\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n // Determine which tables to search\n const tablesToSearch = options?.tables\n ? options.tables.filter((t) => this.config.tables.has(t))\n : Array.from(this.config.tables.keys());\n\n // Escape and prepare the query for FTS5\n const ftsQuery = this.prepareFTSQuery(query, options?.caseSensitive);\n\n for (const tableName of tablesToSearch) {\n const tableConfig = this.config.tables.get(tableName);\n const schema = this.schemas.get(tableName);\n\n if (!tableConfig || !schema) continue;\n\n const ftsTableName = `${tableName}_fts`;\n\n try {\n // Check if FTS table exists\n const ftsExists = await this.ftsTableExists(ftsTableName);\n if (!ftsExists) continue;\n\n // Get the first column for highlighting\n const highlightColumn = tableConfig.columns[0] ?? \"\";\n const highlightIndex = highlightColumn ? tableConfig.columns.indexOf(highlightColumn) : 0;\n\n // Build FTS query with highlight\n const rows = await execAll<Record<string, unknown> & { snippet?: string }>(\n this.db,\n `\n SELECT t.*, highlight(\"${ftsTableName}\", ${highlightIndex}, '<mark>', '</mark>') as snippet\n FROM \"${ftsTableName}\" fts\n JOIN \"${tableName}\" t ON fts.rowid = t.rowid\n WHERE \"${ftsTableName}\" MATCH '${ftsQuery}'\n LIMIT ${Math.ceil(limit / tablesToSearch.length)}\n `,\n );\n\n for (const row of rows) {\n const { snippet, ...rowData } = row;\n results.push(\n buildSearchEntry(\n tableName,\n schema,\n rowData,\n snippet as string | undefined,\n buildOptions,\n ),\n );\n }\n } catch (error) {\n // Log but continue with other tables\n console.warn(`FTS search failed for table ${tableName}:`, error);\n }\n\n // Stop if we have enough results\n if (results.length >= limit) break;\n }\n\n return {\n data: results.slice(0, limit),\n message: results.length === 0 ? `No results found for \"${query}\"` : undefined,\n };\n }\n\n /**\n * Searches within a specific table\n */\n async searchTable(\n tableName: string,\n query: string,\n options?: AFSSearchOptions,\n ): Promise<AFSSearchResult> {\n return this.search(query, { ...options, tables: [tableName] });\n }\n\n /**\n * Checks if FTS is configured for a table\n */\n hasFTS(tableName: string): boolean {\n return this.config.enabled && this.config.tables.has(tableName);\n }\n\n /**\n * Gets FTS configuration for a table\n */\n getFTSConfig(tableName: string): FTSTableConfig | undefined {\n return this.config.tables.get(tableName);\n }\n\n /**\n * Checks if an FTS table exists\n */\n private async ftsTableExists(ftsTableName: string): Promise<boolean> {\n const result = await execAll<{ name: string }>(\n this.db,\n `SELECT name FROM sqlite_master WHERE type = 'table' AND name = '${ftsTableName}'`,\n );\n return result.length > 0;\n }\n\n /**\n * Prepares a query string for FTS5\n * Handles special characters and case sensitivity\n */\n private prepareFTSQuery(query: string, _caseSensitive?: boolean): string {\n // Escape special FTS5 characters\n let prepared = query\n .replace(/\"/g, '\"\"') // Escape double quotes\n .replace(/'/g, \"''\"); // Escape single quotes\n\n // For case-insensitive search (default), we don't need to modify\n // FTS5 is case-insensitive by default for ASCII\n\n // If the query contains multiple words, search for the phrase\n if (prepared.includes(\" \") && !prepared.startsWith('\"')) {\n prepared = `\"${prepared}\"`;\n }\n\n return prepared;\n }\n\n /**\n * Updates the schemas map (after refresh)\n */\n setSchemas(schemas: Map<string, TableSchema>): void {\n this.schemas = schemas;\n }\n\n /**\n * Simple search fallback when FTS is not available\n * Uses LIKE queries on specified columns\n */\n async simpleLikeSearch(\n tableName: string,\n query: string,\n columns: string[],\n options?: AFSSearchOptions,\n ): Promise<AFSSearchResult> {\n const schema = this.schemas.get(tableName);\n if (!schema) {\n return { data: [], message: `Table '${tableName}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n const limit = options?.limit ?? 50;\n const escapedQuery = query.replace(/'/g, \"''\");\n\n // Build LIKE conditions for each column\n const conditions = columns\n .filter((col) => schema.columns.some((c) => c.name === col))\n .map((col) => `\"${col}\" LIKE '%${escapedQuery}%'`)\n .join(\" OR \");\n\n if (!conditions) {\n return { data: [], message: \"No valid columns to search\" };\n }\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n `SELECT * FROM \"${tableName}\" WHERE ${conditions} LIMIT ${limit}`,\n );\n\n return {\n data: rows.map((row) => buildSearchEntry(tableName, schema, row, undefined, buildOptions)),\n };\n }\n}\n\n/**\n * Creates FTS configuration from options\n */\nexport function createFTSConfig(options?: {\n enabled?: boolean;\n tables?: Record<string, string[]>;\n}): FTSConfig {\n const config: FTSConfig = {\n enabled: options?.enabled ?? false,\n tables: new Map(),\n };\n\n if (options?.tables) {\n for (const [table, columns] of Object.entries(options.tables)) {\n config.tables.set(table, { columns });\n }\n }\n\n return config;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search.mjs","names":[],"sources":["../../src/operations/search.ts"],"sourcesContent":["import type { AFSEntry, AFSSearchOptions, AFSSearchResult } from \"@aigne/afs\";\nimport { sql } from \"@aigne/sqlite\";\nimport type { LibSQLDatabase } from \"drizzle-orm/libsql\";\nimport { type BuildEntryOptions, buildSearchEntry } from \"../node/builder.js\";\nimport type { SchemaService } from \"../schema/service.js\";\n\n/**\n * Executes a raw SQL query and returns all rows\n */\nasync function execAll<T>(db: LibSQLDatabase, query: string): Promise<T[]> {\n return db.all<T>(sql.raw(query)).execute();\n}\n\n/**\n * FTS5 search configuration for a table\n */\nexport interface FTSTableConfig {\n /** Columns to include in FTS index */\n columns: string[];\n /** Whether FTS table has been created */\n initialized?: boolean;\n}\n\n/**\n * FTS5 search configuration\n */\nexport interface FTSConfig {\n /** Whether FTS is enabled */\n enabled: boolean;\n /** Per-table FTS configuration */\n tables: Map<string, FTSTableConfig>;\n}\n\n/**\n * FTS5 Search operations for SQLite AFS\n */\nexport class FTSSearch {\n constructor(\n private db: LibSQLDatabase,\n private schemaService: SchemaService,\n private config: FTSConfig,\n private basePath: string = \"\",\n ) {}\n\n /**\n * Performs full-text search across configured tables\n */\n async search(\n query: string,\n options?: AFSSearchOptions & {\n /** Specific tables to search (defaults to all FTS-enabled tables) */\n tables?: string[];\n },\n ): Promise<AFSSearchResult> {\n if (!this.config.enabled) {\n return { data: [], message: \"Full-text search is not enabled\" };\n }\n\n const results: AFSEntry[] = [];\n const limit = options?.limit ?? 50;\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n // Determine which tables to search\n const tablesToSearch = options?.tables\n ? options.tables.filter((t) => this.config.tables.has(t))\n : Array.from(this.config.tables.keys());\n\n // Escape and prepare the query for FTS5\n const ftsQuery = this.prepareFTSQuery(query, options?.caseSensitive);\n\n for (const tableName of tablesToSearch) {\n const tableConfig = this.config.tables.get(tableName);\n const schema = await this.schemaService.getSchema(tableName);\n\n if (!tableConfig || !schema) continue;\n\n const ftsTableName = `${tableName}_fts`;\n\n try {\n // Check if FTS table exists\n const ftsExists = await this.ftsTableExists(ftsTableName);\n if (!ftsExists) continue;\n\n // Get the first column for highlighting\n const highlightColumn = tableConfig.columns[0] ?? \"\";\n const highlightIndex = highlightColumn ? tableConfig.columns.indexOf(highlightColumn) : 0;\n\n // Build FTS query with highlight\n const rows = await execAll<Record<string, unknown> & { snippet?: string }>(\n this.db,\n `\n SELECT t.*, highlight(\"${ftsTableName}\", ${highlightIndex}, '<mark>', '</mark>') as snippet\n FROM \"${ftsTableName}\" fts\n JOIN \"${tableName}\" t ON fts.rowid = t.rowid\n WHERE \"${ftsTableName}\" MATCH '${ftsQuery}'\n LIMIT ${Math.ceil(limit / tablesToSearch.length)}\n `,\n );\n\n for (const row of rows) {\n const { snippet, ...rowData } = row;\n results.push(\n buildSearchEntry(\n tableName,\n schema,\n rowData,\n snippet as string | undefined,\n buildOptions,\n ),\n );\n }\n } catch (error) {\n // Log but continue with other tables\n console.warn(`FTS search failed for table ${tableName}:`, error);\n }\n\n // Stop if we have enough results\n if (results.length >= limit) break;\n }\n\n return {\n data: results.slice(0, limit),\n message: results.length === 0 ? `No results found for \"${query}\"` : undefined,\n };\n }\n\n /**\n * Searches within a specific table\n */\n async searchTable(\n tableName: string,\n query: string,\n options?: AFSSearchOptions,\n ): Promise<AFSSearchResult> {\n return this.search(query, { ...options, tables: [tableName] });\n }\n\n /**\n * Checks if FTS is configured for a table\n */\n hasFTS(tableName: string): boolean {\n return this.config.enabled && this.config.tables.has(tableName);\n }\n\n /**\n * Gets FTS configuration for a table\n */\n getFTSConfig(tableName: string): FTSTableConfig | undefined {\n return this.config.tables.get(tableName);\n }\n\n /**\n * Checks if an FTS table exists\n */\n private async ftsTableExists(ftsTableName: string): Promise<boolean> {\n const result = await execAll<{ name: string }>(\n this.db,\n `SELECT name FROM sqlite_master WHERE type = 'table' AND name = '${ftsTableName}'`,\n );\n return result.length > 0;\n }\n\n /**\n * Prepares a query string for FTS5\n * Handles special characters and case sensitivity\n */\n private prepareFTSQuery(query: string, _caseSensitive?: boolean): string {\n // Escape special FTS5 characters\n let prepared = query\n .replace(/\"/g, '\"\"') // Escape double quotes\n .replace(/'/g, \"''\"); // Escape single quotes\n\n // For case-insensitive search (default), we don't need to modify\n // FTS5 is case-insensitive by default for ASCII\n\n // If the query contains multiple words, search for the phrase\n if (prepared.includes(\" \") && !prepared.startsWith('\"')) {\n prepared = `\"${prepared}\"`;\n }\n\n return prepared;\n }\n\n /**\n * Simple search fallback when FTS is not available\n * Uses LIKE queries on specified columns\n */\n async simpleLikeSearch(\n tableName: string,\n query: string,\n columns: string[],\n options?: AFSSearchOptions,\n ): Promise<AFSSearchResult> {\n const schema = await this.schemaService.getSchema(tableName);\n if (!schema) {\n return { data: [], message: `Table '${tableName}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n const limit = options?.limit ?? 50;\n const escapedQuery = query.replace(/'/g, \"''\");\n\n // Build LIKE conditions for each column\n const conditions = columns\n .filter((col) => schema.columns.some((c) => c.name === col))\n .map((col) => `\"${col}\" LIKE '%${escapedQuery}%'`)\n .join(\" OR \");\n\n if (!conditions) {\n return { data: [], message: \"No valid columns to search\" };\n }\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n `SELECT rowid, * FROM \"${tableName}\" WHERE ${conditions} LIMIT ${limit}`,\n );\n\n return {\n data: rows.map((row) => buildSearchEntry(tableName, schema, row, undefined, buildOptions)),\n };\n }\n}\n\n/**\n * Creates FTS configuration from options\n */\nexport function createFTSConfig(options?: {\n enabled?: boolean;\n tables?: Record<string, string[]>;\n}): FTSConfig {\n const config: FTSConfig = {\n enabled: options?.enabled ?? false,\n tables: new Map(),\n };\n\n if (options?.tables) {\n for (const [table, columns] of Object.entries(options.tables)) {\n config.tables.set(table, { columns });\n }\n }\n\n return config;\n}\n"],"mappings":"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"}
|
|
@@ -8,26 +8,20 @@ let radix3 = require("radix3");
|
|
|
8
8
|
* - / → listTables
|
|
9
9
|
* - /:table → listTable
|
|
10
10
|
* - /:table/new → createRow
|
|
11
|
-
* - /:table/@schema → getSchema
|
|
12
11
|
* - /:table/:pk → readRow
|
|
13
|
-
* - /:table/:pk/@attr → listAttributes
|
|
14
|
-
* - /:table/:pk/@attr/:column → getAttribute
|
|
15
12
|
* - /:table/:pk/@meta → getMeta
|
|
16
|
-
* - /:table/:pk
|
|
17
|
-
* - /:table/:pk
|
|
13
|
+
* - /:table/:pk/.actions → listActions
|
|
14
|
+
* - /:table/:pk/.actions/:action → executeAction
|
|
18
15
|
*/
|
|
19
16
|
function createPathRouter() {
|
|
20
17
|
return (0, radix3.createRouter)({ routes: {
|
|
21
18
|
"/": { action: "listTables" },
|
|
22
19
|
"/:table": { action: "listTable" },
|
|
23
20
|
"/:table/new": { action: "createRow" },
|
|
24
|
-
"/:table/@schema": { action: "getSchema" },
|
|
25
21
|
"/:table/:pk": { action: "readRow" },
|
|
26
|
-
"/:table/:pk/@attr": { action: "listAttributes" },
|
|
27
|
-
"/:table/:pk/@attr/:column": { action: "getAttribute" },
|
|
28
22
|
"/:table/:pk/@meta": { action: "getMeta" },
|
|
29
|
-
"/:table/:pk
|
|
30
|
-
"/:table/:pk
|
|
23
|
+
"/:table/:pk/.actions": { action: "listActions" },
|
|
24
|
+
"/:table/:pk/.actions/:action": { action: "executeAction" }
|
|
31
25
|
} });
|
|
32
26
|
}
|
|
33
27
|
/**
|
|
@@ -55,19 +49,17 @@ function buildPath(table, pk, suffix) {
|
|
|
55
49
|
return parts.join("/").replace(/\/+/g, "/");
|
|
56
50
|
}
|
|
57
51
|
/**
|
|
58
|
-
* Checks if a path segment is a virtual path (@
|
|
52
|
+
* Checks if a path segment is a virtual path (@meta, .actions, .meta)
|
|
59
53
|
*/
|
|
60
54
|
function isVirtualPath(segment) {
|
|
61
|
-
return segment.startsWith("@");
|
|
55
|
+
return segment.startsWith("@") || segment.startsWith(".");
|
|
62
56
|
}
|
|
63
57
|
/**
|
|
64
58
|
* Gets the type of virtual path
|
|
65
59
|
*/
|
|
66
60
|
function getVirtualPathType(segment) {
|
|
67
|
-
if (segment === "@attr") return "attr";
|
|
68
61
|
if (segment === "@meta") return "meta";
|
|
69
|
-
if (segment === "
|
|
70
|
-
if (segment === "@schema") return "schema";
|
|
62
|
+
if (segment === ".actions") return "actions";
|
|
71
63
|
return null;
|
|
72
64
|
}
|
|
73
65
|
|
|
@@ -9,13 +9,10 @@ import { RadixRouter } from "radix3";
|
|
|
9
9
|
* - / → listTables
|
|
10
10
|
* - /:table → listTable
|
|
11
11
|
* - /:table/new → createRow
|
|
12
|
-
* - /:table/@schema → getSchema
|
|
13
12
|
* - /:table/:pk → readRow
|
|
14
|
-
* - /:table/:pk/@attr → listAttributes
|
|
15
|
-
* - /:table/:pk/@attr/:column → getAttribute
|
|
16
13
|
* - /:table/:pk/@meta → getMeta
|
|
17
|
-
* - /:table/:pk
|
|
18
|
-
* - /:table/:pk
|
|
14
|
+
* - /:table/:pk/.actions → listActions
|
|
15
|
+
* - /:table/:pk/.actions/:action → executeAction
|
|
19
16
|
*/
|
|
20
17
|
declare function createPathRouter(): RadixRouter<RouteData>;
|
|
21
18
|
/**
|
|
@@ -30,13 +27,13 @@ declare function matchPath(router: RadixRouter<RouteData>, path: string): RouteM
|
|
|
30
27
|
*/
|
|
31
28
|
declare function buildPath(table?: string, pk?: string, suffix?: string): string;
|
|
32
29
|
/**
|
|
33
|
-
* Checks if a path segment is a virtual path (@
|
|
30
|
+
* Checks if a path segment is a virtual path (@meta, .actions, .meta)
|
|
34
31
|
*/
|
|
35
32
|
declare function isVirtualPath(segment: string): boolean;
|
|
36
33
|
/**
|
|
37
34
|
* Gets the type of virtual path
|
|
38
35
|
*/
|
|
39
|
-
declare function getVirtualPathType(segment: string): "
|
|
36
|
+
declare function getVirtualPathType(segment: string): "meta" | "actions" | null;
|
|
40
37
|
//#endregion
|
|
41
38
|
export { buildPath, createPathRouter, getVirtualPathType, isVirtualPath, matchPath };
|
|
42
39
|
//# sourceMappingURL=path-router.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-router.d.cts","names":[],"sources":["../../src/router/path-router.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"path-router.d.cts","names":[],"sources":["../../src/router/path-router.ts"],"mappings":";;;;;AAiBA;;;;;AAyBA;;;;;;iBAzBgB,gBAAA,CAAA,GAAoB,WAAA,CAAY,SAAA;;;;;;;iBAyBhC,SAAA,CAAU,MAAA,EAAQ,WAAA,CAAY,SAAA,GAAY,IAAA,WAAe,UAAA;;AAazE;;iBAAgB,SAAA,CAAU,KAAA,WAAgB,EAAA,WAAa,MAAA;;;;iBAWvC,aAAA,CAAc,OAAA;;;AAA9B;iBAOgB,kBAAA,CAAmB,OAAA"}
|
|
@@ -9,13 +9,10 @@ import { RadixRouter } from "radix3";
|
|
|
9
9
|
* - / → listTables
|
|
10
10
|
* - /:table → listTable
|
|
11
11
|
* - /:table/new → createRow
|
|
12
|
-
* - /:table/@schema → getSchema
|
|
13
12
|
* - /:table/:pk → readRow
|
|
14
|
-
* - /:table/:pk/@attr → listAttributes
|
|
15
|
-
* - /:table/:pk/@attr/:column → getAttribute
|
|
16
13
|
* - /:table/:pk/@meta → getMeta
|
|
17
|
-
* - /:table/:pk
|
|
18
|
-
* - /:table/:pk
|
|
14
|
+
* - /:table/:pk/.actions → listActions
|
|
15
|
+
* - /:table/:pk/.actions/:action → executeAction
|
|
19
16
|
*/
|
|
20
17
|
declare function createPathRouter(): RadixRouter<RouteData>;
|
|
21
18
|
/**
|
|
@@ -30,13 +27,13 @@ declare function matchPath(router: RadixRouter<RouteData>, path: string): RouteM
|
|
|
30
27
|
*/
|
|
31
28
|
declare function buildPath(table?: string, pk?: string, suffix?: string): string;
|
|
32
29
|
/**
|
|
33
|
-
* Checks if a path segment is a virtual path (@
|
|
30
|
+
* Checks if a path segment is a virtual path (@meta, .actions, .meta)
|
|
34
31
|
*/
|
|
35
32
|
declare function isVirtualPath(segment: string): boolean;
|
|
36
33
|
/**
|
|
37
34
|
* Gets the type of virtual path
|
|
38
35
|
*/
|
|
39
|
-
declare function getVirtualPathType(segment: string): "
|
|
36
|
+
declare function getVirtualPathType(segment: string): "meta" | "actions" | null;
|
|
40
37
|
//#endregion
|
|
41
38
|
export { buildPath, createPathRouter, getVirtualPathType, isVirtualPath, matchPath };
|
|
42
39
|
//# sourceMappingURL=path-router.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-router.d.mts","names":[],"sources":["../../src/router/path-router.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"path-router.d.mts","names":[],"sources":["../../src/router/path-router.ts"],"mappings":";;;;;AAiBA;;;;;AAyBA;;;;;;iBAzBgB,gBAAA,CAAA,GAAoB,WAAA,CAAY,SAAA;;;;;;;iBAyBhC,SAAA,CAAU,MAAA,EAAQ,WAAA,CAAY,SAAA,GAAY,IAAA,WAAe,UAAA;;AAazE;;iBAAgB,SAAA,CAAU,KAAA,WAAgB,EAAA,WAAa,MAAA;;;;iBAWvC,aAAA,CAAc,OAAA;;;AAA9B;iBAOgB,kBAAA,CAAmB,OAAA"}
|
|
@@ -8,26 +8,20 @@ import { createRouter } from "radix3";
|
|
|
8
8
|
* - / → listTables
|
|
9
9
|
* - /:table → listTable
|
|
10
10
|
* - /:table/new → createRow
|
|
11
|
-
* - /:table/@schema → getSchema
|
|
12
11
|
* - /:table/:pk → readRow
|
|
13
|
-
* - /:table/:pk/@attr → listAttributes
|
|
14
|
-
* - /:table/:pk/@attr/:column → getAttribute
|
|
15
12
|
* - /:table/:pk/@meta → getMeta
|
|
16
|
-
* - /:table/:pk
|
|
17
|
-
* - /:table/:pk
|
|
13
|
+
* - /:table/:pk/.actions → listActions
|
|
14
|
+
* - /:table/:pk/.actions/:action → executeAction
|
|
18
15
|
*/
|
|
19
16
|
function createPathRouter() {
|
|
20
17
|
return createRouter({ routes: {
|
|
21
18
|
"/": { action: "listTables" },
|
|
22
19
|
"/:table": { action: "listTable" },
|
|
23
20
|
"/:table/new": { action: "createRow" },
|
|
24
|
-
"/:table/@schema": { action: "getSchema" },
|
|
25
21
|
"/:table/:pk": { action: "readRow" },
|
|
26
|
-
"/:table/:pk/@attr": { action: "listAttributes" },
|
|
27
|
-
"/:table/:pk/@attr/:column": { action: "getAttribute" },
|
|
28
22
|
"/:table/:pk/@meta": { action: "getMeta" },
|
|
29
|
-
"/:table/:pk
|
|
30
|
-
"/:table/:pk
|
|
23
|
+
"/:table/:pk/.actions": { action: "listActions" },
|
|
24
|
+
"/:table/:pk/.actions/:action": { action: "executeAction" }
|
|
31
25
|
} });
|
|
32
26
|
}
|
|
33
27
|
/**
|
|
@@ -55,19 +49,17 @@ function buildPath(table, pk, suffix) {
|
|
|
55
49
|
return parts.join("/").replace(/\/+/g, "/");
|
|
56
50
|
}
|
|
57
51
|
/**
|
|
58
|
-
* Checks if a path segment is a virtual path (@
|
|
52
|
+
* Checks if a path segment is a virtual path (@meta, .actions, .meta)
|
|
59
53
|
*/
|
|
60
54
|
function isVirtualPath(segment) {
|
|
61
|
-
return segment.startsWith("@");
|
|
55
|
+
return segment.startsWith("@") || segment.startsWith(".");
|
|
62
56
|
}
|
|
63
57
|
/**
|
|
64
58
|
* Gets the type of virtual path
|
|
65
59
|
*/
|
|
66
60
|
function getVirtualPathType(segment) {
|
|
67
|
-
if (segment === "@attr") return "attr";
|
|
68
61
|
if (segment === "@meta") return "meta";
|
|
69
|
-
if (segment === "
|
|
70
|
-
if (segment === "@schema") return "schema";
|
|
62
|
+
if (segment === ".actions") return "actions";
|
|
71
63
|
return null;
|
|
72
64
|
}
|
|
73
65
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-router.mjs","names":[],"sources":["../../src/router/path-router.ts"],"sourcesContent":["import { createRouter, type RadixRouter } from \"radix3\";\nimport type { RouteData, RouteMatch, RouteParams } from \"./types.js\";\n\nexport type { RouteData };\n\n/**\n * Creates a radix3 router for SQLite AFS path routing\n *\n * Routes:\n * - / → listTables\n * - /:table → listTable\n * - /:table/new → createRow\n * - /:table
|
|
1
|
+
{"version":3,"file":"path-router.mjs","names":[],"sources":["../../src/router/path-router.ts"],"sourcesContent":["import { createRouter, type RadixRouter } from \"radix3\";\nimport type { RouteData, RouteMatch, RouteParams } from \"./types.js\";\n\nexport type { RouteData };\n\n/**\n * Creates a radix3 router for SQLite AFS path routing\n *\n * Routes:\n * - / → listTables\n * - /:table → listTable\n * - /:table/new → createRow\n * - /:table/:pk → readRow\n * - /:table/:pk/@meta → getMeta\n * - /:table/:pk/.actions → listActions\n * - /:table/:pk/.actions/:action → executeAction\n */\nexport function createPathRouter(): RadixRouter<RouteData> {\n return createRouter<RouteData>({\n routes: {\n // Root - list all tables\n \"/\": { action: \"listTables\" },\n\n // Table-level routes\n \"/:table\": { action: \"listTable\" },\n \"/:table/new\": { action: \"createRow\" },\n\n // Row-level routes\n \"/:table/:pk\": { action: \"readRow\" },\n \"/:table/:pk/@meta\": { action: \"getMeta\" },\n \"/:table/:pk/.actions\": { action: \"listActions\" },\n \"/:table/:pk/.actions/:action\": { action: \"executeAction\" },\n },\n });\n}\n\n/**\n * Parses a path and returns the matched route with params\n * @param router - The radix3 router instance\n * @param path - The path to match\n * @returns RouteMatch if matched, undefined otherwise\n */\nexport function matchPath(router: RadixRouter<RouteData>, path: string): RouteMatch | undefined {\n const result = router.lookup(path);\n if (!result) return undefined;\n\n return {\n action: result.action,\n params: (result.params ?? {}) as RouteParams,\n };\n}\n\n/**\n * Builds a path from components\n */\nexport function buildPath(table?: string, pk?: string, suffix?: string): string {\n const parts = [\"/\"];\n if (table) parts.push(table);\n if (pk) parts.push(pk);\n if (suffix) parts.push(suffix);\n return parts.join(\"/\").replace(/\\/+/g, \"/\");\n}\n\n/**\n * Checks if a path segment is a virtual path (@meta, .actions, .meta)\n */\nexport function isVirtualPath(segment: string): boolean {\n return segment.startsWith(\"@\") || segment.startsWith(\".\");\n}\n\n/**\n * Gets the type of virtual path\n */\nexport function getVirtualPathType(segment: string): \"meta\" | \"actions\" | null {\n if (segment === \"@meta\") return \"meta\";\n if (segment === \".actions\") return \"actions\";\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiBA,SAAgB,mBAA2C;AACzD,QAAO,aAAwB,EAC7B,QAAQ;EAEN,KAAK,EAAE,QAAQ,cAAc;EAG7B,WAAW,EAAE,QAAQ,aAAa;EAClC,eAAe,EAAE,QAAQ,aAAa;EAGtC,eAAe,EAAE,QAAQ,WAAW;EACpC,qBAAqB,EAAE,QAAQ,WAAW;EAC1C,wBAAwB,EAAE,QAAQ,eAAe;EACjD,gCAAgC,EAAE,QAAQ,iBAAiB;EAC5D,EACF,CAAC;;;;;;;;AASJ,SAAgB,UAAU,QAAgC,MAAsC;CAC9F,MAAM,SAAS,OAAO,OAAO,KAAK;AAClC,KAAI,CAAC,OAAQ,QAAO;AAEpB,QAAO;EACL,QAAQ,OAAO;EACf,QAAS,OAAO,UAAU,EAAE;EAC7B;;;;;AAMH,SAAgB,UAAU,OAAgB,IAAa,QAAyB;CAC9E,MAAM,QAAQ,CAAC,IAAI;AACnB,KAAI,MAAO,OAAM,KAAK,MAAM;AAC5B,KAAI,GAAI,OAAM,KAAK,GAAG;AACtB,KAAI,OAAQ,OAAM,KAAK,OAAO;AAC9B,QAAO,MAAM,KAAK,IAAI,CAAC,QAAQ,QAAQ,IAAI;;;;;AAM7C,SAAgB,cAAc,SAA0B;AACtD,QAAO,QAAQ,WAAW,IAAI,IAAI,QAAQ,WAAW,IAAI;;;;;AAM3D,SAAgB,mBAAmB,SAA4C;AAC7E,KAAI,YAAY,QAAS,QAAO;AAChC,KAAI,YAAY,WAAY,QAAO;AACnC,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","names":[],"sources":["../../src/router/types.ts"],"mappings":";;AAGA
|
|
1
|
+
{"version":3,"file":"types.d.cts","names":[],"sources":["../../src/router/types.ts"],"mappings":";;AAGA;;KAAY,WAAA;;;AAeZ;UAAiB,SAAA;;EAEf,MAAA,EAAQ,WAAA;AAAA;AAMV;;;AAAA,UAAiB,UAAA,SAAmB,SAAA;EAClC,MAAA,EAAQ,WAAA;AAAA;;;;UAMO,WAAA;EAAW;EAE1B,KAAA;EAF0B;EAI1B,EAAA;EAAA;EAEA,MAAA;EAEA;EAAA,MAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/router/types.ts"],"mappings":";;AAGA
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/router/types.ts"],"mappings":";;AAGA;;KAAY,WAAA;;;AAeZ;UAAiB,SAAA;;EAEf,MAAA,EAAQ,WAAA;AAAA;AAMV;;;AAAA,UAAiB,UAAA,SAAmB,SAAA;EAClC,MAAA,EAAQ,WAAA;AAAA;;;;UAMO,WAAA;EAAW;EAE1B,KAAA;EAF0B;EAI1B,EAAA;EAAA;EAEA,MAAA;EAEA;EAAA,MAAA;AAAA"}
|
|
@@ -7,41 +7,41 @@ import { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
|
7
7
|
*/
|
|
8
8
|
declare class SchemaIntrospector {
|
|
9
9
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
* Introspects all tables in the database
|
|
11
|
+
* @param db - Drizzle database instance
|
|
12
|
+
* @param options - Introspection options
|
|
13
|
+
* @returns Map of table name to TableSchema
|
|
14
|
+
*/
|
|
15
15
|
introspect(db: LibSQLDatabase, options?: {
|
|
16
16
|
/** Whitelist of tables to include */tables?: string[]; /** Tables to exclude */
|
|
17
17
|
excludeTables?: string[];
|
|
18
18
|
}): Promise<Map<string, TableSchema>>;
|
|
19
19
|
/**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
25
|
introspectTable(db: LibSQLDatabase, tableName: string): Promise<TableSchema>;
|
|
26
26
|
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
* Gets the primary key column name for a table
|
|
28
|
+
* Returns the first PK column, or 'rowid' if no explicit PK
|
|
29
|
+
*/
|
|
30
30
|
getPrimaryKeyColumn(schema: TableSchema): string;
|
|
31
31
|
/**
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
* Checks if a table has FTS (Full-Text Search) enabled
|
|
33
|
+
*/
|
|
34
34
|
hasFTS(db: LibSQLDatabase, tableName: string): Promise<boolean>;
|
|
35
35
|
/**
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
* Creates FTS5 table for full-text search on specified columns
|
|
37
|
+
*/
|
|
38
38
|
createFTS(db: LibSQLDatabase, tableName: string, columns: string[], options?: {
|
|
39
39
|
/** Content table (defaults to tableName) */contentTable?: string; /** Content rowid column (defaults to 'rowid') */
|
|
40
40
|
contentRowid?: string;
|
|
41
41
|
}): Promise<void>;
|
|
42
42
|
/**
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
* Rebuilds FTS index for a table (useful after bulk inserts)
|
|
44
|
+
*/
|
|
45
45
|
rebuildFTS(db: LibSQLDatabase, tableName: string): Promise<void>;
|
|
46
46
|
}
|
|
47
47
|
//#endregion
|