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