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