@cocreate/sqlite 1.0.0 → 1.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.
- package/CHANGELOG.md +7 -0
- package/package.json +15 -10
- package/src/index.js +106 -113
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/CoCreate-app/CoCreate-sqlite/compare/v1.0.0...v1.1.0) (2026-07-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Refactor to use @libsql/client and update database handling ([3569e47](https://github.com/CoCreate-app/CoCreate-sqlite/commit/3569e47a98dc076ff8beb45e59880abefbbf3da7))
|
|
7
|
+
|
|
1
8
|
# 1.0.0 (2026-07-18)
|
|
2
9
|
|
|
3
10
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/sqlite",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Lightweight SQLite CRUD wrapper and multi-tenant state routing client for the CoCreate ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sqlite",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
7
|
+
"sqlite3",
|
|
8
|
+
"sql",
|
|
9
|
+
"json",
|
|
10
|
+
"crud-api",
|
|
11
|
+
"abstract-crud",
|
|
12
|
+
"database-abstraction",
|
|
13
|
+
"multi-tenant",
|
|
14
|
+
"multi-tenant-routing",
|
|
15
|
+
"realtime-sync",
|
|
16
|
+
"state-synchronization",
|
|
17
|
+
"state-routing",
|
|
18
|
+
"embedded-replicas"
|
|
14
19
|
],
|
|
15
20
|
"publishConfig": {
|
|
16
21
|
"access": "public"
|
|
@@ -38,6 +43,6 @@
|
|
|
38
43
|
"main": "./src/index.js",
|
|
39
44
|
"dependencies": {
|
|
40
45
|
"@cocreate/utils": "^1.44.0",
|
|
41
|
-
"
|
|
46
|
+
"@libsql/client": "^0.6.0"
|
|
42
47
|
}
|
|
43
48
|
}
|
package/src/index.js
CHANGED
|
@@ -1,46 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2026 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
import { createClient } from "@libsql/client";
|
|
19
|
+
import {
|
|
3
20
|
dotNotationToObject,
|
|
4
21
|
queryData,
|
|
5
22
|
searchData,
|
|
6
23
|
sortData,
|
|
7
24
|
isValidDate
|
|
8
|
-
}
|
|
25
|
+
} from "@cocreate/utils";
|
|
9
26
|
|
|
10
27
|
const organizations = {};
|
|
11
28
|
|
|
12
29
|
/**
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
db.run(sql, params, function (err) {
|
|
18
|
-
if (err) reject(err);
|
|
19
|
-
else resolve({ lastID: this.lastID, changes: this.changes });
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function allQuery(db, sql, params = []) {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
db.all(sql, params, (err, rows) => {
|
|
27
|
-
if (err) reject(err);
|
|
28
|
-
else resolve(rows);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getQuery(db, sql, params = []) {
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
db.get(sql, params, (err, row) => {
|
|
36
|
-
if (err) reject(err);
|
|
37
|
-
else resolve(row);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Dynamically resolves or caches a long-lived SQLite Connection handle.
|
|
30
|
+
* Dynamically resolves or caches a long-lived libSQL Database Client.
|
|
31
|
+
* Supports standard local SQLite files ("file:local.db"), in-memory paths (":memory:"),
|
|
32
|
+
* and remote Turso connections ("libsql://...") with cloud tokens.
|
|
44
33
|
*/
|
|
45
34
|
async function dbClient(data) {
|
|
46
35
|
if (data.storageUrl) {
|
|
@@ -49,25 +38,28 @@ async function dbClient(data) {
|
|
|
49
38
|
}
|
|
50
39
|
try {
|
|
51
40
|
if (!organizations[data.organization_id][data.storageUrl]) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
41
|
+
const connectionPromise = (async () => {
|
|
42
|
+
let config = { url: data.storageUrl };
|
|
43
|
+
|
|
44
|
+
// If an authentication token is passed inside dynamic payloads for cloud/Turso targets
|
|
45
|
+
if (data.authToken || data.auth_token) {
|
|
46
|
+
config.authToken = data.authToken || data.auth_token;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const client = createClient(config);
|
|
50
|
+
|
|
51
|
+
// Verify active driver connectivity safely
|
|
52
|
+
await client.execute("SELECT 1;");
|
|
53
|
+
return client;
|
|
54
|
+
})();
|
|
61
55
|
|
|
62
56
|
organizations[data.organization_id][data.storageUrl] = connectionPromise;
|
|
63
57
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
organizations[data.organization_id][data.storageUrl] = await organizations[data.organization_id][data.storageUrl];
|
|
67
|
-
return organizations[data.organization_id][data.storageUrl];
|
|
58
|
+
|
|
59
|
+
return await organizations[data.organization_id][data.storageUrl];
|
|
68
60
|
} catch (error) {
|
|
69
61
|
console.error(
|
|
70
|
-
`${data.organization_id}:
|
|
62
|
+
`${data.organization_id}: Connection failed using @libsql/client`
|
|
71
63
|
);
|
|
72
64
|
errorHandler(data, error);
|
|
73
65
|
return { status: false };
|
|
@@ -80,36 +72,32 @@ async function dbClient(data) {
|
|
|
80
72
|
|
|
81
73
|
/**
|
|
82
74
|
* Global Event Listener: orgDeleted
|
|
83
|
-
* Gracefully terminates active
|
|
75
|
+
* Gracefully terminates active libSQL database connections and clears the local pool cache.
|
|
84
76
|
*/
|
|
85
77
|
process.on("orgDeleted", (organization_id) => {
|
|
86
78
|
const orgPools = organizations[organization_id];
|
|
87
79
|
if (orgPools) {
|
|
88
|
-
console.log(`[
|
|
80
|
+
console.log(`[libSQL Utilities] Cleanup Scheduled: Initiating 5-second grace period for Org: ${organization_id}`);
|
|
89
81
|
setTimeout(async () => {
|
|
90
82
|
const currentPools = organizations[organization_id];
|
|
91
83
|
if (!currentPools) return;
|
|
92
84
|
|
|
93
|
-
console.log(`[
|
|
85
|
+
console.log(`[libSQL Utilities] Cleanup Executing: Closing connection clients for Org: ${organization_id}`);
|
|
94
86
|
const dbList = Object.values(currentPools);
|
|
95
87
|
|
|
96
|
-
for (const
|
|
88
|
+
for (const dbPromise of dbList) {
|
|
97
89
|
try {
|
|
90
|
+
const db = await dbPromise;
|
|
98
91
|
if (db && typeof db.close === "function") {
|
|
99
|
-
|
|
100
|
-
db.close((err) => {
|
|
101
|
-
if (err) reject(err);
|
|
102
|
-
else resolve();
|
|
103
|
-
});
|
|
104
|
-
});
|
|
92
|
+
db.close();
|
|
105
93
|
}
|
|
106
94
|
} catch (err) {
|
|
107
|
-
console.error(`[
|
|
95
|
+
console.error(`[libSQL Utilities] Error closing connection for Org ${organization_id}:`, err);
|
|
108
96
|
}
|
|
109
97
|
}
|
|
110
98
|
|
|
111
99
|
delete organizations[organization_id];
|
|
112
|
-
console.log(`[
|
|
100
|
+
console.log(`[libSQL Utilities] Memory Cleared: Registry freed for Org: ${organization_id}`);
|
|
113
101
|
}, 5000);
|
|
114
102
|
}
|
|
115
103
|
});
|
|
@@ -117,7 +105,7 @@ process.on("orgDeleted", (organization_id) => {
|
|
|
117
105
|
/**
|
|
118
106
|
* Universal router routing request methods safely.
|
|
119
107
|
*/
|
|
120
|
-
function send(data) {
|
|
108
|
+
export function send(data) {
|
|
121
109
|
let [type, method] = data.method.split(".");
|
|
122
110
|
if (type === "database") return database(method, data);
|
|
123
111
|
if (type === "array") return array(method, data);
|
|
@@ -128,6 +116,7 @@ function send(data) {
|
|
|
128
116
|
* "database" operations mapped to SQLite Files (emulated catalogs).
|
|
129
117
|
*/
|
|
130
118
|
function database(method, data) {
|
|
119
|
+
const storageName = data.storageName || data.provider || "sqlite";
|
|
131
120
|
return new Promise(
|
|
132
121
|
async (resolve, reject) => {
|
|
133
122
|
let type = "database";
|
|
@@ -135,10 +124,9 @@ function database(method, data) {
|
|
|
135
124
|
|
|
136
125
|
try {
|
|
137
126
|
const db = await dbClient(data);
|
|
138
|
-
if (!db || db.status === false) return data;
|
|
127
|
+
if (!db || db.status === false) return resolve(data);
|
|
139
128
|
|
|
140
129
|
if (method === "read") {
|
|
141
|
-
// Emulating catalog query targeting active database context
|
|
142
130
|
const sql = "PRAGMA database_list;";
|
|
143
131
|
process.emit("usage", {
|
|
144
132
|
type: "egress",
|
|
@@ -146,36 +134,37 @@ function database(method, data) {
|
|
|
146
134
|
organization_id: data.organization_id
|
|
147
135
|
});
|
|
148
136
|
|
|
149
|
-
const res = await
|
|
137
|
+
const res = await db.execute(sql);
|
|
150
138
|
process.emit("usage", {
|
|
151
139
|
type: "ingress",
|
|
152
|
-
data: res,
|
|
140
|
+
data: res.rows,
|
|
153
141
|
organization_id: data.organization_id
|
|
154
142
|
});
|
|
155
143
|
|
|
156
|
-
for (let row of res) {
|
|
144
|
+
for (let row of res.rows) {
|
|
145
|
+
// libSQL ResultSet rows support standard property lookup
|
|
157
146
|
const dbObj = { name: row.file ? row.file.split("/").pop() : "memory" };
|
|
158
147
|
if (data.$filter && data.$filter.query) {
|
|
159
148
|
let isFilter = queryData(dbObj, data.$filter.query);
|
|
160
149
|
if (isFilter) {
|
|
161
150
|
databaseArray.push({
|
|
162
151
|
database: dbObj,
|
|
163
|
-
storage:
|
|
152
|
+
storage: storageName
|
|
164
153
|
});
|
|
165
154
|
}
|
|
166
155
|
} else {
|
|
167
156
|
databaseArray.push({
|
|
168
157
|
database: dbObj,
|
|
169
|
-
storage:
|
|
158
|
+
storage: storageName
|
|
170
159
|
});
|
|
171
160
|
}
|
|
172
161
|
}
|
|
173
162
|
resolve(createData(data, databaseArray, type));
|
|
174
163
|
}
|
|
175
164
|
if (method === "delete") {
|
|
176
|
-
//
|
|
177
|
-
const tables = await
|
|
178
|
-
for (let t of tables) {
|
|
165
|
+
// Drop all non-system tables inside the database context safely
|
|
166
|
+
const tables = await db.execute("SELECT name FROM sqlite_master WHERE type='table';");
|
|
167
|
+
for (let t of tables.rows) {
|
|
179
168
|
if (!t.name.startsWith("sqlite_")) {
|
|
180
169
|
const sql = `DROP TABLE IF EXISTS "${t.name}";`;
|
|
181
170
|
process.emit("usage", {
|
|
@@ -183,7 +172,7 @@ function database(method, data) {
|
|
|
183
172
|
data: { sql },
|
|
184
173
|
organization_id: data.organization_id
|
|
185
174
|
});
|
|
186
|
-
await
|
|
175
|
+
await db.execute(sql);
|
|
187
176
|
}
|
|
188
177
|
}
|
|
189
178
|
resolve({ status: true });
|
|
@@ -204,6 +193,7 @@ function database(method, data) {
|
|
|
204
193
|
* "array" operations mapped to SQL Tables.
|
|
205
194
|
*/
|
|
206
195
|
function array(method, data) {
|
|
196
|
+
const storageName = data.storageName || data.provider || "sqlite";
|
|
207
197
|
return new Promise(
|
|
208
198
|
async (resolve, reject) => {
|
|
209
199
|
let type = "array";
|
|
@@ -211,7 +201,7 @@ function array(method, data) {
|
|
|
211
201
|
|
|
212
202
|
try {
|
|
213
203
|
const db = await dbClient(data);
|
|
214
|
-
if (!db || db.status === false) return data;
|
|
204
|
+
if (!db || db.status === false) return resolve(data);
|
|
215
205
|
|
|
216
206
|
if (data.request) data.array = data.request;
|
|
217
207
|
|
|
@@ -228,14 +218,14 @@ function array(method, data) {
|
|
|
228
218
|
organization_id: data.organization_id
|
|
229
219
|
});
|
|
230
220
|
|
|
231
|
-
const res = await
|
|
221
|
+
const res = await db.execute(sql);
|
|
232
222
|
process.emit("usage", {
|
|
233
223
|
type: "ingress",
|
|
234
|
-
data: res,
|
|
224
|
+
data: res.rows,
|
|
235
225
|
organization_id: data.organization_id
|
|
236
226
|
});
|
|
237
227
|
|
|
238
|
-
for (let row of res) {
|
|
228
|
+
for (let row of res.rows) {
|
|
239
229
|
const tableObj = { name: row.name };
|
|
240
230
|
if (data.$filter && data.$filter.query) {
|
|
241
231
|
let isFilter = queryData(tableObj, data.$filter.query);
|
|
@@ -243,14 +233,14 @@ function array(method, data) {
|
|
|
243
233
|
arrayArray.push({
|
|
244
234
|
name: row.name,
|
|
245
235
|
database,
|
|
246
|
-
storage:
|
|
236
|
+
storage: storageName
|
|
247
237
|
});
|
|
248
238
|
}
|
|
249
239
|
} else {
|
|
250
240
|
arrayArray.push({
|
|
251
241
|
name: row.name,
|
|
252
242
|
database,
|
|
253
|
-
storage:
|
|
243
|
+
storage: storageName
|
|
254
244
|
});
|
|
255
245
|
}
|
|
256
246
|
}
|
|
@@ -283,11 +273,11 @@ function array(method, data) {
|
|
|
283
273
|
organization_id: data.organization_id
|
|
284
274
|
});
|
|
285
275
|
|
|
286
|
-
await
|
|
276
|
+
await db.execute(sql);
|
|
287
277
|
arrayArray.push({
|
|
288
278
|
name: array,
|
|
289
279
|
database,
|
|
290
|
-
storage:
|
|
280
|
+
storage: storageName
|
|
291
281
|
});
|
|
292
282
|
|
|
293
283
|
arraysLength -= 1;
|
|
@@ -306,12 +296,12 @@ function array(method, data) {
|
|
|
306
296
|
organization_id: data.organization_id
|
|
307
297
|
});
|
|
308
298
|
|
|
309
|
-
await
|
|
299
|
+
await db.execute(sql);
|
|
310
300
|
arrayArray.push({
|
|
311
301
|
name: newName,
|
|
312
302
|
oldName: oldName,
|
|
313
303
|
database,
|
|
314
|
-
storage:
|
|
304
|
+
storage: storageName
|
|
315
305
|
});
|
|
316
306
|
|
|
317
307
|
arraysLength -= 1;
|
|
@@ -330,11 +320,11 @@ function array(method, data) {
|
|
|
330
320
|
organization_id: data.organization_id
|
|
331
321
|
});
|
|
332
322
|
|
|
333
|
-
await
|
|
323
|
+
await db.execute(sql);
|
|
334
324
|
arrayArray.push({
|
|
335
325
|
name: array,
|
|
336
326
|
database,
|
|
337
|
-
storage:
|
|
327
|
+
storage: storageName
|
|
338
328
|
});
|
|
339
329
|
|
|
340
330
|
arraysLength -= 1;
|
|
@@ -364,11 +354,12 @@ function array(method, data) {
|
|
|
364
354
|
* "object" operations mapped to SQL Rows.
|
|
365
355
|
*/
|
|
366
356
|
function object(method, data) {
|
|
357
|
+
const storageName = data.storageName || data.provider || "sqlite";
|
|
367
358
|
return new Promise(
|
|
368
359
|
async (resolve, reject) => {
|
|
369
360
|
try {
|
|
370
361
|
const db = await dbClient(data);
|
|
371
|
-
if (!db || db.status === false) return data;
|
|
362
|
+
if (!db || db.status === false) return resolve(data);
|
|
372
363
|
|
|
373
364
|
let type = "object";
|
|
374
365
|
let documents = [];
|
|
@@ -386,7 +377,7 @@ function object(method, data) {
|
|
|
386
377
|
|
|
387
378
|
for (let array of arrays) {
|
|
388
379
|
const reference = {
|
|
389
|
-
$storage:
|
|
380
|
+
$storage: storageName,
|
|
390
381
|
$database: database,
|
|
391
382
|
$array: array
|
|
392
383
|
};
|
|
@@ -411,7 +402,7 @@ function object(method, data) {
|
|
|
411
402
|
if (!Array.isArray($database)) $database = [data[type][i].$database];
|
|
412
403
|
if (!Array.isArray($array)) $array = [data[type][i].$array];
|
|
413
404
|
|
|
414
|
-
if (!$storage.includes(
|
|
405
|
+
if (!$storage.includes(storageName)) $storage.push(storageName);
|
|
415
406
|
if (!$database.includes(database)) $database.push(database);
|
|
416
407
|
if (!$array.includes(array)) $array.push(array);
|
|
417
408
|
|
|
@@ -449,11 +440,14 @@ function object(method, data) {
|
|
|
449
440
|
organization_id: data.organization_id
|
|
450
441
|
});
|
|
451
442
|
|
|
452
|
-
await
|
|
443
|
+
await db.execute({ sql, args: queryParams });
|
|
453
444
|
|
|
454
|
-
const createdDoc = await
|
|
455
|
-
|
|
456
|
-
|
|
445
|
+
const createdDoc = await db.execute({
|
|
446
|
+
sql: `SELECT * FROM "${array}" WHERE _id = ? AND organization_id = ?`,
|
|
447
|
+
args: [id, orgId]
|
|
448
|
+
});
|
|
449
|
+
if (createdDoc.rows.length > 0) {
|
|
450
|
+
const formattedDoc = parseSqlRow(createdDoc.rows[0]);
|
|
457
451
|
data[type][i] = { ...formattedDoc, $storage, $database, $array };
|
|
458
452
|
}
|
|
459
453
|
}
|
|
@@ -488,15 +482,15 @@ function object(method, data) {
|
|
|
488
482
|
organization_id: data.organization_id
|
|
489
483
|
});
|
|
490
484
|
|
|
491
|
-
const
|
|
485
|
+
const result = await db.execute({ sql, args: params });
|
|
492
486
|
process.emit("usage", {
|
|
493
487
|
type: "ingress",
|
|
494
|
-
data: rows,
|
|
488
|
+
data: result.rows,
|
|
495
489
|
organization_id: data.organization_id
|
|
496
490
|
});
|
|
497
491
|
|
|
498
492
|
if (isFilter) {
|
|
499
|
-
for (let row of rows) {
|
|
493
|
+
for (let row of result.rows) {
|
|
500
494
|
const parsedRow = parseSqlRow(row);
|
|
501
495
|
if (data.$filter && data.$filter.search) {
|
|
502
496
|
let isMatch = searchData(parsedRow, data.$filter.search);
|
|
@@ -507,8 +501,8 @@ function object(method, data) {
|
|
|
507
501
|
...reference
|
|
508
502
|
});
|
|
509
503
|
}
|
|
510
|
-
} else if (rows.length > 0) {
|
|
511
|
-
let dbRecord = parseSqlRow(rows[0]);
|
|
504
|
+
} else if (result.rows.length > 0) {
|
|
505
|
+
let dbRecord = parseSqlRow(result.rows[0]);
|
|
512
506
|
|
|
513
507
|
if ($storage.length && data[type][i].modified && data[type][i].modified.on) {
|
|
514
508
|
let clientTime = new Date(data[type][i].modified.on);
|
|
@@ -550,9 +544,9 @@ function object(method, data) {
|
|
|
550
544
|
data: { selectSql, params },
|
|
551
545
|
organization_id: data.organization_id
|
|
552
546
|
});
|
|
553
|
-
const
|
|
547
|
+
const selectResult = await db.execute({ sql: selectSql, args: params });
|
|
554
548
|
|
|
555
|
-
for (let row of
|
|
549
|
+
for (let row of selectResult.rows) {
|
|
556
550
|
const updatedDoc = await executeUpdateTransaction(db, array, row._id, updatePayload, data);
|
|
557
551
|
if (updatedDoc) {
|
|
558
552
|
documents.push({ ...updatedDoc, ...reference });
|
|
@@ -576,7 +570,7 @@ function object(method, data) {
|
|
|
576
570
|
data: { sql, queryParams },
|
|
577
571
|
organization_id: data.organization_id
|
|
578
572
|
});
|
|
579
|
-
const runResult = await
|
|
573
|
+
const runResult = await db.execute({ sql, args: queryParams });
|
|
580
574
|
process.emit("usage", {
|
|
581
575
|
type: "ingress",
|
|
582
576
|
data: runResult,
|
|
@@ -584,7 +578,6 @@ function object(method, data) {
|
|
|
584
578
|
});
|
|
585
579
|
|
|
586
580
|
if (isFilter) {
|
|
587
|
-
// Return deleted references
|
|
588
581
|
documents.push({ _id: data[type][i]._id, ...reference });
|
|
589
582
|
} else {
|
|
590
583
|
data[type][i] = { _id: data[type][i]._id, $storage, $database, $array };
|
|
@@ -610,22 +603,23 @@ function object(method, data) {
|
|
|
610
603
|
}
|
|
611
604
|
|
|
612
605
|
/**
|
|
613
|
-
* Safe transaction helper to update unstructured payloads with MongoDB behavior inside
|
|
606
|
+
* Safe transaction helper to update unstructured payloads with MongoDB behavior inside libSQL.
|
|
614
607
|
*/
|
|
615
608
|
async function executeUpdateTransaction(db, arrayName, docId, rawUpdateInput, requestData) {
|
|
616
609
|
try {
|
|
617
|
-
// SQLite is single-write bound, using IMMEDIATE locks database cleanly
|
|
618
|
-
await runQuery(db, "BEGIN IMMEDIATE TRANSACTION;");
|
|
619
|
-
|
|
620
610
|
const selectSql = `SELECT * FROM "${arrayName}" WHERE _id = ? AND organization_id = ?;`;
|
|
621
|
-
|
|
622
|
-
|
|
611
|
+
|
|
612
|
+
// Run isolation transaction
|
|
613
|
+
const tx = await db.transaction("deferred");
|
|
614
|
+
|
|
615
|
+
const currentRecordRaw = await tx.execute({ sql: selectSql, args: [docId, requestData.organization_id] });
|
|
616
|
+
let currentRecord = currentRecordRaw.rows.length > 0 ? parseSqlRow(currentRecordRaw.rows[0]) : null;
|
|
623
617
|
|
|
624
618
|
if (!currentRecord) {
|
|
625
619
|
if (requestData.upsert || rawUpdateInput.upsert || rawUpdateInput.$upsert) {
|
|
626
620
|
currentRecord = { _id: docId, organization_id: requestData.organization_id };
|
|
627
621
|
} else {
|
|
628
|
-
await
|
|
622
|
+
await tx.rollback();
|
|
629
623
|
return null;
|
|
630
624
|
}
|
|
631
625
|
}
|
|
@@ -657,13 +651,12 @@ async function executeUpdateTransaction(db, arrayName, docId, rawUpdateInput, re
|
|
|
657
651
|
organization_id: requestData.organization_id
|
|
658
652
|
});
|
|
659
653
|
|
|
660
|
-
await
|
|
661
|
-
await
|
|
654
|
+
await tx.execute({ sql: updateSql, args: updateParams });
|
|
655
|
+
await tx.commit();
|
|
662
656
|
|
|
663
|
-
const finalRecordRaw = await
|
|
664
|
-
return parseSqlRow(finalRecordRaw);
|
|
657
|
+
const finalRecordRaw = await db.execute({ sql: selectSql, args: [docId, requestData.organization_id] });
|
|
658
|
+
return parseSqlRow(finalRecordRaw.rows[0]);
|
|
665
659
|
} catch (error) {
|
|
666
|
-
await runQuery(db, "ROLLBACK;");
|
|
667
660
|
throw error;
|
|
668
661
|
}
|
|
669
662
|
}
|
|
@@ -922,4 +915,4 @@ function errorHandler(data, error, database, array) {
|
|
|
922
915
|
}
|
|
923
916
|
}
|
|
924
917
|
|
|
925
|
-
|
|
918
|
+
export default { send };
|