js-bao 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -344,21 +350,36 @@ var init_CursorManager = __esm({
344
350
  return fields;
345
351
  }
346
352
  /**
347
- * Build ORDER BY clause from sort specification
353
+ * Build ORDER BY clause from sort specification.
354
+ *
355
+ * `direction` is the pagination direction (1 = forward, -1 = backward).
356
+ * For backward pagination each field's effective ORDER BY direction is
357
+ * reversed so the cursor's flipped inequality (built by
358
+ * `buildPaginationConditions`) returns the page adjacent to the cursor,
359
+ * not the globally smallest/largest window. Without this,
360
+ * `field < cursor ... ORDER BY field ASC LIMIT n` returns the n smallest
361
+ * rows overall instead of the n rows just below the cursor.
362
+ *
363
+ * The returned `directions` array carries the *declared* per-field sort
364
+ * directions (unaffected by `direction`) — `buildPaginationConditions`
365
+ * needs the declared directions to pick each field's comparison operator.
348
366
  */
349
- static buildOrderClause(sort, fieldFormatter = (field) => field) {
367
+ static buildOrderClause(sort, fieldFormatter = (field) => field, direction = 1) {
350
368
  const fields = this.extractSortFields(sort);
351
369
  const clauses = [];
352
370
  const directions = [];
371
+ const sqlDir = (declared) => {
372
+ const effective = direction === -1 ? declared === 1 ? -1 : 1 : declared;
373
+ return effective === 1 ? "ASC" : "DESC";
374
+ };
353
375
  if (sort && Object.keys(sort).length > 0) {
354
376
  for (const [field, dir] of Object.entries(sort)) {
355
- const sqlDirection = dir === 1 ? "ASC" : "DESC";
356
- clauses.push(`${fieldFormatter(field)} ${sqlDirection}`);
377
+ clauses.push(`${fieldFormatter(field)} ${sqlDir(dir)}`);
357
378
  directions.push(dir);
358
379
  }
359
380
  }
360
381
  if (!sort || !sort.hasOwnProperty("id")) {
361
- clauses.push(`${fieldFormatter("id")} ASC`);
382
+ clauses.push(`${fieldFormatter("id")} ${sqlDir(1)}`);
362
383
  directions.push(1);
363
384
  } else if (sort && sort.hasOwnProperty("id")) {
364
385
  directions.push(sort["id"]);
@@ -381,18 +402,27 @@ var init_CursorManager = __esm({
381
402
  /**
382
403
  * Generate next and previous cursors from result set
383
404
  */
384
- static generateResultCursors(results, sortFields, _requestedDirection, hasMore, isFirstPage = false) {
405
+ static generateResultCursors(results, sortFields, requestedDirection, hasMore, isFirstPage = false) {
385
406
  if (results.length === 0) {
386
407
  return {};
387
408
  }
388
409
  const cursors = {};
389
410
  if (hasMore && results.length > 0) {
390
411
  const lastResult = results[results.length - 1];
391
- cursors.nextCursor = this.generateCursor(lastResult, sortFields, 1);
412
+ cursors.nextCursor = this.generateCursor(
413
+ lastResult,
414
+ sortFields,
415
+ requestedDirection
416
+ );
392
417
  }
393
418
  if (results.length > 0 && !isFirstPage) {
394
419
  const firstResult = results[0];
395
- cursors.prevCursor = this.generateCursor(firstResult, sortFields, -1);
420
+ const opposite = requestedDirection === 1 ? -1 : 1;
421
+ cursors.prevCursor = this.generateCursor(
422
+ firstResult,
423
+ sortFields,
424
+ opposite
425
+ );
396
426
  }
397
427
  return cursors;
398
428
  }
@@ -525,7 +555,8 @@ var init_DocumentQueryTranslator = __esm({
525
555
  const whereClause = this.translateFilter(filter);
526
556
  const orderClause = CursorManager.buildOrderClause(
527
557
  options?.sort,
528
- (field) => this.getQuotedField(field)
558
+ (field) => this.getQuotedField(field),
559
+ options?.direction || 1
529
560
  );
530
561
  const limitClause = this.buildLimitClause(options);
531
562
  const paginationClause = this.buildPaginationClause(
@@ -1197,8 +1228,206 @@ var init_yjsValues = __esm({
1197
1228
  }
1198
1229
  });
1199
1230
 
1200
- // src/models/metaSync.ts
1231
+ // src/models/writeLifecycle.ts
1201
1232
  import * as Y2 from "yjs";
1233
+ import { ulid } from "ulid";
1234
+ function assertNever(x) {
1235
+ throw new Error(`Unhandled field type: ${String(x)}`);
1236
+ }
1237
+ function fieldWriteKind(type) {
1238
+ switch (type) {
1239
+ case "string":
1240
+ case "number":
1241
+ case "boolean":
1242
+ case "date":
1243
+ case "id":
1244
+ return "scalar";
1245
+ case "stringset":
1246
+ return "stringset";
1247
+ default:
1248
+ return assertNever(type);
1249
+ }
1250
+ }
1251
+ function isKnownFieldType(type) {
1252
+ return KNOWN_FIELD_TYPES.has(type);
1253
+ }
1254
+ function ensureStringSetYMap(rm, fieldName) {
1255
+ const nested = rm.get(fieldName);
1256
+ if (nested instanceof Y2.Map) return nested;
1257
+ const fresh = new Y2.Map();
1258
+ if (nested && typeof nested === "object") {
1259
+ for (const [member, marker] of Object.entries(
1260
+ nested
1261
+ )) {
1262
+ if (Boolean(marker)) fresh.set(member, true);
1263
+ }
1264
+ }
1265
+ rm.set(fieldName, fresh);
1266
+ return fresh;
1267
+ }
1268
+ function applyStringSetDelta(target, delta) {
1269
+ if (delta.clear) {
1270
+ for (const member of Array.from(target.keys())) target.delete(member);
1271
+ }
1272
+ if (delta.remove) {
1273
+ for (const member of delta.remove) target.delete(String(member));
1274
+ }
1275
+ if (delta.add) {
1276
+ for (const member of delta.add) {
1277
+ const key = String(member);
1278
+ if (target.get(key) !== true) target.set(key, true);
1279
+ }
1280
+ }
1281
+ }
1282
+ function replaceStringSet(rm, fieldName, members) {
1283
+ const target = ensureStringSetYMap(rm, fieldName);
1284
+ const desired = /* @__PURE__ */ new Set();
1285
+ for (const member of members) desired.add(String(member));
1286
+ for (const existing of Array.from(target.keys())) {
1287
+ if (!desired.has(existing)) target.delete(existing);
1288
+ }
1289
+ for (const member of desired) {
1290
+ if (target.get(member) !== true) target.set(member, true);
1291
+ }
1292
+ }
1293
+ function applyStringSetChange(recordYMap, fieldName, change) {
1294
+ const target = ensureStringSetYMap(recordYMap, fieldName);
1295
+ applyStringSetDelta(target, {
1296
+ add: change.additions,
1297
+ remove: change.removals
1298
+ });
1299
+ }
1300
+ function parseStringSetOp(value) {
1301
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Y2.AbstractType) {
1302
+ return null;
1303
+ }
1304
+ const keys = Object.keys(value);
1305
+ const opKeys = keys.filter((k) => k.startsWith("$"));
1306
+ if (opKeys.length === 0) return null;
1307
+ const known = /* @__PURE__ */ new Set(["$add", "$remove", "$clear"]);
1308
+ const unknown = keys.filter((k) => !known.has(k));
1309
+ if (unknown.length > 0) {
1310
+ throw new Error(
1311
+ `Invalid stringset op: unexpected key(s) [${unknown.join(", ")}]. Only $add, $remove and $clear are supported.`
1312
+ );
1313
+ }
1314
+ const obj = value;
1315
+ const toMembers = (raw, opName) => {
1316
+ if (raw === void 0) return [];
1317
+ if (!Array.isArray(raw)) {
1318
+ throw new Error(`Invalid stringset op: ${opName} must be an array.`);
1319
+ }
1320
+ return raw.map((m) => String(m));
1321
+ };
1322
+ return {
1323
+ add: toMembers(obj.$add, "$add"),
1324
+ remove: toMembers(obj.$remove, "$remove"),
1325
+ clear: obj.$clear === true
1326
+ };
1327
+ }
1328
+ function writeField(rm, declaredType, key, value, modelName) {
1329
+ const kind = declaredType && isKnownFieldType(declaredType) ? fieldWriteKind(declaredType) : "scalar";
1330
+ if (Array.isArray(value)) {
1331
+ if (kind !== "stringset") {
1332
+ throw new Error(
1333
+ `Cannot write array value to field "${key}" on model "${modelName}": its declared type could not be resolved to a collection (stringset). Collection fields require schema metadata in the document (_meta_${modelName}); the model must be saved by a client at least once before a workflow writes one of its collection fields.`
1334
+ );
1335
+ }
1336
+ replaceStringSet(rm, key, value);
1337
+ return;
1338
+ }
1339
+ const op = parseStringSetOp(value);
1340
+ if (op) {
1341
+ if (kind !== "stringset") {
1342
+ throw new Error(
1343
+ `Cannot apply a stringset op ({ $add/$remove/$clear }) to field "${key}" on model "${modelName}": its declared type is ${declaredType ? `"${declaredType}"` : "unresolved"}, not a collection (stringset). Op-objects are only valid for stringset / refersToMany fields with schema metadata (_meta_${modelName}).`
1344
+ );
1345
+ }
1346
+ applyStringSetDelta(ensureStringSetYMap(rm, key), op);
1347
+ return;
1348
+ }
1349
+ if (kind === "stringset") {
1350
+ throw new Error(
1351
+ `Field "${key}" on model "${modelName}" is a stringset and requires an array (replace) or { $add/$remove/$clear } (delta) value; received ${value === null ? "null" : typeof value}.`
1352
+ );
1353
+ }
1354
+ rm.set(key, value);
1355
+ }
1356
+ function registerFunctionDefaultGenerator(name, fn) {
1357
+ FUNCTION_DEFAULT_GENERATORS.set(name, fn);
1358
+ }
1359
+ function decodeFunctionDefault(name) {
1360
+ return FUNCTION_DEFAULT_GENERATORS.get(name);
1361
+ }
1362
+ function resolveLiteralOrFunctionDefault(rawDefault) {
1363
+ return typeof rawDefault === "function" ? rawDefault() : rawDefault;
1364
+ }
1365
+ function resolveDefaultValue(rawDefault, opts = {}) {
1366
+ if (typeof rawDefault === "function") return rawDefault();
1367
+ if (typeof rawDefault === "string" && rawDefault.startsWith("$")) {
1368
+ if (rawDefault.startsWith("$$")) return rawDefault.slice(1);
1369
+ const name = rawDefault.slice(1);
1370
+ const gen = opts.decode?.(name) ?? decodeFunctionDefault(name);
1371
+ if (!gen) {
1372
+ throw new Error(
1373
+ `Unknown function default "${rawDefault}" for field "${opts.fieldName ?? "?"}"${opts.modelName ? ` on model "${opts.modelName}"` : ""}: no generator is registered for "${name}". Register it via registerFunctionDefaultGenerator("${name}", fn) so the server can populate the field the same way the client does.`
1374
+ );
1375
+ }
1376
+ return gen();
1377
+ }
1378
+ return rawDefault;
1379
+ }
1380
+ function applyDefaultsFromSchema(fields, data, opts = {}) {
1381
+ for (const [fieldName, field] of Object.entries(fields)) {
1382
+ if (fieldName === "id") continue;
1383
+ if (!field || field.default === void 0) continue;
1384
+ if (field.default === UNKNOWN_FUNCTION_DEFAULT) {
1385
+ continue;
1386
+ }
1387
+ if (Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== void 0) {
1388
+ continue;
1389
+ }
1390
+ data[fieldName] = resolveDefaultValue(field.default, {
1391
+ fieldName,
1392
+ modelName: opts.modelName,
1393
+ decode: opts.decodeFunctionDefault
1394
+ });
1395
+ }
1396
+ return data;
1397
+ }
1398
+ function applyAutoStampsFromSchema(fields, data, opts) {
1399
+ const { isNew, now } = opts;
1400
+ for (const [fieldName, field] of Object.entries(fields)) {
1401
+ const stamp = field?.autoStamp;
1402
+ if (!stamp) continue;
1403
+ const explicit = Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== null && data[fieldName] !== void 0;
1404
+ if (explicit) continue;
1405
+ if (stamp === "create" && !isNew) continue;
1406
+ data[fieldName] = now;
1407
+ }
1408
+ return data;
1409
+ }
1410
+ var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
1411
+ var init_writeLifecycle = __esm({
1412
+ "src/models/writeLifecycle.ts"() {
1413
+ "use strict";
1414
+ KNOWN_FIELD_TYPE_MAP = {
1415
+ string: true,
1416
+ number: true,
1417
+ boolean: true,
1418
+ date: true,
1419
+ id: true,
1420
+ stringset: true
1421
+ };
1422
+ KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
1423
+ FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
1424
+ registerFunctionDefaultGenerator("generate_ulid", () => ulid());
1425
+ UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
1426
+ }
1427
+ });
1428
+
1429
+ // src/models/metaSync.ts
1430
+ import * as Y3 from "yjs";
1202
1431
  function inferFieldType(value) {
1203
1432
  if (isStringSetYMap(value)) return "stringset";
1204
1433
  switch (typeof value) {
@@ -1219,7 +1448,10 @@ function encodeDefault(value) {
1219
1448
  if (value === void 0 || value === null) return void 0;
1220
1449
  if (typeof value === "function") {
1221
1450
  const name = KNOWN_FUNCTION_DEFAULTS.get(value);
1222
- return name ? `$${name}` : "$unknown_function";
1451
+ return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
1452
+ }
1453
+ if (typeof value === "string" && value.startsWith("$")) {
1454
+ return `$${value}`;
1223
1455
  }
1224
1456
  return value;
1225
1457
  }
@@ -1245,7 +1477,7 @@ function syncModelMeta(yDoc, modelName, schema) {
1245
1477
  if (compoundConstraints.length > 0) {
1246
1478
  let constraints = meta.get("_constraints");
1247
1479
  if (!constraints) {
1248
- constraints = new Y2.Map();
1480
+ constraints = new Y3.Map();
1249
1481
  meta.set("_constraints", constraints);
1250
1482
  }
1251
1483
  for (const constraint of compoundConstraints) {
@@ -1256,7 +1488,7 @@ function syncModelMeta(yDoc, modelName, schema) {
1256
1488
  if (relationships && Object.keys(relationships).length > 0) {
1257
1489
  let rels = meta.get("_relationships");
1258
1490
  if (!rels) {
1259
- rels = new Y2.Map();
1491
+ rels = new Y3.Map();
1260
1492
  meta.set("_relationships", rels);
1261
1493
  }
1262
1494
  for (const [relName, relConfig] of Object.entries(relationships)) {
@@ -1268,7 +1500,7 @@ function syncModelMeta(yDoc, modelName, schema) {
1268
1500
  function syncFieldMeta(metaMap, fieldName, fieldOpts) {
1269
1501
  let fieldMeta = metaMap.get(fieldName);
1270
1502
  if (!fieldMeta) {
1271
- fieldMeta = new Y2.Map();
1503
+ fieldMeta = new Y3.Map();
1272
1504
  metaMap.set(fieldName, fieldMeta);
1273
1505
  }
1274
1506
  setIfChanged(fieldMeta, "type", fieldOpts.type);
@@ -1285,7 +1517,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
1285
1517
  function syncConstraintMeta(constraintsMap, constraint) {
1286
1518
  let cMeta = constraintsMap.get(constraint.name);
1287
1519
  if (!cMeta) {
1288
- cMeta = new Y2.Map();
1520
+ cMeta = new Y3.Map();
1289
1521
  constraintsMap.set(constraint.name, cMeta);
1290
1522
  }
1291
1523
  setIfChanged(cMeta, "type", "unique");
@@ -1295,7 +1527,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
1295
1527
  function syncRelationshipMeta(relsMap, relName, relConfig) {
1296
1528
  let relMeta = relsMap.get(relName);
1297
1529
  if (!relMeta) {
1298
- relMeta = new Y2.Map();
1530
+ relMeta = new Y3.Map();
1299
1531
  relsMap.set(relName, relMeta);
1300
1532
  }
1301
1533
  for (const [key, value] of Object.entries(relConfig)) {
@@ -1310,7 +1542,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
1310
1542
  if (fieldName.startsWith("_")) continue;
1311
1543
  let fieldMeta = meta.get(fieldName);
1312
1544
  if (!fieldMeta) {
1313
- fieldMeta = new Y2.Map();
1545
+ fieldMeta = new Y3.Map();
1314
1546
  meta.set(fieldName, fieldMeta);
1315
1547
  }
1316
1548
  if (!fieldMeta.has("type")) {
@@ -1331,12 +1563,51 @@ var init_metaSync = __esm({
1331
1563
  "src/models/metaSync.ts"() {
1332
1564
  "use strict";
1333
1565
  init_yjsValues();
1566
+ init_writeLifecycle();
1334
1567
  KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
1335
1568
  _syncedCache = /* @__PURE__ */ new WeakMap();
1336
1569
  }
1337
1570
  });
1338
1571
 
1572
+ // src/utils/uniqueIndex.ts
1573
+ import * as Y4 from "yjs";
1574
+ function buildUniqueKey(recordData, fields) {
1575
+ const keyParts = [];
1576
+ for (const field of fields) {
1577
+ const value = recordData instanceof Y4.Map ? recordData.get(field) : recordData[field];
1578
+ if (value === null || value === void 0) {
1579
+ return null;
1580
+ }
1581
+ keyParts.push(String(value));
1582
+ }
1583
+ return keyParts.join("|");
1584
+ }
1585
+ function uniqueIndexMapName(modelName, constraintName) {
1586
+ return `_uniqueIdx_${modelName}_${constraintName}`;
1587
+ }
1588
+ function fieldUniqueConstraintName(modelName, fieldName) {
1589
+ return `${modelName}_${fieldName}_unique`;
1590
+ }
1591
+ var init_uniqueIndex = __esm({
1592
+ "src/utils/uniqueIndex.ts"() {
1593
+ "use strict";
1594
+ }
1595
+ });
1596
+
1339
1597
  // src/models/relationshipManager.ts
1598
+ function warnIfOrderFieldNullable(modelClass, orderByField, relationshipDescription) {
1599
+ if (ALWAYS_PRESENT_ORDER_FIELDS.has(orderByField)) return;
1600
+ const schema = modelClass.getSchema?.();
1601
+ const fields = schema?.fields;
1602
+ if (!fields || typeof fields.get !== "function") return;
1603
+ const fieldOptions = fields.get(orderByField);
1604
+ if (!fieldOptions) return;
1605
+ if (fieldOptions.required !== true) {
1606
+ Logger.warn(
1607
+ `[RelationshipManager] ${relationshipDescription} orders by '${orderByField}', which is not marked required on ${modelClass.name}. The composite (${orderByField}, id) cursor paginates deterministically, but a row whose '${orderByField}' is actually NULL has no stable cursor position and may drop from the tail. Mark '${orderByField}' as required, or order by a required field such as 'id', to remove the risk. (Full NULL-ordering support is tracked in #1316.)`
1608
+ );
1609
+ }
1610
+ }
1340
1611
  function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
1341
1612
  return async function() {
1342
1613
  const relatedId = this[config.relatedIdField];
@@ -1345,6 +1616,16 @@ function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
1345
1616
  };
1346
1617
  }
1347
1618
  function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbEngine) {
1619
+ const {
1620
+ relatedIdField,
1621
+ orderByField = "id",
1622
+ orderDirection = "ASC"
1623
+ } = config;
1624
+ warnIfOrderFieldNullable(
1625
+ targetModelClass,
1626
+ orderByField,
1627
+ `hasMany(${targetModelClass.name})`
1628
+ );
1348
1629
  return async function(paginationArgs) {
1349
1630
  const {
1350
1631
  limit,
@@ -1352,71 +1633,39 @@ function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbE
1352
1633
  beforeCursor,
1353
1634
  direction = "forward"
1354
1635
  } = paginationArgs || {};
1355
- const {
1356
- relatedIdField,
1357
- orderByField = "id",
1358
- orderDirection = "ASC"
1359
- } = config;
1360
1636
  const targetSchema = targetModelClass.getSchema();
1361
1637
  if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
1362
1638
  Logger.error(
1363
1639
  `[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
1364
1640
  );
1365
- return { data: [], nextCursor: null, prevCursor: null };
1641
+ return { data: [], hasMore: false };
1366
1642
  }
1367
- const filter = { [relatedIdField]: this.id };
1643
+ const forward = direction !== "backward";
1644
+ const cursor = forward ? afterCursor : beforeCursor;
1368
1645
  const queryOptions = {
1369
- sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
1646
+ sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 },
1647
+ direction: forward ? 1 : -1
1370
1648
  };
1371
- if (limit !== void 0) {
1372
- queryOptions.limit = limit + 1;
1373
- }
1374
- if (direction === "forward" && afterCursor) {
1375
- filter[orderByField] = {
1376
- [orderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
1377
- };
1378
- } else if (direction === "backward") {
1379
- if (beforeCursor) {
1380
- filter[orderByField] = {
1381
- [orderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
1382
- };
1383
- }
1384
- queryOptions.sort = { [orderByField]: orderDirection === "ASC" ? -1 : 1 };
1385
- }
1386
- const result = await targetModelClass.query(filter, queryOptions);
1387
- const results = result.data;
1388
- let nextCursor = null;
1389
- let prevCursor = null;
1390
- const isFirstPage = direction === "forward" && !afterCursor;
1391
- if (limit !== void 0) {
1392
- if (direction === "forward") {
1393
- if (results.length > limit) {
1394
- nextCursor = results[limit - 1][orderByField];
1395
- results.pop();
1396
- }
1397
- if (results.length > 0 && !isFirstPage) {
1398
- prevCursor = results[0][orderByField];
1399
- }
1400
- } else {
1401
- if (results.length > limit) {
1402
- prevCursor = results[limit - 1][orderByField];
1403
- results.pop();
1404
- }
1405
- results.reverse();
1406
- if (results.length > 0 && beforeCursor) {
1407
- nextCursor = results[results.length - 1][orderByField];
1408
- }
1409
- if (results.length > 0 && !beforeCursor) {
1410
- if (results.length === limit) {
1411
- prevCursor = results[0][orderByField];
1412
- }
1413
- }
1414
- }
1415
- }
1416
- return { data: results, nextCursor, prevCursor };
1649
+ if (limit !== void 0) queryOptions.limit = limit;
1650
+ if (cursor != null) queryOptions.uniqueStartKey = cursor;
1651
+ return targetModelClass.query(
1652
+ { [relatedIdField]: this.id },
1653
+ queryOptions
1654
+ );
1417
1655
  };
1418
1656
  }
1419
1657
  function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClass, joinModelClass, _dbEngine) {
1658
+ const {
1659
+ joinModelLocalField,
1660
+ joinModelRelatedField,
1661
+ joinModelOrderByField = "id",
1662
+ joinModelOrderDirection = "ASC"
1663
+ } = config;
1664
+ warnIfOrderFieldNullable(
1665
+ joinModelClass,
1666
+ joinModelOrderByField,
1667
+ `hasManyThrough(${targetModelClass.name} via ${joinModelClass.name})`
1668
+ );
1420
1669
  return async function(paginationArgs) {
1421
1670
  const {
1422
1671
  limit,
@@ -1430,87 +1679,42 @@ function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClas
1430
1679
  Logger.error(
1431
1680
  `[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
1432
1681
  );
1433
- return { data: [], nextCursor: null, prevCursor: null };
1682
+ return { data: [], hasMore: false };
1434
1683
  }
1435
1684
  if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
1436
1685
  Logger.error(
1437
1686
  `[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
1438
1687
  );
1439
- return { data: [], nextCursor: null, prevCursor: null };
1688
+ return { data: [], hasMore: false };
1440
1689
  }
1441
- const {
1442
- joinModelLocalField,
1443
- joinModelRelatedField,
1444
- joinModelOrderByField = "id",
1445
- joinModelOrderDirection = "ASC"
1446
- } = config;
1447
- const joinFilter = { [joinModelLocalField]: this.id };
1690
+ const forward = direction !== "backward";
1691
+ const cursor = forward ? afterCursor : beforeCursor;
1448
1692
  const joinQueryOptions = {
1449
1693
  sort: {
1450
1694
  [joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
1451
1695
  },
1452
- projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
1696
+ projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 },
1697
+ direction: forward ? 1 : -1
1453
1698
  };
1454
- if (limit !== void 0) {
1455
- joinQueryOptions.limit = limit + 1;
1456
- }
1457
- if (direction === "forward" && afterCursor) {
1458
- joinFilter[joinModelOrderByField] = {
1459
- [joinModelOrderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
1460
- };
1461
- } else if (direction === "backward") {
1462
- if (beforeCursor) {
1463
- joinFilter[joinModelOrderByField] = {
1464
- [joinModelOrderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
1465
- };
1466
- }
1467
- joinQueryOptions.sort = {
1468
- [joinModelOrderByField]: joinModelOrderDirection === "ASC" ? -1 : 1
1469
- };
1470
- }
1699
+ if (limit !== void 0) joinQueryOptions.limit = limit;
1700
+ if (cursor != null) joinQueryOptions.uniqueStartKey = cursor;
1471
1701
  const joinResult = await joinModelClass.query(
1472
- joinFilter,
1702
+ { [joinModelLocalField]: this.id },
1473
1703
  joinQueryOptions
1474
1704
  );
1475
- const joinResults = joinResult.data;
1476
- let nextCursorFromJoin = null;
1477
- let prevCursorFromJoin = null;
1478
- const isFirstPage = direction === "forward" && !afterCursor;
1479
- if (limit !== void 0) {
1480
- if (direction === "forward") {
1481
- if (joinResults.length > limit) {
1482
- nextCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
1483
- joinResults.pop();
1484
- }
1485
- if (joinResults.length > 0 && !isFirstPage) {
1486
- prevCursorFromJoin = joinResults[0][joinModelOrderByField];
1487
- }
1488
- } else {
1489
- if (joinResults.length > limit) {
1490
- prevCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
1491
- joinResults.pop();
1492
- }
1493
- joinResults.reverse();
1494
- if (joinResults.length > 0 && beforeCursor) {
1495
- nextCursorFromJoin = joinResults[joinResults.length - 1][joinModelOrderByField];
1496
- }
1497
- if (joinResults.length > 0 && !beforeCursor) {
1498
- if (joinResults.length === limit) {
1499
- prevCursorFromJoin = joinResults[0][joinModelOrderByField];
1500
- }
1501
- }
1502
- }
1705
+ const relatedIds = joinResult.data.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
1706
+ let targetData = [];
1707
+ if (relatedIds.length > 0) {
1708
+ const targetResult = await targetModelClass.query({
1709
+ id: { $in: relatedIds }
1710
+ });
1711
+ targetData = targetResult.data;
1503
1712
  }
1504
- const relatedIds = joinResults.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
1505
- if (relatedIds.length === 0)
1506
- return { data: [], nextCursor: null, prevCursor: null };
1507
- const targetResult = await targetModelClass.query({
1508
- id: { $in: relatedIds }
1509
- });
1510
1713
  return {
1511
- data: targetResult.data,
1512
- nextCursor: nextCursorFromJoin,
1513
- prevCursor: prevCursorFromJoin
1714
+ data: targetData,
1715
+ nextCursor: joinResult.nextCursor,
1716
+ prevCursor: joinResult.prevCursor,
1717
+ hasMore: joinResult.hasMore
1514
1718
  };
1515
1719
  };
1516
1720
  }
@@ -1634,10 +1838,12 @@ function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetMo
1634
1838
  }, `remove-join-${config.joinModel}-${this.id}-${targetId}`);
1635
1839
  };
1636
1840
  }
1841
+ var ALWAYS_PRESENT_ORDER_FIELDS;
1637
1842
  var init_relationshipManager = __esm({
1638
1843
  "src/models/relationshipManager.ts"() {
1639
1844
  "use strict";
1640
1845
  init_BaseModel();
1846
+ ALWAYS_PRESENT_ORDER_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
1641
1847
  }
1642
1848
  });
1643
1849
 
@@ -2408,7 +2614,13 @@ var init_IncludeResolver = __esm({
2408
2614
  }
2409
2615
  return null;
2410
2616
  }
2411
- /** Apply projection to a single record. */
2617
+ /**
2618
+ * Apply a projection to a single record. Assumes the projection was already
2619
+ * validated upstream (mixed inclusion+exclusion rejected by the translator):
2620
+ * `isInclusion` treats "any value === 1" as inclusion mode. `id` and `type`
2621
+ * are system-identity fields and are always kept in BOTH modes (mirrors the
2622
+ * DatabaseDO `_applyProjection`); `_meta` stays excludable.
2623
+ */
2412
2624
  _applyProjection(record, projection) {
2413
2625
  const fields = Object.entries(projection);
2414
2626
  if (fields.length === 0) return record;
@@ -2427,7 +2639,7 @@ var init_IncludeResolver = __esm({
2427
2639
  } else {
2428
2640
  const result = { ...record };
2429
2641
  for (const [f, v] of fields) {
2430
- if (v === 0) delete result[f];
2642
+ if (v === 0 && f !== "id" && f !== "type") delete result[f];
2431
2643
  }
2432
2644
  return result;
2433
2645
  }
@@ -2446,10 +2658,10 @@ __export(BaseModel_exports, {
2446
2658
  UniqueConstraintViolationError: () => UniqueConstraintViolationError,
2447
2659
  generateULID: () => generateULID
2448
2660
  });
2449
- import * as Y3 from "yjs";
2450
- import { ulid } from "ulid";
2661
+ import * as Y5 from "yjs";
2662
+ import { ulid as ulid2 } from "ulid";
2451
2663
  function generateULID() {
2452
- return ulid();
2664
+ return ulid2();
2453
2665
  }
2454
2666
  var LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
2455
2667
  var init_BaseModel = __esm({
@@ -2461,7 +2673,9 @@ var init_BaseModel = __esm({
2461
2673
  init_CursorManager();
2462
2674
  init_sql();
2463
2675
  init_metaSync();
2676
+ init_writeLifecycle();
2464
2677
  init_yjsValues();
2678
+ init_uniqueIndex();
2465
2679
  LogLevel = /* @__PURE__ */ ((LogLevel2) => {
2466
2680
  LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
2467
2681
  LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
@@ -2846,7 +3060,9 @@ var init_BaseModel = __esm({
2846
3060
  this.setValue(fieldKey, data[fieldKey]);
2847
3061
  this._constructorProvidedFields.add(fieldKey);
2848
3062
  } else if (fieldOptions.default !== void 0) {
2849
- const defaultValue = typeof fieldOptions.default === "function" ? fieldOptions.default() : fieldOptions.default;
3063
+ const defaultValue = resolveLiteralOrFunctionDefault(
3064
+ fieldOptions.default
3065
+ );
2850
3066
  this.setValue(fieldKey, defaultValue);
2851
3067
  }
2852
3068
  }
@@ -3138,7 +3354,7 @@ var init_BaseModel = __esm({
3138
3354
  }
3139
3355
  getStringSetFromYjs(fieldName) {
3140
3356
  const yjsData = this.getFromYjs(fieldName);
3141
- if (yjsData instanceof Y3.Map) {
3357
+ if (yjsData instanceof Y5.Map) {
3142
3358
  return Array.from(yjsData.keys());
3143
3359
  }
3144
3360
  if (yjsData && typeof yjsData === "object") {
@@ -3246,7 +3462,7 @@ var init_BaseModel = __esm({
3246
3462
  }
3247
3463
  const extractStringSetValues = (value) => {
3248
3464
  if (!value) return [];
3249
- if (value instanceof Y3.Map) {
3465
+ if (value instanceof Y5.Map) {
3250
3466
  return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
3251
3467
  }
3252
3468
  if (Array.isArray(value)) {
@@ -3263,7 +3479,7 @@ var init_BaseModel = __esm({
3263
3479
  if (!recordId) continue;
3264
3480
  let itemData;
3265
3481
  const stringSetValuesByField = {};
3266
- if (recordData instanceof Y3.Map) {
3482
+ if (recordData instanceof Y5.Map) {
3267
3483
  itemData = {};
3268
3484
  const unknownFields = [];
3269
3485
  for (const [fieldKey, value] of recordData.entries()) {
@@ -3379,20 +3595,10 @@ var init_BaseModel = __esm({
3379
3595
  Logger.verbose(
3380
3596
  `[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
3381
3597
  );
3382
- const buildUniqueKey = (recordData, fields) => {
3383
- const keyParts = [];
3384
- for (const field of fields) {
3385
- const value = recordData instanceof Y3.Map ? recordData.get(field) : recordData[field];
3386
- if (value === null || value === void 0) {
3387
- return null;
3388
- }
3389
- keyParts.push(String(value));
3390
- }
3391
- return keyParts.join("|");
3392
- };
3598
+ const buildUniqueKey2 = (recordData, fields) => buildUniqueKey(recordData, fields);
3393
3599
  const extractItemData = (key, recordData) => {
3394
3600
  let itemData;
3395
- if (recordData instanceof Y3.Map) {
3601
+ if (recordData instanceof Y5.Map) {
3396
3602
  itemData = {};
3397
3603
  const unknownFields = [];
3398
3604
  for (const [fieldKey, value] of recordData.entries()) {
@@ -3476,7 +3682,7 @@ var init_BaseModel = __esm({
3476
3682
  const recordsByUniqueKey = /* @__PURE__ */ new Map();
3477
3683
  for (const [recordId, recordData] of allRecords.entries()) {
3478
3684
  if (recordIdsToDiscard.has(recordId)) continue;
3479
- const uniqueKey = buildUniqueKey(recordData, constraint.fields);
3685
+ const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
3480
3686
  if (uniqueKey === null) continue;
3481
3687
  if (!recordsByUniqueKey.has(uniqueKey)) {
3482
3688
  recordsByUniqueKey.set(uniqueKey, []);
@@ -3522,7 +3728,7 @@ var init_BaseModel = __esm({
3522
3728
  if (!recordData || !key) continue;
3523
3729
  const itemData = extractItemData(key, recordData);
3524
3730
  if (!itemData) continue;
3525
- if (change.action === "add" && recordData instanceof Y3.Map) {
3731
+ if (change.action === "add" && recordData instanceof Y5.Map) {
3526
3732
  Logger.verbose(
3527
3733
  `[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
3528
3734
  );
@@ -3620,9 +3826,12 @@ var init_BaseModel = __esm({
3620
3826
  const recordData = documentYMap.get(idToDiscard);
3621
3827
  if (!recordData) continue;
3622
3828
  for (const constraint of schema.resolvedUniqueConstraints) {
3623
- const uniqueKey = buildUniqueKey(recordData, constraint.fields);
3829
+ const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
3624
3830
  if (uniqueKey === null) continue;
3625
- const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
3831
+ const constraintMapName = uniqueIndexMapName(
3832
+ modelName,
3833
+ constraint.name
3834
+ );
3626
3835
  const constraintMap = yDoc.getMap(constraintMapName);
3627
3836
  const currentIndexValue = constraintMap.get(uniqueKey);
3628
3837
  if (currentIndexValue === idToDiscard) {
@@ -3640,11 +3849,14 @@ var init_BaseModel = __esm({
3640
3849
  }, `conflict-resolution-${modelName}-${docId}`);
3641
3850
  yDoc.transact(() => {
3642
3851
  for (const constraint of schema.resolvedUniqueConstraints) {
3643
- const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
3852
+ const constraintMapName = uniqueIndexMapName(
3853
+ modelName,
3854
+ constraint.name
3855
+ );
3644
3856
  const constraintMap = yDoc.getMap(constraintMapName);
3645
3857
  for (const [recordId, recordData] of documentYMap.entries()) {
3646
3858
  if (!recordData) continue;
3647
- const uniqueKey = buildUniqueKey(recordData, constraint.fields);
3859
+ const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
3648
3860
  if (uniqueKey === null) continue;
3649
3861
  const currentIndexValue = constraintMap.get(uniqueKey);
3650
3862
  if (currentIndexValue !== recordId) {
@@ -3667,7 +3879,7 @@ var init_BaseModel = __esm({
3667
3879
  `[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
3668
3880
  );
3669
3881
  for (const [recordId, recordData] of documentYMap.entries()) {
3670
- if (recordData instanceof Y3.Map) {
3882
+ if (recordData instanceof Y5.Map) {
3671
3883
  Logger.verbose(
3672
3884
  `[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
3673
3885
  );
@@ -3893,26 +4105,7 @@ var init_BaseModel = __esm({
3893
4105
  * if the field hasn't been touched since the wire-format change in #561.
3894
4106
  */
3895
4107
  applyStringSetChangeToYMap(recordYMap, fieldName, change) {
3896
- let nested = recordYMap.get(fieldName);
3897
- if (!(nested instanceof Y3.Map)) {
3898
- const migrated = new Y3.Map();
3899
- if (nested && typeof nested === "object") {
3900
- for (const [member, marker] of Object.entries(
3901
- nested
3902
- )) {
3903
- if (Boolean(marker)) migrated.set(member, true);
3904
- }
3905
- }
3906
- recordYMap.set(fieldName, migrated);
3907
- nested = migrated;
3908
- }
3909
- const target = nested;
3910
- for (const member of change.additions) {
3911
- target.set(member, true);
3912
- }
3913
- for (const member of change.removals) {
3914
- target.delete(member);
3915
- }
4108
+ applyStringSetChange(recordYMap, fieldName, change);
3916
4109
  }
3917
4110
  /**
3918
4111
  * Deep equality check for comparing field values
@@ -4114,6 +4307,34 @@ var init_BaseModel = __esm({
4114
4307
  `[${modelName}.save] getCurrentJSState() returned:`,
4115
4308
  dataToSave
4116
4309
  );
4310
+ const uniqueMissConflicts = /* @__PURE__ */ new Map();
4311
+ for (const constraint of schema.resolvedUniqueConstraints) {
4312
+ const newUniqueKey = this._buildUniqueKey(
4313
+ constraint.fields,
4314
+ dataToSave,
4315
+ modelName,
4316
+ constraint.name
4317
+ );
4318
+ if (newUniqueKey === null) {
4319
+ continue;
4320
+ }
4321
+ const constraintMap = yDoc.getMap(
4322
+ uniqueIndexMapName(modelName, constraint.name)
4323
+ );
4324
+ if (!constraintMap.get(newUniqueKey)) {
4325
+ const constraintKeyValues = constraint.fields.map(
4326
+ (field) => dataToSave[field]
4327
+ );
4328
+ const conflictingRecord = await modelConstructor._resolveByUniqueFallback(
4329
+ constraint,
4330
+ constraintKeyValues,
4331
+ { excludeId: this.id }
4332
+ );
4333
+ if (conflictingRecord) {
4334
+ uniqueMissConflicts.set(constraint.name, conflictingRecord.id);
4335
+ }
4336
+ }
4337
+ }
4117
4338
  await yDoc.transact(async () => {
4118
4339
  if (upsertOnConstraint && upsertOnKey) {
4119
4340
  const constraintMapName = `_uniqueIdx_${modelName}_${upsertOnConstraint.name}`;
@@ -4155,7 +4376,7 @@ var init_BaseModel = __esm({
4155
4376
  Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
4156
4377
  Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
4157
4378
  if (!recordYMap) {
4158
- recordYMap = new Y3.Map();
4379
+ recordYMap = new Y5.Map();
4159
4380
  Logger.verbose(
4160
4381
  `[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
4161
4382
  );
@@ -4207,7 +4428,10 @@ var init_BaseModel = __esm({
4207
4428
  );
4208
4429
  continue;
4209
4430
  }
4210
- const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
4431
+ const constraintMapName = uniqueIndexMapName(
4432
+ modelName,
4433
+ constraint.name
4434
+ );
4211
4435
  const constraintMap = yDoc.getMap(constraintMapName);
4212
4436
  Logger.verbose(
4213
4437
  `[${modelName}] Save Check (Item '${this.id}'): GET from '${constraintMapName}' with Key '${newUniqueKey.substring(
@@ -4236,6 +4460,26 @@ var init_BaseModel = __esm({
4236
4460
  this.id,
4237
4461
  existingRecordIdWithNewKey
4238
4462
  );
4463
+ } else if (!existingRecordIdWithNewKey) {
4464
+ const conflictingRecordId = uniqueMissConflicts.get(constraint.name);
4465
+ if (conflictingRecordId && conflictingRecordId !== this.id) {
4466
+ Logger.verbose(
4467
+ `[${modelName}] Save Check (Item '${this.id}'): index miss for '${constraint.name}', but engine fallback found conflicting index-less record ${conflictingRecordId}.`
4468
+ );
4469
+ throw new UniqueConstraintViolationError(
4470
+ `Unique constraint '${constraint.name}' violated for model '${modelName}' on fields [${constraint.fields.join(
4471
+ ", "
4472
+ )}]. Attempted ID: ${this.id}. Value(s) starting with '${newUniqueKey.substring(
4473
+ 0,
4474
+ 50
4475
+ )}...' already exist for index-less record ID ${conflictingRecordId}.`,
4476
+ modelName,
4477
+ constraint.name,
4478
+ constraint.fields,
4479
+ this.id,
4480
+ conflictingRecordId
4481
+ );
4482
+ }
4239
4483
  }
4240
4484
  }
4241
4485
  if (isUpdate && oldData) {
@@ -4579,34 +4823,46 @@ var init_BaseModel = __esm({
4579
4823
  const modelName = schema.options.name;
4580
4824
  Logger.verbose(`[${modelName}] Executing query:`, { filter, options });
4581
4825
  const hasIncludes = options?.include && options.include.length > 0;
4582
- const translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
4826
+ const requestedLimit = options?.limit;
4827
+ const overFetch = typeof requestedLimit === "number" && requestedLimit > 0;
4828
+ let translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
4829
+ if (overFetch) {
4830
+ translatorOptions = {
4831
+ ...translatorOptions || {},
4832
+ limit: requestedLimit + 1
4833
+ };
4834
+ }
4583
4835
  const translator = new DocumentQueryTranslator(modelName, schema.fields);
4584
4836
  const translatedQuery = translator.translateFind(filter, translatorOptions);
4585
4837
  Logger.verbose(`[${modelName}] Translated SQL:`, translatedQuery.sql);
4586
4838
  Logger.verbose(`[${modelName}] SQL params:`, translatedQuery.params);
4587
- const results = await _BaseModel.dbInstance.query(
4839
+ let results = await _BaseModel.dbInstance.query(
4588
4840
  translatedQuery.sql,
4589
4841
  translatedQuery.params
4590
4842
  );
4591
4843
  Logger.verbose(`[${modelName}] Query returned ${results.length} results`);
4844
+ let hasMore = false;
4845
+ if (overFetch && results.length > requestedLimit) {
4846
+ hasMore = true;
4847
+ results = results.slice(0, requestedLimit);
4848
+ }
4592
4849
  if (options?.include && options.include.length > 0) {
4593
4850
  const { IncludeResolver: IncludeResolver2 } = await Promise.resolve().then(() => (init_IncludeResolver(), IncludeResolver_exports));
4594
4851
  const resolver = new IncludeResolver2(_BaseModel.dbInstance);
4595
4852
  await resolver.resolve(results, options.include, 0, modelName);
4596
4853
  }
4597
- const requestedLimit = options?.limit;
4598
- const hasMore = CursorManager.hasMoreResults(
4599
- requestedLimit,
4600
- results.length
4601
- );
4602
4854
  const isFirstPage = !options?.uniqueStartKey;
4855
+ const direction = options?.direction || 1;
4603
4856
  const cursors = CursorManager.generateResultCursors(
4604
4857
  results,
4605
4858
  translatedQuery.sortFields,
4606
- options?.direction || 1,
4859
+ direction,
4607
4860
  hasMore,
4608
4861
  isFirstPage
4609
4862
  );
4863
+ if (direction === -1) {
4864
+ results.reverse();
4865
+ }
4610
4866
  let transformedData;
4611
4867
  if (options?.projection && Object.keys(options.projection).length > 0) {
4612
4868
  transformedData = results.map((row) => {
@@ -5182,7 +5438,7 @@ var init_BaseModel = __esm({
5182
5438
  );
5183
5439
  }
5184
5440
  for (const [recordId, recordYMap] of documentYMap.entries()) {
5185
- if (recordYMap instanceof Y3.Map) {
5441
+ if (recordYMap instanceof Y5.Map) {
5186
5442
  const instance = new this({ id: recordId });
5187
5443
  instance._metaDocId = docId;
5188
5444
  const connectedDoc = modelConstructor.connectedDocuments.get(docId);
@@ -5207,6 +5463,50 @@ var init_BaseModel = __esm({
5207
5463
  }
5208
5464
  return allInstances;
5209
5465
  }
5466
+ /**
5467
+ * Resolve a record by its unique-constraint field values via an engine query.
5468
+ * Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
5469
+ * entry for a key.
5470
+ *
5471
+ * The index map is a cache over the SQLite projection, not the source of
5472
+ * truth: every record is loaded into the projection on `initializeForDocument`
5473
+ * regardless of index presence, but a record written by a client predating
5474
+ * index maintenance (legacy data) is missing from the index. Such a record
5475
+ * silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
5476
+ * check. A query over the constraint fields finds it reliably.
5477
+ *
5478
+ * Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
5479
+ * the document that holds it, via `queryOne`) or `null`. Callers decide
5480
+ * whether to heal the missing index entry (write paths) and do their own
5481
+ * logging — this method never mutates the index, so read paths stay
5482
+ * read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
5483
+ * constraint field means the record does not participate in the constraint,
5484
+ * so no query runs.
5485
+ */
5486
+ static async _resolveByUniqueFallback(constraint, keyValues, options) {
5487
+ if (!_BaseModel.dbInstance) {
5488
+ return null;
5489
+ }
5490
+ const filter = {};
5491
+ for (let i = 0; i < constraint.fields.length; i++) {
5492
+ const value = keyValues[i];
5493
+ if (value === null || value === void 0) {
5494
+ return null;
5495
+ }
5496
+ filter[constraint.fields[i]] = value;
5497
+ }
5498
+ if (options?.excludeId) {
5499
+ if ("id" in filter) {
5500
+ if (filter.id === options.excludeId) {
5501
+ return null;
5502
+ }
5503
+ } else {
5504
+ filter.id = { $ne: options.excludeId };
5505
+ }
5506
+ }
5507
+ const found = await this.queryOne(filter);
5508
+ return found ?? null;
5509
+ }
5210
5510
  static async findByUnique(constraintName, value) {
5211
5511
  const modelConstructor = this;
5212
5512
  const schema = modelConstructor.getSchema();
@@ -5245,7 +5545,10 @@ var init_BaseModel = __esm({
5245
5545
  const documentInfo = modelConstructor.connectedDocuments.get(docId);
5246
5546
  if (!documentInfo) continue;
5247
5547
  const yDoc = documentInfo.yDoc;
5248
- const constraintMapName2 = `_uniqueIdx_${currentModelName}_${constraint.name}`;
5548
+ const constraintMapName2 = uniqueIndexMapName(
5549
+ currentModelName,
5550
+ constraint.name
5551
+ );
5249
5552
  const constraintMap2 = yDoc.getMap(constraintMapName2);
5250
5553
  const recordId2 = constraintMap2.get(uniqueKeyString);
5251
5554
  if (recordId2) {
@@ -5253,7 +5556,7 @@ var init_BaseModel = __esm({
5253
5556
  const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
5254
5557
  if (documentYMap) {
5255
5558
  const recordYMap2 = documentYMap.get(recordId2);
5256
- if (recordYMap2 && recordYMap2 instanceof Y3.Map) {
5559
+ if (recordYMap2 && recordYMap2 instanceof Y5.Map) {
5257
5560
  const instance = new modelConstructor({
5258
5561
  id: recordId2
5259
5562
  });
@@ -5267,6 +5570,16 @@ var init_BaseModel = __esm({
5267
5570
  }
5268
5571
  }
5269
5572
  }
5573
+ const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
5574
+ constraint,
5575
+ keyValues
5576
+ );
5577
+ if (fallbackRecord) {
5578
+ Logger.verbose(
5579
+ `[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
5580
+ );
5581
+ return fallbackRecord;
5582
+ }
5270
5583
  Logger.verbose(
5271
5584
  `[${currentModelName}.findByUnique] Record not found in any connected document`
5272
5585
  );
@@ -5280,17 +5593,30 @@ var init_BaseModel = __esm({
5280
5593
  `[${currentModelName}] No Y.Doc is connected for this model. Connect a document via initJsBao(...).connectDocument or call initializeForDocument(yDoc, db, docId, permissionHint) before using legacy findByUnique.`
5281
5594
  );
5282
5595
  }
5283
- const constraintMapName = `_uniqueIdx_${currentModelName}_${constraint.name}`;
5596
+ const constraintMapName = uniqueIndexMapName(
5597
+ currentModelName,
5598
+ constraint.name
5599
+ );
5284
5600
  const constraintMap = legacyDocInfo.yDoc.getMap(constraintMapName);
5285
5601
  const recordId = constraintMap.get(uniqueKeyString);
5286
5602
  if (!recordId) {
5603
+ const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
5604
+ constraint,
5605
+ keyValues
5606
+ );
5607
+ if (fallbackRecord) {
5608
+ Logger.verbose(
5609
+ `[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
5610
+ );
5611
+ return fallbackRecord;
5612
+ }
5287
5613
  return null;
5288
5614
  }
5289
5615
  const recordYMap = legacyDocumentMap.get(recordId);
5290
5616
  if (!recordYMap) {
5291
5617
  return null;
5292
5618
  }
5293
- if (recordYMap instanceof Y3.Map) {
5619
+ if (recordYMap instanceof Y5.Map) {
5294
5620
  const instance = new modelConstructor({
5295
5621
  id: recordId
5296
5622
  });
@@ -5348,7 +5674,10 @@ var init_BaseModel = __esm({
5348
5674
  `[${currentModelName}] upsertByUnique for '${constraintName}': unique key cannot be built due to null/undefined values. Upsert cannot proceed reliably.`
5349
5675
  );
5350
5676
  }
5351
- const constraintMapName = `_uniqueIdx_${currentModelName}_${constraint.name}`;
5677
+ const constraintMapName = uniqueIndexMapName(
5678
+ currentModelName,
5679
+ constraint.name
5680
+ );
5352
5681
  let existingRecord = null;
5353
5682
  if (connectedDocuments && connectedDocuments.size > 0) {
5354
5683
  for (const docId of connectedDocuments.keys()) {
@@ -5366,6 +5695,33 @@ var init_BaseModel = __esm({
5366
5695
  }
5367
5696
  } else {
5368
5697
  }
5698
+ if (!existingRecord) {
5699
+ const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
5700
+ constraint,
5701
+ keyValuesForLookup
5702
+ );
5703
+ if (fallbackRecord) {
5704
+ existingRecord = fallbackRecord;
5705
+ const healDocId = fallbackRecord._metaDocId;
5706
+ const healDocInfo = healDocId ? connectedDocuments.get(healDocId) : void 0;
5707
+ const canHeal = !!healDocInfo && healDocInfo.permissionHint !== "read";
5708
+ if (healDocInfo && canHeal) {
5709
+ healDocInfo.yDoc.transact(() => {
5710
+ const healMap = healDocInfo.yDoc.getMap(constraintMapName);
5711
+ if (healMap.get(uniqueKeyString) !== fallbackRecord.id) {
5712
+ healMap.set(uniqueKeyString, fallbackRecord.id);
5713
+ }
5714
+ }, `heal-unique-index-${currentModelName}-${healDocId}`);
5715
+ Logger.verbose(
5716
+ `[${currentModelName}] upsertByUnique: index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id}; healed missing index entry (legacy index-less record).`
5717
+ );
5718
+ } else {
5719
+ Logger.verbose(
5720
+ `[${currentModelName}] upsertByUnique: index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id}; skipped index heal (owning document is read-only or not connected).`
5721
+ );
5722
+ }
5723
+ }
5724
+ }
5369
5725
  if (options?.objectMustExist) {
5370
5726
  if (!existingRecord) {
5371
5727
  throw new RecordNotFoundError(
@@ -5522,7 +5878,7 @@ var init_BaseModel = __esm({
5522
5878
  for (const [fieldKey, fieldOptions] of schema.fields) {
5523
5879
  if (fieldOptions?.type !== "stringset") continue;
5524
5880
  const value = recordYMap.get(fieldKey);
5525
- if (value instanceof Y3.Map) {
5881
+ if (value instanceof Y5.Map) {
5526
5882
  this.observeStringSetMapOnce(value);
5527
5883
  }
5528
5884
  }
@@ -5707,16 +6063,170 @@ var init_BaseModel = __esm({
5707
6063
  }
5708
6064
  });
5709
6065
 
6066
+ // src/utils/environment.ts
6067
+ var environment_exports = {};
6068
+ __export(environment_exports, {
6069
+ constants: () => constants,
6070
+ detectEnvironment: () => detectEnvironment,
6071
+ features: () => features,
6072
+ isBrowser: () => isBrowser,
6073
+ isNode: () => isNode,
6074
+ isNodeModuleAvailable: () => isNodeModuleAvailable
6075
+ });
6076
+ function detectEnvironment() {
6077
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
6078
+ return "node";
6079
+ }
6080
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
6081
+ return "browser";
6082
+ }
6083
+ if (typeof self !== "undefined" && typeof self.importScripts === "function") {
6084
+ return "browser";
6085
+ }
6086
+ return "unknown";
6087
+ }
6088
+ function isNode() {
6089
+ return detectEnvironment() === "node";
6090
+ }
6091
+ function isBrowser() {
6092
+ return detectEnvironment() === "browser";
6093
+ }
6094
+ async function isNodeModuleAvailable(moduleName) {
6095
+ if (!isNode()) {
6096
+ return false;
6097
+ }
6098
+ try {
6099
+ await import(
6100
+ /* @vite-ignore */
6101
+ moduleName
6102
+ );
6103
+ return true;
6104
+ } catch {
6105
+ return false;
6106
+ }
6107
+ }
6108
+ var features, constants;
6109
+ var init_environment = __esm({
6110
+ "src/utils/environment.ts"() {
6111
+ "use strict";
6112
+ features = {
6113
+ get hasWebWorkers() {
6114
+ return isBrowser() && typeof Worker !== "undefined";
6115
+ },
6116
+ get hasFileSystem() {
6117
+ return isNode();
6118
+ },
6119
+ get hasWebAssembly() {
6120
+ return typeof WebAssembly !== "undefined";
6121
+ },
6122
+ async hasBetterSqlite3() {
6123
+ return await isNodeModuleAvailable("better-sqlite3");
6124
+ }
6125
+ };
6126
+ constants = {
6127
+ DEFAULT_NODE_SQLITE_PATH: ":memory:",
6128
+ DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
6129
+ };
6130
+ }
6131
+ });
6132
+
5710
6133
  // src/engines/SqljsEngine.ts
5711
6134
  import initSqlJs from "sql.js";
5712
6135
  import { Mutex } from "async-mutex";
5713
- var SQL_WASM_URL, TransactionalSQLJSOperations, SqljsEngine;
6136
+ async function resolveNodeWasmPath() {
6137
+ if (!isNode()) {
6138
+ return null;
6139
+ }
6140
+ try {
6141
+ const cjsRequire = typeof __require !== "undefined" ? __require : void 0;
6142
+ if (cjsRequire) {
6143
+ return cjsRequire.resolve("sql.js/dist/sql-wasm.wasm");
6144
+ }
6145
+ } catch {
6146
+ }
6147
+ try {
6148
+ const metaUrl = import.meta.url;
6149
+ if (metaUrl) {
6150
+ const { createRequire } = await import(
6151
+ /* @vite-ignore */
6152
+ "module"
6153
+ );
6154
+ const req = createRequire(metaUrl);
6155
+ return req.resolve("sql.js/dist/sql-wasm.wasm");
6156
+ }
6157
+ } catch {
6158
+ }
6159
+ return null;
6160
+ }
6161
+ async function preflightNodeWasm(wasmPath) {
6162
+ if (!isNode()) {
6163
+ return;
6164
+ }
6165
+ if (/^[a-z]+:\/\//i.test(wasmPath)) {
6166
+ return;
6167
+ }
6168
+ const { existsSync } = await import(
6169
+ /* @vite-ignore */
6170
+ "fs"
6171
+ );
6172
+ if (!existsSync(wasmPath)) {
6173
+ throw new Error(
6174
+ `[SqljsEngine] sql.js wasm not found at "${wasmPath}". Pass \`locateFile\`/\`wasmURL\` pointing at sql-wasm.wasm, or install \`better-sqlite3\` and use the \`node-sqlite\` engine (see getRecommendedNodeEngine()).`
6175
+ );
6176
+ }
6177
+ }
6178
+ async function loadSqlJsModule(locateFile) {
6179
+ let abortReject;
6180
+ const toError = (reason) => reason instanceof Error ? reason : new Error(
6181
+ `[SqljsEngine] sql.js aborted during initialization: ${String(
6182
+ reason
6183
+ )}`
6184
+ );
6185
+ const nodeProcess = isNode() && typeof process !== "undefined" ? process : void 0;
6186
+ const onUnhandled = (reason) => {
6187
+ const err = toError(reason);
6188
+ if (/Aborted/.test(err.message) || /Aborted/.test(String(reason))) {
6189
+ abortReject?.(err);
6190
+ return;
6191
+ }
6192
+ if (nodeProcess && nodeProcess.listenerCount("unhandledRejection") === 1) {
6193
+ nodeProcess.removeListener("unhandledRejection", onUnhandled);
6194
+ void Promise.reject(reason);
6195
+ }
6196
+ };
6197
+ if (nodeProcess) {
6198
+ nodeProcess.on("unhandledRejection", onUnhandled);
6199
+ }
6200
+ const abortGuard = new Promise((_resolve, reject) => {
6201
+ abortReject = reject;
6202
+ });
6203
+ abortGuard.catch(() => {
6204
+ });
6205
+ try {
6206
+ const initPromise = initSqlJs({
6207
+ locateFile,
6208
+ // Layer 1: emscripten onAbort.
6209
+ onAbort: (reason) => abortReject?.(toError(reason))
6210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6211
+ });
6212
+ return await Promise.race([initPromise, abortGuard]);
6213
+ } finally {
6214
+ if (nodeProcess) {
6215
+ setTimeout(() => {
6216
+ nodeProcess.removeListener("unhandledRejection", onUnhandled);
6217
+ }, 0);
6218
+ }
6219
+ }
6220
+ }
6221
+ var SQL_WASM_URL, SQL_WASM_FILENAME, TransactionalSQLJSOperations, SqljsEngine;
5714
6222
  var init_SqljsEngine = __esm({
5715
6223
  "src/engines/SqljsEngine.ts"() {
5716
6224
  "use strict";
5717
6225
  init_BaseModel();
5718
6226
  init_sql();
6227
+ init_environment();
5719
6228
  SQL_WASM_URL = "/sql-wasm.wasm";
6229
+ SQL_WASM_FILENAME = "sql-wasm.wasm";
5720
6230
  TransactionalSQLJSOperations = class {
5721
6231
  db;
5722
6232
  engine;
@@ -5782,22 +6292,37 @@ var init_SqljsEngine = __esm({
5782
6292
  }
5783
6293
  async initialize(engineOptions) {
5784
6294
  Logger.info("[SqljsEngine] Initializing SQL.js engine...");
6295
+ const loadingModuleThisCall = !_SqljsEngine.SQL;
5785
6296
  try {
5786
6297
  if (!_SqljsEngine.SQL) {
5787
- const locateFileConfig = engineOptions?.locateFile || ((_file) => engineOptions?.wasmURL || SQL_WASM_URL);
6298
+ let locateFileConfig;
6299
+ if (engineOptions?.locateFile) {
6300
+ locateFileConfig = engineOptions.locateFile;
6301
+ } else if (engineOptions?.wasmURL) {
6302
+ const url = engineOptions.wasmURL;
6303
+ locateFileConfig = (_file) => url;
6304
+ } else {
6305
+ const nodeWasmPath = await resolveNodeWasmPath();
6306
+ const resolved = nodeWasmPath ?? SQL_WASM_URL;
6307
+ locateFileConfig = (_file) => resolved;
6308
+ }
6309
+ const wasmPath = locateFileConfig(SQL_WASM_FILENAME);
5788
6310
  Logger.debug(
5789
6311
  "[SqljsEngine] Attempting to load SQL.js WASM from:",
5790
- locateFileConfig("sql-wasm.wasm")
6312
+ wasmPath
5791
6313
  );
5792
- _SqljsEngine.SQL = await initSqlJs({
5793
- locateFile: locateFileConfig
5794
- });
6314
+ await preflightNodeWasm(wasmPath);
6315
+ const SQL = await loadSqlJsModule(locateFileConfig);
6316
+ _SqljsEngine.SQL = SQL;
5795
6317
  Logger.info("[SqljsEngine] SQL.js WASM loaded successfully.");
5796
6318
  }
5797
6319
  this.db = new _SqljsEngine.SQL.Database();
5798
6320
  Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
5799
6321
  this.updateTableNames();
5800
6322
  } catch (error) {
6323
+ if (loadingModuleThisCall) {
6324
+ _SqljsEngine.SQL = null;
6325
+ }
5801
6326
  Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
5802
6327
  throw error;
5803
6328
  }
@@ -6085,73 +6610,6 @@ var init_SqljsEngine = __esm({
6085
6610
  }
6086
6611
  });
6087
6612
 
6088
- // src/utils/environment.ts
6089
- var environment_exports = {};
6090
- __export(environment_exports, {
6091
- constants: () => constants,
6092
- detectEnvironment: () => detectEnvironment,
6093
- features: () => features,
6094
- isBrowser: () => isBrowser,
6095
- isNode: () => isNode,
6096
- isNodeModuleAvailable: () => isNodeModuleAvailable
6097
- });
6098
- function detectEnvironment() {
6099
- if (typeof process !== "undefined" && process.versions && process.versions.node) {
6100
- return "node";
6101
- }
6102
- if (typeof window !== "undefined" && typeof document !== "undefined") {
6103
- return "browser";
6104
- }
6105
- if (typeof self !== "undefined" && typeof self.importScripts === "function") {
6106
- return "browser";
6107
- }
6108
- return "unknown";
6109
- }
6110
- function isNode() {
6111
- return detectEnvironment() === "node";
6112
- }
6113
- function isBrowser() {
6114
- return detectEnvironment() === "browser";
6115
- }
6116
- async function isNodeModuleAvailable(moduleName) {
6117
- if (!isNode()) {
6118
- return false;
6119
- }
6120
- try {
6121
- await import(
6122
- /* @vite-ignore */
6123
- moduleName
6124
- );
6125
- return true;
6126
- } catch {
6127
- return false;
6128
- }
6129
- }
6130
- var features, constants;
6131
- var init_environment = __esm({
6132
- "src/utils/environment.ts"() {
6133
- "use strict";
6134
- features = {
6135
- get hasWebWorkers() {
6136
- return isBrowser() && typeof Worker !== "undefined";
6137
- },
6138
- get hasFileSystem() {
6139
- return isNode();
6140
- },
6141
- get hasWebAssembly() {
6142
- return typeof WebAssembly !== "undefined";
6143
- },
6144
- async hasBetterSqlite3() {
6145
- return await isNodeModuleAvailable("better-sqlite3");
6146
- }
6147
- };
6148
- constants = {
6149
- DEFAULT_NODE_SQLITE_PATH: ":memory:",
6150
- DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
6151
- };
6152
- }
6153
- });
6154
-
6155
6613
  // src/engines/node/NodeSqliteEngine.ts
6156
6614
  var NodeSqliteEngine_exports = {};
6157
6615
  __export(NodeSqliteEngine_exports, {
@@ -7095,6 +7553,7 @@ init_StringSet();
7095
7553
  // src/models/schema.ts
7096
7554
  init_BaseModel();
7097
7555
  init_ModelRegistry();
7556
+ init_uniqueIndex();
7098
7557
  function defineModelSchema(input) {
7099
7558
  const { name, fields } = input;
7100
7559
  const options = {
@@ -7167,7 +7626,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
7167
7626
  for (const [fieldName, options] of fields.entries()) {
7168
7627
  if (options.unique) {
7169
7628
  resolved.push({
7170
- name: `${modelName}_${fieldName}_unique`,
7629
+ name: fieldUniqueConstraintName(modelName, fieldName),
7171
7630
  fields: [fieldName]
7172
7631
  });
7173
7632
  }
@@ -7232,7 +7691,7 @@ function attachAndRegisterModel(modelClass, schema) {
7232
7691
 
7233
7692
  // src/utils/yDocSchema.ts
7234
7693
  init_yjsValues();
7235
- import * as Y4 from "yjs";
7694
+ import * as Y6 from "yjs";
7236
7695
  function discoverSchema(yDoc) {
7237
7696
  const models = {};
7238
7697
  const metaNames = /* @__PURE__ */ new Set();
@@ -7266,7 +7725,7 @@ function discoverModelNames(yDoc) {
7266
7725
  function materializeMap(yDoc, key) {
7267
7726
  try {
7268
7727
  const map = yDoc.getMap(key);
7269
- return map instanceof Y4.Map ? map : null;
7728
+ return map instanceof Y6.Map ? map : null;
7270
7729
  } catch {
7271
7730
  return null;
7272
7731
  }
@@ -7276,11 +7735,11 @@ function readModelMeta(metaMap) {
7276
7735
  let constraints;
7277
7736
  let relationships;
7278
7737
  for (const [key, value] of metaMap.entries()) {
7279
- if (key === "_constraints" && value instanceof Y4.Map) {
7738
+ if (key === "_constraints" && value instanceof Y6.Map) {
7280
7739
  constraints = readConstraints(value);
7281
- } else if (key === "_relationships" && value instanceof Y4.Map) {
7740
+ } else if (key === "_relationships" && value instanceof Y6.Map) {
7282
7741
  relationships = readRelationships(value);
7283
- } else if (value instanceof Y4.Map) {
7742
+ } else if (value instanceof Y6.Map) {
7284
7743
  fields[key] = readFieldMeta(value);
7285
7744
  }
7286
7745
  }
@@ -7315,7 +7774,7 @@ function inferModelFromData(dataMap) {
7315
7774
  const fields = {};
7316
7775
  let sampled = 0;
7317
7776
  for (const [_recordId, recordValue] of dataMap.entries()) {
7318
- if (!(recordValue instanceof Y4.Map)) continue;
7777
+ if (!(recordValue instanceof Y6.Map)) continue;
7319
7778
  if (++sampled > 5) break;
7320
7779
  for (const [fieldName, value] of recordValue.entries()) {
7321
7780
  if (fieldName.startsWith("_")) continue;
@@ -7343,7 +7802,7 @@ function inferTypeFromValue(value) {
7343
7802
  function readConstraints(constraintsMap) {
7344
7803
  const out = {};
7345
7804
  for (const [name, value] of constraintsMap.entries()) {
7346
- if (!(value instanceof Y4.Map)) continue;
7805
+ if (!(value instanceof Y6.Map)) continue;
7347
7806
  let fields = [];
7348
7807
  const rawFields = value.get("fields");
7349
7808
  if (typeof rawFields === "string") {
@@ -7362,7 +7821,7 @@ function readConstraints(constraintsMap) {
7362
7821
  function readRelationships(relsMap) {
7363
7822
  const out = {};
7364
7823
  for (const [name, value] of relsMap.entries()) {
7365
- if (!(value instanceof Y4.Map)) continue;
7824
+ if (!(value instanceof Y6.Map)) continue;
7366
7825
  const rel = {};
7367
7826
  for (const [k, v] of value.entries()) {
7368
7827
  rel[k] = v;
@@ -7658,18 +8117,19 @@ async function loadSchemaFromToml(filePath, options = {}) {
7658
8117
 
7659
8118
  // src/node.ts
7660
8119
  init_metaSync();
8120
+ init_writeLifecycle();
7661
8121
 
7662
8122
  // src/utils/yDocDump.ts
7663
- import * as Y5 from "yjs";
8123
+ import * as Y7 from "yjs";
7664
8124
  function toPlain(value) {
7665
- if (value instanceof Y5.Map) {
8125
+ if (value instanceof Y7.Map) {
7666
8126
  const obj = {};
7667
8127
  value.forEach((v, k) => {
7668
8128
  obj[k] = toPlain(v);
7669
8129
  });
7670
8130
  return obj;
7671
8131
  }
7672
- if (value instanceof Y5.Array) {
8132
+ if (value instanceof Y7.Array) {
7673
8133
  return value.toArray().map((item) => toPlain(item));
7674
8134
  }
7675
8135
  return value;
@@ -7678,7 +8138,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
7678
8138
  const result = {};
7679
8139
  const includeIndexes = !!options.includeIndexes;
7680
8140
  yDoc.share.forEach((type, name) => {
7681
- if (!(type instanceof Y5.Map)) return;
8141
+ if (!(type instanceof Y7.Map)) return;
7682
8142
  if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
7683
8143
  const records = {};
7684
8144
  type.forEach((recordValue, recordId) => {
@@ -7738,31 +8198,45 @@ export {
7738
8198
  SqljsEngine,
7739
8199
  StringSet,
7740
8200
  UniqueConstraintViolationError,
8201
+ applyAutoStampsFromSchema,
8202
+ applyDefaultsFromSchema,
8203
+ applyStringSetChange,
8204
+ applyStringSetDelta,
8205
+ assertNever,
7741
8206
  attachAndRegisterModel,
7742
8207
  attachSchemaToClass,
7743
8208
  autoRegisterModel,
7744
8209
  clearMetaSyncCache,
7745
8210
  constants,
7746
8211
  createModelClass,
8212
+ decodeFunctionDefault,
7747
8213
  defineModelSchema,
7748
8214
  detectEnvironment,
7749
8215
  discoverModelNames,
7750
8216
  discoverSchema,
7751
8217
  dumpYDocToPlain,
8218
+ ensureStringSetYMap,
7752
8219
  features,
8220
+ fieldWriteKind,
7753
8221
  generateULID,
7754
8222
  getRecommendedNodeEngine,
7755
8223
  inferFieldType,
7756
8224
  initJsBao,
7757
8225
  isBrowser,
8226
+ isKnownFieldType,
7758
8227
  isNode,
7759
8228
  isNodeModuleAvailable,
7760
8229
  loadSchemaFromToml,
7761
8230
  loadSchemaFromTomlString,
8231
+ parseStringSetOp,
7762
8232
  registerFunctionDefault,
8233
+ registerFunctionDefaultGenerator,
8234
+ replaceStringSet,
7763
8235
  resetJsBao,
8236
+ resolveDefaultValue,
7764
8237
  schemaToToml,
7765
8238
  summarizePlainYDoc,
7766
8239
  syncInferredMeta,
7767
- syncModelMeta
8240
+ syncModelMeta,
8241
+ writeField
7768
8242
  };