js-bao 0.2.10 → 0.2.12
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/README.md +174 -0
- package/dist/BaseModel-5YQCROYE.js +17 -0
- package/dist/BaseModel-5YQCROYE.js.map +1 -0
- package/dist/BaseModel-FCNWDJBH.js +17 -0
- package/dist/BaseModel-FCNWDJBH.js.map +1 -0
- package/dist/BrowserDatabaseFactory-PXOTK2DQ.js +119 -0
- package/dist/BrowserDatabaseFactory-PXOTK2DQ.js.map +1 -0
- package/dist/BrowserDatabaseFactory-WD4VX2VZ.js +119 -0
- package/dist/BrowserDatabaseFactory-WD4VX2VZ.js.map +1 -0
- package/dist/IncludeResolver-RCKQGNPZ.js +385 -0
- package/dist/IncludeResolver-RCKQGNPZ.js.map +1 -0
- package/dist/IncludeResolver-WGSQDMS7.js +385 -0
- package/dist/IncludeResolver-WGSQDMS7.js.map +1 -0
- package/dist/NodeDatabaseFactory-J4Z36UF3.js +165 -0
- package/dist/NodeDatabaseFactory-J4Z36UF3.js.map +1 -0
- package/dist/NodeDatabaseFactory-QIEKAXBM.js +10 -0
- package/dist/NodeDatabaseFactory-QIEKAXBM.js.map +1 -0
- package/dist/NodeSqliteEngine-HJSAYE4E.js +383 -0
- package/dist/NodeSqliteEngine-HJSAYE4E.js.map +1 -0
- package/dist/NodeSqliteEngine-I5SLWLME.js +383 -0
- package/dist/NodeSqliteEngine-I5SLWLME.js.map +1 -0
- package/dist/browser.cjs +3779 -3370
- package/dist/browser.d.cts +18 -1
- package/dist/browser.d.ts +18 -1
- package/dist/browser.js +3750 -3341
- package/dist/chunk-3PZWHUZO.js +4153 -0
- package/dist/chunk-3PZWHUZO.js.map +1 -0
- package/dist/chunk-53MS4MN7.js +373 -0
- package/dist/chunk-53MS4MN7.js.map +1 -0
- package/dist/chunk-65G2P4GL.js +709 -0
- package/dist/chunk-65G2P4GL.js.map +1 -0
- package/dist/chunk-6UX3YSCW.js +4151 -0
- package/dist/chunk-6UX3YSCW.js.map +1 -0
- package/dist/chunk-DANSD6BE.js +709 -0
- package/dist/chunk-DANSD6BE.js.map +1 -0
- package/dist/chunk-DF3JEQXA.js +373 -0
- package/dist/chunk-DF3JEQXA.js.map +1 -0
- package/dist/chunk-GO3APTPX.js +61 -0
- package/dist/chunk-GO3APTPX.js.map +1 -0
- package/dist/chunk-ID4U6IQC.js +53 -0
- package/dist/chunk-ID4U6IQC.js.map +1 -0
- package/dist/chunk-RQVS3LVL.js +165 -0
- package/dist/chunk-RQVS3LVL.js.map +1 -0
- package/dist/client.cjs +837 -0
- package/dist/client.d.cts +1101 -0
- package/dist/client.d.ts +1101 -0
- package/dist/client.js +806 -0
- package/dist/cloudflare-do.cjs +3637 -0
- package/dist/cloudflare-do.d.cts +1366 -0
- package/dist/cloudflare-do.d.ts +1366 -0
- package/dist/cloudflare-do.js +3614 -0
- package/dist/cloudflare.cjs +1048 -0
- package/dist/cloudflare.d.cts +1381 -0
- package/dist/cloudflare.d.ts +1381 -0
- package/dist/cloudflare.js +1017 -0
- package/dist/codegen.cjs +259 -18
- package/dist/environment-TOTQICSE.js +17 -0
- package/dist/environment-TOTQICSE.js.map +1 -0
- package/dist/index.cjs +1906 -1493
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +1871 -1458
- package/dist/node.cjs +4779 -4366
- package/dist/node.d.cts +18 -1
- package/dist/node.d.ts +18 -1
- package/dist/node.js +4602 -4189
- package/package.json +41 -12
package/dist/index.js
CHANGED
|
@@ -327,7 +327,7 @@ var init_CursorManager = __esm({
|
|
|
327
327
|
conditions.push(`(${levelCondition})`);
|
|
328
328
|
params.push(...fieldParams);
|
|
329
329
|
}
|
|
330
|
-
const sql = conditions.join(" OR ")
|
|
330
|
+
const sql = `(${conditions.join(" OR ")})`;
|
|
331
331
|
return { sql, params };
|
|
332
332
|
}
|
|
333
333
|
/**
|
|
@@ -1161,6 +1161,392 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
1161
1161
|
}
|
|
1162
1162
|
});
|
|
1163
1163
|
|
|
1164
|
+
// src/query/IncludeResolver.ts
|
|
1165
|
+
var IncludeResolver_exports = {};
|
|
1166
|
+
__export(IncludeResolver_exports, {
|
|
1167
|
+
IncludeResolver: () => IncludeResolver
|
|
1168
|
+
});
|
|
1169
|
+
var IncludeResolver;
|
|
1170
|
+
var init_IncludeResolver = __esm({
|
|
1171
|
+
"src/query/IncludeResolver.ts"() {
|
|
1172
|
+
"use strict";
|
|
1173
|
+
init_ModelRegistry();
|
|
1174
|
+
init_DocumentQueryTranslator();
|
|
1175
|
+
init_sql();
|
|
1176
|
+
IncludeResolver = class {
|
|
1177
|
+
db;
|
|
1178
|
+
constructor(db) {
|
|
1179
|
+
this.db = db;
|
|
1180
|
+
}
|
|
1181
|
+
/** Main entry — resolve all includes for a set of records. */
|
|
1182
|
+
async resolve(records, includes, depth, parentModelName) {
|
|
1183
|
+
if (records.length === 0) return;
|
|
1184
|
+
if (depth >= 3) return;
|
|
1185
|
+
for (const spec of includes) {
|
|
1186
|
+
const error = this._validateIncludeSpec(spec);
|
|
1187
|
+
if (error) {
|
|
1188
|
+
console.warn(`[include] Skipping invalid spec: ${error}`);
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
const resultKey = spec.as || spec.model;
|
|
1192
|
+
for (const rec of records) {
|
|
1193
|
+
if (!rec._related) rec._related = {};
|
|
1194
|
+
}
|
|
1195
|
+
if (spec.type === "refersTo") {
|
|
1196
|
+
await this._resolveRefersTo(records, spec, resultKey);
|
|
1197
|
+
} else if (spec.type === "refersToMany") {
|
|
1198
|
+
await this._resolveRefersToMany(records, spec, resultKey, parentModelName);
|
|
1199
|
+
} else {
|
|
1200
|
+
await this._resolveHasMany(records, spec, resultKey);
|
|
1201
|
+
}
|
|
1202
|
+
if (spec.include && spec.include.length > 0) {
|
|
1203
|
+
const allRelated = [];
|
|
1204
|
+
for (const rec of records) {
|
|
1205
|
+
const val = rec._related[resultKey];
|
|
1206
|
+
if (Array.isArray(val)) {
|
|
1207
|
+
allRelated.push(...val);
|
|
1208
|
+
} else if (val) {
|
|
1209
|
+
allRelated.push(val);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (allRelated.length > 0) {
|
|
1213
|
+
await this.resolve(allRelated, spec.include, depth + 1, spec.model);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
/** Resolve a refersTo include (parent FK → single related record). */
|
|
1219
|
+
async _resolveRefersTo(records, spec, resultKey) {
|
|
1220
|
+
const sourceField = spec.sourceField;
|
|
1221
|
+
const uniqueValues = [
|
|
1222
|
+
...new Set(
|
|
1223
|
+
records.map((r) => r[sourceField]).filter((v) => v != null && v !== "")
|
|
1224
|
+
)
|
|
1225
|
+
];
|
|
1226
|
+
if (uniqueValues.length === 0) {
|
|
1227
|
+
for (const rec of records) {
|
|
1228
|
+
rec._related[resultKey] = null;
|
|
1229
|
+
}
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const related = await this._chunkedIn(
|
|
1233
|
+
spec.model,
|
|
1234
|
+
"id",
|
|
1235
|
+
uniqueValues,
|
|
1236
|
+
spec.filter,
|
|
1237
|
+
{ projection: spec.projection }
|
|
1238
|
+
);
|
|
1239
|
+
const lookupMap = /* @__PURE__ */ new Map();
|
|
1240
|
+
for (const r of related) {
|
|
1241
|
+
lookupMap.set(r.id, r);
|
|
1242
|
+
}
|
|
1243
|
+
for (const rec of records) {
|
|
1244
|
+
const fk = rec[sourceField];
|
|
1245
|
+
rec._related[resultKey] = fk && lookupMap.get(fk) || null;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/** Resolve a refersToMany include (parent StringSet field → array of related records). */
|
|
1249
|
+
async _resolveRefersToMany(records, spec, resultKey, parentModelName) {
|
|
1250
|
+
const sourceField = spec.sourceField;
|
|
1251
|
+
if (!parentModelName) {
|
|
1252
|
+
console.warn(
|
|
1253
|
+
`[include] refersToMany requires parentModelName, skipping`
|
|
1254
|
+
);
|
|
1255
|
+
for (const rec of records) {
|
|
1256
|
+
rec._related[resultKey] = [];
|
|
1257
|
+
}
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
const junctionTable = `model_${parentModelName.toLowerCase()}_${sourceField.toLowerCase()}`;
|
|
1261
|
+
const fkColumn = `${parentModelName.toLowerCase()}_id`;
|
|
1262
|
+
const parentIds = [
|
|
1263
|
+
...new Set(
|
|
1264
|
+
records.map((r) => r.id).filter((v) => v != null && v !== "")
|
|
1265
|
+
)
|
|
1266
|
+
];
|
|
1267
|
+
if (parentIds.length === 0) {
|
|
1268
|
+
for (const rec of records) {
|
|
1269
|
+
rec._related[resultKey] = [];
|
|
1270
|
+
}
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const junctionMap = /* @__PURE__ */ new Map();
|
|
1274
|
+
const chunkSize = 99;
|
|
1275
|
+
for (let i = 0; i < parentIds.length; i += chunkSize) {
|
|
1276
|
+
const chunk = parentIds.slice(i, i + chunkSize);
|
|
1277
|
+
const inPlaceholders = chunk.map(() => "?").join(", ");
|
|
1278
|
+
const sql = `SELECT ${quoteIdentifier(fkColumn)}, "value" FROM ${quoteIdentifier(junctionTable)} WHERE ${quoteIdentifier(fkColumn)} IN (${inPlaceholders})`;
|
|
1279
|
+
const rows = await this.db.query(sql, chunk);
|
|
1280
|
+
for (const row of rows) {
|
|
1281
|
+
const parentId = row[fkColumn];
|
|
1282
|
+
const value = row.value;
|
|
1283
|
+
if (!junctionMap.has(parentId)) junctionMap.set(parentId, []);
|
|
1284
|
+
junctionMap.get(parentId).push(value);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
const allTargetIds = [...new Set(
|
|
1288
|
+
Array.from(junctionMap.values()).flat()
|
|
1289
|
+
)];
|
|
1290
|
+
if (allTargetIds.length === 0) {
|
|
1291
|
+
for (const rec of records) {
|
|
1292
|
+
rec._related[resultKey] = [];
|
|
1293
|
+
}
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const targets = await this._chunkedIn(
|
|
1297
|
+
spec.model,
|
|
1298
|
+
"id",
|
|
1299
|
+
allTargetIds,
|
|
1300
|
+
spec.filter,
|
|
1301
|
+
{ projection: spec.projection }
|
|
1302
|
+
);
|
|
1303
|
+
const targetLookup = /* @__PURE__ */ new Map();
|
|
1304
|
+
for (const t of targets) {
|
|
1305
|
+
targetLookup.set(t.id, t);
|
|
1306
|
+
}
|
|
1307
|
+
for (const rec of records) {
|
|
1308
|
+
const targetIds = junctionMap.get(rec.id) || [];
|
|
1309
|
+
rec._related[resultKey] = targetIds.map((id) => targetLookup.get(id)).filter(Boolean);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
/** Resolve a hasMany include (target FK → array of related records). */
|
|
1313
|
+
async _resolveHasMany(records, spec, resultKey) {
|
|
1314
|
+
const localField = spec.localField || "id";
|
|
1315
|
+
const uniqueValues = [
|
|
1316
|
+
...new Set(
|
|
1317
|
+
records.map((r) => r[localField]).filter((v) => v != null && v !== "")
|
|
1318
|
+
)
|
|
1319
|
+
];
|
|
1320
|
+
if (uniqueValues.length === 0) {
|
|
1321
|
+
for (const rec of records) {
|
|
1322
|
+
rec._related[resultKey] = [];
|
|
1323
|
+
}
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
let related;
|
|
1327
|
+
if (spec.limit) {
|
|
1328
|
+
related = await this._resolveHasManyWithLimit(spec, uniqueValues);
|
|
1329
|
+
} else {
|
|
1330
|
+
related = await this._resolveHasManySimple(spec, uniqueValues);
|
|
1331
|
+
}
|
|
1332
|
+
const foreignKey = spec.foreignKey;
|
|
1333
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1334
|
+
for (const r of related) {
|
|
1335
|
+
const fk = r[foreignKey];
|
|
1336
|
+
if (!grouped.has(fk)) grouped.set(fk, []);
|
|
1337
|
+
grouped.get(fk).push(r);
|
|
1338
|
+
}
|
|
1339
|
+
if (spec.projection) {
|
|
1340
|
+
for (const [key, list] of grouped) {
|
|
1341
|
+
grouped.set(
|
|
1342
|
+
key,
|
|
1343
|
+
list.map((r) => this._applyProjection(r, spec.projection))
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
for (const rec of records) {
|
|
1348
|
+
rec._related[resultKey] = grouped.get(rec[localField]) || [];
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
/** Simple hasMany without per-parent limit. */
|
|
1352
|
+
async _resolveHasManySimple(spec, parentValues) {
|
|
1353
|
+
return this._chunkedIn(spec.model, spec.foreignKey, parentValues, spec.filter, {
|
|
1354
|
+
sort: spec.sort
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
/** hasMany with per-parent limit using ROW_NUMBER(). */
|
|
1358
|
+
async _resolveHasManyWithLimit(spec, parentValues) {
|
|
1359
|
+
const foreignKey = spec.foreignKey;
|
|
1360
|
+
assertValidIdentifier(foreignKey, "include foreignKey");
|
|
1361
|
+
const modelInfo = ModelRegistry.getInstance().getModelInfo(spec.model);
|
|
1362
|
+
if (!modelInfo) {
|
|
1363
|
+
console.warn(
|
|
1364
|
+
`[include] Target model "${spec.model}" not found in registry, skipping`
|
|
1365
|
+
);
|
|
1366
|
+
return [];
|
|
1367
|
+
}
|
|
1368
|
+
const tableName = `model_${spec.model.toLowerCase()}`;
|
|
1369
|
+
const quotedTable = quoteIdentifier(tableName);
|
|
1370
|
+
let sortClause = `"id" ASC`;
|
|
1371
|
+
if (spec.sort) {
|
|
1372
|
+
const parts = [];
|
|
1373
|
+
for (const [field, dir] of Object.entries(spec.sort)) {
|
|
1374
|
+
assertValidIdentifier(field, "include sort field");
|
|
1375
|
+
parts.push(`${quoteIdentifier(field)} ${dir === -1 ? "DESC" : "ASC"}`);
|
|
1376
|
+
}
|
|
1377
|
+
sortClause = parts.join(", ");
|
|
1378
|
+
}
|
|
1379
|
+
const translator = new DocumentQueryTranslator(
|
|
1380
|
+
spec.model,
|
|
1381
|
+
modelInfo.fields
|
|
1382
|
+
);
|
|
1383
|
+
let filterClause = "";
|
|
1384
|
+
let filterParams = [];
|
|
1385
|
+
if (spec.filter) {
|
|
1386
|
+
const translated = translator.translateFilter(spec.filter);
|
|
1387
|
+
if (translated.sql) {
|
|
1388
|
+
filterClause = ` AND ${translated.sql}`;
|
|
1389
|
+
filterParams = translated.params;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
const reservedParams = filterParams.length + 1;
|
|
1393
|
+
const chunkSize = Math.max(1, 100 - reservedParams);
|
|
1394
|
+
const allResults = [];
|
|
1395
|
+
for (let i = 0; i < parentValues.length; i += chunkSize) {
|
|
1396
|
+
const chunk = parentValues.slice(i, i + chunkSize);
|
|
1397
|
+
const inPlaceholders = chunk.map(() => "?").join(", ");
|
|
1398
|
+
const sql = `
|
|
1399
|
+
SELECT * FROM (
|
|
1400
|
+
SELECT *,
|
|
1401
|
+
ROW_NUMBER() OVER (
|
|
1402
|
+
PARTITION BY ${quoteIdentifier(foreignKey)}
|
|
1403
|
+
ORDER BY ${sortClause}
|
|
1404
|
+
) as _rn
|
|
1405
|
+
FROM ${quotedTable}
|
|
1406
|
+
WHERE ${quoteIdentifier(foreignKey)} IN (${inPlaceholders})
|
|
1407
|
+
${filterClause}
|
|
1408
|
+
) WHERE _rn <= ?
|
|
1409
|
+
`;
|
|
1410
|
+
const params = [...chunk, ...filterParams, spec.limit];
|
|
1411
|
+
const rows = await this.db.query(sql, params);
|
|
1412
|
+
for (const row of rows) {
|
|
1413
|
+
delete row._rn;
|
|
1414
|
+
allResults.push(row);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return allResults;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Execute IN queries in chunks to stay under SQLite's parameter limit.
|
|
1421
|
+
* Uses 100-param chunks for consistency with the DO backend.
|
|
1422
|
+
*/
|
|
1423
|
+
async _chunkedIn(model, field, values, extraFilter, extraOptions) {
|
|
1424
|
+
const modelInfo = ModelRegistry.getInstance().getModelInfo(model);
|
|
1425
|
+
if (!modelInfo) {
|
|
1426
|
+
console.warn(
|
|
1427
|
+
`[include] Target model "${model}" not found in registry, skipping`
|
|
1428
|
+
);
|
|
1429
|
+
return [];
|
|
1430
|
+
}
|
|
1431
|
+
const translator = new DocumentQueryTranslator(model, modelInfo.fields);
|
|
1432
|
+
let filterParamCount = 0;
|
|
1433
|
+
if (extraFilter) {
|
|
1434
|
+
const translated = translator.translateFilter(extraFilter);
|
|
1435
|
+
filterParamCount = translated.params.length;
|
|
1436
|
+
}
|
|
1437
|
+
const chunkSize = Math.max(1, 100 - filterParamCount);
|
|
1438
|
+
const allResults = [];
|
|
1439
|
+
for (let i = 0; i < values.length; i += chunkSize) {
|
|
1440
|
+
const chunk = values.slice(i, i + chunkSize);
|
|
1441
|
+
const filter = {
|
|
1442
|
+
[field]: { $in: chunk },
|
|
1443
|
+
...extraFilter || {}
|
|
1444
|
+
};
|
|
1445
|
+
const options = {};
|
|
1446
|
+
if (extraOptions?.sort) options.sort = extraOptions.sort;
|
|
1447
|
+
if (extraOptions?.projection) options.projection = extraOptions.projection;
|
|
1448
|
+
const { sql, params } = translator.translateFind(filter, options);
|
|
1449
|
+
const rows = await this.db.query(sql, params);
|
|
1450
|
+
allResults.push(...rows);
|
|
1451
|
+
}
|
|
1452
|
+
return allResults;
|
|
1453
|
+
}
|
|
1454
|
+
/** Validate an include spec, returning an error string or null if valid. */
|
|
1455
|
+
_validateIncludeSpec(spec) {
|
|
1456
|
+
if (!spec.model || typeof spec.model !== "string") {
|
|
1457
|
+
return "include: 'model' is required";
|
|
1458
|
+
}
|
|
1459
|
+
try {
|
|
1460
|
+
assertValidIdentifier(spec.model, "include model");
|
|
1461
|
+
} catch {
|
|
1462
|
+
return `include: invalid model name '${spec.model}'`;
|
|
1463
|
+
}
|
|
1464
|
+
if (spec.type !== "refersTo" && spec.type !== "hasMany" && spec.type !== "refersToMany") {
|
|
1465
|
+
return `include: 'type' must be 'refersTo', 'hasMany', or 'refersToMany', got '${spec.type}'`;
|
|
1466
|
+
}
|
|
1467
|
+
if (spec.type === "refersTo") {
|
|
1468
|
+
if (!spec.sourceField) {
|
|
1469
|
+
return "include refersTo: 'sourceField' is required";
|
|
1470
|
+
}
|
|
1471
|
+
try {
|
|
1472
|
+
assertValidIdentifier(spec.sourceField, "include sourceField");
|
|
1473
|
+
} catch {
|
|
1474
|
+
return `include refersTo: invalid sourceField '${spec.sourceField}'`;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (spec.type === "refersToMany") {
|
|
1478
|
+
if (!spec.sourceField) {
|
|
1479
|
+
return "include refersToMany: 'sourceField' is required";
|
|
1480
|
+
}
|
|
1481
|
+
try {
|
|
1482
|
+
assertValidIdentifier(spec.sourceField, "include sourceField");
|
|
1483
|
+
} catch {
|
|
1484
|
+
return `include refersToMany: invalid sourceField '${spec.sourceField}'`;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
if (spec.type === "hasMany") {
|
|
1488
|
+
if (!spec.foreignKey) {
|
|
1489
|
+
return "include hasMany: 'foreignKey' is required";
|
|
1490
|
+
}
|
|
1491
|
+
try {
|
|
1492
|
+
assertValidIdentifier(spec.foreignKey, "include foreignKey");
|
|
1493
|
+
} catch {
|
|
1494
|
+
return `include hasMany: invalid foreignKey '${spec.foreignKey}'`;
|
|
1495
|
+
}
|
|
1496
|
+
if (spec.localField) {
|
|
1497
|
+
try {
|
|
1498
|
+
assertValidIdentifier(spec.localField, "include localField");
|
|
1499
|
+
} catch {
|
|
1500
|
+
return `include hasMany: invalid localField '${spec.localField}'`;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (spec.as) {
|
|
1505
|
+
try {
|
|
1506
|
+
assertValidIdentifier(spec.as, "include as");
|
|
1507
|
+
} catch {
|
|
1508
|
+
return `include: invalid alias '${spec.as}'`;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (spec.sort) {
|
|
1512
|
+
for (const field of Object.keys(spec.sort)) {
|
|
1513
|
+
try {
|
|
1514
|
+
assertValidIdentifier(field, "include sort field");
|
|
1515
|
+
} catch {
|
|
1516
|
+
return `include: invalid sort field '${field}'`;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
/** Apply projection to a single record. */
|
|
1523
|
+
_applyProjection(record, projection) {
|
|
1524
|
+
const fields = Object.entries(projection);
|
|
1525
|
+
if (fields.length === 0) return record;
|
|
1526
|
+
const isInclusion = fields.some(([, v]) => v === 1);
|
|
1527
|
+
if (isInclusion) {
|
|
1528
|
+
const result = {};
|
|
1529
|
+
if (record.id !== void 0) result.id = record.id;
|
|
1530
|
+
if (record.type !== void 0) result.type = record.type;
|
|
1531
|
+
if (record._related !== void 0) result._related = record._related;
|
|
1532
|
+
for (const [f, v] of fields) {
|
|
1533
|
+
if (v === 1 && record[f] !== void 0) {
|
|
1534
|
+
result[f] = record[f];
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return result;
|
|
1538
|
+
} else {
|
|
1539
|
+
const result = { ...record };
|
|
1540
|
+
for (const [f, v] of fields) {
|
|
1541
|
+
if (v === 0) delete result[f];
|
|
1542
|
+
}
|
|
1543
|
+
return result;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1164
1550
|
// src/models/BaseModel.ts
|
|
1165
1551
|
var BaseModel_exports = {};
|
|
1166
1552
|
__export(BaseModel_exports, {
|
|
@@ -1288,7 +1674,7 @@ var init_BaseModel = __esm({
|
|
|
1288
1674
|
Object.setPrototypeOf(this, _RecordNotFoundError.prototype);
|
|
1289
1675
|
}
|
|
1290
1676
|
};
|
|
1291
|
-
SCHEMA_ACCESSORS_KEY = Symbol("jsBaoSchemaAccessors");
|
|
1677
|
+
SCHEMA_ACCESSORS_KEY = /* @__PURE__ */ Symbol("jsBaoSchemaAccessors");
|
|
1292
1678
|
BaseModel = class _BaseModel {
|
|
1293
1679
|
static modelName;
|
|
1294
1680
|
static listenersMap = /* @__PURE__ */ new Map();
|
|
@@ -3132,8 +3518,10 @@ var init_BaseModel = __esm({
|
|
|
3132
3518
|
}
|
|
3133
3519
|
const modelName = schema.options.name;
|
|
3134
3520
|
Logger.verbose(`[${modelName}] Executing query:`, { filter, options });
|
|
3521
|
+
const hasIncludes = options?.include && options.include.length > 0;
|
|
3522
|
+
const translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
|
|
3135
3523
|
const translator = new DocumentQueryTranslator(modelName, schema.fields);
|
|
3136
|
-
const translatedQuery = translator.translateFind(filter,
|
|
3524
|
+
const translatedQuery = translator.translateFind(filter, translatorOptions);
|
|
3137
3525
|
Logger.verbose(`[${modelName}] Translated SQL:`, translatedQuery.sql);
|
|
3138
3526
|
Logger.verbose(`[${modelName}] SQL params:`, translatedQuery.params);
|
|
3139
3527
|
const results = await _BaseModel.dbInstance.query(
|
|
@@ -3141,6 +3529,11 @@ var init_BaseModel = __esm({
|
|
|
3141
3529
|
translatedQuery.params
|
|
3142
3530
|
);
|
|
3143
3531
|
Logger.verbose(`[${modelName}] Query returned ${results.length} results`);
|
|
3532
|
+
if (options?.include && options.include.length > 0) {
|
|
3533
|
+
const { IncludeResolver: IncludeResolver2 } = await Promise.resolve().then(() => (init_IncludeResolver(), IncludeResolver_exports));
|
|
3534
|
+
const resolver = new IncludeResolver2(_BaseModel.dbInstance);
|
|
3535
|
+
await resolver.resolve(results, options.include, 0, modelName);
|
|
3536
|
+
}
|
|
3144
3537
|
const requestedLimit = options?.limit;
|
|
3145
3538
|
const hasMore = CursorManager.hasMoreResults(
|
|
3146
3539
|
requestedLimit,
|
|
@@ -3166,6 +3559,7 @@ var init_BaseModel = __esm({
|
|
|
3166
3559
|
projected[field] = row[field];
|
|
3167
3560
|
}
|
|
3168
3561
|
}
|
|
3562
|
+
if (row._related) projected._related = row._related;
|
|
3169
3563
|
return projected;
|
|
3170
3564
|
});
|
|
3171
3565
|
} else {
|
|
@@ -3197,6 +3591,7 @@ var init_BaseModel = __esm({
|
|
|
3197
3591
|
if (instance && typeof instance === "object" && instance instanceof _BaseModel) {
|
|
3198
3592
|
instance._metaDocId = foundDocId;
|
|
3199
3593
|
instance._metaPermissionHint = "read-write";
|
|
3594
|
+
if (data._related) instance._related = data._related;
|
|
3200
3595
|
}
|
|
3201
3596
|
return instance;
|
|
3202
3597
|
}).filter(Boolean);
|
|
@@ -4201,536 +4596,877 @@ var init_BaseModel = __esm({
|
|
|
4201
4596
|
}
|
|
4202
4597
|
});
|
|
4203
4598
|
|
|
4204
|
-
// src/
|
|
4205
|
-
function
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
}
|
|
4212
|
-
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
4213
|
-
return "browser";
|
|
4214
|
-
}
|
|
4215
|
-
return "unknown";
|
|
4216
|
-
}
|
|
4217
|
-
function isNode() {
|
|
4218
|
-
return detectEnvironment() === "node";
|
|
4219
|
-
}
|
|
4220
|
-
function isBrowser() {
|
|
4221
|
-
return detectEnvironment() === "browser";
|
|
4222
|
-
}
|
|
4223
|
-
async function isNodeModuleAvailable(moduleName) {
|
|
4224
|
-
if (!isNode()) {
|
|
4225
|
-
return false;
|
|
4226
|
-
}
|
|
4227
|
-
try {
|
|
4228
|
-
await import(
|
|
4229
|
-
/* @vite-ignore */
|
|
4230
|
-
moduleName
|
|
4231
|
-
);
|
|
4232
|
-
return true;
|
|
4233
|
-
} catch {
|
|
4234
|
-
return false;
|
|
4235
|
-
}
|
|
4599
|
+
// src/models/relationshipManager.ts
|
|
4600
|
+
function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
4601
|
+
return async function() {
|
|
4602
|
+
const relatedId = this[config.relatedIdField];
|
|
4603
|
+
if (relatedId === null || typeof relatedId === "undefined") return null;
|
|
4604
|
+
return targetModelClass.find(relatedId);
|
|
4605
|
+
};
|
|
4236
4606
|
}
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4607
|
+
function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbEngine) {
|
|
4608
|
+
return async function(paginationArgs) {
|
|
4609
|
+
const {
|
|
4610
|
+
limit,
|
|
4611
|
+
afterCursor,
|
|
4612
|
+
beforeCursor,
|
|
4613
|
+
direction = "forward"
|
|
4614
|
+
} = paginationArgs || {};
|
|
4615
|
+
const {
|
|
4616
|
+
relatedIdField,
|
|
4617
|
+
orderByField = "id",
|
|
4618
|
+
orderDirection = "ASC"
|
|
4619
|
+
} = config;
|
|
4620
|
+
const targetSchema = targetModelClass.getSchema();
|
|
4621
|
+
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
4622
|
+
Logger.error(
|
|
4623
|
+
`[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
|
|
4624
|
+
);
|
|
4625
|
+
return { data: [], nextCursor: null, prevCursor: null };
|
|
4626
|
+
}
|
|
4627
|
+
const filter = { [relatedIdField]: this.id };
|
|
4628
|
+
const queryOptions = {
|
|
4629
|
+
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
|
|
4254
4630
|
};
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
SQL_WASM_URL = "/sql-wasm.wasm";
|
|
4268
|
-
TransactionalSQLJSOperations = class {
|
|
4269
|
-
db;
|
|
4270
|
-
engine;
|
|
4271
|
-
// For helper methods, changed type to SqljsEngine
|
|
4272
|
-
constructor(db, engine) {
|
|
4273
|
-
this.db = db;
|
|
4274
|
-
this.engine = engine;
|
|
4275
|
-
}
|
|
4276
|
-
async insert(modelName, data) {
|
|
4277
|
-
if (!this.db) throw new Error("SQL.js DB not available in transaction");
|
|
4278
|
-
const tableName = this.engine.getTableName(modelName);
|
|
4279
|
-
const quotedTableName = quoteIdentifier(tableName);
|
|
4280
|
-
const columns = Object.keys(data);
|
|
4281
|
-
const quotedColumns = columns.map((column) => quoteIdentifier(column));
|
|
4282
|
-
const placeholders = columns.map(() => "?").join(", ");
|
|
4283
|
-
const values = Object.values(data);
|
|
4284
|
-
const insertSQL = `INSERT OR REPLACE INTO ${quotedTableName} (${quotedColumns.join(
|
|
4285
|
-
", "
|
|
4286
|
-
)}) VALUES (${placeholders});`;
|
|
4287
|
-
this.db.run(insertSQL, values);
|
|
4288
|
-
}
|
|
4289
|
-
async delete(modelName, id) {
|
|
4290
|
-
if (!this.db) throw new Error("SQL.js DB not available in transaction");
|
|
4291
|
-
const tableName = this.engine.getTableName(modelName);
|
|
4292
|
-
const deleteSQL = `DELETE FROM ${quoteIdentifier(tableName)} WHERE ${quoteIdentifier(
|
|
4293
|
-
"id"
|
|
4294
|
-
)} = ?;`;
|
|
4295
|
-
this.db.run(deleteSQL, [id]);
|
|
4631
|
+
if (limit !== void 0) {
|
|
4632
|
+
queryOptions.limit = limit + 1;
|
|
4633
|
+
}
|
|
4634
|
+
if (direction === "forward" && afterCursor) {
|
|
4635
|
+
filter[orderByField] = {
|
|
4636
|
+
[orderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
|
|
4637
|
+
};
|
|
4638
|
+
} else if (direction === "backward") {
|
|
4639
|
+
if (beforeCursor) {
|
|
4640
|
+
filter[orderByField] = {
|
|
4641
|
+
[orderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
4642
|
+
};
|
|
4296
4643
|
}
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4644
|
+
queryOptions.sort = { [orderByField]: orderDirection === "ASC" ? -1 : 1 };
|
|
4645
|
+
}
|
|
4646
|
+
const result = await targetModelClass.query(filter, queryOptions);
|
|
4647
|
+
const results = result.data;
|
|
4648
|
+
let nextCursor = null;
|
|
4649
|
+
let prevCursor = null;
|
|
4650
|
+
const isFirstPage = direction === "forward" && !afterCursor;
|
|
4651
|
+
if (limit !== void 0) {
|
|
4652
|
+
if (direction === "forward") {
|
|
4653
|
+
if (results.length > limit) {
|
|
4654
|
+
nextCursor = results[limit - 1][orderByField];
|
|
4655
|
+
results.pop();
|
|
4656
|
+
}
|
|
4657
|
+
if (results.length > 0 && !isFirstPage) {
|
|
4658
|
+
prevCursor = results[0][orderByField];
|
|
4659
|
+
}
|
|
4660
|
+
} else {
|
|
4661
|
+
if (results.length > limit) {
|
|
4662
|
+
prevCursor = results[limit - 1][orderByField];
|
|
4663
|
+
results.pop();
|
|
4664
|
+
}
|
|
4665
|
+
results.reverse();
|
|
4666
|
+
if (results.length > 0 && beforeCursor) {
|
|
4667
|
+
nextCursor = results[results.length - 1][orderByField];
|
|
4668
|
+
}
|
|
4669
|
+
if (results.length > 0 && !beforeCursor) {
|
|
4670
|
+
if (results.length === limit) {
|
|
4671
|
+
prevCursor = results[0][orderByField];
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4309
4674
|
}
|
|
4675
|
+
}
|
|
4676
|
+
return { data: results, nextCursor, prevCursor };
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClass, joinModelClass, _dbEngine) {
|
|
4680
|
+
return async function(paginationArgs) {
|
|
4681
|
+
const {
|
|
4682
|
+
limit,
|
|
4683
|
+
afterCursor,
|
|
4684
|
+
beforeCursor,
|
|
4685
|
+
direction = "forward"
|
|
4686
|
+
} = paginationArgs || {};
|
|
4687
|
+
const joinSchema = joinModelClass.getSchema();
|
|
4688
|
+
const targetSchema = targetModelClass.getSchema();
|
|
4689
|
+
if (!joinSchema || !joinSchema.options || !joinSchema.options.name) {
|
|
4690
|
+
Logger.error(
|
|
4691
|
+
`[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
4692
|
+
);
|
|
4693
|
+
return { data: [], nextCursor: null, prevCursor: null };
|
|
4694
|
+
}
|
|
4695
|
+
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
4696
|
+
Logger.error(
|
|
4697
|
+
`[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
4698
|
+
);
|
|
4699
|
+
return { data: [], nextCursor: null, prevCursor: null };
|
|
4700
|
+
}
|
|
4701
|
+
const {
|
|
4702
|
+
joinModelLocalField,
|
|
4703
|
+
joinModelRelatedField,
|
|
4704
|
+
joinModelOrderByField = "id",
|
|
4705
|
+
joinModelOrderDirection = "ASC"
|
|
4706
|
+
} = config;
|
|
4707
|
+
const joinFilter = { [joinModelLocalField]: this.id };
|
|
4708
|
+
const joinQueryOptions = {
|
|
4709
|
+
sort: {
|
|
4710
|
+
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
|
|
4711
|
+
},
|
|
4712
|
+
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
|
|
4310
4713
|
};
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
// Make readyPromise accessible if DatabaseFactory wants to await it,
|
|
4324
|
-
// though direct call to initialize from constructor and awaiting it is simpler here.
|
|
4325
|
-
async ensureReady() {
|
|
4326
|
-
return this.readyPromise;
|
|
4714
|
+
if (limit !== void 0) {
|
|
4715
|
+
joinQueryOptions.limit = limit + 1;
|
|
4716
|
+
}
|
|
4717
|
+
if (direction === "forward" && afterCursor) {
|
|
4718
|
+
joinFilter[joinModelOrderByField] = {
|
|
4719
|
+
[joinModelOrderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
|
|
4720
|
+
};
|
|
4721
|
+
} else if (direction === "backward") {
|
|
4722
|
+
if (beforeCursor) {
|
|
4723
|
+
joinFilter[joinModelOrderByField] = {
|
|
4724
|
+
[joinModelOrderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
4725
|
+
};
|
|
4327
4726
|
}
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4727
|
+
joinQueryOptions.sort = {
|
|
4728
|
+
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? -1 : 1
|
|
4729
|
+
};
|
|
4730
|
+
}
|
|
4731
|
+
const joinResult = await joinModelClass.query(
|
|
4732
|
+
joinFilter,
|
|
4733
|
+
joinQueryOptions
|
|
4734
|
+
);
|
|
4735
|
+
const joinResults = joinResult.data;
|
|
4736
|
+
let nextCursorFromJoin = null;
|
|
4737
|
+
let prevCursorFromJoin = null;
|
|
4738
|
+
const isFirstPage = direction === "forward" && !afterCursor;
|
|
4739
|
+
if (limit !== void 0) {
|
|
4740
|
+
if (direction === "forward") {
|
|
4741
|
+
if (joinResults.length > limit) {
|
|
4742
|
+
nextCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
4743
|
+
joinResults.pop();
|
|
4744
|
+
}
|
|
4745
|
+
if (joinResults.length > 0 && !isFirstPage) {
|
|
4746
|
+
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
4747
|
+
}
|
|
4748
|
+
} else {
|
|
4749
|
+
if (joinResults.length > limit) {
|
|
4750
|
+
prevCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
4751
|
+
joinResults.pop();
|
|
4752
|
+
}
|
|
4753
|
+
joinResults.reverse();
|
|
4754
|
+
if (joinResults.length > 0 && beforeCursor) {
|
|
4755
|
+
nextCursorFromJoin = joinResults[joinResults.length - 1][joinModelOrderByField];
|
|
4756
|
+
}
|
|
4757
|
+
if (joinResults.length > 0 && !beforeCursor) {
|
|
4758
|
+
if (joinResults.length === limit) {
|
|
4759
|
+
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
4341
4760
|
}
|
|
4342
|
-
this.db = new _SqljsEngine.SQL.Database();
|
|
4343
|
-
Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
|
|
4344
|
-
this.updateTableNames();
|
|
4345
|
-
} catch (error) {
|
|
4346
|
-
Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
|
|
4347
|
-
throw error;
|
|
4348
4761
|
}
|
|
4349
4762
|
}
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4763
|
+
}
|
|
4764
|
+
const relatedIds = joinResults.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
|
|
4765
|
+
if (relatedIds.length === 0)
|
|
4766
|
+
return { data: [], nextCursor: null, prevCursor: null };
|
|
4767
|
+
const targetResult = await targetModelClass.query({
|
|
4768
|
+
id: { $in: relatedIds }
|
|
4769
|
+
});
|
|
4770
|
+
return {
|
|
4771
|
+
data: targetResult.data,
|
|
4772
|
+
nextCursor: nextCursorFromJoin,
|
|
4773
|
+
prevCursor: prevCursorFromJoin
|
|
4774
|
+
};
|
|
4775
|
+
};
|
|
4776
|
+
}
|
|
4777
|
+
function generateHasManyThroughAddMethod(_sourceModelClass, config, _targetModelClass, joinModelClass) {
|
|
4778
|
+
return async function(targetInstanceOrId) {
|
|
4779
|
+
const targetId = typeof targetInstanceOrId === "string" ? targetInstanceOrId : targetInstanceOrId.id;
|
|
4780
|
+
if (!targetId) {
|
|
4781
|
+
Logger.error("[RelationshipManager.add] Target ID is missing.");
|
|
4782
|
+
throw new Error("Target ID is missing for add operation.");
|
|
4783
|
+
}
|
|
4784
|
+
const joinData = {};
|
|
4785
|
+
joinData[config.joinModelLocalField] = this.id;
|
|
4786
|
+
joinData[config.joinModelRelatedField] = targetId;
|
|
4787
|
+
let yDoc = null;
|
|
4788
|
+
let targetDocId = null;
|
|
4789
|
+
const sourceModelConstructor = this.constructor;
|
|
4790
|
+
const connectedDocuments = sourceModelConstructor.connectedDocuments;
|
|
4791
|
+
if (this._metaDocId && connectedDocuments) {
|
|
4792
|
+
targetDocId = this._metaDocId;
|
|
4793
|
+
if (targetDocId) {
|
|
4794
|
+
const documentInfo = connectedDocuments.get(targetDocId);
|
|
4795
|
+
if (documentInfo) {
|
|
4796
|
+
yDoc = documentInfo.yDoc;
|
|
4360
4797
|
}
|
|
4361
4798
|
}
|
|
4362
|
-
|
|
4363
|
-
|
|
4799
|
+
}
|
|
4800
|
+
if (!yDoc) {
|
|
4801
|
+
const legacyDocId = "__legacy_default__";
|
|
4802
|
+
const legacyDocInfo = joinModelClass.connectedDocuments?.get(
|
|
4803
|
+
legacyDocId
|
|
4804
|
+
);
|
|
4805
|
+
if (legacyDocInfo) {
|
|
4806
|
+
yDoc = legacyDocInfo.yDoc;
|
|
4364
4807
|
}
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4808
|
+
}
|
|
4809
|
+
if (!yDoc) {
|
|
4810
|
+
throw new Error(
|
|
4811
|
+
`[${joinModelClass.name}] No Y.Doc is connected for this join model. Connect a document via initJsBao(...).connectDocument or call initializeForDocument(yDoc, db, docId, permissionHint) before managing relationships.`
|
|
4812
|
+
);
|
|
4813
|
+
}
|
|
4814
|
+
await yDoc.transact(async () => {
|
|
4815
|
+
const newJoinInstance = new joinModelClass(joinData);
|
|
4816
|
+
if (targetDocId) {
|
|
4817
|
+
await newJoinInstance.save({ targetDocument: targetDocId });
|
|
4818
|
+
} else {
|
|
4819
|
+
await newJoinInstance.save();
|
|
4820
|
+
}
|
|
4821
|
+
Logger.debug(
|
|
4822
|
+
`[RelationshipManager.add] Created join entry in ${config.joinModel}:`,
|
|
4823
|
+
newJoinInstance.toJSON()
|
|
4824
|
+
);
|
|
4825
|
+
}, `add-join-${config.joinModel}-${this.id}-${targetId}`);
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetModelClass, joinModelClass) {
|
|
4829
|
+
return async function(targetInstanceOrId) {
|
|
4830
|
+
const targetId = typeof targetInstanceOrId === "string" ? targetInstanceOrId : targetInstanceOrId.id;
|
|
4831
|
+
if (!targetId) {
|
|
4832
|
+
Logger.error("[RelationshipManager.remove] Target ID is missing.");
|
|
4833
|
+
throw new Error("Target ID is missing for remove operation.");
|
|
4834
|
+
}
|
|
4835
|
+
let yDoc = null;
|
|
4836
|
+
let targetDocId = null;
|
|
4837
|
+
const sourceModelConstructor = this.constructor;
|
|
4838
|
+
const connectedDocuments = sourceModelConstructor.connectedDocuments;
|
|
4839
|
+
if (this._metaDocId && connectedDocuments) {
|
|
4840
|
+
targetDocId = this._metaDocId;
|
|
4841
|
+
if (targetDocId) {
|
|
4842
|
+
const documentInfo = connectedDocuments.get(targetDocId);
|
|
4843
|
+
if (documentInfo) {
|
|
4844
|
+
yDoc = documentInfo.yDoc;
|
|
4377
4845
|
}
|
|
4378
4846
|
}
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4847
|
+
}
|
|
4848
|
+
if (!yDoc) {
|
|
4849
|
+
const legacyDocId = "__legacy_default__";
|
|
4850
|
+
const legacyDocInfo = joinModelClass.connectedDocuments?.get(
|
|
4851
|
+
legacyDocId
|
|
4852
|
+
);
|
|
4853
|
+
if (legacyDocInfo) {
|
|
4854
|
+
yDoc = legacyDocInfo.yDoc;
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
if (!yDoc) {
|
|
4858
|
+
throw new Error(
|
|
4859
|
+
`[${joinModelClass.name}] No Y.Doc is connected for this join model. Connect a document via initJsBao(...).connectDocument or call initializeForDocument(yDoc, db, docId, permissionHint) before managing relationships.`
|
|
4860
|
+
);
|
|
4861
|
+
}
|
|
4862
|
+
await yDoc.transact(async () => {
|
|
4863
|
+
const joinRecordsResult = await joinModelClass.query(
|
|
4864
|
+
{
|
|
4865
|
+
[config.joinModelLocalField]: this.id,
|
|
4866
|
+
[config.joinModelRelatedField]: targetId
|
|
4867
|
+
},
|
|
4868
|
+
{
|
|
4869
|
+
limit: 1,
|
|
4870
|
+
projection: { id: 1 }
|
|
4388
4871
|
}
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
)
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
const indexName = `idx_${tableName}_${field}`;
|
|
4405
|
-
const createIndexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
4406
|
-
indexName
|
|
4407
|
-
)} ON ${quotedTableName} (${quoteIdentifier(field)});`;
|
|
4408
|
-
this.db.run(createIndexSQL);
|
|
4872
|
+
);
|
|
4873
|
+
if (joinRecordsResult.data.length > 0) {
|
|
4874
|
+
for (const record of joinRecordsResult.data) {
|
|
4875
|
+
const instanceToDelete = await joinModelClass.find(
|
|
4876
|
+
record.id
|
|
4877
|
+
);
|
|
4878
|
+
if (instanceToDelete) {
|
|
4879
|
+
await instanceToDelete.delete();
|
|
4880
|
+
Logger.debug(
|
|
4881
|
+
`[RelationshipManager.remove] Deleted join entry from ${config.joinModel} with ID: ${record.id}`
|
|
4882
|
+
);
|
|
4883
|
+
} else {
|
|
4884
|
+
Logger.warn(
|
|
4885
|
+
`[RelationshipManager.remove] Join entry with ID ${record.id} found by query but not in YMap for deletion.`
|
|
4886
|
+
);
|
|
4409
4887
|
}
|
|
4410
4888
|
}
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
)
|
|
4415
|
-
this.db.run(docIdIndexSQL);
|
|
4416
|
-
this.updateTableNames();
|
|
4889
|
+
} else {
|
|
4890
|
+
Logger.debug(
|
|
4891
|
+
`[RelationshipManager.remove] No join entry found in ${config.joinModel} for localId: ${this.id}, relatedId: ${targetId}. No action taken.`
|
|
4892
|
+
);
|
|
4417
4893
|
}
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4894
|
+
}, `remove-join-${config.joinModel}-${this.id}-${targetId}`);
|
|
4895
|
+
};
|
|
4896
|
+
}
|
|
4897
|
+
var init_relationshipManager = __esm({
|
|
4898
|
+
"src/models/relationshipManager.ts"() {
|
|
4899
|
+
"use strict";
|
|
4900
|
+
init_BaseModel();
|
|
4901
|
+
}
|
|
4902
|
+
});
|
|
4903
|
+
|
|
4904
|
+
// src/models/ModelRegistry.ts
|
|
4905
|
+
var ModelRegistry;
|
|
4906
|
+
var init_ModelRegistry = __esm({
|
|
4907
|
+
"src/models/ModelRegistry.ts"() {
|
|
4908
|
+
"use strict";
|
|
4909
|
+
init_BaseModel();
|
|
4910
|
+
init_relationshipManager();
|
|
4911
|
+
ModelRegistry = class _ModelRegistry {
|
|
4912
|
+
static instance;
|
|
4913
|
+
// Stores globally registered model classes by their name (from @Model decorator)
|
|
4914
|
+
models = /* @__PURE__ */ new Map();
|
|
4915
|
+
// Stores options for globally registered models (from @Model decorator)
|
|
4916
|
+
modelOptions = /* @__PURE__ */ new Map();
|
|
4917
|
+
// Stores fields for globally registered models (from @Model decorator, or a static getter on class)
|
|
4918
|
+
// For simplicity, let's assume fields can be derived or are less critical for this registry part
|
|
4919
|
+
// private modelFields: Map<string, Map<string, FieldOptions>> = new Map();
|
|
4920
|
+
// Holds the subset of models explicitly set for the current initialization session
|
|
4921
|
+
activeSessionModels = null;
|
|
4922
|
+
dbEngine = null;
|
|
4923
|
+
// Store dbEngine for relationship methods
|
|
4924
|
+
constructor() {
|
|
4925
|
+
}
|
|
4926
|
+
static getInstance() {
|
|
4927
|
+
if (!_ModelRegistry.instance) {
|
|
4928
|
+
_ModelRegistry.instance = new _ModelRegistry();
|
|
4929
|
+
}
|
|
4930
|
+
return _ModelRegistry.instance;
|
|
4931
|
+
}
|
|
4932
|
+
registerModel(modelClass, options, fields) {
|
|
4933
|
+
if (!options.name) {
|
|
4421
4934
|
throw new Error(
|
|
4422
|
-
|
|
4935
|
+
`[ModelRegistry] Model class is missing a name in its @Model options. Ensure the @Model decorator includes a 'name' property.`
|
|
4423
4936
|
);
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
const quotedIdColumn = quoteIdentifier("id");
|
|
4429
|
-
const quotedValueColumn = quoteIdentifier("value");
|
|
4430
|
-
if (this.tableNames.has(junctionTableName)) {
|
|
4431
|
-
Logger.debug(
|
|
4432
|
-
`[SqljsEngine] StringSet junction table ${junctionTableName} already exists, skipping creation.`
|
|
4937
|
+
}
|
|
4938
|
+
if (this.models.has(options.name)) {
|
|
4939
|
+
console.warn(
|
|
4940
|
+
`[ModelRegistry] Model "${options.name}" is already registered. Overwriting.`
|
|
4433
4941
|
);
|
|
4434
|
-
return;
|
|
4435
4942
|
}
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
`[SqljsEngine] Creating StringSet junction table ${junctionTableName} for ${modelName}.${fieldName}`
|
|
4444
|
-
);
|
|
4445
|
-
this.db.run(createTableSQL);
|
|
4446
|
-
const indexName = `idx_${junctionTableName}_${modelName.toLowerCase()}_id_value`;
|
|
4447
|
-
const indexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
4448
|
-
indexName
|
|
4449
|
-
)} ON ${quotedJunctionTable} (${quotedForeignKey}, ${quotedValueColumn});`;
|
|
4450
|
-
this.db.run(indexSQL);
|
|
4451
|
-
this.updateTableNames();
|
|
4452
|
-
Logger.info(
|
|
4453
|
-
`[SqljsEngine] StringSet junction table ${junctionTableName} created successfully.`
|
|
4943
|
+
this.models.set(options.name, modelClass);
|
|
4944
|
+
if (fields) {
|
|
4945
|
+
BaseModel.attachFieldAccessors(modelClass, fields);
|
|
4946
|
+
}
|
|
4947
|
+
this.modelOptions.set(options.name, options);
|
|
4948
|
+
console.log(
|
|
4949
|
+
`[ModelRegistry] Model "${options.name}" registered successfully.`
|
|
4454
4950
|
);
|
|
4455
4951
|
}
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
throw new Error("SQL.js not initialized for insertStringSetValues");
|
|
4460
|
-
if (values.length === 0) return;
|
|
4461
|
-
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
4462
|
-
const modelIdColumn = `${modelName.toLowerCase()}_id`;
|
|
4463
|
-
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
4464
|
-
const quotedIdColumn = quoteIdentifier("id");
|
|
4465
|
-
const quotedModelIdColumn = quoteIdentifier(modelIdColumn);
|
|
4466
|
-
const quotedValueColumn = quoteIdentifier("value");
|
|
4467
|
-
for (const value of values) {
|
|
4468
|
-
const insertSQL = `INSERT OR IGNORE INTO ${quotedJunctionTable} (${quotedIdColumn}, ${quotedModelIdColumn}, ${quotedValueColumn}) VALUES (?, ?, ?);`;
|
|
4469
|
-
const junctionId = `${recordId}_${value}`;
|
|
4470
|
-
this.db.run(insertSQL, [junctionId, recordId, value]);
|
|
4471
|
-
}
|
|
4952
|
+
// Gets the class for a globally registered model
|
|
4953
|
+
getModelClass(name) {
|
|
4954
|
+
return this.models.get(name);
|
|
4472
4955
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
const
|
|
4479
|
-
const
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
for (const value of values) {
|
|
4484
|
-
const deleteSQL = `DELETE FROM ${quotedJunctionTable} WHERE ${quotedModelIdColumn} = ? AND ${quotedValueColumn} = ?;`;
|
|
4485
|
-
this.db.run(deleteSQL, [recordId, value]);
|
|
4956
|
+
getModelOptions(name) {
|
|
4957
|
+
return this.modelOptions.get(name);
|
|
4958
|
+
}
|
|
4959
|
+
// This method might need to be adapted based on how fields are ultimately managed
|
|
4960
|
+
getModelInfo(modelName) {
|
|
4961
|
+
const modelClass = this.models.get(modelName);
|
|
4962
|
+
const options = this.modelOptions.get(modelName);
|
|
4963
|
+
if (modelClass && options) {
|
|
4964
|
+
const fields = modelClass.getSchema ? modelClass.getSchema().fields : /* @__PURE__ */ new Map();
|
|
4965
|
+
return { class: modelClass, options, fields };
|
|
4486
4966
|
}
|
|
4967
|
+
return void 0;
|
|
4487
4968
|
}
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
const insertSQL = `INSERT OR REPLACE INTO ${quotedTableName} (${quotedColumns.join(
|
|
4498
|
-
", "
|
|
4499
|
-
)}) VALUES (${placeholders});`;
|
|
4500
|
-
this.db.run(insertSQL, values);
|
|
4969
|
+
getAllRegisteredModelsInfo() {
|
|
4970
|
+
const infos = [];
|
|
4971
|
+
for (const modelName of this.models.keys()) {
|
|
4972
|
+
const info = this.getModelInfo(modelName);
|
|
4973
|
+
if (info) {
|
|
4974
|
+
infos.push(info);
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
return infos;
|
|
4501
4978
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
const tableName = this.getTableName(modelName);
|
|
4506
|
-
const deleteSQL = `DELETE FROM ${quoteIdentifier(tableName)} WHERE ${quoteIdentifier(
|
|
4507
|
-
"id"
|
|
4508
|
-
)} = ?;`;
|
|
4509
|
-
this.db.run(deleteSQL, [id]);
|
|
4979
|
+
// Helper to get model name from class (assuming static property from @Model decorator)
|
|
4980
|
+
getModelNameFromClass(modelClass) {
|
|
4981
|
+
return modelClass.modelName;
|
|
4510
4982
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
`[SqljsEngine] Skipping deleteByDocumentId for ${tableName}; table does not exist.`
|
|
4520
|
-
);
|
|
4521
|
-
return;
|
|
4522
|
-
}
|
|
4523
|
-
const deleteSQL = `DELETE FROM ${quotedTableName} WHERE ${quoteIdentifier(
|
|
4524
|
-
"_meta_doc_id"
|
|
4525
|
-
)} = ?;`;
|
|
4526
|
-
this.db.run(deleteSQL, [docId]);
|
|
4527
|
-
for (const existingTableName of this.tableNames) {
|
|
4528
|
-
if (existingTableName.startsWith(`${tableName}_`)) {
|
|
4529
|
-
const quotedExistingTable = quoteIdentifier(existingTableName);
|
|
4530
|
-
const quotedForeignKey = quoteIdentifier(`${modelName.toLowerCase()}_id`);
|
|
4531
|
-
const junctionDeleteSQL = `DELETE FROM ${quotedExistingTable} WHERE ${quotedForeignKey} IN (
|
|
4532
|
-
SELECT ${quoteIdentifier("id")} FROM ${quotedTableName} WHERE ${quoteIdentifier(
|
|
4533
|
-
"_meta_doc_id"
|
|
4534
|
-
)} = ?
|
|
4535
|
-
);`;
|
|
4536
|
-
try {
|
|
4537
|
-
this.db.run(junctionDeleteSQL, [docId]);
|
|
4538
|
-
const rowsCleared = this.db.getRowsModified();
|
|
4539
|
-
Logger.info(
|
|
4540
|
-
`[SqljsEngine] Cleared junction table ${existingTableName} for document ${docId}; removed ${rowsCleared} rows.`
|
|
4983
|
+
setExplicitModelsForSession(modelClasses) {
|
|
4984
|
+
if (modelClasses && modelClasses.length > 0) {
|
|
4985
|
+
this.activeSessionModels = /* @__PURE__ */ new Map();
|
|
4986
|
+
modelClasses.forEach((modelClass) => {
|
|
4987
|
+
const modelName = this.getModelNameFromClass(modelClass);
|
|
4988
|
+
if (!modelName) {
|
|
4989
|
+
console.warn(
|
|
4990
|
+
`[ModelRegistry] A model class provided to setExplicitModelsForSession does not have a static 'modelName' property or it's undefined. It will be ignored. Ensure @Model decorator sets this.`
|
|
4541
4991
|
);
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4992
|
+
return;
|
|
4993
|
+
}
|
|
4994
|
+
if (!this.models.has(modelName) || this.models.get(modelName) !== modelClass) {
|
|
4995
|
+
console.warn(
|
|
4996
|
+
`[ModelRegistry] Model class with name ${modelName} provided to setExplicitModelsForSession was not found in the global decorator-based registry or does not match. It will be ignored. Ensure the model file is imported and the @Model decorator has run correctly.`
|
|
4546
4997
|
);
|
|
4998
|
+
return;
|
|
4999
|
+
}
|
|
5000
|
+
if (this.activeSessionModels) {
|
|
5001
|
+
this.activeSessionModels.set(modelName, modelClass);
|
|
5002
|
+
}
|
|
5003
|
+
});
|
|
5004
|
+
let sessionModelNamesMessage = "none (no models were successfully added or session map is null)";
|
|
5005
|
+
if (this.activeSessionModels) {
|
|
5006
|
+
const currentSessionModelKeys = Array.from(
|
|
5007
|
+
this.activeSessionModels.keys()
|
|
5008
|
+
);
|
|
5009
|
+
if (currentSessionModelKeys.length > 0) {
|
|
5010
|
+
sessionModelNamesMessage = currentSessionModelKeys.join(", ");
|
|
5011
|
+
} else {
|
|
5012
|
+
sessionModelNamesMessage = "none (session map initialized but no models added)";
|
|
4547
5013
|
}
|
|
4548
5014
|
}
|
|
5015
|
+
Logger.debug(
|
|
5016
|
+
`[ModelRegistry] Explicit models set for session: ${sessionModelNamesMessage}`
|
|
5017
|
+
);
|
|
5018
|
+
} else if (modelClasses && modelClasses.length === 0) {
|
|
5019
|
+
this.activeSessionModels = /* @__PURE__ */ new Map();
|
|
5020
|
+
Logger.debug(
|
|
5021
|
+
"[ModelRegistry] Explicitly no models for session (empty array passed)."
|
|
5022
|
+
);
|
|
5023
|
+
} else {
|
|
5024
|
+
this.activeSessionModels = null;
|
|
5025
|
+
console.log(
|
|
5026
|
+
"[ModelRegistry] No explicit models specified for session (undefined), will use all decorator-registered models."
|
|
5027
|
+
);
|
|
4549
5028
|
}
|
|
5029
|
+
this.validateSessionModels();
|
|
4550
5030
|
}
|
|
4551
|
-
async
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
const
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
5031
|
+
async initializeAll(yDoc, dbEngine) {
|
|
5032
|
+
Logger.debug("[ModelRegistry] Attempting to initialize models...");
|
|
5033
|
+
this.dbEngine = dbEngine;
|
|
5034
|
+
const modelsToInitialize = this.activeSessionModels || this.models;
|
|
5035
|
+
if (modelsToInitialize.size === 0) {
|
|
5036
|
+
console.warn(
|
|
5037
|
+
"[ModelRegistry] No models to initialize (either no explicit models set for session or no models globally registered via @Model decorator)."
|
|
5038
|
+
);
|
|
5039
|
+
return;
|
|
4558
5040
|
}
|
|
4559
|
-
|
|
4560
|
-
|
|
5041
|
+
console.log(
|
|
5042
|
+
`[ModelRegistry] Initializing models: ${Array.from(
|
|
5043
|
+
modelsToInitialize.keys()
|
|
5044
|
+
).join(", ")}`
|
|
5045
|
+
);
|
|
5046
|
+
for (const [modelName, modelClass] of modelsToInitialize.entries()) {
|
|
5047
|
+
if (modelClass && typeof modelClass.initialize === "function") {
|
|
5048
|
+
Logger.debug(`[ModelRegistry] Initializing model: ${modelName}`);
|
|
5049
|
+
await modelClass.initialize(yDoc, dbEngine);
|
|
5050
|
+
} else {
|
|
5051
|
+
const staticModelName = modelClass?.modelName || "unknown";
|
|
5052
|
+
console.warn(
|
|
5053
|
+
`[ModelRegistry] Model ${staticModelName} (class name: ${modelClass?.name}) does not have a static initialize method or class is not defined.`
|
|
5054
|
+
);
|
|
5055
|
+
}
|
|
4561
5056
|
}
|
|
4562
|
-
|
|
4563
|
-
return results;
|
|
5057
|
+
Logger.debug("[ModelRegistry] Model initialization phase complete.");
|
|
4564
5058
|
}
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
5059
|
+
// New method to initialize models for a specific document
|
|
5060
|
+
async initializeAllForDocument(yDoc, dbEngine, docId, permissionHint) {
|
|
5061
|
+
Logger.debug(
|
|
5062
|
+
`[ModelRegistry] Initializing models for document ${docId}...`
|
|
5063
|
+
);
|
|
5064
|
+
this.dbEngine = dbEngine;
|
|
5065
|
+
const modelsToInitialize = this.activeSessionModels || this.models;
|
|
5066
|
+
if (modelsToInitialize.size === 0) {
|
|
5067
|
+
Logger.warn(
|
|
5068
|
+
"[ModelRegistry] No models to initialize for document (either no explicit models set for session or no models globally registered via @Model decorator)."
|
|
5069
|
+
);
|
|
5070
|
+
return;
|
|
5071
|
+
}
|
|
5072
|
+
Logger.debug(
|
|
5073
|
+
`[ModelRegistry] Initializing models for document ${docId}: ${Array.from(
|
|
5074
|
+
modelsToInitialize.keys()
|
|
5075
|
+
).join(", ")}`
|
|
5076
|
+
);
|
|
5077
|
+
for (const [modelName, modelClass] of modelsToInitialize.entries()) {
|
|
5078
|
+
if (modelClass && typeof modelClass.initializeForDocument === "function") {
|
|
5079
|
+
Logger.debug(
|
|
5080
|
+
`[ModelRegistry] Initializing model ${modelName} for document ${docId}`
|
|
5081
|
+
);
|
|
5082
|
+
await modelClass.initializeForDocument(
|
|
5083
|
+
yDoc,
|
|
5084
|
+
dbEngine,
|
|
5085
|
+
docId,
|
|
5086
|
+
permissionHint
|
|
5087
|
+
);
|
|
5088
|
+
} else {
|
|
5089
|
+
const staticModelName = modelClass?.modelName || "unknown";
|
|
5090
|
+
console.warn(
|
|
5091
|
+
`[ModelRegistry] Model ${staticModelName} (class name: ${modelClass?.name}) does not have a static initializeForDocument method or class is not defined.`
|
|
5092
|
+
);
|
|
4575
5093
|
}
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
5094
|
+
}
|
|
5095
|
+
await this.initializeRelationships();
|
|
5096
|
+
Logger.debug(
|
|
5097
|
+
`[ModelRegistry] Model initialization complete for document ${docId}`
|
|
5098
|
+
);
|
|
5099
|
+
}
|
|
5100
|
+
// New method to remove data from a disconnected document
|
|
5101
|
+
async removeDocumentData(docId, dbEngine) {
|
|
5102
|
+
Logger.debug(`[ModelRegistry] Removing data for document ${docId}...`);
|
|
5103
|
+
const modelsToCleanup = this.activeSessionModels || this.models;
|
|
5104
|
+
if (modelsToCleanup.size === 0) {
|
|
5105
|
+
console.warn(
|
|
5106
|
+
"[ModelRegistry] No models to cleanup for document (either no explicit models set for session or no models globally registered via @Model decorator)."
|
|
5107
|
+
);
|
|
5108
|
+
return;
|
|
5109
|
+
}
|
|
5110
|
+
for (const [modelName, modelClass] of modelsToCleanup.entries()) {
|
|
5111
|
+
try {
|
|
5112
|
+
Logger.debug(
|
|
5113
|
+
`[ModelRegistry] Removing ${modelName} data for document ${docId}`
|
|
5114
|
+
);
|
|
5115
|
+
await dbEngine.deleteByDocumentId(modelName, docId);
|
|
5116
|
+
if (modelClass && typeof modelClass.cleanupDocumentData === "function") {
|
|
5117
|
+
await modelClass.cleanupDocumentData(docId);
|
|
4586
5118
|
}
|
|
4587
|
-
})
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
5119
|
+
} catch (error) {
|
|
5120
|
+
console.error(
|
|
5121
|
+
`[ModelRegistry] Error removing ${modelName} data for document ${docId}:`,
|
|
5122
|
+
error
|
|
5123
|
+
);
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
Logger.debug(`[ModelRegistry] Data removal complete for document ${docId}`);
|
|
5127
|
+
}
|
|
5128
|
+
// New method to initialize relationships
|
|
5129
|
+
async initializeRelationships() {
|
|
5130
|
+
if (!this.dbEngine) {
|
|
5131
|
+
console.error(
|
|
5132
|
+
"[ModelRegistry] DB engine not available for initializing relationships. Ensure initializeAll has been called."
|
|
5133
|
+
);
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
const dbEngine = this.dbEngine;
|
|
5137
|
+
Logger.debug(
|
|
5138
|
+
"[ModelRegistry] Initializing relationships for active models..."
|
|
5139
|
+
);
|
|
5140
|
+
const activeModels = this.getActiveModels();
|
|
5141
|
+
for (const [modelName, modelClass] of activeModels.entries()) {
|
|
5142
|
+
const modelSchema = modelClass.getSchema ? modelClass.getSchema() : null;
|
|
5143
|
+
const relationshipsConfig = modelSchema?.options?.relationships;
|
|
5144
|
+
if (relationshipsConfig) {
|
|
5145
|
+
Logger.debug(
|
|
5146
|
+
`[ModelRegistry] Setting up relationships for ${modelName}`
|
|
5147
|
+
);
|
|
5148
|
+
for (const relName in relationshipsConfig) {
|
|
5149
|
+
const config = relationshipsConfig[relName];
|
|
5150
|
+
if (!config.model) {
|
|
5151
|
+
throw new Error(
|
|
5152
|
+
`[ModelRegistry] Relationship "${relName}" on model "${modelName}" is missing a target model name.`
|
|
5153
|
+
);
|
|
5154
|
+
}
|
|
5155
|
+
const targetModelClass = this.getModelClass(config.model);
|
|
5156
|
+
if (!targetModelClass) {
|
|
5157
|
+
throw new Error(
|
|
5158
|
+
`[ModelRegistry] Relationship "${relName}" on model "${modelName}" refers to an unknown model "${config.model}". Ensure "${config.model}" is registered and included in the session.`
|
|
5159
|
+
);
|
|
5160
|
+
}
|
|
5161
|
+
if (typeof targetModelClass.getSchema !== "function") {
|
|
5162
|
+
throw new Error(
|
|
5163
|
+
`[ModelRegistry] Target model "${config.model}" for relationship "${relName}" on "${modelName}" does not have a getSchema method.`
|
|
5164
|
+
);
|
|
5165
|
+
}
|
|
5166
|
+
switch (config.type) {
|
|
5167
|
+
case "refersTo":
|
|
5168
|
+
Logger.debug(
|
|
5169
|
+
` - Adding '${relName}' (refersTo ${config.model}) to ${modelName}`
|
|
5170
|
+
);
|
|
5171
|
+
const refersToMethod = generateRefersToMethod(
|
|
5172
|
+
modelClass,
|
|
5173
|
+
config,
|
|
5174
|
+
targetModelClass
|
|
5175
|
+
);
|
|
5176
|
+
this.addPrototypeMethod(modelClass, relName, refersToMethod);
|
|
5177
|
+
break;
|
|
5178
|
+
case "hasMany":
|
|
5179
|
+
Logger.debug(
|
|
5180
|
+
` - Adding '${relName}' (hasMany ${config.model}) to ${modelName}`
|
|
5181
|
+
);
|
|
5182
|
+
const hasManyMethod = generateHasManyMethod(
|
|
5183
|
+
modelClass,
|
|
5184
|
+
config,
|
|
5185
|
+
targetModelClass,
|
|
5186
|
+
dbEngine
|
|
5187
|
+
);
|
|
5188
|
+
this.addPrototypeMethod(modelClass, relName, hasManyMethod);
|
|
5189
|
+
break;
|
|
5190
|
+
case "hasManyThrough":
|
|
5191
|
+
const joinModelName = config.joinModel;
|
|
5192
|
+
const joinModelClass = this.getModelClass(joinModelName);
|
|
5193
|
+
if (!joinModelClass) {
|
|
5194
|
+
throw new Error(
|
|
5195
|
+
`[ModelRegistry] Join model "${joinModelName}" for relationship "${relName}" on model "${modelName}" not found. Ensure it is registered and included in the session.`
|
|
5196
|
+
);
|
|
5197
|
+
}
|
|
5198
|
+
if (typeof joinModelClass.getSchema !== "function") {
|
|
5199
|
+
throw new Error(
|
|
5200
|
+
`[ModelRegistry] Join model "${joinModelName}" for relationship "${relName}" on "${modelName}" does not have a getSchema method.`
|
|
5201
|
+
);
|
|
5202
|
+
}
|
|
5203
|
+
Logger.debug(
|
|
5204
|
+
` - Adding '${relName}' (hasManyThrough ${config.model} via ${joinModelName}) to ${modelName}`
|
|
5205
|
+
);
|
|
5206
|
+
const hasManyThroughFetchMethod = generateHasManyThroughMethod(
|
|
5207
|
+
modelClass,
|
|
5208
|
+
config,
|
|
5209
|
+
targetModelClass,
|
|
5210
|
+
joinModelClass,
|
|
5211
|
+
dbEngine
|
|
5212
|
+
);
|
|
5213
|
+
this.addPrototypeMethod(
|
|
5214
|
+
modelClass,
|
|
5215
|
+
relName,
|
|
5216
|
+
hasManyThroughFetchMethod
|
|
5217
|
+
);
|
|
5218
|
+
const relNameSingular = config.model.endsWith("s") ? config.model.slice(0, -1) : config.model;
|
|
5219
|
+
const capitalizedSingularRelName = relNameSingular.charAt(0).toUpperCase() + relNameSingular.slice(1);
|
|
5220
|
+
const addMethodName = `add${capitalizedSingularRelName}`;
|
|
5221
|
+
const addMethodLogic = generateHasManyThroughAddMethod(
|
|
5222
|
+
modelClass,
|
|
5223
|
+
config,
|
|
5224
|
+
targetModelClass,
|
|
5225
|
+
joinModelClass
|
|
5226
|
+
);
|
|
5227
|
+
this.addPrototypeMethod(
|
|
5228
|
+
modelClass,
|
|
5229
|
+
addMethodName,
|
|
5230
|
+
addMethodLogic
|
|
5231
|
+
);
|
|
5232
|
+
Logger.debug(
|
|
5233
|
+
` - Adding '${addMethodName}' helper to ${modelName} (for relationship ${relName})`
|
|
5234
|
+
);
|
|
5235
|
+
const removeMethodName = `remove${capitalizedSingularRelName}`;
|
|
5236
|
+
const removeMethodLogic = generateHasManyThroughRemoveMethod(
|
|
5237
|
+
modelClass,
|
|
5238
|
+
config,
|
|
5239
|
+
targetModelClass,
|
|
5240
|
+
joinModelClass
|
|
5241
|
+
);
|
|
5242
|
+
this.addPrototypeMethod(
|
|
5243
|
+
modelClass,
|
|
5244
|
+
removeMethodName,
|
|
5245
|
+
removeMethodLogic
|
|
5246
|
+
);
|
|
5247
|
+
Logger.debug(
|
|
5248
|
+
` - Adding '${removeMethodName}' helper to ${modelName} (for relationship ${relName})`
|
|
5249
|
+
);
|
|
5250
|
+
break;
|
|
5251
|
+
default:
|
|
5252
|
+
console.warn(
|
|
5253
|
+
`[ModelRegistry] Unknown relationship type "${config.type}" for ${relName} on ${modelName}. Skipping.`
|
|
5254
|
+
);
|
|
4599
5255
|
}
|
|
4600
5256
|
}
|
|
4601
|
-
|
|
4602
|
-
});
|
|
4603
|
-
throw error;
|
|
5257
|
+
}
|
|
4604
5258
|
}
|
|
5259
|
+
Logger.debug("[ModelRegistry] Relationship initialization phase complete.");
|
|
4605
5260
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
5261
|
+
// Helper to add methods to prototype
|
|
5262
|
+
addPrototypeMethod(modelClass, methodName, methodLogic) {
|
|
5263
|
+
Object.defineProperty(modelClass.prototype, methodName, {
|
|
5264
|
+
value: methodLogic,
|
|
5265
|
+
writable: true,
|
|
5266
|
+
enumerable: false,
|
|
5267
|
+
// Keep it clean, like other prototype methods
|
|
5268
|
+
configurable: true
|
|
5269
|
+
});
|
|
4613
5270
|
}
|
|
4614
|
-
|
|
4615
|
-
|
|
5271
|
+
clearSessionState() {
|
|
5272
|
+
this.activeSessionModels = null;
|
|
5273
|
+
Logger.debug(
|
|
5274
|
+
"[ModelRegistry] Session state cleared (activeSessionModels reset)."
|
|
5275
|
+
);
|
|
4616
5276
|
}
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
this.db.close();
|
|
4620
|
-
Logger.info("[SqljsEngine] Database closed.");
|
|
4621
|
-
}
|
|
5277
|
+
getActiveModels() {
|
|
5278
|
+
return this.activeSessionModels || this.models;
|
|
4622
5279
|
}
|
|
4623
|
-
|
|
4624
|
-
|
|
5280
|
+
validateSessionModels() {
|
|
5281
|
+
const activeModels = this.getActiveModels();
|
|
5282
|
+
if (!activeModels || activeModels.size === 0) {
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5285
|
+
for (const [modelName, modelClass] of activeModels.entries()) {
|
|
5286
|
+
const modelSchema = modelClass.getSchema ? modelClass.getSchema() : null;
|
|
5287
|
+
const relationships = modelSchema?.options?.relationships;
|
|
5288
|
+
if (relationships) {
|
|
5289
|
+
for (const relName in relationships) {
|
|
5290
|
+
const config = relationships[relName];
|
|
5291
|
+
const targetModelName = config.model;
|
|
5292
|
+
if (!activeModels.has(targetModelName)) {
|
|
5293
|
+
throw new Error(
|
|
5294
|
+
`[ModelRegistry] Validation Error: Model "${modelName}" has a relationship "${relName}" that refers to model "${targetModelName}", but "${targetModelName}" is not active in the current session. Please include it in the 'models' array during initialization.`
|
|
5295
|
+
);
|
|
5296
|
+
}
|
|
5297
|
+
if (config.type === "hasManyThrough") {
|
|
5298
|
+
const joinModelName = config.joinModel;
|
|
5299
|
+
if (!activeModels.has(joinModelName)) {
|
|
5300
|
+
throw new Error(
|
|
5301
|
+
`[ModelRegistry] Validation Error: Model "${modelName}" has a relationship "${relName}" that uses join model "${joinModelName}", but "${joinModelName}" is not active in the current session. Please include it in the 'models' array during initialization.`
|
|
5302
|
+
);
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
4625
5308
|
}
|
|
4626
5309
|
};
|
|
4627
5310
|
}
|
|
4628
5311
|
});
|
|
4629
5312
|
|
|
4630
|
-
// src/
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
5313
|
+
// src/utils/environment.ts
|
|
5314
|
+
function detectEnvironment() {
|
|
5315
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
5316
|
+
return "node";
|
|
5317
|
+
}
|
|
5318
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
5319
|
+
return "browser";
|
|
5320
|
+
}
|
|
5321
|
+
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
5322
|
+
return "browser";
|
|
5323
|
+
}
|
|
5324
|
+
return "unknown";
|
|
5325
|
+
}
|
|
5326
|
+
function isNode() {
|
|
5327
|
+
return detectEnvironment() === "node";
|
|
5328
|
+
}
|
|
5329
|
+
function isBrowser() {
|
|
5330
|
+
return detectEnvironment() === "browser";
|
|
5331
|
+
}
|
|
5332
|
+
async function isNodeModuleAvailable(moduleName) {
|
|
5333
|
+
if (!isNode()) {
|
|
5334
|
+
return false;
|
|
5335
|
+
}
|
|
5336
|
+
try {
|
|
5337
|
+
await import(
|
|
5338
|
+
/* @vite-ignore */
|
|
5339
|
+
moduleName
|
|
5340
|
+
);
|
|
5341
|
+
return true;
|
|
5342
|
+
} catch {
|
|
5343
|
+
return false;
|
|
5344
|
+
}
|
|
5345
|
+
}
|
|
5346
|
+
var features;
|
|
5347
|
+
var init_environment = __esm({
|
|
5348
|
+
"src/utils/environment.ts"() {
|
|
5349
|
+
"use strict";
|
|
5350
|
+
features = {
|
|
5351
|
+
get hasWebWorkers() {
|
|
5352
|
+
return isBrowser() && typeof Worker !== "undefined";
|
|
5353
|
+
},
|
|
5354
|
+
get hasFileSystem() {
|
|
5355
|
+
return isNode();
|
|
5356
|
+
},
|
|
5357
|
+
get hasWebAssembly() {
|
|
5358
|
+
return typeof WebAssembly !== "undefined";
|
|
5359
|
+
},
|
|
5360
|
+
async hasBetterSqlite3() {
|
|
5361
|
+
return await isNodeModuleAvailable("better-sqlite3");
|
|
5362
|
+
}
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
4634
5365
|
});
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
5366
|
+
|
|
5367
|
+
// src/engines/SqljsEngine.ts
|
|
5368
|
+
import initSqlJs from "sql.js";
|
|
5369
|
+
import { Mutex } from "async-mutex";
|
|
5370
|
+
var SQL_WASM_URL, TransactionalSQLJSOperations, SqljsEngine;
|
|
5371
|
+
var init_SqljsEngine = __esm({
|
|
5372
|
+
"src/engines/SqljsEngine.ts"() {
|
|
4639
5373
|
"use strict";
|
|
4640
5374
|
init_BaseModel();
|
|
4641
5375
|
init_sql();
|
|
4642
|
-
|
|
5376
|
+
SQL_WASM_URL = "/sql-wasm.wasm";
|
|
5377
|
+
TransactionalSQLJSOperations = class {
|
|
4643
5378
|
db;
|
|
4644
5379
|
engine;
|
|
5380
|
+
// For helper methods, changed type to SqljsEngine
|
|
4645
5381
|
constructor(db, engine) {
|
|
4646
5382
|
this.db = db;
|
|
4647
5383
|
this.engine = engine;
|
|
4648
5384
|
}
|
|
4649
5385
|
async insert(modelName, data) {
|
|
5386
|
+
if (!this.db) throw new Error("SQL.js DB not available in transaction");
|
|
4650
5387
|
const tableName = this.engine.getTableName(modelName);
|
|
5388
|
+
const quotedTableName = quoteIdentifier(tableName);
|
|
4651
5389
|
const columns = Object.keys(data);
|
|
5390
|
+
const quotedColumns = columns.map((column) => quoteIdentifier(column));
|
|
4652
5391
|
const placeholders = columns.map(() => "?").join(", ");
|
|
4653
|
-
const values = Object.values(data)
|
|
4654
|
-
|
|
4655
|
-
return value ? 1 : 0;
|
|
4656
|
-
}
|
|
4657
|
-
return value;
|
|
4658
|
-
});
|
|
4659
|
-
const insertSQL = `INSERT OR REPLACE INTO ${tableName} (${columns.join(
|
|
5392
|
+
const values = Object.values(data);
|
|
5393
|
+
const insertSQL = `INSERT OR REPLACE INTO ${quotedTableName} (${quotedColumns.join(
|
|
4660
5394
|
", "
|
|
4661
|
-
)}) VALUES (${placeholders})
|
|
4662
|
-
|
|
4663
|
-
stmt.run(...values);
|
|
5395
|
+
)}) VALUES (${placeholders});`;
|
|
5396
|
+
this.db.run(insertSQL, values);
|
|
4664
5397
|
}
|
|
4665
5398
|
async delete(modelName, id) {
|
|
5399
|
+
if (!this.db) throw new Error("SQL.js DB not available in transaction");
|
|
4666
5400
|
const tableName = this.engine.getTableName(modelName);
|
|
4667
|
-
const deleteSQL = `DELETE FROM ${tableName} WHERE
|
|
4668
|
-
|
|
4669
|
-
|
|
5401
|
+
const deleteSQL = `DELETE FROM ${quoteIdentifier(tableName)} WHERE ${quoteIdentifier(
|
|
5402
|
+
"id"
|
|
5403
|
+
)} = ?;`;
|
|
5404
|
+
this.db.run(deleteSQL, [id]);
|
|
4670
5405
|
}
|
|
4671
5406
|
async query(sql, params) {
|
|
5407
|
+
if (!this.db) throw new Error("SQL.js DB not available in transaction");
|
|
5408
|
+
const results = [];
|
|
4672
5409
|
const stmt = this.db.prepare(sql);
|
|
4673
|
-
|
|
5410
|
+
if (params) {
|
|
5411
|
+
stmt.bind(params);
|
|
5412
|
+
}
|
|
5413
|
+
while (stmt.step()) {
|
|
5414
|
+
results.push(stmt.getAsObject());
|
|
5415
|
+
}
|
|
5416
|
+
stmt.free();
|
|
4674
5417
|
return results;
|
|
4675
5418
|
}
|
|
4676
5419
|
};
|
|
4677
|
-
|
|
5420
|
+
SqljsEngine = class _SqljsEngine {
|
|
5421
|
+
static SQL = null;
|
|
4678
5422
|
db = null;
|
|
4679
5423
|
tableNames = /* @__PURE__ */ new Set();
|
|
4680
|
-
mutex = new Mutex2();
|
|
4681
|
-
readyPromise;
|
|
4682
|
-
betterSqlite3 = null;
|
|
4683
5424
|
numOpenTransactions = 0;
|
|
5425
|
+
mutex = new Mutex();
|
|
5426
|
+
readyPromise;
|
|
4684
5427
|
constructor(options) {
|
|
4685
|
-
Logger.debug("[
|
|
5428
|
+
Logger.debug("[SqljsEngine] Constructor called. Initializing...");
|
|
5429
|
+
this.numOpenTransactions = 0;
|
|
4686
5430
|
this.readyPromise = this.initialize(options);
|
|
4687
5431
|
}
|
|
5432
|
+
// Make readyPromise accessible if DatabaseFactory wants to await it,
|
|
5433
|
+
// though direct call to initialize from constructor and awaiting it is simpler here.
|
|
4688
5434
|
async ensureReady() {
|
|
4689
5435
|
return this.readyPromise;
|
|
4690
5436
|
}
|
|
4691
|
-
async initialize(
|
|
4692
|
-
Logger.info("[
|
|
5437
|
+
async initialize(engineOptions) {
|
|
5438
|
+
Logger.info("[SqljsEngine] Initializing SQL.js engine...");
|
|
4693
5439
|
try {
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
}
|
|
4705
|
-
if (filePath !== ":memory:") {
|
|
4706
|
-
this.db.exec("PRAGMA journal_mode = WAL");
|
|
5440
|
+
if (!_SqljsEngine.SQL) {
|
|
5441
|
+
const locateFileConfig = engineOptions?.locateFile || ((_file) => engineOptions?.wasmURL || SQL_WASM_URL);
|
|
5442
|
+
Logger.debug(
|
|
5443
|
+
"[SqljsEngine] Attempting to load SQL.js WASM from:",
|
|
5444
|
+
locateFileConfig("sql-wasm.wasm")
|
|
5445
|
+
);
|
|
5446
|
+
_SqljsEngine.SQL = await initSqlJs({
|
|
5447
|
+
locateFile: locateFileConfig
|
|
5448
|
+
});
|
|
5449
|
+
Logger.info("[SqljsEngine] SQL.js WASM loaded successfully.");
|
|
4707
5450
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
);
|
|
5451
|
+
this.db = new _SqljsEngine.SQL.Database();
|
|
5452
|
+
Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
|
|
4711
5453
|
this.updateTableNames();
|
|
4712
5454
|
} catch (error) {
|
|
4713
|
-
Logger.error(
|
|
4714
|
-
|
|
4715
|
-
error
|
|
4716
|
-
);
|
|
4717
|
-
throw new Error(
|
|
4718
|
-
`Failed to initialize Node.js SQLite engine: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4719
|
-
);
|
|
5455
|
+
Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
|
|
5456
|
+
throw error;
|
|
4720
5457
|
}
|
|
4721
5458
|
}
|
|
4722
5459
|
updateTableNames() {
|
|
4723
5460
|
if (!this.db) return;
|
|
4724
|
-
const
|
|
4725
|
-
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
5461
|
+
const results = this.db.exec(
|
|
5462
|
+
"SELECT name FROM sqlite_master WHERE type='table';"
|
|
4726
5463
|
);
|
|
4727
|
-
const tables = stmt.all();
|
|
4728
5464
|
this.tableNames.clear();
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
5465
|
+
if (results.length > 0 && results[0].values) {
|
|
5466
|
+
results[0].values.forEach(
|
|
5467
|
+
(row) => this.tableNames.add(row[0])
|
|
5468
|
+
);
|
|
5469
|
+
}
|
|
4734
5470
|
}
|
|
4735
5471
|
getTableName(modelName) {
|
|
4736
5472
|
return `model_${modelName.toLowerCase()}`;
|
|
@@ -4750,16 +5486,13 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4750
5486
|
}
|
|
4751
5487
|
}
|
|
4752
5488
|
async createTable(modelName, schema, _options) {
|
|
4753
|
-
await this.
|
|
4754
|
-
if (!this.db) throw new Error("
|
|
5489
|
+
await this.readyPromise;
|
|
5490
|
+
if (!this.db) throw new Error("SQL.js not initialized for createTable");
|
|
4755
5491
|
const tableName = this.getTableName(modelName);
|
|
4756
5492
|
const quotedTableName = quoteIdentifier(tableName);
|
|
4757
5493
|
const idColumn = quoteIdentifier("id");
|
|
4758
5494
|
const typeColumn = quoteIdentifier("type");
|
|
4759
5495
|
if (this.tableNames.has(tableName)) {
|
|
4760
|
-
Logger.debug(
|
|
4761
|
-
`[NodeSqliteEngine] Table ${tableName} already exists, skipping creation.`
|
|
4762
|
-
);
|
|
4763
5496
|
return;
|
|
4764
5497
|
}
|
|
4765
5498
|
const columns = Array.from(schema.entries()).filter(([field]) => field !== "id" && field !== "type").map(
|
|
@@ -4770,38 +5503,32 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4770
5503
|
`${quoteIdentifier("_meta_permission_hint")} TEXT`
|
|
4771
5504
|
];
|
|
4772
5505
|
const createTableSQL = `CREATE TABLE IF NOT EXISTS ${quotedTableName} (
|
|
4773
|
-
${idColumn} TEXT PRIMARY KEY,
|
|
4774
|
-
${typeColumn} TEXT,
|
|
5506
|
+
${idColumn} TEXT PRIMARY KEY,
|
|
5507
|
+
${typeColumn} TEXT,
|
|
4775
5508
|
${columns.join(", ")}${columns.length > 0 ? ", " : ""}${metadataColumns.join(", ")}
|
|
4776
|
-
)
|
|
4777
|
-
|
|
4778
|
-
`[NodeSqliteEngine] Creating table ${tableName}:`,
|
|
4779
|
-
createTableSQL
|
|
4780
|
-
);
|
|
4781
|
-
this.db.exec(createTableSQL);
|
|
5509
|
+
);`;
|
|
5510
|
+
this.db.run(createTableSQL);
|
|
4782
5511
|
for (const [field, fieldOptions] of schema.entries()) {
|
|
4783
5512
|
if (fieldOptions && fieldOptions.indexed && field !== "id") {
|
|
4784
5513
|
const indexName = `idx_${tableName}_${field}`;
|
|
4785
|
-
const
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
this.db.exec(createIndexSQL);
|
|
5514
|
+
const createIndexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
5515
|
+
indexName
|
|
5516
|
+
)} ON ${quotedTableName} (${quoteIdentifier(field)});`;
|
|
5517
|
+
this.db.run(createIndexSQL);
|
|
4790
5518
|
}
|
|
4791
5519
|
}
|
|
4792
5520
|
const docIdIndexName = `idx_${tableName}_meta_doc_id`;
|
|
4793
5521
|
const docIdIndexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
4794
5522
|
docIdIndexName
|
|
4795
|
-
)} ON ${quotedTableName} (${quoteIdentifier("_meta_doc_id")})
|
|
4796
|
-
this.db.
|
|
5523
|
+
)} ON ${quotedTableName} (${quoteIdentifier("_meta_doc_id")});`;
|
|
5524
|
+
this.db.run(docIdIndexSQL);
|
|
4797
5525
|
this.updateTableNames();
|
|
4798
|
-
Logger.info(`[NodeSqliteEngine] Table ${tableName} created successfully.`);
|
|
4799
5526
|
}
|
|
4800
5527
|
async createStringSetJunctionTable(modelName, fieldName) {
|
|
4801
|
-
await this.
|
|
5528
|
+
await this.readyPromise;
|
|
4802
5529
|
if (!this.db)
|
|
4803
5530
|
throw new Error(
|
|
4804
|
-
"
|
|
5531
|
+
"SQL.js not initialized for createStringSetJunctionTable"
|
|
4805
5532
|
);
|
|
4806
5533
|
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
4807
5534
|
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
@@ -4811,7 +5538,7 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4811
5538
|
const quotedValueColumn = quoteIdentifier("value");
|
|
4812
5539
|
if (this.tableNames.has(junctionTableName)) {
|
|
4813
5540
|
Logger.debug(
|
|
4814
|
-
`[
|
|
5541
|
+
`[SqljsEngine] StringSet junction table ${junctionTableName} already exists, skipping creation.`
|
|
4815
5542
|
);
|
|
4816
5543
|
return;
|
|
4817
5544
|
}
|
|
@@ -4820,100 +5547,92 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4820
5547
|
${quotedForeignKey} TEXT NOT NULL,
|
|
4821
5548
|
${quotedValueColumn} TEXT NOT NULL,
|
|
4822
5549
|
UNIQUE(${quotedForeignKey}, ${quotedValueColumn})
|
|
4823
|
-
)
|
|
5550
|
+
);`;
|
|
4824
5551
|
Logger.info(
|
|
4825
|
-
`[
|
|
5552
|
+
`[SqljsEngine] Creating StringSet junction table ${junctionTableName} for ${modelName}.${fieldName}`
|
|
4826
5553
|
);
|
|
4827
|
-
this.db.
|
|
4828
|
-
const indexName = `idx_${junctionTableName}_${modelName.toLowerCase()}
|
|
5554
|
+
this.db.run(createTableSQL);
|
|
5555
|
+
const indexName = `idx_${junctionTableName}_${modelName.toLowerCase()}_id_value`;
|
|
4829
5556
|
const indexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
4830
5557
|
indexName
|
|
4831
|
-
)} ON ${quotedJunctionTable} (${quotedForeignKey})
|
|
4832
|
-
this.db.
|
|
5558
|
+
)} ON ${quotedJunctionTable} (${quotedForeignKey}, ${quotedValueColumn});`;
|
|
5559
|
+
this.db.run(indexSQL);
|
|
4833
5560
|
this.updateTableNames();
|
|
4834
5561
|
Logger.info(
|
|
4835
|
-
`[
|
|
5562
|
+
`[SqljsEngine] StringSet junction table ${junctionTableName} created successfully.`
|
|
4836
5563
|
);
|
|
4837
5564
|
}
|
|
4838
5565
|
async insertStringSetValues(modelName, fieldName, recordId, values) {
|
|
4839
|
-
await this.
|
|
5566
|
+
await this.readyPromise;
|
|
4840
5567
|
if (!this.db)
|
|
4841
|
-
throw new Error("
|
|
5568
|
+
throw new Error("SQL.js not initialized for insertStringSetValues");
|
|
4842
5569
|
if (values.length === 0) return;
|
|
4843
5570
|
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
5571
|
+
const modelIdColumn = `${modelName.toLowerCase()}_id`;
|
|
4844
5572
|
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
4845
5573
|
const quotedIdColumn = quoteIdentifier("id");
|
|
4846
|
-
const
|
|
5574
|
+
const quotedModelIdColumn = quoteIdentifier(modelIdColumn);
|
|
4847
5575
|
const quotedValueColumn = quoteIdentifier("value");
|
|
4848
|
-
const insertSQL = `INSERT OR IGNORE INTO ${quotedJunctionTable} (${quotedIdColumn}, ${quotedForeignKey}, ${quotedValueColumn}) VALUES (?, ?, ?)`;
|
|
4849
|
-
const stmt = this.db.prepare(insertSQL);
|
|
4850
5576
|
for (const value of values) {
|
|
4851
|
-
const
|
|
4852
|
-
|
|
5577
|
+
const insertSQL = `INSERT OR IGNORE INTO ${quotedJunctionTable} (${quotedIdColumn}, ${quotedModelIdColumn}, ${quotedValueColumn}) VALUES (?, ?, ?);`;
|
|
5578
|
+
const junctionId = `${recordId}_${value}`;
|
|
5579
|
+
this.db.run(insertSQL, [junctionId, recordId, value]);
|
|
4853
5580
|
}
|
|
4854
5581
|
}
|
|
4855
5582
|
async removeStringSetValues(modelName, fieldName, recordId, values) {
|
|
4856
|
-
await this.
|
|
5583
|
+
await this.readyPromise;
|
|
4857
5584
|
if (!this.db)
|
|
4858
|
-
throw new Error("
|
|
5585
|
+
throw new Error("SQL.js not initialized for removeStringSetValues");
|
|
4859
5586
|
if (values.length === 0) return;
|
|
4860
5587
|
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
5588
|
+
const modelIdColumn = `${modelName.toLowerCase()}_id`;
|
|
4861
5589
|
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
4862
|
-
const
|
|
5590
|
+
const quotedModelIdColumn = quoteIdentifier(modelIdColumn);
|
|
4863
5591
|
const quotedValueColumn = quoteIdentifier("value");
|
|
4864
|
-
const deleteSQL = `DELETE FROM ${quotedJunctionTable} WHERE ${quotedForeignKey} = ? AND ${quotedValueColumn} = ?`;
|
|
4865
|
-
const stmt = this.db.prepare(deleteSQL);
|
|
4866
5592
|
for (const value of values) {
|
|
4867
|
-
|
|
5593
|
+
const deleteSQL = `DELETE FROM ${quotedJunctionTable} WHERE ${quotedModelIdColumn} = ? AND ${quotedValueColumn} = ?;`;
|
|
5594
|
+
this.db.run(deleteSQL, [recordId, value]);
|
|
4868
5595
|
}
|
|
4869
5596
|
}
|
|
4870
5597
|
async insert(modelName, data) {
|
|
4871
|
-
await this.
|
|
4872
|
-
if (!this.db) throw new Error("
|
|
5598
|
+
await this.readyPromise;
|
|
5599
|
+
if (!this.db) throw new Error("SQL.js not initialized for insert");
|
|
4873
5600
|
const tableName = this.getTableName(modelName);
|
|
4874
5601
|
const quotedTableName = quoteIdentifier(tableName);
|
|
4875
5602
|
const columns = Object.keys(data);
|
|
4876
5603
|
const quotedColumns = columns.map((column) => quoteIdentifier(column));
|
|
4877
5604
|
const placeholders = columns.map(() => "?").join(", ");
|
|
4878
|
-
const values = Object.values(data)
|
|
4879
|
-
if (typeof value === "boolean") {
|
|
4880
|
-
return value ? 1 : 0;
|
|
4881
|
-
}
|
|
4882
|
-
return value;
|
|
4883
|
-
});
|
|
5605
|
+
const values = Object.values(data);
|
|
4884
5606
|
const insertSQL = `INSERT OR REPLACE INTO ${quotedTableName} (${quotedColumns.join(
|
|
4885
5607
|
", "
|
|
4886
|
-
)}) VALUES (${placeholders})
|
|
4887
|
-
|
|
4888
|
-
stmt.run(...values);
|
|
5608
|
+
)}) VALUES (${placeholders});`;
|
|
5609
|
+
this.db.run(insertSQL, values);
|
|
4889
5610
|
}
|
|
4890
5611
|
async delete(modelName, id) {
|
|
4891
|
-
await this.
|
|
4892
|
-
if (!this.db) throw new Error("
|
|
5612
|
+
await this.readyPromise;
|
|
5613
|
+
if (!this.db) throw new Error("SQL.js not initialized for delete");
|
|
4893
5614
|
const tableName = this.getTableName(modelName);
|
|
4894
|
-
const deleteSQL = `DELETE FROM ${quoteIdentifier(
|
|
4895
|
-
|
|
4896
|
-
)}
|
|
4897
|
-
|
|
4898
|
-
stmt.run(id);
|
|
5615
|
+
const deleteSQL = `DELETE FROM ${quoteIdentifier(tableName)} WHERE ${quoteIdentifier(
|
|
5616
|
+
"id"
|
|
5617
|
+
)} = ?;`;
|
|
5618
|
+
this.db.run(deleteSQL, [id]);
|
|
4899
5619
|
}
|
|
4900
5620
|
async deleteByDocumentId(modelName, docId) {
|
|
4901
|
-
await this.
|
|
5621
|
+
await this.readyPromise;
|
|
4902
5622
|
if (!this.db)
|
|
4903
|
-
throw new Error("
|
|
5623
|
+
throw new Error("SQL.js not initialized for deleteByDocumentId");
|
|
4904
5624
|
const tableName = this.getTableName(modelName);
|
|
4905
5625
|
const quotedTableName = quoteIdentifier(tableName);
|
|
4906
5626
|
if (!this.tableNames.has(tableName)) {
|
|
4907
5627
|
Logger.debug(
|
|
4908
|
-
`[
|
|
5628
|
+
`[SqljsEngine] Skipping deleteByDocumentId for ${tableName}; table does not exist.`
|
|
4909
5629
|
);
|
|
4910
5630
|
return;
|
|
4911
5631
|
}
|
|
4912
5632
|
const deleteSQL = `DELETE FROM ${quotedTableName} WHERE ${quoteIdentifier(
|
|
4913
5633
|
"_meta_doc_id"
|
|
4914
|
-
)} =
|
|
4915
|
-
|
|
4916
|
-
stmt.run(docId);
|
|
5634
|
+
)} = ?;`;
|
|
5635
|
+
this.db.run(deleteSQL, [docId]);
|
|
4917
5636
|
for (const existingTableName of this.tableNames) {
|
|
4918
5637
|
if (existingTableName.startsWith(`${tableName}_`)) {
|
|
4919
5638
|
const quotedExistingTable = quoteIdentifier(existingTableName);
|
|
@@ -4922,16 +5641,16 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4922
5641
|
SELECT ${quoteIdentifier("id")} FROM ${quotedTableName} WHERE ${quoteIdentifier(
|
|
4923
5642
|
"_meta_doc_id"
|
|
4924
5643
|
)} = ?
|
|
4925
|
-
)
|
|
5644
|
+
);`;
|
|
4926
5645
|
try {
|
|
4927
|
-
|
|
4928
|
-
const
|
|
5646
|
+
this.db.run(junctionDeleteSQL, [docId]);
|
|
5647
|
+
const rowsCleared = this.db.getRowsModified();
|
|
4929
5648
|
Logger.info(
|
|
4930
|
-
`[
|
|
5649
|
+
`[SqljsEngine] Cleared junction table ${existingTableName} for document ${docId}; removed ${rowsCleared} rows.`
|
|
4931
5650
|
);
|
|
4932
5651
|
} catch (error) {
|
|
4933
|
-
|
|
4934
|
-
`[
|
|
5652
|
+
Logger.warn(
|
|
5653
|
+
`[SqljsEngine] Warning: Could not clean up junction table ${existingTableName}:`,
|
|
4935
5654
|
error
|
|
4936
5655
|
);
|
|
4937
5656
|
}
|
|
@@ -4939,50 +5658,53 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4939
5658
|
}
|
|
4940
5659
|
}
|
|
4941
5660
|
async query(sql, params) {
|
|
4942
|
-
await this.
|
|
4943
|
-
if (!this.db) throw new Error("
|
|
5661
|
+
await this.readyPromise;
|
|
5662
|
+
if (!this.db) throw new Error("SQL.js not initialized for query");
|
|
5663
|
+
const results = [];
|
|
4944
5664
|
const stmt = this.db.prepare(sql);
|
|
4945
|
-
|
|
5665
|
+
if (params) {
|
|
5666
|
+
stmt.bind(params);
|
|
5667
|
+
}
|
|
5668
|
+
while (stmt.step()) {
|
|
5669
|
+
results.push(stmt.getAsObject());
|
|
5670
|
+
}
|
|
5671
|
+
stmt.free();
|
|
4946
5672
|
return results;
|
|
4947
5673
|
}
|
|
4948
5674
|
async withTransaction(callback) {
|
|
4949
|
-
await this.
|
|
4950
|
-
if (!this.db)
|
|
5675
|
+
await this.readyPromise;
|
|
5676
|
+
if (!this.db)
|
|
5677
|
+
throw new Error("SQL.js Database instance not available for transaction");
|
|
4951
5678
|
let transactionStartedHere = false;
|
|
4952
5679
|
await this.mutex.runExclusive(async () => {
|
|
4953
5680
|
if (this.numOpenTransactions === 0) {
|
|
4954
|
-
this.db
|
|
5681
|
+
if (!this.db) throw new Error("SQL.js DB not available for BEGIN");
|
|
5682
|
+
this.db.run("BEGIN TRANSACTION");
|
|
4955
5683
|
transactionStartedHere = true;
|
|
4956
5684
|
}
|
|
4957
5685
|
this.numOpenTransactions++;
|
|
4958
5686
|
});
|
|
4959
|
-
const transactionalOps = new
|
|
4960
|
-
this.db,
|
|
4961
|
-
this
|
|
4962
|
-
);
|
|
5687
|
+
const transactionalOps = new TransactionalSQLJSOperations(this.db, this);
|
|
4963
5688
|
try {
|
|
4964
5689
|
const result = await callback(transactionalOps);
|
|
4965
5690
|
await this.mutex.runExclusive(async () => {
|
|
4966
5691
|
this.numOpenTransactions--;
|
|
4967
5692
|
if (this.numOpenTransactions === 0) {
|
|
4968
|
-
this.db
|
|
5693
|
+
if (!this.db) throw new Error("SQL.js DB not available for COMMIT");
|
|
5694
|
+
this.db.run("COMMIT");
|
|
4969
5695
|
}
|
|
4970
5696
|
});
|
|
4971
5697
|
return result;
|
|
4972
5698
|
} catch (error) {
|
|
4973
|
-
|
|
4974
|
-
"[NodeSqliteEngine] Error in transaction, rolling back:",
|
|
4975
|
-
error
|
|
4976
|
-
);
|
|
5699
|
+
Logger.error("[SqljsEngine] Error in transaction, rolling back:", error);
|
|
4977
5700
|
await this.mutex.runExclusive(async () => {
|
|
4978
5701
|
if (this.db && this.numOpenTransactions > 0 && transactionStartedHere) {
|
|
4979
5702
|
try {
|
|
4980
|
-
this.db
|
|
5703
|
+
if (!this.db)
|
|
5704
|
+
throw new Error("SQL.js DB not available for ROLLBACK");
|
|
5705
|
+
this.db.run("ROLLBACK");
|
|
4981
5706
|
} catch (rollbackError) {
|
|
4982
|
-
|
|
4983
|
-
"[NodeSqliteEngine] Error during ROLLBACK:",
|
|
4984
|
-
rollbackError
|
|
4985
|
-
);
|
|
5707
|
+
Logger.error("[SqljsEngine] Error during ROLLBACK:", rollbackError);
|
|
4986
5708
|
}
|
|
4987
5709
|
}
|
|
4988
5710
|
this.numOpenTransactions = 0;
|
|
@@ -4993,19 +5715,19 @@ var init_NodeSqliteEngine = __esm({
|
|
|
4993
5715
|
async close() {
|
|
4994
5716
|
await this.readyPromise;
|
|
4995
5717
|
if (this.db) {
|
|
4996
|
-
Logger.info("[NodeSqliteEngine] Closing SQLite database...");
|
|
4997
5718
|
this.db.close();
|
|
4998
5719
|
this.db = null;
|
|
4999
|
-
|
|
5720
|
+
_SqljsEngine.SQL = null;
|
|
5000
5721
|
}
|
|
5001
5722
|
}
|
|
5002
|
-
async destroy() {
|
|
5003
|
-
await this.close();
|
|
5004
|
-
this.tableNames.clear();
|
|
5005
|
-
Logger.info("[NodeSqliteEngine] SQLite engine destroyed.");
|
|
5006
|
-
}
|
|
5007
5723
|
async getTableSchema(_tableName) {
|
|
5008
|
-
return
|
|
5724
|
+
return void 0;
|
|
5725
|
+
}
|
|
5726
|
+
async destroy() {
|
|
5727
|
+
if (this.db) {
|
|
5728
|
+
this.db.close();
|
|
5729
|
+
Logger.info("[SqljsEngine] Database closed.");
|
|
5730
|
+
}
|
|
5009
5731
|
}
|
|
5010
5732
|
getLastErrorMessage() {
|
|
5011
5733
|
return void 0;
|
|
@@ -5014,996 +5736,680 @@ var init_NodeSqliteEngine = __esm({
|
|
|
5014
5736
|
}
|
|
5015
5737
|
});
|
|
5016
5738
|
|
|
5017
|
-
// src/engines/
|
|
5018
|
-
var
|
|
5019
|
-
__export(
|
|
5020
|
-
|
|
5739
|
+
// src/engines/node/NodeSqliteEngine.ts
|
|
5740
|
+
var NodeSqliteEngine_exports = {};
|
|
5741
|
+
__export(NodeSqliteEngine_exports, {
|
|
5742
|
+
NodeSqliteEngine: () => NodeSqliteEngine
|
|
5021
5743
|
});
|
|
5022
|
-
|
|
5023
|
-
var
|
|
5024
|
-
|
|
5744
|
+
import { Mutex as Mutex2 } from "async-mutex";
|
|
5745
|
+
var TransactionalNodeSqliteOperations, NodeSqliteEngine;
|
|
5746
|
+
var init_NodeSqliteEngine = __esm({
|
|
5747
|
+
"src/engines/node/NodeSqliteEngine.ts"() {
|
|
5025
5748
|
"use strict";
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5749
|
+
init_BaseModel();
|
|
5750
|
+
init_sql();
|
|
5751
|
+
TransactionalNodeSqliteOperations = class {
|
|
5752
|
+
db;
|
|
5753
|
+
engine;
|
|
5754
|
+
constructor(db, engine) {
|
|
5755
|
+
this.db = db;
|
|
5756
|
+
this.engine = engine;
|
|
5757
|
+
}
|
|
5758
|
+
async insert(modelName, data) {
|
|
5759
|
+
const tableName = this.engine.getTableName(modelName);
|
|
5760
|
+
const columns = Object.keys(data);
|
|
5761
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
5762
|
+
const values = Object.values(data).map((value) => {
|
|
5763
|
+
if (typeof value === "boolean") {
|
|
5764
|
+
return value ? 1 : 0;
|
|
5765
|
+
}
|
|
5766
|
+
return value;
|
|
5767
|
+
});
|
|
5768
|
+
const insertSQL = `INSERT OR REPLACE INTO ${tableName} (${columns.join(
|
|
5769
|
+
", "
|
|
5770
|
+
)}) VALUES (${placeholders})`;
|
|
5771
|
+
const stmt = this.db.prepare(insertSQL);
|
|
5772
|
+
stmt.run(...values);
|
|
5773
|
+
}
|
|
5774
|
+
async delete(modelName, id) {
|
|
5775
|
+
const tableName = this.engine.getTableName(modelName);
|
|
5776
|
+
const deleteSQL = `DELETE FROM ${tableName} WHERE id = ?`;
|
|
5777
|
+
const stmt = this.db.prepare(deleteSQL);
|
|
5778
|
+
stmt.run(id);
|
|
5779
|
+
}
|
|
5780
|
+
async query(sql, params) {
|
|
5781
|
+
const stmt = this.db.prepare(sql);
|
|
5782
|
+
const results = params ? stmt.all(...params) : stmt.all();
|
|
5783
|
+
return results;
|
|
5784
|
+
}
|
|
5785
|
+
};
|
|
5786
|
+
NodeSqliteEngine = class {
|
|
5787
|
+
db = null;
|
|
5788
|
+
tableNames = /* @__PURE__ */ new Set();
|
|
5789
|
+
mutex = new Mutex2();
|
|
5790
|
+
readyPromise;
|
|
5791
|
+
betterSqlite3 = null;
|
|
5792
|
+
numOpenTransactions = 0;
|
|
5793
|
+
constructor(options) {
|
|
5794
|
+
Logger.debug("[NodeSqliteEngine] Constructor called. Initializing...");
|
|
5795
|
+
this.readyPromise = this.initialize(options);
|
|
5796
|
+
}
|
|
5797
|
+
async ensureReady() {
|
|
5798
|
+
return this.readyPromise;
|
|
5799
|
+
}
|
|
5800
|
+
async initialize(options) {
|
|
5801
|
+
Logger.info("[NodeSqliteEngine] Initializing Node.js SQLite engine...");
|
|
5035
5802
|
try {
|
|
5036
|
-
const
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5803
|
+
const betterSqlite3Module = await import("better-sqlite3");
|
|
5804
|
+
this.betterSqlite3 = betterSqlite3Module.default || betterSqlite3Module;
|
|
5805
|
+
const filePath = options?.filePath || ":memory:";
|
|
5806
|
+
const dbOptions = options?.options || {};
|
|
5807
|
+
Logger.debug(
|
|
5808
|
+
`[NodeSqliteEngine] Opening SQLite database at: ${filePath}`
|
|
5809
|
+
);
|
|
5810
|
+
this.db = new this.betterSqlite3(filePath, dbOptions);
|
|
5811
|
+
if (!this.db) {
|
|
5812
|
+
throw new Error("Failed to create SQLite database instance");
|
|
5813
|
+
}
|
|
5814
|
+
if (filePath !== ":memory:") {
|
|
5815
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
5043
5816
|
}
|
|
5817
|
+
Logger.info(
|
|
5818
|
+
"[NodeSqliteEngine] SQLite database initialized successfully."
|
|
5819
|
+
);
|
|
5820
|
+
this.updateTableNames();
|
|
5044
5821
|
} catch (error) {
|
|
5045
|
-
|
|
5046
|
-
"[
|
|
5822
|
+
Logger.error(
|
|
5823
|
+
"[NodeSqliteEngine] Error during SQLite initialization:",
|
|
5047
5824
|
error
|
|
5048
5825
|
);
|
|
5826
|
+
throw new Error(
|
|
5827
|
+
`Failed to initialize Node.js SQLite engine: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
5828
|
+
);
|
|
5049
5829
|
}
|
|
5050
5830
|
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
const hasBetterSqlite3 = await features.hasBetterSqlite3();
|
|
5056
|
-
if (hasBetterSqlite3) {
|
|
5057
|
-
return "node-sqlite";
|
|
5058
|
-
}
|
|
5059
|
-
if (features.hasWebAssembly) {
|
|
5060
|
-
return "sqljs";
|
|
5061
|
-
}
|
|
5062
|
-
throw new Error(
|
|
5063
|
-
"No compatible database engine found. Please install better-sqlite3."
|
|
5831
|
+
updateTableNames() {
|
|
5832
|
+
if (!this.db) return;
|
|
5833
|
+
const stmt = this.db.prepare(
|
|
5834
|
+
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
5064
5835
|
);
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
console.warn(
|
|
5072
|
-
`[NodeDatabaseFactory] Requested engine '${requestedType}' is not available. Falling back to '${fallbackType}'.`
|
|
5836
|
+
const tables = stmt.all();
|
|
5837
|
+
this.tableNames.clear();
|
|
5838
|
+
tables.forEach((table) => this.tableNames.add(table.name));
|
|
5839
|
+
Logger.debug(
|
|
5840
|
+
"[NodeSqliteEngine] Updated table names:",
|
|
5841
|
+
Array.from(this.tableNames)
|
|
5073
5842
|
);
|
|
5074
|
-
const fallbackConfig = { ...config, type: fallbackType };
|
|
5075
|
-
return this.createEngine(fallbackConfig, false);
|
|
5076
5843
|
}
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
if (!hasBetterSqlite3) {
|
|
5091
|
-
if (allowFallback) {
|
|
5092
|
-
console.warn(
|
|
5093
|
-
"[NodeDatabaseFactory] better-sqlite3 not installed, falling back to alternative engine"
|
|
5094
|
-
);
|
|
5095
|
-
return this.createFallbackEngine(type, config);
|
|
5096
|
-
}
|
|
5097
|
-
throw new Error(
|
|
5098
|
-
"better-sqlite3 package is required for node-sqlite engine. Install it with: npm install better-sqlite3"
|
|
5099
|
-
);
|
|
5100
|
-
}
|
|
5101
|
-
throw new Error(
|
|
5102
|
-
"NodeSqliteEngine not loaded. This should not happen."
|
|
5103
|
-
);
|
|
5104
|
-
}
|
|
5105
|
-
const nodeSqliteEngine = new NodeSqliteEngine2(options);
|
|
5106
|
-
await nodeSqliteEngine.ensureReady();
|
|
5107
|
-
return nodeSqliteEngine;
|
|
5108
|
-
case "alasql":
|
|
5109
|
-
throw new Error("AlaSQLEngine not yet implemented.");
|
|
5844
|
+
getTableName(modelName) {
|
|
5845
|
+
return `model_${modelName.toLowerCase()}`;
|
|
5846
|
+
}
|
|
5847
|
+
mapTypeToSQL(type) {
|
|
5848
|
+
switch (type.toLowerCase()) {
|
|
5849
|
+
case "string":
|
|
5850
|
+
return "TEXT";
|
|
5851
|
+
case "number":
|
|
5852
|
+
return "REAL";
|
|
5853
|
+
case "boolean":
|
|
5854
|
+
return "INTEGER";
|
|
5855
|
+
case "date":
|
|
5856
|
+
return "TEXT";
|
|
5110
5857
|
default:
|
|
5111
|
-
|
|
5112
|
-
if (typeStr === "duckdb") {
|
|
5113
|
-
if (allowFallback) {
|
|
5114
|
-
console.warn(
|
|
5115
|
-
"[NodeDatabaseFactory] DuckDB-WASM not available in Node.js, falling back to alternative engine"
|
|
5116
|
-
);
|
|
5117
|
-
return this.createFallbackEngine(typeStr, config);
|
|
5118
|
-
}
|
|
5119
|
-
throw new Error(
|
|
5120
|
-
"DuckDB-WASM engine is only available in browser environments"
|
|
5121
|
-
);
|
|
5122
|
-
}
|
|
5123
|
-
if (typeStr === "node-duckdb") {
|
|
5124
|
-
if (allowFallback) {
|
|
5125
|
-
console.warn(
|
|
5126
|
-
"[NodeDatabaseFactory] Node DuckDB engine is no longer supported, falling back to alternative engine"
|
|
5127
|
-
);
|
|
5128
|
-
return this.createFallbackEngine(typeStr, config);
|
|
5129
|
-
}
|
|
5130
|
-
throw new Error("Node DuckDB engine is no longer supported");
|
|
5131
|
-
}
|
|
5132
|
-
throw new Error(`Unsupported database engine type: ${typeStr}`);
|
|
5858
|
+
return "TEXT";
|
|
5133
5859
|
}
|
|
5134
5860
|
}
|
|
5135
|
-
|
|
5136
|
-
await this.
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5861
|
+
async createTable(modelName, schema, _options) {
|
|
5862
|
+
await this.ensureReady();
|
|
5863
|
+
if (!this.db) throw new Error("SQLite not initialized for createTable");
|
|
5864
|
+
const tableName = this.getTableName(modelName);
|
|
5865
|
+
const quotedTableName = quoteIdentifier(tableName);
|
|
5866
|
+
const idColumn = quoteIdentifier("id");
|
|
5867
|
+
const typeColumn = quoteIdentifier("type");
|
|
5868
|
+
if (this.tableNames.has(tableName)) {
|
|
5869
|
+
Logger.debug(
|
|
5870
|
+
`[NodeSqliteEngine] Table ${tableName} already exists, skipping creation.`
|
|
5141
5871
|
);
|
|
5872
|
+
return;
|
|
5142
5873
|
}
|
|
5143
|
-
|
|
5144
|
-
|
|
5874
|
+
const columns = Array.from(schema.entries()).filter(([field]) => field !== "id" && field !== "type").map(
|
|
5875
|
+
([field, fieldOptions]) => `${quoteIdentifier(field)} ${this.mapTypeToSQL(fieldOptions.type)}`
|
|
5145
5876
|
);
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5877
|
+
const metadataColumns = [
|
|
5878
|
+
`${quoteIdentifier("_meta_doc_id")} TEXT`,
|
|
5879
|
+
`${quoteIdentifier("_meta_permission_hint")} TEXT`
|
|
5880
|
+
];
|
|
5881
|
+
const createTableSQL = `CREATE TABLE IF NOT EXISTS ${quotedTableName} (
|
|
5882
|
+
${idColumn} TEXT PRIMARY KEY,
|
|
5883
|
+
${typeColumn} TEXT,
|
|
5884
|
+
${columns.join(", ")}${columns.length > 0 ? ", " : ""}${metadataColumns.join(", ")}
|
|
5885
|
+
)`;
|
|
5886
|
+
Logger.debug(
|
|
5887
|
+
`[NodeSqliteEngine] Creating table ${tableName}:`,
|
|
5888
|
+
createTableSQL
|
|
5889
|
+
);
|
|
5890
|
+
this.db.exec(createTableSQL);
|
|
5891
|
+
for (const [field, fieldOptions] of schema.entries()) {
|
|
5892
|
+
if (fieldOptions && fieldOptions.indexed && field !== "id") {
|
|
5893
|
+
const indexName = `idx_${tableName}_${field}`;
|
|
5894
|
+
const quotedIndexName = quoteIdentifier(indexName);
|
|
5895
|
+
const quotedField = quoteIdentifier(field);
|
|
5896
|
+
const createIndexSQL = `CREATE INDEX IF NOT EXISTS ${quotedIndexName} ON ${quotedTableName} (${quotedField})`;
|
|
5897
|
+
Logger.debug(`[NodeSqliteEngine] Creating index ${indexName}`);
|
|
5898
|
+
this.db.exec(createIndexSQL);
|
|
5899
|
+
}
|
|
5158
5900
|
}
|
|
5901
|
+
const docIdIndexName = `idx_${tableName}_meta_doc_id`;
|
|
5902
|
+
const docIdIndexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
5903
|
+
docIdIndexName
|
|
5904
|
+
)} ON ${quotedTableName} (${quoteIdentifier("_meta_doc_id")})`;
|
|
5905
|
+
this.db.exec(docIdIndexSQL);
|
|
5906
|
+
this.updateTableNames();
|
|
5907
|
+
Logger.info(`[NodeSqliteEngine] Table ${tableName} created successfully.`);
|
|
5159
5908
|
}
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
return engines;
|
|
5178
|
-
}
|
|
5179
|
-
};
|
|
5180
|
-
}
|
|
5181
|
-
});
|
|
5182
|
-
|
|
5183
|
-
// src/engines/BrowserDatabaseFactory.ts
|
|
5184
|
-
var BrowserDatabaseFactory_exports = {};
|
|
5185
|
-
__export(BrowserDatabaseFactory_exports, {
|
|
5186
|
-
BrowserDatabaseFactory: () => BrowserDatabaseFactory
|
|
5187
|
-
});
|
|
5188
|
-
var BrowserDatabaseFactory;
|
|
5189
|
-
var init_BrowserDatabaseFactory = __esm({
|
|
5190
|
-
"src/engines/BrowserDatabaseFactory.ts"() {
|
|
5191
|
-
"use strict";
|
|
5192
|
-
init_SqljsEngine();
|
|
5193
|
-
init_environment();
|
|
5194
|
-
init_BaseModel();
|
|
5195
|
-
BrowserDatabaseFactory = class {
|
|
5196
|
-
/**
|
|
5197
|
-
* Dynamically loads browser-compatible engines only
|
|
5198
|
-
*/
|
|
5199
|
-
static async loadBrowserEngines() {
|
|
5200
|
-
Logger.debug("[BrowserDatabaseFactory] Browser engines ready");
|
|
5201
|
-
}
|
|
5202
|
-
/**
|
|
5203
|
-
* Auto-detects the best available engine for browser
|
|
5204
|
-
*/
|
|
5205
|
-
static getRecommendedEngineType() {
|
|
5206
|
-
if (features.hasWebAssembly) {
|
|
5207
|
-
return "sqljs";
|
|
5909
|
+
async createStringSetJunctionTable(modelName, fieldName) {
|
|
5910
|
+
await this.ensureReady();
|
|
5911
|
+
if (!this.db)
|
|
5912
|
+
throw new Error(
|
|
5913
|
+
"SQLite not initialized for createStringSetJunctionTable"
|
|
5914
|
+
);
|
|
5915
|
+
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
5916
|
+
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
5917
|
+
const foreignKeyColumn = `${modelName.toLowerCase()}_id`;
|
|
5918
|
+
const quotedForeignKey = quoteIdentifier(foreignKeyColumn);
|
|
5919
|
+
const quotedIdColumn = quoteIdentifier("id");
|
|
5920
|
+
const quotedValueColumn = quoteIdentifier("value");
|
|
5921
|
+
if (this.tableNames.has(junctionTableName)) {
|
|
5922
|
+
Logger.debug(
|
|
5923
|
+
`[NodeSqliteEngine] StringSet junction table ${junctionTableName} already exists, skipping creation.`
|
|
5924
|
+
);
|
|
5925
|
+
return;
|
|
5208
5926
|
}
|
|
5209
|
-
|
|
5210
|
-
|
|
5927
|
+
const createTableSQL = `CREATE TABLE IF NOT EXISTS ${quotedJunctionTable} (
|
|
5928
|
+
${quotedIdColumn} TEXT PRIMARY KEY,
|
|
5929
|
+
${quotedForeignKey} TEXT NOT NULL,
|
|
5930
|
+
${quotedValueColumn} TEXT NOT NULL,
|
|
5931
|
+
UNIQUE(${quotedForeignKey}, ${quotedValueColumn})
|
|
5932
|
+
)`;
|
|
5933
|
+
Logger.info(
|
|
5934
|
+
`[NodeSqliteEngine] Creating StringSet junction table ${junctionTableName} for ${modelName}.${fieldName}`
|
|
5211
5935
|
);
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5936
|
+
this.db.exec(createTableSQL);
|
|
5937
|
+
const indexName = `idx_${junctionTableName}_${modelName.toLowerCase()}_id`;
|
|
5938
|
+
const indexSQL = `CREATE INDEX IF NOT EXISTS ${quoteIdentifier(
|
|
5939
|
+
indexName
|
|
5940
|
+
)} ON ${quotedJunctionTable} (${quotedForeignKey})`;
|
|
5941
|
+
this.db.exec(indexSQL);
|
|
5942
|
+
this.updateTableNames();
|
|
5943
|
+
Logger.info(
|
|
5944
|
+
`[NodeSqliteEngine] StringSet junction table ${junctionTableName} created successfully.`
|
|
5220
5945
|
);
|
|
5221
|
-
const fallbackConfig = { ...config, type: fallbackType };
|
|
5222
|
-
return this.createEngine(fallbackConfig, false);
|
|
5223
5946
|
}
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
const
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
if (allowFallback) {
|
|
5240
|
-
console.warn(
|
|
5241
|
-
"[BrowserDatabaseFactory] DuckDB engine is no longer supported, falling back to alternative engine"
|
|
5242
|
-
);
|
|
5243
|
-
return this.createFallbackEngine(typeStr, config);
|
|
5244
|
-
}
|
|
5245
|
-
throw new Error("DuckDB engine is no longer supported");
|
|
5246
|
-
}
|
|
5247
|
-
if (typeStr === "node-sqlite" || typeStr === "node-duckdb") {
|
|
5248
|
-
if (allowFallback) {
|
|
5249
|
-
console.warn(
|
|
5250
|
-
`[BrowserDatabaseFactory] Engine '${typeStr}' is not available in browser. Falling back to browser-compatible engine.`
|
|
5251
|
-
);
|
|
5252
|
-
return this.createFallbackEngine(typeStr, config);
|
|
5253
|
-
}
|
|
5254
|
-
throw new Error(
|
|
5255
|
-
`Engine '${typeStr}' is only available in Node.js environments`
|
|
5256
|
-
);
|
|
5257
|
-
}
|
|
5258
|
-
throw new Error(`Unsupported database engine type: ${typeStr}`);
|
|
5947
|
+
async insertStringSetValues(modelName, fieldName, recordId, values) {
|
|
5948
|
+
await this.ensureReady();
|
|
5949
|
+
if (!this.db)
|
|
5950
|
+
throw new Error("SQLite not initialized for insertStringSetValues");
|
|
5951
|
+
if (values.length === 0) return;
|
|
5952
|
+
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
5953
|
+
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
5954
|
+
const quotedIdColumn = quoteIdentifier("id");
|
|
5955
|
+
const quotedForeignKey = quoteIdentifier(`${modelName.toLowerCase()}_id`);
|
|
5956
|
+
const quotedValueColumn = quoteIdentifier("value");
|
|
5957
|
+
const insertSQL = `INSERT OR IGNORE INTO ${quotedJunctionTable} (${quotedIdColumn}, ${quotedForeignKey}, ${quotedValueColumn}) VALUES (?, ?, ?)`;
|
|
5958
|
+
const stmt = this.db.prepare(insertSQL);
|
|
5959
|
+
for (const value of values) {
|
|
5960
|
+
const id = `${recordId}_${value}`;
|
|
5961
|
+
stmt.run(id, recordId, value);
|
|
5259
5962
|
}
|
|
5260
5963
|
}
|
|
5261
|
-
|
|
5262
|
-
await this.
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
);
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
`[BrowserDatabaseFactory] Failed to create engine for type ${config.type}:`,
|
|
5275
|
-
error
|
|
5276
|
-
);
|
|
5277
|
-
throw error;
|
|
5964
|
+
async removeStringSetValues(modelName, fieldName, recordId, values) {
|
|
5965
|
+
await this.ensureReady();
|
|
5966
|
+
if (!this.db)
|
|
5967
|
+
throw new Error("SQLite not initialized for removeStringSetValues");
|
|
5968
|
+
if (values.length === 0) return;
|
|
5969
|
+
const junctionTableName = `${this.getTableName(modelName)}_${fieldName}`;
|
|
5970
|
+
const quotedJunctionTable = quoteIdentifier(junctionTableName);
|
|
5971
|
+
const quotedForeignKey = quoteIdentifier(`${modelName.toLowerCase()}_id`);
|
|
5972
|
+
const quotedValueColumn = quoteIdentifier("value");
|
|
5973
|
+
const deleteSQL = `DELETE FROM ${quotedJunctionTable} WHERE ${quotedForeignKey} = ? AND ${quotedValueColumn} = ?`;
|
|
5974
|
+
const stmt = this.db.prepare(deleteSQL);
|
|
5975
|
+
for (const value of values) {
|
|
5976
|
+
stmt.run(recordId, value);
|
|
5278
5977
|
}
|
|
5279
5978
|
}
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
const
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
type: "node-sqlite",
|
|
5292
|
-
available: false,
|
|
5293
|
-
reason: "Node.js engines not available in browser"
|
|
5979
|
+
async insert(modelName, data) {
|
|
5980
|
+
await this.ensureReady();
|
|
5981
|
+
if (!this.db) throw new Error("SQLite not initialized for insert");
|
|
5982
|
+
const tableName = this.getTableName(modelName);
|
|
5983
|
+
const quotedTableName = quoteIdentifier(tableName);
|
|
5984
|
+
const columns = Object.keys(data);
|
|
5985
|
+
const quotedColumns = columns.map((column) => quoteIdentifier(column));
|
|
5986
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
5987
|
+
const values = Object.values(data).map((value) => {
|
|
5988
|
+
if (typeof value === "boolean") {
|
|
5989
|
+
return value ? 1 : 0;
|
|
5294
5990
|
}
|
|
5295
|
-
|
|
5296
|
-
|
|
5991
|
+
return value;
|
|
5992
|
+
});
|
|
5993
|
+
const insertSQL = `INSERT OR REPLACE INTO ${quotedTableName} (${quotedColumns.join(
|
|
5994
|
+
", "
|
|
5995
|
+
)}) VALUES (${placeholders})`;
|
|
5996
|
+
const stmt = this.db.prepare(insertSQL);
|
|
5997
|
+
stmt.run(...values);
|
|
5297
5998
|
}
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
5308
|
-
return async function() {
|
|
5309
|
-
const relatedId = this[config.relatedIdField];
|
|
5310
|
-
if (relatedId === null || typeof relatedId === "undefined") return null;
|
|
5311
|
-
return targetModelClass.find(relatedId);
|
|
5312
|
-
};
|
|
5313
|
-
}
|
|
5314
|
-
function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbEngine) {
|
|
5315
|
-
return async function(paginationArgs) {
|
|
5316
|
-
const {
|
|
5317
|
-
limit,
|
|
5318
|
-
afterCursor,
|
|
5319
|
-
beforeCursor,
|
|
5320
|
-
direction = "forward"
|
|
5321
|
-
} = paginationArgs || {};
|
|
5322
|
-
const {
|
|
5323
|
-
relatedIdField,
|
|
5324
|
-
orderByField = "id",
|
|
5325
|
-
orderDirection = "ASC"
|
|
5326
|
-
} = config;
|
|
5327
|
-
const targetSchema = targetModelClass.getSchema();
|
|
5328
|
-
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
5329
|
-
Logger.error(
|
|
5330
|
-
`[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
|
|
5331
|
-
);
|
|
5332
|
-
return { data: [], nextCursor: null, prevCursor: null };
|
|
5333
|
-
}
|
|
5334
|
-
const filter = { [relatedIdField]: this.id };
|
|
5335
|
-
const queryOptions = {
|
|
5336
|
-
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
|
|
5337
|
-
};
|
|
5338
|
-
if (limit !== void 0) {
|
|
5339
|
-
queryOptions.limit = limit + 1;
|
|
5340
|
-
}
|
|
5341
|
-
if (direction === "forward" && afterCursor) {
|
|
5342
|
-
filter[orderByField] = {
|
|
5343
|
-
[orderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
|
|
5344
|
-
};
|
|
5345
|
-
} else if (direction === "backward") {
|
|
5346
|
-
if (beforeCursor) {
|
|
5347
|
-
filter[orderByField] = {
|
|
5348
|
-
[orderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
5349
|
-
};
|
|
5999
|
+
async delete(modelName, id) {
|
|
6000
|
+
await this.ensureReady();
|
|
6001
|
+
if (!this.db) throw new Error("SQLite not initialized for delete");
|
|
6002
|
+
const tableName = this.getTableName(modelName);
|
|
6003
|
+
const deleteSQL = `DELETE FROM ${quoteIdentifier(
|
|
6004
|
+
tableName
|
|
6005
|
+
)} WHERE ${quoteIdentifier("id")} = ?`;
|
|
6006
|
+
const stmt = this.db.prepare(deleteSQL);
|
|
6007
|
+
stmt.run(id);
|
|
5350
6008
|
}
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
results.pop();
|
|
5363
|
-
}
|
|
5364
|
-
if (results.length > 0 && !isFirstPage) {
|
|
5365
|
-
prevCursor = results[0][orderByField];
|
|
5366
|
-
}
|
|
5367
|
-
} else {
|
|
5368
|
-
if (results.length > limit) {
|
|
5369
|
-
prevCursor = results[limit - 1][orderByField];
|
|
5370
|
-
results.pop();
|
|
5371
|
-
}
|
|
5372
|
-
results.reverse();
|
|
5373
|
-
if (results.length > 0 && beforeCursor) {
|
|
5374
|
-
nextCursor = results[results.length - 1][orderByField];
|
|
6009
|
+
async deleteByDocumentId(modelName, docId) {
|
|
6010
|
+
await this.ensureReady();
|
|
6011
|
+
if (!this.db)
|
|
6012
|
+
throw new Error("SQLite not initialized for deleteByDocumentId");
|
|
6013
|
+
const tableName = this.getTableName(modelName);
|
|
6014
|
+
const quotedTableName = quoteIdentifier(tableName);
|
|
6015
|
+
if (!this.tableNames.has(tableName)) {
|
|
6016
|
+
Logger.debug(
|
|
6017
|
+
`[NodeSqliteEngine] Skipping deleteByDocumentId for ${tableName}; table does not exist.`
|
|
6018
|
+
);
|
|
6019
|
+
return;
|
|
5375
6020
|
}
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
6021
|
+
const deleteSQL = `DELETE FROM ${quotedTableName} WHERE ${quoteIdentifier(
|
|
6022
|
+
"_meta_doc_id"
|
|
6023
|
+
)} = ?`;
|
|
6024
|
+
const stmt = this.db.prepare(deleteSQL);
|
|
6025
|
+
stmt.run(docId);
|
|
6026
|
+
for (const existingTableName of this.tableNames) {
|
|
6027
|
+
if (existingTableName.startsWith(`${tableName}_`)) {
|
|
6028
|
+
const quotedExistingTable = quoteIdentifier(existingTableName);
|
|
6029
|
+
const quotedForeignKey = quoteIdentifier(`${modelName.toLowerCase()}_id`);
|
|
6030
|
+
const junctionDeleteSQL = `DELETE FROM ${quotedExistingTable} WHERE ${quotedForeignKey} IN (
|
|
6031
|
+
SELECT ${quoteIdentifier("id")} FROM ${quotedTableName} WHERE ${quoteIdentifier(
|
|
6032
|
+
"_meta_doc_id"
|
|
6033
|
+
)} = ?
|
|
6034
|
+
)`;
|
|
6035
|
+
try {
|
|
6036
|
+
const junctionStmt = this.db.prepare(junctionDeleteSQL);
|
|
6037
|
+
const result = junctionStmt.run(docId);
|
|
6038
|
+
Logger.info(
|
|
6039
|
+
`[NodeSqliteEngine] Cleared junction table ${existingTableName} for document ${docId}; removed ${result.changes} rows.`
|
|
6040
|
+
);
|
|
6041
|
+
} catch (error) {
|
|
6042
|
+
console.warn(
|
|
6043
|
+
`[NodeSqliteEngine] Warning: Could not clean up junction table ${existingTableName}:`,
|
|
6044
|
+
error
|
|
6045
|
+
);
|
|
6046
|
+
}
|
|
5379
6047
|
}
|
|
5380
6048
|
}
|
|
5381
6049
|
}
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
const {
|
|
5389
|
-
limit,
|
|
5390
|
-
afterCursor,
|
|
5391
|
-
beforeCursor,
|
|
5392
|
-
direction = "forward"
|
|
5393
|
-
} = paginationArgs || {};
|
|
5394
|
-
const joinSchema = joinModelClass.getSchema();
|
|
5395
|
-
const targetSchema = targetModelClass.getSchema();
|
|
5396
|
-
if (!joinSchema || !joinSchema.options || !joinSchema.options.name) {
|
|
5397
|
-
Logger.error(
|
|
5398
|
-
`[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
5399
|
-
);
|
|
5400
|
-
return { data: [], nextCursor: null, prevCursor: null };
|
|
5401
|
-
}
|
|
5402
|
-
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
5403
|
-
Logger.error(
|
|
5404
|
-
`[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
5405
|
-
);
|
|
5406
|
-
return { data: [], nextCursor: null, prevCursor: null };
|
|
5407
|
-
}
|
|
5408
|
-
const {
|
|
5409
|
-
joinModelLocalField,
|
|
5410
|
-
joinModelRelatedField,
|
|
5411
|
-
joinModelOrderByField = "id",
|
|
5412
|
-
joinModelOrderDirection = "ASC"
|
|
5413
|
-
} = config;
|
|
5414
|
-
const joinFilter = { [joinModelLocalField]: this.id };
|
|
5415
|
-
const joinQueryOptions = {
|
|
5416
|
-
sort: {
|
|
5417
|
-
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
|
|
5418
|
-
},
|
|
5419
|
-
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
|
|
5420
|
-
};
|
|
5421
|
-
if (limit !== void 0) {
|
|
5422
|
-
joinQueryOptions.limit = limit + 1;
|
|
5423
|
-
}
|
|
5424
|
-
if (direction === "forward" && afterCursor) {
|
|
5425
|
-
joinFilter[joinModelOrderByField] = {
|
|
5426
|
-
[joinModelOrderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
|
|
5427
|
-
};
|
|
5428
|
-
} else if (direction === "backward") {
|
|
5429
|
-
if (beforeCursor) {
|
|
5430
|
-
joinFilter[joinModelOrderByField] = {
|
|
5431
|
-
[joinModelOrderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
5432
|
-
};
|
|
6050
|
+
async query(sql, params) {
|
|
6051
|
+
await this.ensureReady();
|
|
6052
|
+
if (!this.db) throw new Error("SQLite not initialized for query");
|
|
6053
|
+
const stmt = this.db.prepare(sql);
|
|
6054
|
+
const results = params ? stmt.all(...params) : stmt.all();
|
|
6055
|
+
return results;
|
|
5433
6056
|
}
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
const joinResults = joinResult.data;
|
|
5443
|
-
let nextCursorFromJoin = null;
|
|
5444
|
-
let prevCursorFromJoin = null;
|
|
5445
|
-
const isFirstPage = direction === "forward" && !afterCursor;
|
|
5446
|
-
if (limit !== void 0) {
|
|
5447
|
-
if (direction === "forward") {
|
|
5448
|
-
if (joinResults.length > limit) {
|
|
5449
|
-
nextCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
5450
|
-
joinResults.pop();
|
|
5451
|
-
}
|
|
5452
|
-
if (joinResults.length > 0 && !isFirstPage) {
|
|
5453
|
-
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
5454
|
-
}
|
|
5455
|
-
} else {
|
|
5456
|
-
if (joinResults.length > limit) {
|
|
5457
|
-
prevCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
5458
|
-
joinResults.pop();
|
|
5459
|
-
}
|
|
5460
|
-
joinResults.reverse();
|
|
5461
|
-
if (joinResults.length > 0 && beforeCursor) {
|
|
5462
|
-
nextCursorFromJoin = joinResults[joinResults.length - 1][joinModelOrderByField];
|
|
5463
|
-
}
|
|
5464
|
-
if (joinResults.length > 0 && !beforeCursor) {
|
|
5465
|
-
if (joinResults.length === limit) {
|
|
5466
|
-
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
6057
|
+
async withTransaction(callback) {
|
|
6058
|
+
await this.ensureReady();
|
|
6059
|
+
if (!this.db) throw new Error("SQLite not initialized for transaction");
|
|
6060
|
+
let transactionStartedHere = false;
|
|
6061
|
+
await this.mutex.runExclusive(async () => {
|
|
6062
|
+
if (this.numOpenTransactions === 0) {
|
|
6063
|
+
this.db.exec("BEGIN TRANSACTION");
|
|
6064
|
+
transactionStartedHere = true;
|
|
5467
6065
|
}
|
|
6066
|
+
this.numOpenTransactions++;
|
|
6067
|
+
});
|
|
6068
|
+
const transactionalOps = new TransactionalNodeSqliteOperations(
|
|
6069
|
+
this.db,
|
|
6070
|
+
this
|
|
6071
|
+
);
|
|
6072
|
+
try {
|
|
6073
|
+
const result = await callback(transactionalOps);
|
|
6074
|
+
await this.mutex.runExclusive(async () => {
|
|
6075
|
+
this.numOpenTransactions--;
|
|
6076
|
+
if (this.numOpenTransactions === 0) {
|
|
6077
|
+
this.db.exec("COMMIT");
|
|
6078
|
+
}
|
|
6079
|
+
});
|
|
6080
|
+
return result;
|
|
6081
|
+
} catch (error) {
|
|
6082
|
+
console.error(
|
|
6083
|
+
"[NodeSqliteEngine] Error in transaction, rolling back:",
|
|
6084
|
+
error
|
|
6085
|
+
);
|
|
6086
|
+
await this.mutex.runExclusive(async () => {
|
|
6087
|
+
if (this.db && this.numOpenTransactions > 0 && transactionStartedHere) {
|
|
6088
|
+
try {
|
|
6089
|
+
this.db.exec("ROLLBACK");
|
|
6090
|
+
} catch (rollbackError) {
|
|
6091
|
+
console.error(
|
|
6092
|
+
"[NodeSqliteEngine] Error during ROLLBACK:",
|
|
6093
|
+
rollbackError
|
|
6094
|
+
);
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
this.numOpenTransactions = 0;
|
|
6098
|
+
});
|
|
6099
|
+
throw error;
|
|
5468
6100
|
}
|
|
5469
6101
|
}
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
data: targetResult.data,
|
|
5479
|
-
nextCursor: nextCursorFromJoin,
|
|
5480
|
-
prevCursor: prevCursorFromJoin
|
|
5481
|
-
};
|
|
5482
|
-
};
|
|
5483
|
-
}
|
|
5484
|
-
function generateHasManyThroughAddMethod(_sourceModelClass, config, _targetModelClass, joinModelClass) {
|
|
5485
|
-
return async function(targetInstanceOrId) {
|
|
5486
|
-
const targetId = typeof targetInstanceOrId === "string" ? targetInstanceOrId : targetInstanceOrId.id;
|
|
5487
|
-
if (!targetId) {
|
|
5488
|
-
Logger.error("[RelationshipManager.add] Target ID is missing.");
|
|
5489
|
-
throw new Error("Target ID is missing for add operation.");
|
|
5490
|
-
}
|
|
5491
|
-
const joinData = {};
|
|
5492
|
-
joinData[config.joinModelLocalField] = this.id;
|
|
5493
|
-
joinData[config.joinModelRelatedField] = targetId;
|
|
5494
|
-
let yDoc = null;
|
|
5495
|
-
let targetDocId = null;
|
|
5496
|
-
const sourceModelConstructor = this.constructor;
|
|
5497
|
-
const connectedDocuments = sourceModelConstructor.connectedDocuments;
|
|
5498
|
-
if (this._metaDocId && connectedDocuments) {
|
|
5499
|
-
targetDocId = this._metaDocId;
|
|
5500
|
-
if (targetDocId) {
|
|
5501
|
-
const documentInfo = connectedDocuments.get(targetDocId);
|
|
5502
|
-
if (documentInfo) {
|
|
5503
|
-
yDoc = documentInfo.yDoc;
|
|
5504
|
-
}
|
|
5505
|
-
}
|
|
5506
|
-
}
|
|
5507
|
-
if (!yDoc) {
|
|
5508
|
-
const legacyDocId = "__legacy_default__";
|
|
5509
|
-
const legacyDocInfo = joinModelClass.connectedDocuments?.get(
|
|
5510
|
-
legacyDocId
|
|
5511
|
-
);
|
|
5512
|
-
if (legacyDocInfo) {
|
|
5513
|
-
yDoc = legacyDocInfo.yDoc;
|
|
6102
|
+
async close() {
|
|
6103
|
+
await this.readyPromise;
|
|
6104
|
+
if (this.db) {
|
|
6105
|
+
Logger.info("[NodeSqliteEngine] Closing SQLite database...");
|
|
6106
|
+
this.db.close();
|
|
6107
|
+
this.db = null;
|
|
6108
|
+
Logger.info("[NodeSqliteEngine] SQLite database closed.");
|
|
6109
|
+
}
|
|
5514
6110
|
}
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
);
|
|
5520
|
-
}
|
|
5521
|
-
await yDoc.transact(async () => {
|
|
5522
|
-
const newJoinInstance = new joinModelClass(joinData);
|
|
5523
|
-
if (targetDocId) {
|
|
5524
|
-
await newJoinInstance.save({ targetDocument: targetDocId });
|
|
5525
|
-
} else {
|
|
5526
|
-
await newJoinInstance.save();
|
|
6111
|
+
async destroy() {
|
|
6112
|
+
await this.close();
|
|
6113
|
+
this.tableNames.clear();
|
|
6114
|
+
Logger.info("[NodeSqliteEngine] SQLite engine destroyed.");
|
|
5527
6115
|
}
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
newJoinInstance.toJSON()
|
|
5531
|
-
);
|
|
5532
|
-
}, `add-join-${config.joinModel}-${this.id}-${targetId}`);
|
|
5533
|
-
};
|
|
5534
|
-
}
|
|
5535
|
-
function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetModelClass, joinModelClass) {
|
|
5536
|
-
return async function(targetInstanceOrId) {
|
|
5537
|
-
const targetId = typeof targetInstanceOrId === "string" ? targetInstanceOrId : targetInstanceOrId.id;
|
|
5538
|
-
if (!targetId) {
|
|
5539
|
-
Logger.error("[RelationshipManager.remove] Target ID is missing.");
|
|
5540
|
-
throw new Error("Target ID is missing for remove operation.");
|
|
5541
|
-
}
|
|
5542
|
-
let yDoc = null;
|
|
5543
|
-
let targetDocId = null;
|
|
5544
|
-
const sourceModelConstructor = this.constructor;
|
|
5545
|
-
const connectedDocuments = sourceModelConstructor.connectedDocuments;
|
|
5546
|
-
if (this._metaDocId && connectedDocuments) {
|
|
5547
|
-
targetDocId = this._metaDocId;
|
|
5548
|
-
if (targetDocId) {
|
|
5549
|
-
const documentInfo = connectedDocuments.get(targetDocId);
|
|
5550
|
-
if (documentInfo) {
|
|
5551
|
-
yDoc = documentInfo.yDoc;
|
|
5552
|
-
}
|
|
6116
|
+
async getTableSchema(_tableName) {
|
|
6117
|
+
return {};
|
|
5553
6118
|
}
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
const legacyDocId = "__legacy_default__";
|
|
5557
|
-
const legacyDocInfo = joinModelClass.connectedDocuments?.get(
|
|
5558
|
-
legacyDocId
|
|
5559
|
-
);
|
|
5560
|
-
if (legacyDocInfo) {
|
|
5561
|
-
yDoc = legacyDocInfo.yDoc;
|
|
6119
|
+
getLastErrorMessage() {
|
|
6120
|
+
return void 0;
|
|
5562
6121
|
}
|
|
5563
|
-
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
`[RelationshipManager.remove] Join entry with ID ${record.id} found by query but not in YMap for deletion.`
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
});
|
|
6125
|
+
|
|
6126
|
+
// src/engines/NodeDatabaseFactory.ts
|
|
6127
|
+
var NodeDatabaseFactory_exports = {};
|
|
6128
|
+
__export(NodeDatabaseFactory_exports, {
|
|
6129
|
+
NodeDatabaseFactory: () => NodeDatabaseFactory
|
|
6130
|
+
});
|
|
6131
|
+
var NodeSqliteEngine2, NodeDatabaseFactory;
|
|
6132
|
+
var init_NodeDatabaseFactory = __esm({
|
|
6133
|
+
"src/engines/NodeDatabaseFactory.ts"() {
|
|
6134
|
+
"use strict";
|
|
6135
|
+
init_SqljsEngine();
|
|
6136
|
+
init_environment();
|
|
6137
|
+
NodeSqliteEngine2 = null;
|
|
6138
|
+
NodeDatabaseFactory = class {
|
|
6139
|
+
static engines = /* @__PURE__ */ new Map();
|
|
6140
|
+
/**
|
|
6141
|
+
* Dynamically loads Node.js engines
|
|
6142
|
+
*/
|
|
6143
|
+
static async loadNodeEngines() {
|
|
6144
|
+
try {
|
|
6145
|
+
const hasBetterSqlite3 = await features.hasBetterSqlite3();
|
|
6146
|
+
if (hasBetterSqlite3 && !NodeSqliteEngine2) {
|
|
6147
|
+
const module = await Promise.resolve().then(() => (init_NodeSqliteEngine(), NodeSqliteEngine_exports));
|
|
6148
|
+
NodeSqliteEngine2 = module.NodeSqliteEngine;
|
|
6149
|
+
console.log(
|
|
6150
|
+
"[NodeDatabaseFactory] NodeSqliteEngine loaded successfully"
|
|
5593
6151
|
);
|
|
5594
6152
|
}
|
|
6153
|
+
} catch (error) {
|
|
6154
|
+
console.warn(
|
|
6155
|
+
"[NodeDatabaseFactory] Failed to load NodeSqliteEngine:",
|
|
6156
|
+
error
|
|
6157
|
+
);
|
|
5595
6158
|
}
|
|
5596
|
-
}
|
|
5597
|
-
|
|
5598
|
-
|
|
6159
|
+
}
|
|
6160
|
+
/**
|
|
6161
|
+
* Auto-detects the best available engine for Node.js
|
|
6162
|
+
*/
|
|
6163
|
+
static async getRecommendedEngineType() {
|
|
6164
|
+
const hasBetterSqlite3 = await features.hasBetterSqlite3();
|
|
6165
|
+
if (hasBetterSqlite3) {
|
|
6166
|
+
return "node-sqlite";
|
|
6167
|
+
}
|
|
6168
|
+
if (features.hasWebAssembly) {
|
|
6169
|
+
return "sqljs";
|
|
6170
|
+
}
|
|
6171
|
+
throw new Error(
|
|
6172
|
+
"No compatible database engine found. Please install better-sqlite3."
|
|
5599
6173
|
);
|
|
5600
6174
|
}
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
modelOptions = /* @__PURE__ */ new Map();
|
|
5612
|
-
// Stores fields for globally registered models (from @Model decorator, or a static getter on class)
|
|
5613
|
-
// For simplicity, let's assume fields can be derived or are less critical for this registry part
|
|
5614
|
-
// private modelFields: Map<string, Map<string, FieldOptions>> = new Map();
|
|
5615
|
-
// Holds the subset of models explicitly set for the current initialization session
|
|
5616
|
-
activeSessionModels = null;
|
|
5617
|
-
dbEngine = null;
|
|
5618
|
-
// Store dbEngine for relationship methods
|
|
5619
|
-
constructor() {
|
|
5620
|
-
}
|
|
5621
|
-
static getInstance() {
|
|
5622
|
-
if (!_ModelRegistry.instance) {
|
|
5623
|
-
_ModelRegistry.instance = new _ModelRegistry();
|
|
5624
|
-
}
|
|
5625
|
-
return _ModelRegistry.instance;
|
|
5626
|
-
}
|
|
5627
|
-
registerModel(modelClass, options, fields) {
|
|
5628
|
-
if (!options.name) {
|
|
5629
|
-
throw new Error(
|
|
5630
|
-
`[ModelRegistry] Model class is missing a name in its @Model options. Ensure the @Model decorator includes a 'name' property.`
|
|
5631
|
-
);
|
|
5632
|
-
}
|
|
5633
|
-
if (this.models.has(options.name)) {
|
|
5634
|
-
console.warn(
|
|
5635
|
-
`[ModelRegistry] Model "${options.name}" is already registered. Overwriting.`
|
|
5636
|
-
);
|
|
5637
|
-
}
|
|
5638
|
-
this.models.set(options.name, modelClass);
|
|
5639
|
-
if (fields) {
|
|
5640
|
-
BaseModel.attachFieldAccessors(modelClass, fields);
|
|
5641
|
-
}
|
|
5642
|
-
this.modelOptions.set(options.name, options);
|
|
5643
|
-
console.log(
|
|
5644
|
-
`[ModelRegistry] Model "${options.name}" registered successfully.`
|
|
5645
|
-
);
|
|
5646
|
-
}
|
|
5647
|
-
// Gets the class for a globally registered model
|
|
5648
|
-
getModelClass(name) {
|
|
5649
|
-
return this.models.get(name);
|
|
5650
|
-
}
|
|
5651
|
-
getModelOptions(name) {
|
|
5652
|
-
return this.modelOptions.get(name);
|
|
5653
|
-
}
|
|
5654
|
-
// This method might need to be adapted based on how fields are ultimately managed
|
|
5655
|
-
getModelInfo(modelName) {
|
|
5656
|
-
const modelClass = this.models.get(modelName);
|
|
5657
|
-
const options = this.modelOptions.get(modelName);
|
|
5658
|
-
if (modelClass && options) {
|
|
5659
|
-
const fields = modelClass.getSchema ? modelClass.getSchema().fields : /* @__PURE__ */ new Map();
|
|
5660
|
-
return { class: modelClass, options, fields };
|
|
5661
|
-
}
|
|
5662
|
-
return void 0;
|
|
5663
|
-
}
|
|
5664
|
-
getAllRegisteredModelsInfo() {
|
|
5665
|
-
const infos = [];
|
|
5666
|
-
for (const modelName of this.models.keys()) {
|
|
5667
|
-
const info = this.getModelInfo(modelName);
|
|
5668
|
-
if (info) {
|
|
5669
|
-
infos.push(info);
|
|
6175
|
+
/**
|
|
6176
|
+
* Creates a fallback engine when the requested engine is not available
|
|
6177
|
+
*/
|
|
6178
|
+
static async createFallbackEngine(requestedType, config) {
|
|
6179
|
+
const fallbackType = await this.getRecommendedEngineType();
|
|
6180
|
+
console.warn(
|
|
6181
|
+
`[NodeDatabaseFactory] Requested engine '${requestedType}' is not available. Falling back to '${fallbackType}'.`
|
|
6182
|
+
);
|
|
6183
|
+
const fallbackConfig = { ...config, type: fallbackType };
|
|
6184
|
+
return this.createEngine(fallbackConfig, false);
|
|
5670
6185
|
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
6186
|
+
/**
|
|
6187
|
+
* Creates the specified database engine (Node.js compatible only)
|
|
6188
|
+
*/
|
|
6189
|
+
static async createEngine(config, allowFallback = true) {
|
|
6190
|
+
const { type, options } = config;
|
|
6191
|
+
switch (type) {
|
|
6192
|
+
case "sqljs":
|
|
6193
|
+
const sqljsEngine = new SqljsEngine(options);
|
|
6194
|
+
await sqljsEngine.ensureReady();
|
|
6195
|
+
return sqljsEngine;
|
|
6196
|
+
case "node-sqlite":
|
|
6197
|
+
if (!NodeSqliteEngine2) {
|
|
6198
|
+
const hasBetterSqlite3 = await features.hasBetterSqlite3();
|
|
6199
|
+
if (!hasBetterSqlite3) {
|
|
6200
|
+
if (allowFallback) {
|
|
6201
|
+
console.warn(
|
|
6202
|
+
"[NodeDatabaseFactory] better-sqlite3 not installed, falling back to alternative engine"
|
|
6203
|
+
);
|
|
6204
|
+
return this.createFallbackEngine(type, config);
|
|
6205
|
+
}
|
|
6206
|
+
throw new Error(
|
|
6207
|
+
"better-sqlite3 package is required for node-sqlite engine. Install it with: npm install better-sqlite3"
|
|
6208
|
+
);
|
|
6209
|
+
}
|
|
6210
|
+
throw new Error(
|
|
6211
|
+
"NodeSqliteEngine not loaded. This should not happen."
|
|
6212
|
+
);
|
|
6213
|
+
}
|
|
6214
|
+
const nodeSqliteEngine = new NodeSqliteEngine2(options);
|
|
6215
|
+
await nodeSqliteEngine.ensureReady();
|
|
6216
|
+
return nodeSqliteEngine;
|
|
6217
|
+
case "alasql":
|
|
6218
|
+
throw new Error("AlaSQLEngine not yet implemented.");
|
|
6219
|
+
default:
|
|
6220
|
+
const typeStr = type;
|
|
6221
|
+
if (typeStr === "duckdb") {
|
|
6222
|
+
if (allowFallback) {
|
|
6223
|
+
console.warn(
|
|
6224
|
+
"[NodeDatabaseFactory] DuckDB-WASM not available in Node.js, falling back to alternative engine"
|
|
6225
|
+
);
|
|
6226
|
+
return this.createFallbackEngine(typeStr, config);
|
|
6227
|
+
}
|
|
6228
|
+
throw new Error(
|
|
6229
|
+
"DuckDB-WASM engine is only available in browser environments"
|
|
6230
|
+
);
|
|
6231
|
+
}
|
|
6232
|
+
if (typeStr === "node-duckdb") {
|
|
6233
|
+
if (allowFallback) {
|
|
6234
|
+
console.warn(
|
|
6235
|
+
"[NodeDatabaseFactory] Node DuckDB engine is no longer supported, falling back to alternative engine"
|
|
6236
|
+
);
|
|
6237
|
+
return this.createFallbackEngine(typeStr, config);
|
|
6238
|
+
}
|
|
6239
|
+
throw new Error("Node DuckDB engine is no longer supported");
|
|
6240
|
+
}
|
|
6241
|
+
throw new Error(`Unsupported database engine type: ${typeStr}`);
|
|
5688
6242
|
}
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
6243
|
+
}
|
|
6244
|
+
static async getEngine(config) {
|
|
6245
|
+
await this.loadNodeEngines();
|
|
6246
|
+
const configKey = JSON.stringify(config);
|
|
6247
|
+
if (this.engines.has(configKey)) {
|
|
6248
|
+
console.log(
|
|
6249
|
+
`[NodeDatabaseFactory] Returning cached engine for config: ${configKey}`
|
|
5692
6250
|
);
|
|
5693
|
-
return;
|
|
5694
|
-
}
|
|
5695
|
-
if (this.activeSessionModels) {
|
|
5696
|
-
this.activeSessionModels.set(modelName, modelClass);
|
|
5697
6251
|
}
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
if (this.activeSessionModels) {
|
|
5701
|
-
const currentSessionModelKeys = Array.from(
|
|
5702
|
-
this.activeSessionModels.keys()
|
|
6252
|
+
console.log(
|
|
6253
|
+
`[NodeDatabaseFactory] Creating engine for type: ${config.type}`
|
|
5703
6254
|
);
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
6255
|
+
try {
|
|
6256
|
+
const engine = await this.createEngine(config);
|
|
6257
|
+
console.log(
|
|
6258
|
+
`[NodeDatabaseFactory] Engine for type ${config.type} is ready.`
|
|
6259
|
+
);
|
|
6260
|
+
return engine;
|
|
6261
|
+
} catch (error) {
|
|
6262
|
+
console.error(
|
|
6263
|
+
`[NodeDatabaseFactory] Failed to create engine for type ${config.type}:`,
|
|
6264
|
+
error
|
|
6265
|
+
);
|
|
6266
|
+
throw error;
|
|
5708
6267
|
}
|
|
5709
6268
|
}
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
this.dbEngine = dbEngine;
|
|
5729
|
-
const modelsToInitialize = this.activeSessionModels || this.models;
|
|
5730
|
-
if (modelsToInitialize.size === 0) {
|
|
5731
|
-
console.warn(
|
|
5732
|
-
"[ModelRegistry] No models to initialize (either no explicit models set for session or no models globally registered via @Model decorator)."
|
|
5733
|
-
);
|
|
5734
|
-
return;
|
|
5735
|
-
}
|
|
5736
|
-
console.log(
|
|
5737
|
-
`[ModelRegistry] Initializing models: ${Array.from(
|
|
5738
|
-
modelsToInitialize.keys()
|
|
5739
|
-
).join(", ")}`
|
|
5740
|
-
);
|
|
5741
|
-
for (const [modelName, modelClass] of modelsToInitialize.entries()) {
|
|
5742
|
-
if (modelClass && typeof modelClass.initialize === "function") {
|
|
5743
|
-
Logger.debug(`[ModelRegistry] Initializing model: ${modelName}`);
|
|
5744
|
-
await modelClass.initialize(yDoc, dbEngine);
|
|
5745
|
-
} else {
|
|
5746
|
-
const staticModelName = modelClass?.modelName || "unknown";
|
|
5747
|
-
console.warn(
|
|
5748
|
-
`[ModelRegistry] Model ${staticModelName} (class name: ${modelClass?.name}) does not have a static initialize method or class is not defined.`
|
|
5749
|
-
);
|
|
6269
|
+
/**
|
|
6270
|
+
* Gets information about available engines in Node.js environment
|
|
6271
|
+
*/
|
|
6272
|
+
static async getAvailableEngines() {
|
|
6273
|
+
const hasBetterSqlite3 = await features.hasBetterSqlite3();
|
|
6274
|
+
const engines = [
|
|
6275
|
+
{
|
|
6276
|
+
type: "sqljs",
|
|
6277
|
+
available: features.hasWebAssembly,
|
|
6278
|
+
reason: !features.hasWebAssembly ? "WebAssembly not supported" : void 0
|
|
6279
|
+
},
|
|
6280
|
+
{
|
|
6281
|
+
type: "node-sqlite",
|
|
6282
|
+
available: hasBetterSqlite3,
|
|
6283
|
+
reason: !hasBetterSqlite3 ? "better-sqlite3 package not installed" : void 0
|
|
6284
|
+
}
|
|
6285
|
+
];
|
|
6286
|
+
return engines;
|
|
5750
6287
|
}
|
|
5751
|
-
}
|
|
5752
|
-
Logger.debug("[ModelRegistry] Model initialization phase complete.");
|
|
6288
|
+
};
|
|
5753
6289
|
}
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
Logger.debug(
|
|
5775
|
-
`[ModelRegistry] Initializing model ${modelName} for document ${docId}`
|
|
5776
|
-
);
|
|
5777
|
-
await modelClass.initializeForDocument(
|
|
5778
|
-
yDoc,
|
|
5779
|
-
dbEngine,
|
|
5780
|
-
docId,
|
|
5781
|
-
permissionHint
|
|
5782
|
-
);
|
|
5783
|
-
} else {
|
|
5784
|
-
const staticModelName = modelClass?.modelName || "unknown";
|
|
5785
|
-
console.warn(
|
|
5786
|
-
`[ModelRegistry] Model ${staticModelName} (class name: ${modelClass?.name}) does not have a static initializeForDocument method or class is not defined.`
|
|
5787
|
-
);
|
|
6290
|
+
});
|
|
6291
|
+
|
|
6292
|
+
// src/engines/BrowserDatabaseFactory.ts
|
|
6293
|
+
var BrowserDatabaseFactory_exports = {};
|
|
6294
|
+
__export(BrowserDatabaseFactory_exports, {
|
|
6295
|
+
BrowserDatabaseFactory: () => BrowserDatabaseFactory
|
|
6296
|
+
});
|
|
6297
|
+
var BrowserDatabaseFactory;
|
|
6298
|
+
var init_BrowserDatabaseFactory = __esm({
|
|
6299
|
+
"src/engines/BrowserDatabaseFactory.ts"() {
|
|
6300
|
+
"use strict";
|
|
6301
|
+
init_SqljsEngine();
|
|
6302
|
+
init_environment();
|
|
6303
|
+
init_BaseModel();
|
|
6304
|
+
BrowserDatabaseFactory = class {
|
|
6305
|
+
/**
|
|
6306
|
+
* Dynamically loads browser-compatible engines only
|
|
6307
|
+
*/
|
|
6308
|
+
static async loadBrowserEngines() {
|
|
6309
|
+
Logger.debug("[BrowserDatabaseFactory] Browser engines ready");
|
|
5788
6310
|
}
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
// New method to remove data from a disconnected document
|
|
5796
|
-
async removeDocumentData(docId, dbEngine) {
|
|
5797
|
-
Logger.debug(`[ModelRegistry] Removing data for document ${docId}...`);
|
|
5798
|
-
const modelsToCleanup = this.activeSessionModels || this.models;
|
|
5799
|
-
if (modelsToCleanup.size === 0) {
|
|
5800
|
-
console.warn(
|
|
5801
|
-
"[ModelRegistry] No models to cleanup for document (either no explicit models set for session or no models globally registered via @Model decorator)."
|
|
5802
|
-
);
|
|
5803
|
-
return;
|
|
5804
|
-
}
|
|
5805
|
-
for (const [modelName, modelClass] of modelsToCleanup.entries()) {
|
|
5806
|
-
try {
|
|
5807
|
-
Logger.debug(
|
|
5808
|
-
`[ModelRegistry] Removing ${modelName} data for document ${docId}`
|
|
5809
|
-
);
|
|
5810
|
-
await dbEngine.deleteByDocumentId(modelName, docId);
|
|
5811
|
-
if (modelClass && typeof modelClass.cleanupDocumentData === "function") {
|
|
5812
|
-
await modelClass.cleanupDocumentData(docId);
|
|
6311
|
+
/**
|
|
6312
|
+
* Auto-detects the best available engine for browser
|
|
6313
|
+
*/
|
|
6314
|
+
static getRecommendedEngineType() {
|
|
6315
|
+
if (features.hasWebAssembly) {
|
|
6316
|
+
return "sqljs";
|
|
5813
6317
|
}
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
`[ModelRegistry] Error removing ${modelName} data for document ${docId}:`,
|
|
5817
|
-
error
|
|
6318
|
+
throw new Error(
|
|
6319
|
+
"No compatible database engine found. WebAssembly is required for browser environments."
|
|
5818
6320
|
);
|
|
5819
6321
|
}
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
"[ModelRegistry] DB engine not available for initializing relationships. Ensure initializeAll has been called."
|
|
5828
|
-
);
|
|
5829
|
-
return;
|
|
5830
|
-
}
|
|
5831
|
-
const dbEngine = this.dbEngine;
|
|
5832
|
-
Logger.debug(
|
|
5833
|
-
"[ModelRegistry] Initializing relationships for active models..."
|
|
5834
|
-
);
|
|
5835
|
-
const activeModels = this.getActiveModels();
|
|
5836
|
-
for (const [modelName, modelClass] of activeModels.entries()) {
|
|
5837
|
-
const modelSchema = modelClass.getSchema ? modelClass.getSchema() : null;
|
|
5838
|
-
const relationshipsConfig = modelSchema?.options?.relationships;
|
|
5839
|
-
if (relationshipsConfig) {
|
|
5840
|
-
Logger.debug(
|
|
5841
|
-
`[ModelRegistry] Setting up relationships for ${modelName}`
|
|
6322
|
+
/**
|
|
6323
|
+
* Creates a fallback engine when the requested engine is not available
|
|
6324
|
+
*/
|
|
6325
|
+
static async createFallbackEngine(requestedType, config) {
|
|
6326
|
+
const fallbackType = this.getRecommendedEngineType();
|
|
6327
|
+
console.warn(
|
|
6328
|
+
`[BrowserDatabaseFactory] Requested engine '${requestedType}' is not available. Falling back to '${fallbackType}'.`
|
|
5842
6329
|
);
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
);
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
` - Adding '${relName}' (refersTo ${config.model}) to ${modelName}`
|
|
5865
|
-
);
|
|
5866
|
-
const refersToMethod = generateRefersToMethod(
|
|
5867
|
-
modelClass,
|
|
5868
|
-
config,
|
|
5869
|
-
targetModelClass
|
|
5870
|
-
);
|
|
5871
|
-
this.addPrototypeMethod(modelClass, relName, refersToMethod);
|
|
5872
|
-
break;
|
|
5873
|
-
case "hasMany":
|
|
5874
|
-
Logger.debug(
|
|
5875
|
-
` - Adding '${relName}' (hasMany ${config.model}) to ${modelName}`
|
|
5876
|
-
);
|
|
5877
|
-
const hasManyMethod = generateHasManyMethod(
|
|
5878
|
-
modelClass,
|
|
5879
|
-
config,
|
|
5880
|
-
targetModelClass,
|
|
5881
|
-
dbEngine
|
|
5882
|
-
);
|
|
5883
|
-
this.addPrototypeMethod(modelClass, relName, hasManyMethod);
|
|
5884
|
-
break;
|
|
5885
|
-
case "hasManyThrough":
|
|
5886
|
-
const joinModelName = config.joinModel;
|
|
5887
|
-
const joinModelClass = this.getModelClass(joinModelName);
|
|
5888
|
-
if (!joinModelClass) {
|
|
5889
|
-
throw new Error(
|
|
5890
|
-
`[ModelRegistry] Join model "${joinModelName}" for relationship "${relName}" on model "${modelName}" not found. Ensure it is registered and included in the session.`
|
|
6330
|
+
const fallbackConfig = { ...config, type: fallbackType };
|
|
6331
|
+
return this.createEngine(fallbackConfig, false);
|
|
6332
|
+
}
|
|
6333
|
+
/**
|
|
6334
|
+
* Creates the specified database engine (browser-compatible only)
|
|
6335
|
+
*/
|
|
6336
|
+
static async createEngine(config, allowFallback = true) {
|
|
6337
|
+
const { type, options } = config;
|
|
6338
|
+
const typeStr = type;
|
|
6339
|
+
switch (type) {
|
|
6340
|
+
case "sqljs":
|
|
6341
|
+
const sqljsEngine = new SqljsEngine(options);
|
|
6342
|
+
await sqljsEngine.ensureReady();
|
|
6343
|
+
return sqljsEngine;
|
|
6344
|
+
case "alasql":
|
|
6345
|
+
throw new Error("AlaSQLEngine not yet implemented.");
|
|
6346
|
+
default:
|
|
6347
|
+
if (typeStr === "duckdb") {
|
|
6348
|
+
if (allowFallback) {
|
|
6349
|
+
console.warn(
|
|
6350
|
+
"[BrowserDatabaseFactory] DuckDB engine is no longer supported, falling back to alternative engine"
|
|
5891
6351
|
);
|
|
6352
|
+
return this.createFallbackEngine(typeStr, config);
|
|
5892
6353
|
}
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
6354
|
+
throw new Error("DuckDB engine is no longer supported");
|
|
6355
|
+
}
|
|
6356
|
+
if (typeStr === "node-sqlite" || typeStr === "node-duckdb") {
|
|
6357
|
+
if (allowFallback) {
|
|
6358
|
+
console.warn(
|
|
6359
|
+
`[BrowserDatabaseFactory] Engine '${typeStr}' is not available in browser. Falling back to browser-compatible engine.`
|
|
5896
6360
|
);
|
|
6361
|
+
return this.createFallbackEngine(typeStr, config);
|
|
5897
6362
|
}
|
|
5898
|
-
Logger.debug(
|
|
5899
|
-
` - Adding '${relName}' (hasManyThrough ${config.model} via ${joinModelName}) to ${modelName}`
|
|
5900
|
-
);
|
|
5901
|
-
const hasManyThroughFetchMethod = generateHasManyThroughMethod(
|
|
5902
|
-
modelClass,
|
|
5903
|
-
config,
|
|
5904
|
-
targetModelClass,
|
|
5905
|
-
joinModelClass,
|
|
5906
|
-
dbEngine
|
|
5907
|
-
);
|
|
5908
|
-
this.addPrototypeMethod(
|
|
5909
|
-
modelClass,
|
|
5910
|
-
relName,
|
|
5911
|
-
hasManyThroughFetchMethod
|
|
5912
|
-
);
|
|
5913
|
-
const relNameSingular = config.model.endsWith("s") ? config.model.slice(0, -1) : config.model;
|
|
5914
|
-
const capitalizedSingularRelName = relNameSingular.charAt(0).toUpperCase() + relNameSingular.slice(1);
|
|
5915
|
-
const addMethodName = `add${capitalizedSingularRelName}`;
|
|
5916
|
-
const addMethodLogic = generateHasManyThroughAddMethod(
|
|
5917
|
-
modelClass,
|
|
5918
|
-
config,
|
|
5919
|
-
targetModelClass,
|
|
5920
|
-
joinModelClass
|
|
5921
|
-
);
|
|
5922
|
-
this.addPrototypeMethod(
|
|
5923
|
-
modelClass,
|
|
5924
|
-
addMethodName,
|
|
5925
|
-
addMethodLogic
|
|
5926
|
-
);
|
|
5927
|
-
Logger.debug(
|
|
5928
|
-
` - Adding '${addMethodName}' helper to ${modelName} (for relationship ${relName})`
|
|
5929
|
-
);
|
|
5930
|
-
const removeMethodName = `remove${capitalizedSingularRelName}`;
|
|
5931
|
-
const removeMethodLogic = generateHasManyThroughRemoveMethod(
|
|
5932
|
-
modelClass,
|
|
5933
|
-
config,
|
|
5934
|
-
targetModelClass,
|
|
5935
|
-
joinModelClass
|
|
5936
|
-
);
|
|
5937
|
-
this.addPrototypeMethod(
|
|
5938
|
-
modelClass,
|
|
5939
|
-
removeMethodName,
|
|
5940
|
-
removeMethodLogic
|
|
5941
|
-
);
|
|
5942
|
-
Logger.debug(
|
|
5943
|
-
` - Adding '${removeMethodName}' helper to ${modelName} (for relationship ${relName})`
|
|
5944
|
-
);
|
|
5945
|
-
break;
|
|
5946
|
-
default:
|
|
5947
|
-
console.warn(
|
|
5948
|
-
`[ModelRegistry] Unknown relationship type "${config.type}" for ${relName} on ${modelName}. Skipping.`
|
|
5949
|
-
);
|
|
5950
|
-
}
|
|
5951
|
-
}
|
|
5952
|
-
}
|
|
5953
|
-
}
|
|
5954
|
-
Logger.debug("[ModelRegistry] Relationship initialization phase complete.");
|
|
5955
|
-
}
|
|
5956
|
-
// Helper to add methods to prototype
|
|
5957
|
-
addPrototypeMethod(modelClass, methodName, methodLogic) {
|
|
5958
|
-
Object.defineProperty(modelClass.prototype, methodName, {
|
|
5959
|
-
value: methodLogic,
|
|
5960
|
-
writable: true,
|
|
5961
|
-
enumerable: false,
|
|
5962
|
-
// Keep it clean, like other prototype methods
|
|
5963
|
-
configurable: true
|
|
5964
|
-
});
|
|
5965
|
-
}
|
|
5966
|
-
clearSessionState() {
|
|
5967
|
-
this.activeSessionModels = null;
|
|
5968
|
-
Logger.debug(
|
|
5969
|
-
"[ModelRegistry] Session state cleared (activeSessionModels reset)."
|
|
5970
|
-
);
|
|
5971
|
-
}
|
|
5972
|
-
getActiveModels() {
|
|
5973
|
-
return this.activeSessionModels || this.models;
|
|
5974
|
-
}
|
|
5975
|
-
validateSessionModels() {
|
|
5976
|
-
const activeModels = this.getActiveModels();
|
|
5977
|
-
if (!activeModels || activeModels.size === 0) {
|
|
5978
|
-
return;
|
|
5979
|
-
}
|
|
5980
|
-
for (const [modelName, modelClass] of activeModels.entries()) {
|
|
5981
|
-
const modelSchema = modelClass.getSchema ? modelClass.getSchema() : null;
|
|
5982
|
-
const relationships = modelSchema?.options?.relationships;
|
|
5983
|
-
if (relationships) {
|
|
5984
|
-
for (const relName in relationships) {
|
|
5985
|
-
const config = relationships[relName];
|
|
5986
|
-
const targetModelName = config.model;
|
|
5987
|
-
if (!activeModels.has(targetModelName)) {
|
|
5988
|
-
throw new Error(
|
|
5989
|
-
`[ModelRegistry] Validation Error: Model "${modelName}" has a relationship "${relName}" that refers to model "${targetModelName}", but "${targetModelName}" is not active in the current session. Please include it in the 'models' array during initialization.`
|
|
5990
|
-
);
|
|
5991
|
-
}
|
|
5992
|
-
if (config.type === "hasManyThrough") {
|
|
5993
|
-
const joinModelName = config.joinModel;
|
|
5994
|
-
if (!activeModels.has(joinModelName)) {
|
|
5995
6363
|
throw new Error(
|
|
5996
|
-
`
|
|
6364
|
+
`Engine '${typeStr}' is only available in Node.js environments`
|
|
5997
6365
|
);
|
|
5998
6366
|
}
|
|
5999
|
-
|
|
6367
|
+
throw new Error(`Unsupported database engine type: ${typeStr}`);
|
|
6000
6368
|
}
|
|
6001
6369
|
}
|
|
6002
|
-
|
|
6370
|
+
static async getEngine(config) {
|
|
6371
|
+
await this.loadBrowserEngines();
|
|
6372
|
+
Logger.debug(
|
|
6373
|
+
`[BrowserDatabaseFactory] Creating engine for type: ${config.type}`
|
|
6374
|
+
);
|
|
6375
|
+
try {
|
|
6376
|
+
const engine = await this.createEngine(config);
|
|
6377
|
+
Logger.debug(
|
|
6378
|
+
`[BrowserDatabaseFactory] Engine for type ${config.type} is ready.`
|
|
6379
|
+
);
|
|
6380
|
+
return engine;
|
|
6381
|
+
} catch (error) {
|
|
6382
|
+
console.error(
|
|
6383
|
+
`[BrowserDatabaseFactory] Failed to create engine for type ${config.type}:`,
|
|
6384
|
+
error
|
|
6385
|
+
);
|
|
6386
|
+
throw error;
|
|
6387
|
+
}
|
|
6388
|
+
}
|
|
6389
|
+
/**
|
|
6390
|
+
* Gets information about available engines in browser environment
|
|
6391
|
+
*/
|
|
6392
|
+
static getAvailableEngines() {
|
|
6393
|
+
const engines = [
|
|
6394
|
+
{
|
|
6395
|
+
type: "sqljs",
|
|
6396
|
+
available: features.hasWebAssembly,
|
|
6397
|
+
reason: !features.hasWebAssembly ? "WebAssembly not supported" : void 0
|
|
6398
|
+
},
|
|
6399
|
+
{
|
|
6400
|
+
type: "node-sqlite",
|
|
6401
|
+
available: false,
|
|
6402
|
+
reason: "Node.js engines not available in browser"
|
|
6403
|
+
}
|
|
6404
|
+
];
|
|
6405
|
+
return engines;
|
|
6406
|
+
}
|
|
6407
|
+
};
|
|
6003
6408
|
}
|
|
6004
|
-
};
|
|
6409
|
+
});
|
|
6005
6410
|
|
|
6006
6411
|
// src/initialize.ts
|
|
6412
|
+
init_ModelRegistry();
|
|
6007
6413
|
init_BaseModel();
|
|
6008
6414
|
init_environment();
|
|
6009
6415
|
async function getDatabaseFactory() {
|
|
@@ -6200,12 +6606,16 @@ async function resetJsBao() {
|
|
|
6200
6606
|
ormInitializationPromise = null;
|
|
6201
6607
|
const { BaseModel: BaseModel3 } = await Promise.resolve().then(() => (init_BaseModel(), BaseModel_exports));
|
|
6202
6608
|
BaseModel3.dbInstance = null;
|
|
6203
|
-
|
|
6609
|
+
BaseModel3.connectedDocuments = /* @__PURE__ */ new Map();
|
|
6610
|
+
BaseModel3.documentYMaps = /* @__PURE__ */ new Map();
|
|
6611
|
+
BaseModel3.clearModelDefaultDocumentIds();
|
|
6612
|
+
BaseModel3.clearGlobalDefaultDocumentId();
|
|
6613
|
+
Logger.debug("[resetJsBao] BaseModel database instance and document state cleared.");
|
|
6204
6614
|
const modelRegistryInstance = ModelRegistry.getInstance();
|
|
6205
6615
|
modelRegistryInstance.clearSessionState();
|
|
6206
6616
|
Logger.debug("[resetJsBao] ModelRegistry session state cleared.");
|
|
6207
6617
|
Logger.info(
|
|
6208
|
-
"[resetJsBao] js-bao
|
|
6618
|
+
"[resetJsBao] js-bao state fully reset. DB engine destroyed. Document mappings cleared. ModelRegistry session cleared."
|
|
6209
6619
|
);
|
|
6210
6620
|
}
|
|
6211
6621
|
|
|
@@ -6214,6 +6624,7 @@ init_documentTypes();
|
|
|
6214
6624
|
init_BaseModel();
|
|
6215
6625
|
|
|
6216
6626
|
// src/models/decorators.ts
|
|
6627
|
+
init_ModelRegistry();
|
|
6217
6628
|
init_BaseModel();
|
|
6218
6629
|
init_sql();
|
|
6219
6630
|
var SCHEMA_FIELDS_PROPERTY = "_jsbaoSchemaFields";
|
|
@@ -6342,6 +6753,7 @@ init_StringSet();
|
|
|
6342
6753
|
|
|
6343
6754
|
// src/models/schema.ts
|
|
6344
6755
|
init_BaseModel();
|
|
6756
|
+
init_ModelRegistry();
|
|
6345
6757
|
function defineModelSchema(input) {
|
|
6346
6758
|
const { name, fields } = input;
|
|
6347
6759
|
const options = {
|
|
@@ -6520,6 +6932,7 @@ var DatabaseEngine = class {
|
|
|
6520
6932
|
};
|
|
6521
6933
|
|
|
6522
6934
|
// src/index.ts
|
|
6935
|
+
init_ModelRegistry();
|
|
6523
6936
|
init_BaseModel();
|
|
6524
6937
|
|
|
6525
6938
|
// src/utils/yDocDump.ts
|