pomegranate-db 0.1.0

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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +38 -0
  3. package/PomegranateDB.podspec +67 -0
  4. package/README.md +122 -0
  5. package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +34 -0
  6. package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +155 -0
  7. package/dist/adapters/expo-sqlite/index.d.ts +2 -0
  8. package/dist/adapters/expo-sqlite/index.js +6 -0
  9. package/dist/adapters/index.d.ts +7 -0
  10. package/dist/adapters/index.js +13 -0
  11. package/dist/adapters/loki/LokiAdapter.d.ts +100 -0
  12. package/dist/adapters/loki/LokiAdapter.js +144 -0
  13. package/dist/adapters/loki/index.d.ts +6 -0
  14. package/dist/adapters/loki/index.js +12 -0
  15. package/dist/adapters/loki/worker/LokiDispatcher.d.ts +21 -0
  16. package/dist/adapters/loki/worker/LokiDispatcher.js +63 -0
  17. package/dist/adapters/loki/worker/LokiExecutor.d.ts +96 -0
  18. package/dist/adapters/loki/worker/LokiExecutor.js +462 -0
  19. package/dist/adapters/loki/worker/SynchronousWorker.d.ts +22 -0
  20. package/dist/adapters/loki/worker/SynchronousWorker.js +76 -0
  21. package/dist/adapters/loki/worker/loki.worker.d.ts +14 -0
  22. package/dist/adapters/loki/worker/loki.worker.js +112 -0
  23. package/dist/adapters/loki/worker/types.d.ts +44 -0
  24. package/dist/adapters/loki/worker/types.js +11 -0
  25. package/dist/adapters/native-sqlite/NativeSQLiteDriver.d.ts +55 -0
  26. package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +145 -0
  27. package/dist/adapters/native-sqlite/index.d.ts +2 -0
  28. package/dist/adapters/native-sqlite/index.js +6 -0
  29. package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +49 -0
  30. package/dist/adapters/op-sqlite/OpSQLiteDriver.js +140 -0
  31. package/dist/adapters/op-sqlite/index.d.ts +2 -0
  32. package/dist/adapters/op-sqlite/index.js +6 -0
  33. package/dist/adapters/sqlite/SQLiteAdapter.d.ts +70 -0
  34. package/dist/adapters/sqlite/SQLiteAdapter.js +264 -0
  35. package/dist/adapters/sqlite/index.d.ts +2 -0
  36. package/dist/adapters/sqlite/index.js +6 -0
  37. package/dist/adapters/sqlite/sql.d.ts +35 -0
  38. package/dist/adapters/sqlite/sql.js +258 -0
  39. package/dist/adapters/types.d.ts +93 -0
  40. package/dist/adapters/types.js +9 -0
  41. package/dist/collection/Collection.d.ts +103 -0
  42. package/dist/collection/Collection.js +245 -0
  43. package/dist/collection/index.d.ts +2 -0
  44. package/dist/collection/index.js +6 -0
  45. package/dist/database/Database.d.ts +128 -0
  46. package/dist/database/Database.js +245 -0
  47. package/dist/database/index.d.ts +2 -0
  48. package/dist/database/index.js +6 -0
  49. package/dist/encryption/index.d.ts +62 -0
  50. package/dist/encryption/index.js +276 -0
  51. package/dist/encryption/nodeCrypto.d.ts +18 -0
  52. package/dist/encryption/nodeCrypto.js +25 -0
  53. package/dist/encryption/nodeCrypto.native.d.ts +13 -0
  54. package/dist/encryption/nodeCrypto.native.js +26 -0
  55. package/dist/expo.d.ts +12 -0
  56. package/dist/expo.js +32 -0
  57. package/dist/hooks/index.d.ts +115 -0
  58. package/dist/hooks/index.js +285 -0
  59. package/dist/index.d.ts +29 -0
  60. package/dist/index.js +57 -0
  61. package/dist/model/Model.d.ts +92 -0
  62. package/dist/model/Model.js +251 -0
  63. package/dist/model/index.d.ts +2 -0
  64. package/dist/model/index.js +7 -0
  65. package/dist/observable/Subject.d.ts +60 -0
  66. package/dist/observable/Subject.js +132 -0
  67. package/dist/observable/index.d.ts +2 -0
  68. package/dist/observable/index.js +10 -0
  69. package/dist/query/QueryBuilder.d.ts +51 -0
  70. package/dist/query/QueryBuilder.js +165 -0
  71. package/dist/query/index.d.ts +2 -0
  72. package/dist/query/index.js +7 -0
  73. package/dist/query/types.d.ts +60 -0
  74. package/dist/query/types.js +9 -0
  75. package/dist/schema/builder.d.ts +68 -0
  76. package/dist/schema/builder.js +168 -0
  77. package/dist/schema/index.d.ts +2 -0
  78. package/dist/schema/index.js +7 -0
  79. package/dist/schema/types.d.ts +108 -0
  80. package/dist/schema/types.js +9 -0
  81. package/dist/sync/index.d.ts +2 -0
  82. package/dist/sync/index.js +6 -0
  83. package/dist/sync/sync.d.ts +15 -0
  84. package/dist/sync/sync.js +182 -0
  85. package/dist/sync/types.d.ts +41 -0
  86. package/dist/sync/types.js +6 -0
  87. package/dist/utils/index.d.ts +45 -0
  88. package/dist/utils/index.js +99 -0
  89. package/expo-plugin/index.d.ts +68 -0
  90. package/expo-plugin/index.js +83 -0
  91. package/native/android-jsi/build.gradle +45 -0
  92. package/native/android-jsi/src/main/AndroidManifest.xml +2 -0
  93. package/native/android-jsi/src/main/cpp/CMakeLists.txt +73 -0
  94. package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.cpp +107 -0
  95. package/native/android-jsi/src/main/cpp/DatabasePlatformAndroid.h +16 -0
  96. package/native/android-jsi/src/main/cpp/JSIInstaller.cpp +27 -0
  97. package/native/android-jsi/src/main/java/com/pomegranate/jsi/JSIInstaller.kt +43 -0
  98. package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIModule.kt +39 -0
  99. package/native/android-jsi/src/main/java/com/pomegranate/jsi/PomegranateJSIPackage.kt +17 -0
  100. package/native/ios/DatabasePlatformIOS.mm +83 -0
  101. package/native/ios/PomegranateJSI.h +15 -0
  102. package/native/ios/PomegranateJSI.mm +59 -0
  103. package/native/shared/Database.cpp +283 -0
  104. package/native/shared/Database.h +84 -0
  105. package/native/shared/Sqlite.cpp +61 -0
  106. package/native/shared/Sqlite.h +67 -0
  107. package/native/shared/sqlite3/sqlite3.c +260493 -0
  108. package/native/shared/sqlite3/sqlite3.h +13583 -0
  109. package/package.json +127 -0
  110. package/react-native.config.js +28 -0
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite Adapter.
4
+ *
5
+ * Implements the StorageAdapter interface using a SQL driver.
6
+ * The actual SQLite driver is injected — this adapter generates SQL
7
+ * and delegates execution, enabling different drivers for
8
+ * React Native (react-native-sqlite-storage) and web (sql.js).
9
+ *
10
+ * The driver interface is intentionally minimal so it can wrap
11
+ * any SQLite library.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.SQLiteAdapter = void 0;
15
+ const sql_1 = require("./sql");
16
+ // ─── SQLite Adapter ───────────────────────────────────────────────────────
17
+ class SQLiteAdapter {
18
+ _driver;
19
+ _databaseName;
20
+ _encryption;
21
+ _initialized = false;
22
+ constructor(config) {
23
+ this._databaseName = config.databaseName;
24
+ this._encryption = config.encryption;
25
+ if (config.driver) {
26
+ this._driver = config.driver;
27
+ }
28
+ else {
29
+ // Use a no-op driver that throws — user must provide one
30
+ this._driver = createStubDriver();
31
+ }
32
+ }
33
+ // ─── Initialize ──────────────────────────────────────────────────────
34
+ async initialize(schema) {
35
+ if (this._initialized)
36
+ return;
37
+ await this._driver.open(this._databaseName);
38
+ // Create metadata table
39
+ await this._driver.execute(`CREATE TABLE IF NOT EXISTS "__pomegranate_metadata" (
40
+ "key" TEXT PRIMARY KEY NOT NULL,
41
+ "value" TEXT
42
+ )`);
43
+ // Check existing version
44
+ const rows = await this._driver.query('SELECT "value" FROM "__pomegranate_metadata" WHERE "key" = \'schema_version\'');
45
+ const existingVersion = rows.length > 0 ? Number.parseInt(rows[0].value, 10) : 0;
46
+ if (existingVersion === 0) {
47
+ // Fresh install — create all tables
48
+ for (const table of schema.tables) {
49
+ const sql = (0, sql_1.createTableSQL)(table);
50
+ // Split multi-statement SQL
51
+ for (const stmt of sql.split(';\n')) {
52
+ const trimmed = stmt.trim().replace(/;$/, '');
53
+ if (trimmed) {
54
+ await this._driver.execute(trimmed);
55
+ }
56
+ }
57
+ }
58
+ // Store version
59
+ await this._driver.execute('INSERT OR REPLACE INTO "__pomegranate_metadata" ("key", "value") VALUES (\'schema_version\', ?)', [String(schema.version)]);
60
+ }
61
+ this._initialized = true;
62
+ }
63
+ // ─── Query ──────────────────────────────────────────────────────────
64
+ async find(query) {
65
+ const { sql, bindings } = (0, sql_1.selectSQL)(query);
66
+ const rows = await this._driver.query(sql, bindings);
67
+ return rows;
68
+ }
69
+ async count(query) {
70
+ const { sql, bindings } = (0, sql_1.countSQL)(query);
71
+ const rows = await this._driver.query(sql, bindings);
72
+ return rows[0]?.count ?? 0;
73
+ }
74
+ async findById(table, id) {
75
+ const rows = await this._driver.query(`SELECT * FROM "${table}" WHERE "id" = ?`, [id]);
76
+ return rows[0] ?? null;
77
+ }
78
+ // ─── Insert / Update / Delete ────────────────────────────────────────
79
+ async insert(table, raw) {
80
+ const { sql, bindings } = (0, sql_1.insertSQL)(table, raw);
81
+ await this._driver.execute(sql, bindings);
82
+ }
83
+ async update(table, raw) {
84
+ const { sql, bindings } = (0, sql_1.updateSQL)(table, raw);
85
+ await this._driver.execute(sql, bindings);
86
+ }
87
+ async markAsDeleted(table, id) {
88
+ await this._driver.execute(`UPDATE "${table}" SET "_status" = 'deleted' WHERE "id" = ?`, [id]);
89
+ }
90
+ async destroyPermanently(table, id) {
91
+ const { sql, bindings } = (0, sql_1.deleteSQL)(table, id);
92
+ await this._driver.execute(sql, bindings);
93
+ }
94
+ // ─── Batch ──────────────────────────────────────────────────────────
95
+ async batch(operations) {
96
+ await this._driver.executeInTransaction(async () => {
97
+ for (const op of operations) {
98
+ switch (op.type) {
99
+ case 'create': {
100
+ const { sql, bindings } = (0, sql_1.insertSQL)(op.table, op.rawRecord);
101
+ await this._driver.execute(sql, bindings);
102
+ break;
103
+ }
104
+ case 'update': {
105
+ const { sql, bindings } = (0, sql_1.updateSQL)(op.table, op.rawRecord);
106
+ await this._driver.execute(sql, bindings);
107
+ break;
108
+ }
109
+ case 'delete':
110
+ await this._driver.execute(`UPDATE "${op.table}" SET "_status" = 'deleted' WHERE "id" = ?`, [op.id]);
111
+ break;
112
+ case 'destroyPermanently': {
113
+ const { sql, bindings } = (0, sql_1.deleteSQL)(op.table, op.id);
114
+ await this._driver.execute(sql, bindings);
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ });
120
+ }
121
+ // ─── Search ──────────────────────────────────────────────────────────
122
+ async search(descriptor) {
123
+ const { sql, countSql, bindings, countBindings } = (0, sql_1.searchSQL)(descriptor);
124
+ const [rows, countRows] = await Promise.all([
125
+ this._driver.query(sql, bindings),
126
+ this._driver.query(countSql, countBindings),
127
+ ]);
128
+ return {
129
+ records: rows,
130
+ total: countRows[0]?.count ?? 0,
131
+ };
132
+ }
133
+ // ─── Sync helpers ──────────────────────────────────────────────────
134
+ async getLocalChanges(tables) {
135
+ const result = {};
136
+ for (const table of tables) {
137
+ const created = await this._driver.query(`SELECT * FROM "${table}" WHERE "_status" = 'created'`);
138
+ const updated = await this._driver.query(`SELECT * FROM "${table}" WHERE "_status" = 'updated'`);
139
+ const deletedRows = await this._driver.query(`SELECT "id" FROM "${table}" WHERE "_status" = 'deleted'`);
140
+ result[table] = {
141
+ created: created,
142
+ updated: updated,
143
+ deleted: deletedRows.map((r) => r.id),
144
+ };
145
+ }
146
+ return result;
147
+ }
148
+ async applyRemoteChanges(changes) {
149
+ await this._driver.executeInTransaction(async () => {
150
+ for (const [table, tableChanges] of Object.entries(changes)) {
151
+ // Apply created records
152
+ for (const raw of tableChanges.created) {
153
+ const record = { ...raw, _status: 'synced', _changed: '' };
154
+ // Use INSERT OR REPLACE in case the record already exists locally
155
+ const { sql, bindings } = (0, sql_1.insertSQL)(table, record);
156
+ const replaceSql = sql.replace('INSERT INTO', 'INSERT OR REPLACE INTO');
157
+ await this._driver.execute(replaceSql, bindings);
158
+ }
159
+ // Apply updated records
160
+ for (const raw of tableChanges.updated) {
161
+ const record = { ...raw, _status: 'synced', _changed: '' };
162
+ // Check if record exists
163
+ const existing = await this._driver.query(`SELECT "_status" FROM "${table}" WHERE "id" = ?`, [raw.id]);
164
+ if (existing.length > 0) {
165
+ const { sql, bindings } = (0, sql_1.updateSQL)(table, record);
166
+ await this._driver.execute(sql, bindings);
167
+ }
168
+ else {
169
+ const { sql, bindings } = (0, sql_1.insertSQL)(table, record);
170
+ await this._driver.execute(sql, bindings);
171
+ }
172
+ }
173
+ // Apply deletions
174
+ for (const id of tableChanges.deleted) {
175
+ await this._driver.execute(`DELETE FROM "${table}" WHERE "id" = ?`, [id]);
176
+ }
177
+ }
178
+ });
179
+ }
180
+ async markAsSynced(table, ids) {
181
+ if (ids.length === 0)
182
+ return;
183
+ const placeholders = ids.map(() => '?').join(', ');
184
+ await this._driver.execute(`UPDATE "${table}" SET "_status" = 'synced', "_changed" = '' WHERE "id" IN (${placeholders})`, ids);
185
+ }
186
+ // ─── Schema version ──────────────────────────────────────────────────
187
+ async getSchemaVersion() {
188
+ try {
189
+ const rows = await this._driver.query('SELECT "value" FROM "__pomegranate_metadata" WHERE "key" = \'schema_version\'');
190
+ return rows.length > 0 ? Number.parseInt(rows[0].value, 10) : 0;
191
+ }
192
+ catch {
193
+ return 0;
194
+ }
195
+ }
196
+ // ─── Migration ──────────────────────────────────────────────────────
197
+ async migrate(migrations) {
198
+ const currentVersion = await this.getSchemaVersion();
199
+ const applicable = migrations
200
+ .filter((m) => m.fromVersion >= currentVersion)
201
+ .toSorted((a, b) => a.fromVersion - b.fromVersion);
202
+ await this._driver.executeInTransaction(async () => {
203
+ for (const migration of applicable) {
204
+ for (const step of migration.steps) {
205
+ switch (step.type) {
206
+ case 'createTable': {
207
+ const sql = (0, sql_1.createTableSQL)(step.schema);
208
+ for (const stmt of sql.split(';\n')) {
209
+ const trimmed = stmt.trim().replace(/;$/, '');
210
+ if (trimmed)
211
+ await this._driver.execute(trimmed);
212
+ }
213
+ break;
214
+ }
215
+ case 'addColumn':
216
+ await this._driver.execute(`ALTER TABLE "${step.table}" ADD COLUMN "${step.column}" ${step.columnType}${step.isOptional ? '' : ' NOT NULL DEFAULT ""'}`);
217
+ break;
218
+ case 'destroyTable':
219
+ await this._driver.execute(`DROP TABLE IF EXISTS "${step.table}"`);
220
+ break;
221
+ case 'sql':
222
+ await this._driver.execute(step.query);
223
+ break;
224
+ }
225
+ }
226
+ await this._driver.execute('INSERT OR REPLACE INTO "__pomegranate_metadata" ("key", "value") VALUES (\'schema_version\', ?)', [String(migration.toVersion)]);
227
+ }
228
+ });
229
+ }
230
+ // ─── Reset ──────────────────────────────────────────────────────────
231
+ async reset() {
232
+ // Get all user tables
233
+ const tables = await this._driver.query('SELECT name FROM sqlite_master WHERE type=\'table\' AND name NOT LIKE \'sqlite_%\'');
234
+ await this._driver.executeInTransaction(async () => {
235
+ for (const t of tables) {
236
+ await this._driver.execute(`DROP TABLE IF EXISTS "${t.name}"`);
237
+ }
238
+ });
239
+ this._initialized = false;
240
+ }
241
+ // ─── Close ──────────────────────────────────────────────────────────
242
+ async close() {
243
+ await this._driver.close();
244
+ }
245
+ }
246
+ exports.SQLiteAdapter = SQLiteAdapter;
247
+ // ─── Stub Driver ──────────────────────────────────────────────────────────
248
+ function notConfigured() {
249
+ throw new Error('No SQLite driver configured. Provide a driver in SQLiteAdapterConfig.driver, ' +
250
+ 'or use LokiAdapter for in-memory/web use.');
251
+ }
252
+ function createStubDriver() {
253
+ return {
254
+ open: async () => notConfigured(),
255
+ execute: async () => notConfigured(),
256
+ query: async () => {
257
+ notConfigured();
258
+ return [];
259
+ },
260
+ executeInTransaction: async () => notConfigured(),
261
+ close: async () => { },
262
+ };
263
+ }
264
+ //# sourceMappingURL=SQLiteAdapter.js.map
@@ -0,0 +1,2 @@
1
+ export { SQLiteAdapter } from './SQLiteAdapter';
2
+ export type { SQLiteDriver, SQLiteAdapterConfig } from './SQLiteAdapter';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SQLiteAdapter = void 0;
4
+ var SQLiteAdapter_1 = require("./SQLiteAdapter");
5
+ Object.defineProperty(exports, "SQLiteAdapter", { enumerable: true, get: function () { return SQLiteAdapter_1.SQLiteAdapter; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SQL generation utilities.
3
+ *
4
+ * Translates QueryDescriptor / SearchDescriptor into SQL strings + bindings.
5
+ * Used by the SQLite adapter.
6
+ */
7
+ import type { QueryDescriptor, SearchDescriptor } from '../../query/types';
8
+ import type { TableSchema } from '../../schema/types';
9
+ export declare function createTableSQL(table: TableSchema): string;
10
+ export declare function selectSQL(descriptor: QueryDescriptor): {
11
+ sql: string;
12
+ bindings: unknown[];
13
+ };
14
+ export declare function countSQL(descriptor: QueryDescriptor): {
15
+ sql: string;
16
+ bindings: unknown[];
17
+ };
18
+ export declare function searchSQL(descriptor: SearchDescriptor): {
19
+ sql: string;
20
+ countSql: string;
21
+ bindings: unknown[];
22
+ countBindings: unknown[];
23
+ };
24
+ export declare function insertSQL(table: string, raw: Record<string, unknown>): {
25
+ sql: string;
26
+ bindings: unknown[];
27
+ };
28
+ export declare function updateSQL(table: string, raw: Record<string, unknown>): {
29
+ sql: string;
30
+ bindings: unknown[];
31
+ };
32
+ export declare function deleteSQL(table: string, id: string): {
33
+ sql: string;
34
+ bindings: unknown[];
35
+ };
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ /**
3
+ * SQL generation utilities.
4
+ *
5
+ * Translates QueryDescriptor / SearchDescriptor into SQL strings + bindings.
6
+ * Used by the SQLite adapter.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.createTableSQL = createTableSQL;
10
+ exports.selectSQL = selectSQL;
11
+ exports.countSQL = countSQL;
12
+ exports.searchSQL = searchSQL;
13
+ exports.insertSQL = insertSQL;
14
+ exports.updateSQL = updateSQL;
15
+ exports.deleteSQL = deleteSQL;
16
+ const utils_1 = require("../../utils");
17
+ // ─── CREATE TABLE ──────────────────────────────────────────────────────────
18
+ function createTableSQL(table) {
19
+ const tableName = (0, utils_1.sanitizeTableName)(table.name);
20
+ const columnDefs = [
21
+ '"id" TEXT PRIMARY KEY NOT NULL',
22
+ '"_status" TEXT NOT NULL DEFAULT \'created\'',
23
+ '"_changed" TEXT NOT NULL DEFAULT \'\'',
24
+ ...table.columns.map((col) => {
25
+ const name = (0, utils_1.sanitizeColumnName)(col.name);
26
+ let sqlType;
27
+ switch (col.type) {
28
+ case 'text':
29
+ sqlType = 'TEXT';
30
+ break;
31
+ case 'number':
32
+ sqlType = 'REAL';
33
+ break;
34
+ case 'boolean':
35
+ sqlType = 'INTEGER';
36
+ break;
37
+ case 'date':
38
+ sqlType = 'REAL';
39
+ break;
40
+ default:
41
+ sqlType = 'TEXT';
42
+ }
43
+ const nullable = col.isOptional ? '' : ' NOT NULL';
44
+ const defaultVal = col.isOptional ? ' DEFAULT NULL' : getDefaultClause(col.type);
45
+ return `"${name}" ${sqlType}${nullable}${defaultVal}`;
46
+ }),
47
+ ];
48
+ const createSQL = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columnDefs.join(', ')})`;
49
+ // Build index statements
50
+ const indexes = table.columns
51
+ .filter((col) => col.isIndexed)
52
+ .map((col) => {
53
+ const colName = (0, utils_1.sanitizeColumnName)(col.name);
54
+ return `CREATE INDEX IF NOT EXISTS "${tableName}_${colName}" ON "${tableName}" ("${colName}")`;
55
+ });
56
+ // Always index _status
57
+ indexes.unshift(`CREATE INDEX IF NOT EXISTS "${tableName}__status" ON "${tableName}" ("_status")`);
58
+ return [createSQL, ...indexes].join(';\n') + ';';
59
+ }
60
+ function getDefaultClause(type) {
61
+ switch (type) {
62
+ case 'text':
63
+ return " DEFAULT ''";
64
+ case 'number':
65
+ return ' DEFAULT 0';
66
+ case 'boolean':
67
+ return ' DEFAULT 0';
68
+ case 'date':
69
+ return ' DEFAULT NULL';
70
+ default:
71
+ return '';
72
+ }
73
+ }
74
+ // ─── SELECT query ──────────────────────────────────────────────────────────
75
+ function selectSQL(descriptor) {
76
+ const bindings = [];
77
+ const table = (0, utils_1.sanitizeTableName)(descriptor.table);
78
+ let sql = `SELECT * FROM "${table}"`;
79
+ // JOINs
80
+ for (const join of descriptor.joins) {
81
+ const joinTable = (0, utils_1.sanitizeTableName)(join.table);
82
+ const leftCol = (0, utils_1.sanitizeColumnName)(join.leftColumn);
83
+ const rightCol = (0, utils_1.sanitizeColumnName)(join.rightColumn);
84
+ sql += ` JOIN "${joinTable}" ON "${table}"."${leftCol}" = "${joinTable}"."${rightCol}"`;
85
+ }
86
+ // WHERE
87
+ if (descriptor.conditions.length > 0) {
88
+ const whereClause = conditionsToSQL(descriptor.conditions, bindings);
89
+ sql += ` WHERE ${whereClause}`;
90
+ }
91
+ // ORDER BY
92
+ if (descriptor.orderBy.length > 0) {
93
+ const orderClauses = descriptor.orderBy.map((ob) => {
94
+ const col = (0, utils_1.sanitizeColumnName)(ob.column);
95
+ return `"${col}" ${ob.order === 'desc' ? 'DESC' : 'ASC'}`;
96
+ });
97
+ sql += ` ORDER BY ${orderClauses.join(', ')}`;
98
+ }
99
+ // LIMIT / OFFSET
100
+ if (descriptor.limit !== undefined) {
101
+ sql += ' LIMIT ?';
102
+ bindings.push(descriptor.limit);
103
+ }
104
+ if (descriptor.offset !== undefined) {
105
+ sql += ' OFFSET ?';
106
+ bindings.push(descriptor.offset);
107
+ }
108
+ return { sql, bindings };
109
+ }
110
+ // ─── COUNT query ───────────────────────────────────────────────────────────
111
+ function countSQL(descriptor) {
112
+ const bindings = [];
113
+ const table = (0, utils_1.sanitizeTableName)(descriptor.table);
114
+ let sql = `SELECT COUNT(*) as count FROM "${table}"`;
115
+ if (descriptor.conditions.length > 0) {
116
+ const whereClause = conditionsToSQL(descriptor.conditions, bindings);
117
+ sql += ` WHERE ${whereClause}`;
118
+ }
119
+ return { sql, bindings };
120
+ }
121
+ // ─── SEARCH query (LIKE-based) ─────────────────────────────────────────────
122
+ function searchSQL(descriptor) {
123
+ const bindings = [];
124
+ const countBindings = [];
125
+ const table = (0, utils_1.sanitizeTableName)(descriptor.table);
126
+ const pattern = `%${escapeLike(descriptor.term)}%`;
127
+ // Build search conditions
128
+ const searchConditions = descriptor.fields.map((field) => {
129
+ const col = (0, utils_1.sanitizeColumnName)(field);
130
+ bindings.push(pattern);
131
+ countBindings.push(pattern);
132
+ return `"${col}" LIKE ?`;
133
+ });
134
+ const searchWhere = `(${searchConditions.join(' OR ')})`;
135
+ // Additional conditions
136
+ let extraWhere = '';
137
+ if (descriptor.conditions.length > 0) {
138
+ const extraBindings = [];
139
+ extraWhere = ` AND ${conditionsToSQL(descriptor.conditions, extraBindings)}`;
140
+ bindings.push(...extraBindings);
141
+ countBindings.push(...extraBindings);
142
+ }
143
+ // Count query
144
+ const countSql = `SELECT COUNT(*) as count FROM "${table}" WHERE ${searchWhere}${extraWhere}`;
145
+ // Result query
146
+ let sql = `SELECT * FROM "${table}" WHERE ${searchWhere}${extraWhere}`;
147
+ if (descriptor.orderBy.length > 0) {
148
+ const orderClauses = descriptor.orderBy.map((ob) => {
149
+ const col = (0, utils_1.sanitizeColumnName)(ob.column);
150
+ return `"${col}" ${ob.order === 'desc' ? 'DESC' : 'ASC'}`;
151
+ });
152
+ sql += ` ORDER BY ${orderClauses.join(', ')}`;
153
+ }
154
+ sql += ' LIMIT ? OFFSET ?';
155
+ bindings.push(descriptor.limit, descriptor.offset);
156
+ return { sql, countSql, bindings, countBindings };
157
+ }
158
+ // ─── INSERT ────────────────────────────────────────────────────────────────
159
+ function insertSQL(table, raw) {
160
+ const tableName = (0, utils_1.sanitizeTableName)(table);
161
+ const keys = Object.keys(raw);
162
+ const columns = keys.map((k) => `"${(0, utils_1.sanitizeColumnName)(k)}"`).join(', ');
163
+ const placeholders = keys.map(() => '?').join(', ');
164
+ const bindings = keys.map((k) => raw[k]);
165
+ return {
166
+ sql: `INSERT INTO "${tableName}" (${columns}) VALUES (${placeholders})`,
167
+ bindings,
168
+ };
169
+ }
170
+ // ─── UPDATE ────────────────────────────────────────────────────────────────
171
+ function updateSQL(table, raw) {
172
+ const tableName = (0, utils_1.sanitizeTableName)(table);
173
+ const keys = Object.keys(raw).filter((k) => k !== 'id');
174
+ const setClauses = keys.map((k) => `"${(0, utils_1.sanitizeColumnName)(k)}" = ?`).join(', ');
175
+ const bindings = [...keys.map((k) => raw[k]), raw.id];
176
+ return {
177
+ sql: `UPDATE "${tableName}" SET ${setClauses} WHERE "id" = ?`,
178
+ bindings,
179
+ };
180
+ }
181
+ // ─── DELETE ────────────────────────────────────────────────────────────────
182
+ function deleteSQL(table, id) {
183
+ return {
184
+ sql: `DELETE FROM "${(0, utils_1.sanitizeTableName)(table)}" WHERE "id" = ?`,
185
+ bindings: [id],
186
+ };
187
+ }
188
+ // ─── Condition helpers ─────────────────────────────────────────────────────
189
+ function conditionsToSQL(conditions, bindings) {
190
+ return conditions.map((c) => conditionToSQL(c, bindings)).join(' AND ');
191
+ }
192
+ function conditionToSQL(condition, bindings) {
193
+ switch (condition.type) {
194
+ case 'where':
195
+ return whereToSQL(condition, bindings);
196
+ case 'and':
197
+ return `(${condition.conditions.map((c) => conditionToSQL(c, bindings)).join(' AND ')})`;
198
+ case 'or':
199
+ return `(${condition.conditions.map((c) => conditionToSQL(c, bindings)).join(' OR ')})`;
200
+ case 'not':
201
+ return `NOT (${conditionToSQL(condition.condition, bindings)})`;
202
+ default:
203
+ throw new Error(`Unknown condition type: ${condition.type}`);
204
+ }
205
+ }
206
+ function whereToSQL(clause, bindings) {
207
+ const col = `"${(0, utils_1.sanitizeColumnName)(clause.column)}"`;
208
+ switch (clause.operator) {
209
+ case 'eq':
210
+ bindings.push(clause.value);
211
+ return `${col} = ?`;
212
+ case 'neq':
213
+ bindings.push(clause.value);
214
+ return `${col} != ?`;
215
+ case 'gt':
216
+ bindings.push(clause.value);
217
+ return `${col} > ?`;
218
+ case 'gte':
219
+ bindings.push(clause.value);
220
+ return `${col} >= ?`;
221
+ case 'lt':
222
+ bindings.push(clause.value);
223
+ return `${col} < ?`;
224
+ case 'lte':
225
+ bindings.push(clause.value);
226
+ return `${col} <= ?`;
227
+ case 'in':
228
+ const arr = clause.value;
229
+ const placeholders = arr.map(() => '?').join(', ');
230
+ bindings.push(...arr);
231
+ return `${col} IN (${placeholders})`;
232
+ case 'notIn':
233
+ const arr2 = clause.value;
234
+ const placeholders2 = arr2.map(() => '?').join(', ');
235
+ bindings.push(...arr2);
236
+ return `${col} NOT IN (${placeholders2})`;
237
+ case 'like':
238
+ bindings.push(clause.value);
239
+ return `${col} LIKE ?`;
240
+ case 'notLike':
241
+ bindings.push(clause.value);
242
+ return `${col} NOT LIKE ?`;
243
+ case 'between':
244
+ const [low, high] = clause.value;
245
+ bindings.push(low, high);
246
+ return `${col} BETWEEN ? AND ?`;
247
+ case 'isNull':
248
+ return `${col} IS NULL`;
249
+ case 'isNotNull':
250
+ return `${col} IS NOT NULL`;
251
+ default:
252
+ throw new Error(`Unknown operator: ${clause.operator}`);
253
+ }
254
+ }
255
+ function escapeLike(str) {
256
+ return str.replaceAll('%', String.raw `\%`).replaceAll('_', String.raw `\_`);
257
+ }
258
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Storage adapter interface.
3
+ *
4
+ * All database operations go through this interface, enabling
5
+ * pluggable backends (SQLite, LokiJS, etc.).
6
+ */
7
+ import type { QueryDescriptor, SearchDescriptor, BatchOperation } from '../query/types';
8
+ import type { DatabaseSchema, RawRecord, TableSchema } from '../schema/types';
9
+ export interface AdapterConfig {
10
+ readonly databaseName: string;
11
+ /** Optional schema version override; normally derived from DatabaseSchema. */
12
+ readonly schemaVersion?: number;
13
+ }
14
+ export interface EncryptionConfig {
15
+ readonly enabled: boolean;
16
+ readonly keyProvider: () => Promise<Uint8Array>;
17
+ }
18
+ export interface Migration {
19
+ readonly fromVersion: number;
20
+ readonly toVersion: number;
21
+ readonly steps: MigrationStep[];
22
+ }
23
+ export type MigrationStep = {
24
+ type: 'createTable';
25
+ schema: TableSchema;
26
+ } | {
27
+ type: 'addColumn';
28
+ table: string;
29
+ column: string;
30
+ columnType: string;
31
+ isOptional?: boolean;
32
+ } | {
33
+ type: 'destroyTable';
34
+ table: string;
35
+ } | {
36
+ type: 'sql';
37
+ query: string;
38
+ };
39
+ export interface StorageAdapter {
40
+ /** Initialize the adapter. Creates tables if needed. */
41
+ initialize(schema: DatabaseSchema): Promise<void>;
42
+ /** Find records matching a query descriptor. */
43
+ find(query: QueryDescriptor): Promise<RawRecord[]>;
44
+ /** Count records matching a query descriptor. */
45
+ count(query: QueryDescriptor): Promise<number>;
46
+ /** Find a single record by ID. */
47
+ findById(table: string, id: string): Promise<RawRecord | null>;
48
+ /** Insert a new raw record. */
49
+ insert(table: string, raw: RawRecord): Promise<void>;
50
+ /** Update an existing raw record. */
51
+ update(table: string, raw: RawRecord): Promise<void>;
52
+ /** Mark a record as deleted (_status = 'deleted'). */
53
+ markAsDeleted(table: string, id: string): Promise<void>;
54
+ /** Permanently remove a record from the database. */
55
+ destroyPermanently(table: string, id: string): Promise<void>;
56
+ /** Execute a batch of operations atomically. */
57
+ batch(operations: BatchOperation[]): Promise<void>;
58
+ /** Full-text search. */
59
+ search(descriptor: SearchDescriptor): Promise<{
60
+ records: RawRecord[];
61
+ total: number;
62
+ }>;
63
+ /** Return all records with _status != 'synced' */
64
+ getLocalChanges(tables: string[]): Promise<Record<string, {
65
+ created: RawRecord[];
66
+ updated: RawRecord[];
67
+ deleted: string[];
68
+ }>>;
69
+ /** Apply synced changes from remote (in a transaction). */
70
+ applyRemoteChanges(changes: Record<string, {
71
+ created: RawRecord[];
72
+ updated: RawRecord[];
73
+ deleted: string[];
74
+ }>): Promise<void>;
75
+ /** Mark synced records as _status = 'synced'. */
76
+ markAsSynced(table: string, ids: string[]): Promise<void>;
77
+ /** Get the database schema version currently stored. */
78
+ getSchemaVersion(): Promise<number>;
79
+ /** Run migrations. */
80
+ migrate(migrations: Migration[]): Promise<void>;
81
+ /** Completely reset the database. */
82
+ reset(): Promise<void>;
83
+ /** Close the database connection. */
84
+ close(): Promise<void>;
85
+ }
86
+ export type AdapterEvent = {
87
+ type: 'initialized';
88
+ } | {
89
+ type: 'batch_completed';
90
+ operations: BatchOperation[];
91
+ } | {
92
+ type: 'reset';
93
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * Storage adapter interface.
4
+ *
5
+ * All database operations go through this interface, enabling
6
+ * pluggable backends (SQLite, LokiJS, etc.).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=types.js.map