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