@aigne/afs-sqlite 1.11.0-beta.1 → 1.11.0-beta.11

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 (93) hide show
  1. package/README.md +3 -3
  2. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  4. package/dist/_virtual/rolldown_runtime.mjs +7 -0
  5. package/dist/actions/built-in.cjs +1262 -15
  6. package/dist/actions/built-in.d.cts.map +1 -1
  7. package/dist/actions/built-in.d.mts.map +1 -1
  8. package/dist/actions/built-in.mjs +1262 -15
  9. package/dist/actions/built-in.mjs.map +1 -1
  10. package/dist/actions/operators.cjs +156 -0
  11. package/dist/actions/operators.mjs +157 -0
  12. package/dist/actions/operators.mjs.map +1 -0
  13. package/dist/actions/registry.cjs +35 -3
  14. package/dist/actions/registry.d.cts +36 -17
  15. package/dist/actions/registry.d.cts.map +1 -1
  16. package/dist/actions/registry.d.mts +36 -17
  17. package/dist/actions/registry.d.mts.map +1 -1
  18. package/dist/actions/registry.mjs +35 -3
  19. package/dist/actions/registry.mjs.map +1 -1
  20. package/dist/actions/types.cjs +33 -0
  21. package/dist/actions/types.d.cts +46 -5
  22. package/dist/actions/types.d.cts.map +1 -1
  23. package/dist/actions/types.d.mts +46 -5
  24. package/dist/actions/types.d.mts.map +1 -1
  25. package/dist/actions/types.mjs +32 -0
  26. package/dist/actions/types.mjs.map +1 -0
  27. package/dist/config.cjs +1 -1
  28. package/dist/config.d.cts +9 -36
  29. package/dist/config.d.cts.map +1 -1
  30. package/dist/config.d.mts +9 -36
  31. package/dist/config.d.mts.map +1 -1
  32. package/dist/config.mjs +1 -1
  33. package/dist/config.mjs.map +1 -1
  34. package/dist/index.cjs +4 -3
  35. package/dist/index.d.cts +4 -3
  36. package/dist/index.d.mts +4 -3
  37. package/dist/index.mjs +3 -2
  38. package/dist/node/builder.cjs +74 -91
  39. package/dist/node/builder.d.cts +11 -15
  40. package/dist/node/builder.d.cts.map +1 -1
  41. package/dist/node/builder.d.mts +11 -15
  42. package/dist/node/builder.d.mts.map +1 -1
  43. package/dist/node/builder.mjs +72 -89
  44. package/dist/node/builder.mjs.map +1 -1
  45. package/dist/operations/crud.cjs +24 -68
  46. package/dist/operations/crud.d.cts +23 -38
  47. package/dist/operations/crud.d.cts.map +1 -1
  48. package/dist/operations/crud.d.mts +23 -38
  49. package/dist/operations/crud.d.mts.map +1 -1
  50. package/dist/operations/crud.mjs +25 -69
  51. package/dist/operations/crud.mjs.map +1 -1
  52. package/dist/operations/query-builder.cjs +2 -2
  53. package/dist/operations/query-builder.d.cts.map +1 -1
  54. package/dist/operations/query-builder.d.mts.map +1 -1
  55. package/dist/operations/query-builder.mjs +2 -2
  56. package/dist/operations/query-builder.mjs.map +1 -1
  57. package/dist/operations/search.cjs +5 -11
  58. package/dist/operations/search.d.cts +19 -23
  59. package/dist/operations/search.d.cts.map +1 -1
  60. package/dist/operations/search.d.mts +19 -23
  61. package/dist/operations/search.d.mts.map +1 -1
  62. package/dist/operations/search.mjs +5 -11
  63. package/dist/operations/search.mjs.map +1 -1
  64. package/dist/router/path-router.cjs +7 -15
  65. package/dist/router/path-router.d.cts +4 -7
  66. package/dist/router/path-router.d.cts.map +1 -1
  67. package/dist/router/path-router.d.mts +4 -7
  68. package/dist/router/path-router.d.mts.map +1 -1
  69. package/dist/router/path-router.mjs +7 -15
  70. package/dist/router/path-router.mjs.map +1 -1
  71. package/dist/router/types.d.cts.map +1 -1
  72. package/dist/router/types.d.mts.map +1 -1
  73. package/dist/schema/introspector.d.cts +19 -19
  74. package/dist/schema/introspector.d.cts.map +1 -1
  75. package/dist/schema/introspector.d.mts +19 -19
  76. package/dist/schema/introspector.d.mts.map +1 -1
  77. package/dist/schema/service.cjs +86 -0
  78. package/dist/schema/service.d.cts +52 -0
  79. package/dist/schema/service.d.cts.map +1 -0
  80. package/dist/schema/service.d.mts +52 -0
  81. package/dist/schema/service.d.mts.map +1 -0
  82. package/dist/schema/service.mjs +87 -0
  83. package/dist/schema/service.mjs.map +1 -0
  84. package/dist/schema/types.d.cts.map +1 -1
  85. package/dist/schema/types.d.mts.map +1 -1
  86. package/dist/sqlite-afs.cjs +693 -121
  87. package/dist/sqlite-afs.d.cts +279 -101
  88. package/dist/sqlite-afs.d.cts.map +1 -1
  89. package/dist/sqlite-afs.d.mts +279 -101
  90. package/dist/sqlite-afs.d.mts.map +1 -1
  91. package/dist/sqlite-afs.mjs +696 -123
  92. package/dist/sqlite-afs.mjs.map +1 -1
  93. package/package.json +5 -4
@@ -1,6 +1,7 @@
1
- import { buildAttributeEntry, buildAttributeListEntry, buildMetaEntry, buildRowEntry, buildSchemaEntry, buildTableEntry } from "../node/builder.mjs";
1
+ import { buildMetaEntry, buildRowEntry, buildTableEntry } from "../node/builder.mjs";
2
2
  import { buildDelete, buildGetLastRowId, buildInsert, buildSelectAll, buildSelectByPK, buildUpdate } from "./query-builder.mjs";
3
3
  import { sql } from "@aigne/sqlite";
4
+ import { AFSNotFoundError } from "@aigne/afs";
4
5
 
5
6
  //#region src/operations/crud.ts
6
7
  /**
@@ -19,9 +20,9 @@ async function execRun(db, query) {
19
20
  * CRUD operations for SQLite AFS
20
21
  */
21
22
  var CRUDOperations = class {
22
- constructor(db, schemas, basePath = "") {
23
+ constructor(db, schemaService, basePath = "") {
23
24
  this.db = db;
24
- this.schemas = schemas;
25
+ this.schemaService = schemaService;
25
26
  this.basePath = basePath;
26
27
  }
27
28
  /**
@@ -30,7 +31,8 @@ var CRUDOperations = class {
30
31
  async listTables() {
31
32
  const entries = [];
32
33
  const buildOptions = { basePath: this.basePath };
33
- for (const [name, schema] of this.schemas) {
34
+ const schemas = await this.schemaService.getAllSchemas();
35
+ for (const [name, schema] of schemas) {
34
36
  const rowCount = (await execAll(this.db, `SELECT COUNT(*) as count FROM "${name}"`))[0]?.count ?? 0;
35
37
  entries.push(buildTableEntry(name, schema, {
36
38
  ...buildOptions,
@@ -43,11 +45,8 @@ var CRUDOperations = class {
43
45
  * Lists rows in a table
44
46
  */
45
47
  async listTable(table, options) {
46
- const schema = this.schemas.get(table);
47
- if (!schema) return {
48
- data: [],
49
- message: `Table '${table}' not found`
50
- };
48
+ const schema = await this.schemaService.getSchema(table);
49
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
51
50
  const buildOptions = { basePath: this.basePath };
52
51
  const queryStr = buildSelectAll(table, {
53
52
  limit: options?.limit ?? 100,
@@ -59,67 +58,30 @@ var CRUDOperations = class {
59
58
  * Reads a single row by primary key
60
59
  */
61
60
  async readRow(table, pk) {
62
- const schema = this.schemas.get(table);
63
- if (!schema) return { message: `Table '${table}' not found` };
61
+ const schema = await this.schemaService.getSchema(table);
62
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
64
63
  const buildOptions = { basePath: this.basePath };
65
64
  const row = (await execAll(this.db, buildSelectByPK(table, schema, pk)))[0];
66
- if (!row) return { message: `Row with pk '${pk}' not found in table '${table}'` };
65
+ if (!row) throw new AFSNotFoundError(`/${table}/${pk}`);
67
66
  return { data: buildRowEntry(table, schema, row, buildOptions) };
68
67
  }
69
68
  /**
70
- * Gets table schema
71
- */
72
- getSchema(table) {
73
- const schema = this.schemas.get(table);
74
- if (!schema) return { message: `Table '${table}' not found` };
75
- return { data: buildSchemaEntry(table, schema, { basePath: this.basePath }) };
76
- }
77
- /**
78
- * Lists attributes (columns) for a row
79
- */
80
- async listAttributes(table, pk) {
81
- const schema = this.schemas.get(table);
82
- if (!schema) return {
83
- data: [],
84
- message: `Table '${table}' not found`
85
- };
86
- const buildOptions = { basePath: this.basePath };
87
- const row = (await execAll(this.db, buildSelectByPK(table, schema, pk)))[0];
88
- if (!row) return {
89
- data: [],
90
- message: `Row with pk '${pk}' not found`
91
- };
92
- return { data: buildAttributeListEntry(table, schema, pk, row, buildOptions) };
93
- }
94
- /**
95
- * Gets a single attribute (column value) for a row
96
- */
97
- async getAttribute(table, pk, column) {
98
- const schema = this.schemas.get(table);
99
- if (!schema) return { message: `Table '${table}' not found` };
100
- if (!schema.columns.find((c) => c.name === column)) return { message: `Column '${column}' not found in table '${table}'` };
101
- const buildOptions = { basePath: this.basePath };
102
- const rows = await execAll(this.db, buildSelectByPK(table, schema, pk));
103
- if (rows.length === 0) return { message: `Row with pk '${pk}' not found` };
104
- return { data: buildAttributeEntry(table, pk, column, rows[0]?.[column], buildOptions) };
105
- }
106
- /**
107
69
  * Gets row metadata
108
70
  */
109
71
  async getMeta(table, pk) {
110
- const schema = this.schemas.get(table);
111
- if (!schema) return { message: `Table '${table}' not found` };
72
+ const schema = await this.schemaService.getSchema(table);
73
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
112
74
  const buildOptions = { basePath: this.basePath };
113
75
  const row = (await execAll(this.db, buildSelectByPK(table, schema, pk)))[0];
114
- if (!row) return { message: `Row with pk '${pk}' not found` };
76
+ if (!row) throw new AFSNotFoundError(`/${table}/${pk}/@meta`);
115
77
  return { data: buildMetaEntry(table, schema, pk, row, buildOptions) };
116
78
  }
117
79
  /**
118
80
  * Creates a new row in a table
119
81
  */
120
82
  async createRow(table, content) {
121
- const schema = this.schemas.get(table);
122
- if (!schema) throw new Error(`Table '${table}' not found`);
83
+ const schema = await this.schemaService.getSchema(table);
84
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
123
85
  const buildOptions = { basePath: this.basePath };
124
86
  await execRun(this.db, buildInsert(table, schema, content));
125
87
  const lastId = (await execAll(this.db, buildGetLastRowId()))[0]?.id;
@@ -134,8 +96,8 @@ var CRUDOperations = class {
134
96
  * Updates an existing row
135
97
  */
136
98
  async updateRow(table, pk, content) {
137
- const schema = this.schemas.get(table);
138
- if (!schema) throw new Error(`Table '${table}' not found`);
99
+ const schema = await this.schemaService.getSchema(table);
100
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
139
101
  const buildOptions = { basePath: this.basePath };
140
102
  await execRun(this.db, buildUpdate(table, schema, pk, content));
141
103
  const row = (await execAll(this.db, buildSelectByPK(table, schema, pk)))[0];
@@ -146,29 +108,23 @@ var CRUDOperations = class {
146
108
  * Deletes a row by primary key
147
109
  */
148
110
  async deleteRow(table, pk) {
149
- const schema = this.schemas.get(table);
150
- if (!schema) throw new Error(`Table '${table}' not found`);
151
- if ((await execAll(this.db, buildSelectByPK(table, schema, pk))).length === 0) return { message: `Row with pk '${pk}' not found in table '${table}'` };
111
+ const schema = await this.schemaService.getSchema(table);
112
+ if (!schema) throw new AFSNotFoundError(`/${table}`);
113
+ if ((await execAll(this.db, buildSelectByPK(table, schema, pk))).length === 0) throw new AFSNotFoundError(`/${table}/${pk}`);
152
114
  await execRun(this.db, buildDelete(table, schema, pk));
153
115
  return { message: `Deleted row '${pk}' from table '${table}'` };
154
116
  }
155
117
  /**
156
118
  * Checks if a table exists
157
119
  */
158
- hasTable(table) {
159
- return this.schemas.has(table);
120
+ async hasTable(table) {
121
+ return this.schemaService.hasTable(table);
160
122
  }
161
123
  /**
162
124
  * Gets the schema for a table
163
125
  */
164
- getTableSchema(table) {
165
- return this.schemas.get(table);
166
- }
167
- /**
168
- * Updates the schemas map (after refresh)
169
- */
170
- setSchemas(schemas) {
171
- this.schemas = schemas;
126
+ async getTableSchema(table) {
127
+ return this.schemaService.getSchema(table);
172
128
  }
173
129
  };
174
130
 
@@ -1 +1 @@
1
- {"version":3,"file":"crud.mjs","names":[],"sources":["../../src/operations/crud.ts"],"sourcesContent":["import type {\n AFSDeleteResult,\n AFSEntry,\n AFSListOptions,\n AFSListResult,\n AFSReadResult,\n AFSWriteResult,\n} from \"@aigne/afs\";\nimport { sql } from \"@aigne/sqlite\";\nimport type { LibSQLDatabase } from \"drizzle-orm/libsql\";\nimport {\n type BuildEntryOptions,\n buildAttributeEntry,\n buildAttributeListEntry,\n buildMetaEntry,\n buildRowEntry,\n buildSchemaEntry,\n buildTableEntry,\n} from \"../node/builder.js\";\nimport type { TableSchema } from \"../schema/types.js\";\nimport {\n buildDelete,\n buildGetLastRowId,\n buildInsert,\n buildSelectAll,\n buildSelectByPK,\n buildUpdate,\n} from \"./query-builder.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 * Executes a raw SQL query (for INSERT, UPDATE, DELETE)\n */\nasync function execRun(db: LibSQLDatabase, query: string): Promise<void> {\n await db.run(sql.raw(query)).execute();\n}\n\n/**\n * CRUD operations for SQLite AFS\n */\nexport class CRUDOperations {\n constructor(\n private db: LibSQLDatabase,\n private schemas: Map<string, TableSchema>,\n private basePath: string = \"\",\n ) {}\n\n /**\n * Lists all tables\n */\n async listTables(): Promise<AFSListResult> {\n const entries: AFSEntry[] = [];\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n for (const [name, schema] of this.schemas) {\n // Get row count for each table\n const countResult = await execAll<{ count: number }>(\n this.db,\n `SELECT COUNT(*) as count FROM \"${name}\"`,\n );\n const rowCount = countResult[0]?.count ?? 0;\n\n entries.push(buildTableEntry(name, schema, { ...buildOptions, rowCount }));\n }\n\n return { data: entries };\n }\n\n /**\n * Lists rows in a table\n */\n async listTable(table: string, options?: AFSListOptions): Promise<AFSListResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n return { data: [], message: `Table '${table}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const queryStr = buildSelectAll(table, {\n limit: options?.limit ?? 100,\n orderBy: options?.orderBy,\n });\n\n const rows = await execAll<Record<string, unknown>>(this.db, queryStr);\n\n const entries = rows.map((row) => buildRowEntry(table, schema, row, buildOptions));\n\n return { data: entries };\n }\n\n /**\n * Reads a single row by primary key\n */\n async readRow(table: string, pk: string): Promise<AFSReadResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n return { message: `Table '${table}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n return { message: `Row with pk '${pk}' not found in table '${table}'` };\n }\n\n return { data: buildRowEntry(table, schema, row, buildOptions) };\n }\n\n /**\n * Gets table schema\n */\n getSchema(table: string): AFSReadResult {\n const schema = this.schemas.get(table);\n if (!schema) {\n return { message: `Table '${table}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n return { data: buildSchemaEntry(table, schema, buildOptions) };\n }\n\n /**\n * Lists attributes (columns) for a row\n */\n async listAttributes(table: string, pk: string): Promise<AFSListResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n return { data: [], message: `Table '${table}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n return { data: [], message: `Row with pk '${pk}' not found` };\n }\n\n return {\n data: buildAttributeListEntry(table, schema, pk, row, buildOptions),\n };\n }\n\n /**\n * Gets a single attribute (column value) for a row\n */\n async getAttribute(table: string, pk: string, column: string): Promise<AFSReadResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n return { message: `Table '${table}' not found` };\n }\n\n // Validate column exists\n const colInfo = schema.columns.find((c) => c.name === column);\n if (!colInfo) {\n return { message: `Column '${column}' not found in table '${table}'` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n if (rows.length === 0) {\n return { message: `Row with pk '${pk}' not found` };\n }\n\n return {\n data: buildAttributeEntry(table, pk, column, rows[0]?.[column], buildOptions),\n };\n }\n\n /**\n * Gets row metadata\n */\n async getMeta(table: string, pk: string): Promise<AFSReadResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n return { message: `Table '${table}' not found` };\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n return { message: `Row with pk '${pk}' not found` };\n }\n\n return { data: buildMetaEntry(table, schema, pk, row, buildOptions) };\n }\n\n /**\n * Creates a new row in a table\n */\n async createRow(table: string, content: Record<string, unknown>): Promise<AFSWriteResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n throw new Error(`Table '${table}' not found`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n // Insert the row\n await execRun(this.db, buildInsert(table, schema, content));\n\n // Get the last inserted rowid\n const lastIdResult = await execAll<{ id: number }>(this.db, buildGetLastRowId());\n const lastId = lastIdResult[0]?.id;\n\n if (lastId === undefined) {\n throw new Error(\"Failed to get last inserted row ID\");\n }\n\n // Fetch the inserted row\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n const pk = content[pkColumn] !== undefined ? String(content[pkColumn]) : String(lastId);\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n throw new Error(\"Failed to fetch inserted row\");\n }\n\n return { data: buildRowEntry(table, schema, row, buildOptions) };\n }\n\n /**\n * Updates an existing row\n */\n async updateRow(\n table: string,\n pk: string,\n content: Record<string, unknown>,\n ): Promise<AFSWriteResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n throw new Error(`Table '${table}' not found`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n // Update the row\n await execRun(this.db, buildUpdate(table, schema, pk, content));\n\n // Fetch the updated row\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n throw new Error(`Row with pk '${pk}' not found after update`);\n }\n\n return { data: buildRowEntry(table, schema, row, buildOptions) };\n }\n\n /**\n * Deletes a row by primary key\n */\n async deleteRow(table: string, pk: string): Promise<AFSDeleteResult> {\n const schema = this.schemas.get(table);\n if (!schema) {\n throw new Error(`Table '${table}' not found`);\n }\n\n // Check if row exists first\n const existing = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n if (existing.length === 0) {\n return { message: `Row with pk '${pk}' not found in table '${table}'` };\n }\n\n // Delete the row\n await execRun(this.db, buildDelete(table, schema, pk));\n\n return { message: `Deleted row '${pk}' from table '${table}'` };\n }\n\n /**\n * Checks if a table exists\n */\n hasTable(table: string): boolean {\n return this.schemas.has(table);\n }\n\n /**\n * Gets the schema for a table\n */\n getTableSchema(table: string): TableSchema | undefined {\n return this.schemas.get(table);\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"],"mappings":"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"}
1
+ {"version":3,"file":"crud.mjs","names":[],"sources":["../../src/operations/crud.ts"],"sourcesContent":["import {\n type AFSDeleteResult,\n type AFSEntry,\n type AFSListOptions,\n type AFSListResult,\n AFSNotFoundError,\n type AFSReadResult,\n type AFSWriteResult,\n} from \"@aigne/afs\";\nimport { sql } from \"@aigne/sqlite\";\nimport type { LibSQLDatabase } from \"drizzle-orm/libsql\";\nimport {\n type BuildEntryOptions,\n buildMetaEntry,\n buildRowEntry,\n buildTableEntry,\n} from \"../node/builder.js\";\nimport type { SchemaService } from \"../schema/service.js\";\nimport type { TableSchema } from \"../schema/types.js\";\nimport {\n buildDelete,\n buildGetLastRowId,\n buildInsert,\n buildSelectAll,\n buildSelectByPK,\n buildUpdate,\n} from \"./query-builder.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 * Executes a raw SQL query (for INSERT, UPDATE, DELETE)\n */\nasync function execRun(db: LibSQLDatabase, query: string): Promise<void> {\n await db.run(sql.raw(query)).execute();\n}\n\n/**\n * CRUD operations for SQLite AFS\n */\nexport class CRUDOperations {\n constructor(\n private db: LibSQLDatabase,\n private schemaService: SchemaService,\n private basePath: string = \"\",\n ) {}\n\n /**\n * Lists all tables\n */\n async listTables(): Promise<AFSListResult> {\n const entries: AFSEntry[] = [];\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const schemas = await this.schemaService.getAllSchemas();\n for (const [name, schema] of schemas) {\n // Get row count for each table\n const countResult = await execAll<{ count: number }>(\n this.db,\n `SELECT COUNT(*) as count FROM \"${name}\"`,\n );\n const rowCount = countResult[0]?.count ?? 0;\n\n entries.push(buildTableEntry(name, schema, { ...buildOptions, rowCount }));\n }\n\n return { data: entries };\n }\n\n /**\n * Lists rows in a table\n */\n async listTable(table: string, options?: AFSListOptions): Promise<AFSListResult> {\n const schema = await this.schemaService.getSchema(table);\n if (!schema) {\n throw new AFSNotFoundError(`/${table}`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const queryStr = buildSelectAll(table, {\n limit: options?.limit ?? 100,\n orderBy: options?.orderBy,\n });\n\n const rows = await execAll<Record<string, unknown>>(this.db, queryStr);\n\n const entries = rows.map((row) => buildRowEntry(table, schema, row, buildOptions));\n\n return { data: entries };\n }\n\n /**\n * Reads a single row by primary key\n */\n async readRow(table: string, pk: string): Promise<AFSReadResult> {\n const schema = await this.schemaService.getSchema(table);\n if (!schema) {\n throw new AFSNotFoundError(`/${table}`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n throw new AFSNotFoundError(`/${table}/${pk}`);\n }\n\n return { data: buildRowEntry(table, schema, row, buildOptions) };\n }\n\n /**\n * Gets row metadata\n */\n async getMeta(table: string, pk: string): Promise<AFSReadResult> {\n const schema = await this.schemaService.getSchema(table);\n if (!schema) {\n throw new AFSNotFoundError(`/${table}`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n throw new AFSNotFoundError(`/${table}/${pk}/@meta`);\n }\n\n return { data: buildMetaEntry(table, schema, pk, row, buildOptions) };\n }\n\n /**\n * Creates a new row in a table\n */\n async createRow(table: string, content: Record<string, unknown>): Promise<AFSWriteResult> {\n const schema = await this.schemaService.getSchema(table);\n if (!schema) {\n throw new AFSNotFoundError(`/${table}`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n // Insert the row\n await execRun(this.db, buildInsert(table, schema, content));\n\n // Get the last inserted rowid\n const lastIdResult = await execAll<{ id: number }>(this.db, buildGetLastRowId());\n const lastId = lastIdResult[0]?.id;\n\n if (lastId === undefined) {\n throw new Error(\"Failed to get last inserted row ID\");\n }\n\n // Fetch the inserted row\n const pkColumn = schema.primaryKey[0] ?? \"rowid\";\n const pk = content[pkColumn] !== undefined ? String(content[pkColumn]) : String(lastId);\n\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n throw new Error(\"Failed to fetch inserted row\");\n }\n\n return { data: buildRowEntry(table, schema, row, buildOptions) };\n }\n\n /**\n * Updates an existing row\n */\n async updateRow(\n table: string,\n pk: string,\n content: Record<string, unknown>,\n ): Promise<AFSWriteResult> {\n const schema = await this.schemaService.getSchema(table);\n if (!schema) {\n throw new AFSNotFoundError(`/${table}`);\n }\n\n const buildOptions: BuildEntryOptions = { basePath: this.basePath };\n\n // Update the row\n await execRun(this.db, buildUpdate(table, schema, pk, content));\n\n // Fetch the updated row\n const rows = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n const row = rows[0];\n if (!row) {\n throw new Error(`Row with pk '${pk}' not found after update`);\n }\n\n return { data: buildRowEntry(table, schema, row, buildOptions) };\n }\n\n /**\n * Deletes a row by primary key\n */\n async deleteRow(table: string, pk: string): Promise<AFSDeleteResult> {\n const schema = await this.schemaService.getSchema(table);\n if (!schema) {\n throw new AFSNotFoundError(`/${table}`);\n }\n\n // Check if row exists first\n const existing = await execAll<Record<string, unknown>>(\n this.db,\n buildSelectByPK(table, schema, pk),\n );\n\n if (existing.length === 0) {\n throw new AFSNotFoundError(`/${table}/${pk}`);\n }\n\n // Delete the row\n await execRun(this.db, buildDelete(table, schema, pk));\n\n return { message: `Deleted row '${pk}' from table '${table}'` };\n }\n\n /**\n * Checks if a table exists\n */\n async hasTable(table: string): Promise<boolean> {\n return this.schemaService.hasTable(table);\n }\n\n /**\n * Gets the schema for a table\n */\n async getTableSchema(table: string): Promise<TableSchema | undefined> {\n return this.schemaService.getSchema(table);\n }\n}\n"],"mappings":"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"}
@@ -4,13 +4,13 @@
4
4
  * Builds a SELECT query string for a single row by primary key
5
5
  */
6
6
  function buildSelectByPK(tableName, schema, pk) {
7
- return `SELECT * FROM "${tableName}" WHERE "${schema.primaryKey[0] ?? "rowid"}" = '${escapeSQLString(pk)}'`;
7
+ return `SELECT rowid, * FROM "${tableName}" WHERE "${schema.primaryKey[0] ?? "rowid"}" = '${escapeSQLString(pk)}'`;
8
8
  }
9
9
  /**
10
10
  * Builds a SELECT query string for listing rows with optional limit and offset
11
11
  */
12
12
  function buildSelectAll(tableName, options) {
13
- let query = `SELECT * FROM "${tableName}"`;
13
+ let query = `SELECT rowid, * FROM "${tableName}"`;
14
14
  if (options?.orderBy?.length) {
15
15
  const orderClauses = options.orderBy.map(([col, dir]) => `"${col}" ${dir.toUpperCase()}`);
16
16
  query += ` ORDER BY ${orderClauses.join(", ")}`;
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.d.cts","names":[],"sources":["../../src/operations/query-builder.ts"],"mappings":";;;;AAKA;AAQA;iBARgB,eAAA,CAAA,SAAA,UAAA,MAAA,EAA2C,WAAA,EAAA,EAAA;AAAA;AAQ3D;AA6BA;AArC2D,iBAQ3C,cAAA,CAAA,SAAA,UAAA,OAAA;EAAA,KAAA;EAAA,MAAA;EAAA,OAAA;AAAA;AAAA;AA6BhB;AAsBA;AAnDgB,iBA6BA,WAAA,CAAA,SAAA,UAAA,MAAA,EAEN,WAAA,EAAA,OAAA,EACC,MAAA;AAAA;AAmBX;AA0BA;AA7CW,iBAmBK,WAAA,CAAA,SAAA,UAAA,MAAA,EAEN,WAAA,EAAA,EAAA,UAAA,OAAA,EAEC,MAAA;AAAA;AAsBX;AA0CA;AAhEW,iBAsBK,WAAA,CAAA,SAAA,UAAA,MAAA,EAAuC,WAAA,EAAA,EAAA;AAAA;AA0CvD;;AA1CuD,iBA0CvC,iBAAA,CAAA"}
1
+ {"version":3,"file":"query-builder.d.cts","names":[],"sources":["../../src/operations/query-builder.ts"],"mappings":";;;;;AAKA;iBAAgB,eAAA,CAAgB,SAAA,UAAmB,MAAA,EAAQ,WAAA,EAAa,EAAA;;;;iBASxD,cAAA,CACd,SAAA,UACA,OAAA;EACE,KAAA;EACA,MAAA;EACA,OAAA;AAAA;AALJ;;;AAAA,iBA8BgB,WAAA,CACd,SAAA,UACA,MAAA,EAAQ,WAAA,EACR,OAAA,EAAS,MAAA;;;;iBAmBK,WAAA,CACd,SAAA,UACA,MAAA,EAAQ,WAAA,EACR,EAAA,UACA,OAAA,EAAS,MAAA;;;;iBAsBK,WAAA,CAAY,SAAA,UAAmB,MAAA,EAAQ,WAAA,EAAa,EAAA;;;AA1BpE;iBAoEgB,iBAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.d.mts","names":[],"sources":["../../src/operations/query-builder.ts"],"mappings":";;;;AAKA;AAQA;iBARgB,eAAA,CAAA,SAAA,UAAA,MAAA,EAA2C,WAAA,EAAA,EAAA;AAAA;AAQ3D;AA6BA;AArC2D,iBAQ3C,cAAA,CAAA,SAAA,UAAA,OAAA;EAAA,KAAA;EAAA,MAAA;EAAA,OAAA;AAAA;AAAA;AA6BhB;AAsBA;AAnDgB,iBA6BA,WAAA,CAAA,SAAA,UAAA,MAAA,EAEN,WAAA,EAAA,OAAA,EACC,MAAA;AAAA;AAmBX;AA0BA;AA7CW,iBAmBK,WAAA,CAAA,SAAA,UAAA,MAAA,EAEN,WAAA,EAAA,EAAA,UAAA,OAAA,EAEC,MAAA;AAAA;AAsBX;AA0CA;AAhEW,iBAsBK,WAAA,CAAA,SAAA,UAAA,MAAA,EAAuC,WAAA,EAAA,EAAA;AAAA;AA0CvD;;AA1CuD,iBA0CvC,iBAAA,CAAA"}
1
+ {"version":3,"file":"query-builder.d.mts","names":[],"sources":["../../src/operations/query-builder.ts"],"mappings":";;;;;AAKA;iBAAgB,eAAA,CAAgB,SAAA,UAAmB,MAAA,EAAQ,WAAA,EAAa,EAAA;;;;iBASxD,cAAA,CACd,SAAA,UACA,OAAA;EACE,KAAA;EACA,MAAA;EACA,OAAA;AAAA;AALJ;;;AAAA,iBA8BgB,WAAA,CACd,SAAA,UACA,MAAA,EAAQ,WAAA,EACR,OAAA,EAAS,MAAA;;;;iBAmBK,WAAA,CACd,SAAA,UACA,MAAA,EAAQ,WAAA,EACR,EAAA,UACA,OAAA,EAAS,MAAA;;;;iBAsBK,WAAA,CAAY,SAAA,UAAmB,MAAA,EAAQ,WAAA,EAAa,EAAA;;;AA1BpE;iBAoEgB,iBAAA,CAAA"}
@@ -3,13 +3,13 @@
3
3
  * Builds a SELECT query string for a single row by primary key
4
4
  */
5
5
  function buildSelectByPK(tableName, schema, pk) {
6
- return `SELECT * FROM "${tableName}" WHERE "${schema.primaryKey[0] ?? "rowid"}" = '${escapeSQLString(pk)}'`;
6
+ return `SELECT rowid, * FROM "${tableName}" WHERE "${schema.primaryKey[0] ?? "rowid"}" = '${escapeSQLString(pk)}'`;
7
7
  }
8
8
  /**
9
9
  * Builds a SELECT query string for listing rows with optional limit and offset
10
10
  */
11
11
  function buildSelectAll(tableName, options) {
12
- let query = `SELECT * FROM "${tableName}"`;
12
+ let query = `SELECT rowid, * FROM "${tableName}"`;
13
13
  if (options?.orderBy?.length) {
14
14
  const orderClauses = options.orderBy.map(([col, dir]) => `"${col}" ${dir.toUpperCase()}`);
15
15
  query += ` ORDER BY ${orderClauses.join(", ")}`;
@@ -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;AAE1F,QAAO,kBAAkB,UAAU,WADlB,OAAO,WAAW,MAAM,QACc,OAAO,gBAAgB,GAAG,CAAC;;;;;AAMpF,SAAgB,eACd,WACA,SAKQ;CACR,IAAI,QAAQ,kBAAkB,UAAU;AAExC,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"}
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, schemas, config, basePath = "") {
15
+ constructor(db, schemaService, config, basePath = "") {
16
16
  this.db = db;
17
- this.schemas = schemas;
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.schemas.get(tableName);
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.schemas.get(tableName);
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 { TableSchema } from "../schema/types.cjs";
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 schemas;
29
+ private schemaService;
30
30
  private config;
31
31
  private basePath;
32
- constructor(db: LibSQLDatabase, schemas: Map<string, TableSchema>, config: FTSConfig, basePath?: string);
32
+ constructor(db: LibSQLDatabase, schemaService: SchemaService, config: FTSConfig, basePath?: string);
33
33
  /**
34
- * Performs full-text search across configured tables
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
- * Searches within a specific table
41
- */
40
+ * Searches within a specific table
41
+ */
42
42
  searchTable(tableName: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
43
43
  /**
44
- * Checks if FTS is configured for a table
45
- */
44
+ * Checks if FTS is configured for a table
45
+ */
46
46
  hasFTS(tableName: string): boolean;
47
47
  /**
48
- * Gets FTS configuration for a table
49
- */
48
+ * Gets FTS configuration for a table
49
+ */
50
50
  getFTSConfig(tableName: string): FTSTableConfig | undefined;
51
51
  /**
52
- * Checks if an FTS table exists
53
- */
52
+ * Checks if an FTS table exists
53
+ */
54
54
  private ftsTableExists;
55
55
  /**
56
- * Prepares a query string for FTS5
57
- * Handles special characters and case sensitivity
58
- */
56
+ * Prepares a query string for FTS5
57
+ * Handles special characters and case sensitivity
58
+ */
59
59
  private prepareFTSQuery;
60
60
  /**
61
- * Updates the schemas map (after refresh)
62
- */
63
- setSchemas(schemas: Map<string, TableSchema>): void;
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":";;;;;;AAgBA;AAUA;UAViB,cAAA;EAAA;EAAA,OAAA;EAAA;EAAA,WAAA;AAAA;AAAA;AAUjB;AAUA;AApBiB,UAUA,SAAA;EAAA;EAAA,OAAA;EAAA;EAAA,MAAA,EAIP,GAAA,SAAY,cAAA;AAAA;AAAA;AAMtB;;AANsB,cAMT,SAAA;EAAA,QAAA,EAAA;EAAA,QAAA,OAAA;EAAA,QAAA,MAAA;EAAA,QAAA,QAAA;EAAA,YAAA,EAAA,EAEG,cAAA,EAAA,OAAA,EACK,GAAA,SAAY,WAAA,GAAA,MAAA,EACb,SAAA,EAAA,QAAA;EAAA;;;EAAA,OAAA,KAAA,UAAA,OAAA,GASN,gBAAA;IAAA,qEAAA,MAAA;EAAA,IAIT,OAAA,CAAQ,eAAA;EAAA;;;EAAA,YAAA,SAAA,UAAA,KAAA,UAAA,OAAA,GA+EC,gBAAA,GACT,OAAA,CAAQ,eAAA;EAAA;;;EAAA,OAAA,SAAA;EAAA;;;EAAA,aAAA,SAAA,WAcsB,cAAA;EAAA;;;EAAA,QAAA,cAAA;EAAA;;;;EAAA,QAAA,eAAA;EAAA;;;EAAA,WAAA,OAAA,EAuCb,GAAA,SAAY,WAAA;EAAA;;;;EAAA,iBAAA,SAAA,UAAA,KAAA,UAAA,OAAA,YAAA,OAAA,GAYpB,gBAAA,GACT,OAAA,CAAQ,eAAA;AAAA;AAAA;;;AAAA,iBAkCG,eAAA,CAAA,OAAA;EAAA,OAAA;EAAA,MAAA,GAEL,MAAA;AAAA,IACP,SAAA"}
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 { TableSchema } from "../schema/types.mjs";
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 schemas;
29
+ private schemaService;
30
30
  private config;
31
31
  private basePath;
32
- constructor(db: LibSQLDatabase, schemas: Map<string, TableSchema>, config: FTSConfig, basePath?: string);
32
+ constructor(db: LibSQLDatabase, schemaService: SchemaService, config: FTSConfig, basePath?: string);
33
33
  /**
34
- * Performs full-text search across configured tables
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
- * Searches within a specific table
41
- */
40
+ * Searches within a specific table
41
+ */
42
42
  searchTable(tableName: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
43
43
  /**
44
- * Checks if FTS is configured for a table
45
- */
44
+ * Checks if FTS is configured for a table
45
+ */
46
46
  hasFTS(tableName: string): boolean;
47
47
  /**
48
- * Gets FTS configuration for a table
49
- */
48
+ * Gets FTS configuration for a table
49
+ */
50
50
  getFTSConfig(tableName: string): FTSTableConfig | undefined;
51
51
  /**
52
- * Checks if an FTS table exists
53
- */
52
+ * Checks if an FTS table exists
53
+ */
54
54
  private ftsTableExists;
55
55
  /**
56
- * Prepares a query string for FTS5
57
- * Handles special characters and case sensitivity
58
- */
56
+ * Prepares a query string for FTS5
57
+ * Handles special characters and case sensitivity
58
+ */
59
59
  private prepareFTSQuery;
60
60
  /**
61
- * Updates the schemas map (after refresh)
62
- */
63
- setSchemas(schemas: Map<string, TableSchema>): void;
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":";;;;;;AAgBA;AAUA;UAViB,cAAA;EAAA;EAAA,OAAA;EAAA;EAAA,WAAA;AAAA;AAAA;AAUjB;AAUA;AApBiB,UAUA,SAAA;EAAA;EAAA,OAAA;EAAA;EAAA,MAAA,EAIP,GAAA,SAAY,cAAA;AAAA;AAAA;AAMtB;;AANsB,cAMT,SAAA;EAAA,QAAA,EAAA;EAAA,QAAA,OAAA;EAAA,QAAA,MAAA;EAAA,QAAA,QAAA;EAAA,YAAA,EAAA,EAEG,cAAA,EAAA,OAAA,EACK,GAAA,SAAY,WAAA,GAAA,MAAA,EACb,SAAA,EAAA,QAAA;EAAA;;;EAAA,OAAA,KAAA,UAAA,OAAA,GASN,gBAAA;IAAA,qEAAA,MAAA;EAAA,IAIT,OAAA,CAAQ,eAAA;EAAA;;;EAAA,YAAA,SAAA,UAAA,KAAA,UAAA,OAAA,GA+EC,gBAAA,GACT,OAAA,CAAQ,eAAA;EAAA;;;EAAA,OAAA,SAAA;EAAA;;;EAAA,aAAA,SAAA,WAcsB,cAAA;EAAA;;;EAAA,QAAA,cAAA;EAAA;;;;EAAA,QAAA,eAAA;EAAA;;;EAAA,WAAA,OAAA,EAuCb,GAAA,SAAY,WAAA;EAAA;;;;EAAA,iBAAA,SAAA,UAAA,KAAA,UAAA,OAAA,YAAA,OAAA,GAYpB,gBAAA,GACT,OAAA,CAAQ,eAAA;AAAA;AAAA;;;AAAA,iBAkCG,eAAA,CAAA,OAAA;EAAA,OAAA;EAAA,MAAA,GAEL,MAAA;AAAA,IACP,SAAA"}
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, schemas, config, basePath = "") {
15
+ constructor(db, schemaService, config, basePath = "") {
16
16
  this.db = db;
17
- this.schemas = schemas;
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.schemas.get(tableName);
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.schemas.get(tableName);
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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"}