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.cjs CHANGED
@@ -366,21 +366,36 @@ var init_CursorManager = __esm({
366
366
  return fields;
367
367
  }
368
368
  /**
369
- * Build ORDER BY clause from sort specification
369
+ * Build ORDER BY clause from sort specification.
370
+ *
371
+ * `direction` is the pagination direction (1 = forward, -1 = backward).
372
+ * For backward pagination each field's effective ORDER BY direction is
373
+ * reversed so the cursor's flipped inequality (built by
374
+ * `buildPaginationConditions`) returns the page adjacent to the cursor,
375
+ * not the globally smallest/largest window. Without this,
376
+ * `field < cursor ... ORDER BY field ASC LIMIT n` returns the n smallest
377
+ * rows overall instead of the n rows just below the cursor.
378
+ *
379
+ * The returned `directions` array carries the *declared* per-field sort
380
+ * directions (unaffected by `direction`) — `buildPaginationConditions`
381
+ * needs the declared directions to pick each field's comparison operator.
370
382
  */
371
- static buildOrderClause(sort, fieldFormatter = (field) => field) {
383
+ static buildOrderClause(sort, fieldFormatter = (field) => field, direction = 1) {
372
384
  const fields = this.extractSortFields(sort);
373
385
  const clauses = [];
374
386
  const directions = [];
387
+ const sqlDir = (declared) => {
388
+ const effective = direction === -1 ? declared === 1 ? -1 : 1 : declared;
389
+ return effective === 1 ? "ASC" : "DESC";
390
+ };
375
391
  if (sort && Object.keys(sort).length > 0) {
376
392
  for (const [field, dir] of Object.entries(sort)) {
377
- const sqlDirection = dir === 1 ? "ASC" : "DESC";
378
- clauses.push(`${fieldFormatter(field)} ${sqlDirection}`);
393
+ clauses.push(`${fieldFormatter(field)} ${sqlDir(dir)}`);
379
394
  directions.push(dir);
380
395
  }
381
396
  }
382
397
  if (!sort || !sort.hasOwnProperty("id")) {
383
- clauses.push(`${fieldFormatter("id")} ASC`);
398
+ clauses.push(`${fieldFormatter("id")} ${sqlDir(1)}`);
384
399
  directions.push(1);
385
400
  } else if (sort && sort.hasOwnProperty("id")) {
386
401
  directions.push(sort["id"]);
@@ -403,18 +418,27 @@ var init_CursorManager = __esm({
403
418
  /**
404
419
  * Generate next and previous cursors from result set
405
420
  */
406
- static generateResultCursors(results, sortFields, _requestedDirection, hasMore, isFirstPage = false) {
421
+ static generateResultCursors(results, sortFields, requestedDirection, hasMore, isFirstPage = false) {
407
422
  if (results.length === 0) {
408
423
  return {};
409
424
  }
410
425
  const cursors = {};
411
426
  if (hasMore && results.length > 0) {
412
427
  const lastResult = results[results.length - 1];
413
- cursors.nextCursor = this.generateCursor(lastResult, sortFields, 1);
428
+ cursors.nextCursor = this.generateCursor(
429
+ lastResult,
430
+ sortFields,
431
+ requestedDirection
432
+ );
414
433
  }
415
434
  if (results.length > 0 && !isFirstPage) {
416
435
  const firstResult = results[0];
417
- cursors.prevCursor = this.generateCursor(firstResult, sortFields, -1);
436
+ const opposite = requestedDirection === 1 ? -1 : 1;
437
+ cursors.prevCursor = this.generateCursor(
438
+ firstResult,
439
+ sortFields,
440
+ opposite
441
+ );
418
442
  }
419
443
  return cursors;
420
444
  }
@@ -547,7 +571,8 @@ var init_DocumentQueryTranslator = __esm({
547
571
  const whereClause = this.translateFilter(filter);
548
572
  const orderClause = CursorManager.buildOrderClause(
549
573
  options?.sort,
550
- (field) => this.getQuotedField(field)
574
+ (field) => this.getQuotedField(field),
575
+ options?.direction || 1
551
576
  );
552
577
  const limitClause = this.buildLimitClause(options);
553
578
  const paginationClause = this.buildPaginationClause(
@@ -1220,6 +1245,204 @@ var init_yjsValues = __esm({
1220
1245
  }
1221
1246
  });
1222
1247
 
1248
+ // src/models/writeLifecycle.ts
1249
+ function assertNever(x) {
1250
+ throw new Error(`Unhandled field type: ${String(x)}`);
1251
+ }
1252
+ function fieldWriteKind(type) {
1253
+ switch (type) {
1254
+ case "string":
1255
+ case "number":
1256
+ case "boolean":
1257
+ case "date":
1258
+ case "id":
1259
+ return "scalar";
1260
+ case "stringset":
1261
+ return "stringset";
1262
+ default:
1263
+ return assertNever(type);
1264
+ }
1265
+ }
1266
+ function isKnownFieldType(type) {
1267
+ return KNOWN_FIELD_TYPES.has(type);
1268
+ }
1269
+ function ensureStringSetYMap(rm, fieldName) {
1270
+ const nested = rm.get(fieldName);
1271
+ if (nested instanceof Y2.Map) return nested;
1272
+ const fresh = new Y2.Map();
1273
+ if (nested && typeof nested === "object") {
1274
+ for (const [member, marker] of Object.entries(
1275
+ nested
1276
+ )) {
1277
+ if (Boolean(marker)) fresh.set(member, true);
1278
+ }
1279
+ }
1280
+ rm.set(fieldName, fresh);
1281
+ return fresh;
1282
+ }
1283
+ function applyStringSetDelta(target, delta) {
1284
+ if (delta.clear) {
1285
+ for (const member of Array.from(target.keys())) target.delete(member);
1286
+ }
1287
+ if (delta.remove) {
1288
+ for (const member of delta.remove) target.delete(String(member));
1289
+ }
1290
+ if (delta.add) {
1291
+ for (const member of delta.add) {
1292
+ const key = String(member);
1293
+ if (target.get(key) !== true) target.set(key, true);
1294
+ }
1295
+ }
1296
+ }
1297
+ function replaceStringSet(rm, fieldName, members) {
1298
+ const target = ensureStringSetYMap(rm, fieldName);
1299
+ const desired = /* @__PURE__ */ new Set();
1300
+ for (const member of members) desired.add(String(member));
1301
+ for (const existing of Array.from(target.keys())) {
1302
+ if (!desired.has(existing)) target.delete(existing);
1303
+ }
1304
+ for (const member of desired) {
1305
+ if (target.get(member) !== true) target.set(member, true);
1306
+ }
1307
+ }
1308
+ function applyStringSetChange(recordYMap, fieldName, change) {
1309
+ const target = ensureStringSetYMap(recordYMap, fieldName);
1310
+ applyStringSetDelta(target, {
1311
+ add: change.additions,
1312
+ remove: change.removals
1313
+ });
1314
+ }
1315
+ function parseStringSetOp(value) {
1316
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Y2.AbstractType) {
1317
+ return null;
1318
+ }
1319
+ const keys = Object.keys(value);
1320
+ const opKeys = keys.filter((k) => k.startsWith("$"));
1321
+ if (opKeys.length === 0) return null;
1322
+ const known = /* @__PURE__ */ new Set(["$add", "$remove", "$clear"]);
1323
+ const unknown = keys.filter((k) => !known.has(k));
1324
+ if (unknown.length > 0) {
1325
+ throw new Error(
1326
+ `Invalid stringset op: unexpected key(s) [${unknown.join(", ")}]. Only $add, $remove and $clear are supported.`
1327
+ );
1328
+ }
1329
+ const obj = value;
1330
+ const toMembers = (raw, opName) => {
1331
+ if (raw === void 0) return [];
1332
+ if (!Array.isArray(raw)) {
1333
+ throw new Error(`Invalid stringset op: ${opName} must be an array.`);
1334
+ }
1335
+ return raw.map((m) => String(m));
1336
+ };
1337
+ return {
1338
+ add: toMembers(obj.$add, "$add"),
1339
+ remove: toMembers(obj.$remove, "$remove"),
1340
+ clear: obj.$clear === true
1341
+ };
1342
+ }
1343
+ function writeField(rm, declaredType, key, value, modelName) {
1344
+ const kind = declaredType && isKnownFieldType(declaredType) ? fieldWriteKind(declaredType) : "scalar";
1345
+ if (Array.isArray(value)) {
1346
+ if (kind !== "stringset") {
1347
+ throw new Error(
1348
+ `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.`
1349
+ );
1350
+ }
1351
+ replaceStringSet(rm, key, value);
1352
+ return;
1353
+ }
1354
+ const op = parseStringSetOp(value);
1355
+ if (op) {
1356
+ if (kind !== "stringset") {
1357
+ throw new Error(
1358
+ `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}).`
1359
+ );
1360
+ }
1361
+ applyStringSetDelta(ensureStringSetYMap(rm, key), op);
1362
+ return;
1363
+ }
1364
+ if (kind === "stringset") {
1365
+ throw new Error(
1366
+ `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}.`
1367
+ );
1368
+ }
1369
+ rm.set(key, value);
1370
+ }
1371
+ function registerFunctionDefaultGenerator(name, fn) {
1372
+ FUNCTION_DEFAULT_GENERATORS.set(name, fn);
1373
+ }
1374
+ function decodeFunctionDefault(name) {
1375
+ return FUNCTION_DEFAULT_GENERATORS.get(name);
1376
+ }
1377
+ function resolveLiteralOrFunctionDefault(rawDefault) {
1378
+ return typeof rawDefault === "function" ? rawDefault() : rawDefault;
1379
+ }
1380
+ function resolveDefaultValue(rawDefault, opts = {}) {
1381
+ if (typeof rawDefault === "function") return rawDefault();
1382
+ if (typeof rawDefault === "string" && rawDefault.startsWith("$")) {
1383
+ if (rawDefault.startsWith("$$")) return rawDefault.slice(1);
1384
+ const name = rawDefault.slice(1);
1385
+ const gen = opts.decode?.(name) ?? decodeFunctionDefault(name);
1386
+ if (!gen) {
1387
+ throw new Error(
1388
+ `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.`
1389
+ );
1390
+ }
1391
+ return gen();
1392
+ }
1393
+ return rawDefault;
1394
+ }
1395
+ function applyDefaultsFromSchema(fields, data, opts = {}) {
1396
+ for (const [fieldName, field] of Object.entries(fields)) {
1397
+ if (fieldName === "id") continue;
1398
+ if (!field || field.default === void 0) continue;
1399
+ if (field.default === UNKNOWN_FUNCTION_DEFAULT) {
1400
+ continue;
1401
+ }
1402
+ if (Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== void 0) {
1403
+ continue;
1404
+ }
1405
+ data[fieldName] = resolveDefaultValue(field.default, {
1406
+ fieldName,
1407
+ modelName: opts.modelName,
1408
+ decode: opts.decodeFunctionDefault
1409
+ });
1410
+ }
1411
+ return data;
1412
+ }
1413
+ function applyAutoStampsFromSchema(fields, data, opts) {
1414
+ const { isNew, now } = opts;
1415
+ for (const [fieldName, field] of Object.entries(fields)) {
1416
+ const stamp = field?.autoStamp;
1417
+ if (!stamp) continue;
1418
+ const explicit = Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== null && data[fieldName] !== void 0;
1419
+ if (explicit) continue;
1420
+ if (stamp === "create" && !isNew) continue;
1421
+ data[fieldName] = now;
1422
+ }
1423
+ return data;
1424
+ }
1425
+ var Y2, import_ulid, KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
1426
+ var init_writeLifecycle = __esm({
1427
+ "src/models/writeLifecycle.ts"() {
1428
+ "use strict";
1429
+ Y2 = __toESM(require("yjs"), 1);
1430
+ import_ulid = require("ulid");
1431
+ KNOWN_FIELD_TYPE_MAP = {
1432
+ string: true,
1433
+ number: true,
1434
+ boolean: true,
1435
+ date: true,
1436
+ id: true,
1437
+ stringset: true
1438
+ };
1439
+ KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
1440
+ FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
1441
+ registerFunctionDefaultGenerator("generate_ulid", () => (0, import_ulid.ulid)());
1442
+ UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
1443
+ }
1444
+ });
1445
+
1223
1446
  // src/models/metaSync.ts
1224
1447
  function inferFieldType(value) {
1225
1448
  if (isStringSetYMap(value)) return "stringset";
@@ -1241,7 +1464,10 @@ function encodeDefault(value) {
1241
1464
  if (value === void 0 || value === null) return void 0;
1242
1465
  if (typeof value === "function") {
1243
1466
  const name = KNOWN_FUNCTION_DEFAULTS.get(value);
1244
- return name ? `$${name}` : "$unknown_function";
1467
+ return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
1468
+ }
1469
+ if (typeof value === "string" && value.startsWith("$")) {
1470
+ return `$${value}`;
1245
1471
  }
1246
1472
  return value;
1247
1473
  }
@@ -1267,7 +1493,7 @@ function syncModelMeta(yDoc, modelName, schema) {
1267
1493
  if (compoundConstraints.length > 0) {
1268
1494
  let constraints = meta.get("_constraints");
1269
1495
  if (!constraints) {
1270
- constraints = new Y2.Map();
1496
+ constraints = new Y3.Map();
1271
1497
  meta.set("_constraints", constraints);
1272
1498
  }
1273
1499
  for (const constraint of compoundConstraints) {
@@ -1278,7 +1504,7 @@ function syncModelMeta(yDoc, modelName, schema) {
1278
1504
  if (relationships && Object.keys(relationships).length > 0) {
1279
1505
  let rels = meta.get("_relationships");
1280
1506
  if (!rels) {
1281
- rels = new Y2.Map();
1507
+ rels = new Y3.Map();
1282
1508
  meta.set("_relationships", rels);
1283
1509
  }
1284
1510
  for (const [relName, relConfig] of Object.entries(relationships)) {
@@ -1290,7 +1516,7 @@ function syncModelMeta(yDoc, modelName, schema) {
1290
1516
  function syncFieldMeta(metaMap, fieldName, fieldOpts) {
1291
1517
  let fieldMeta = metaMap.get(fieldName);
1292
1518
  if (!fieldMeta) {
1293
- fieldMeta = new Y2.Map();
1519
+ fieldMeta = new Y3.Map();
1294
1520
  metaMap.set(fieldName, fieldMeta);
1295
1521
  }
1296
1522
  setIfChanged(fieldMeta, "type", fieldOpts.type);
@@ -1307,7 +1533,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
1307
1533
  function syncConstraintMeta(constraintsMap, constraint) {
1308
1534
  let cMeta = constraintsMap.get(constraint.name);
1309
1535
  if (!cMeta) {
1310
- cMeta = new Y2.Map();
1536
+ cMeta = new Y3.Map();
1311
1537
  constraintsMap.set(constraint.name, cMeta);
1312
1538
  }
1313
1539
  setIfChanged(cMeta, "type", "unique");
@@ -1317,7 +1543,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
1317
1543
  function syncRelationshipMeta(relsMap, relName, relConfig) {
1318
1544
  let relMeta = relsMap.get(relName);
1319
1545
  if (!relMeta) {
1320
- relMeta = new Y2.Map();
1546
+ relMeta = new Y3.Map();
1321
1547
  relsMap.set(relName, relMeta);
1322
1548
  }
1323
1549
  for (const [key, value] of Object.entries(relConfig)) {
@@ -1332,7 +1558,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
1332
1558
  if (fieldName.startsWith("_")) continue;
1333
1559
  let fieldMeta = meta.get(fieldName);
1334
1560
  if (!fieldMeta) {
1335
- fieldMeta = new Y2.Map();
1561
+ fieldMeta = new Y3.Map();
1336
1562
  meta.set(fieldName, fieldMeta);
1337
1563
  }
1338
1564
  if (!fieldMeta.has("type")) {
@@ -1348,18 +1574,58 @@ function setIfChanged(map, key, value) {
1348
1574
  map.set(key, value);
1349
1575
  }
1350
1576
  }
1351
- var Y2, KNOWN_FUNCTION_DEFAULTS, _syncedCache;
1577
+ var Y3, KNOWN_FUNCTION_DEFAULTS, _syncedCache;
1352
1578
  var init_metaSync = __esm({
1353
1579
  "src/models/metaSync.ts"() {
1354
1580
  "use strict";
1355
- Y2 = __toESM(require("yjs"), 1);
1581
+ Y3 = __toESM(require("yjs"), 1);
1356
1582
  init_yjsValues();
1583
+ init_writeLifecycle();
1357
1584
  KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
1358
1585
  _syncedCache = /* @__PURE__ */ new WeakMap();
1359
1586
  }
1360
1587
  });
1361
1588
 
1589
+ // src/utils/uniqueIndex.ts
1590
+ function buildUniqueKey(recordData, fields) {
1591
+ const keyParts = [];
1592
+ for (const field of fields) {
1593
+ const value = recordData instanceof Y4.Map ? recordData.get(field) : recordData[field];
1594
+ if (value === null || value === void 0) {
1595
+ return null;
1596
+ }
1597
+ keyParts.push(String(value));
1598
+ }
1599
+ return keyParts.join("|");
1600
+ }
1601
+ function uniqueIndexMapName(modelName, constraintName) {
1602
+ return `_uniqueIdx_${modelName}_${constraintName}`;
1603
+ }
1604
+ function fieldUniqueConstraintName(modelName, fieldName) {
1605
+ return `${modelName}_${fieldName}_unique`;
1606
+ }
1607
+ var Y4;
1608
+ var init_uniqueIndex = __esm({
1609
+ "src/utils/uniqueIndex.ts"() {
1610
+ "use strict";
1611
+ Y4 = __toESM(require("yjs"), 1);
1612
+ }
1613
+ });
1614
+
1362
1615
  // src/models/relationshipManager.ts
1616
+ function warnIfOrderFieldNullable(modelClass, orderByField, relationshipDescription) {
1617
+ if (ALWAYS_PRESENT_ORDER_FIELDS.has(orderByField)) return;
1618
+ const schema = modelClass.getSchema?.();
1619
+ const fields = schema?.fields;
1620
+ if (!fields || typeof fields.get !== "function") return;
1621
+ const fieldOptions = fields.get(orderByField);
1622
+ if (!fieldOptions) return;
1623
+ if (fieldOptions.required !== true) {
1624
+ Logger.warn(
1625
+ `[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.)`
1626
+ );
1627
+ }
1628
+ }
1363
1629
  function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
1364
1630
  return async function() {
1365
1631
  const relatedId = this[config.relatedIdField];
@@ -1368,6 +1634,16 @@ function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
1368
1634
  };
1369
1635
  }
1370
1636
  function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbEngine) {
1637
+ const {
1638
+ relatedIdField,
1639
+ orderByField = "id",
1640
+ orderDirection = "ASC"
1641
+ } = config;
1642
+ warnIfOrderFieldNullable(
1643
+ targetModelClass,
1644
+ orderByField,
1645
+ `hasMany(${targetModelClass.name})`
1646
+ );
1371
1647
  return async function(paginationArgs) {
1372
1648
  const {
1373
1649
  limit,
@@ -1375,71 +1651,39 @@ function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbE
1375
1651
  beforeCursor,
1376
1652
  direction = "forward"
1377
1653
  } = paginationArgs || {};
1378
- const {
1379
- relatedIdField,
1380
- orderByField = "id",
1381
- orderDirection = "ASC"
1382
- } = config;
1383
1654
  const targetSchema = targetModelClass.getSchema();
1384
1655
  if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
1385
1656
  Logger.error(
1386
1657
  `[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
1387
1658
  );
1388
- return { data: [], nextCursor: null, prevCursor: null };
1659
+ return { data: [], hasMore: false };
1389
1660
  }
1390
- const filter = { [relatedIdField]: this.id };
1661
+ const forward = direction !== "backward";
1662
+ const cursor = forward ? afterCursor : beforeCursor;
1391
1663
  const queryOptions = {
1392
- sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
1664
+ sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 },
1665
+ direction: forward ? 1 : -1
1393
1666
  };
1394
- if (limit !== void 0) {
1395
- queryOptions.limit = limit + 1;
1396
- }
1397
- if (direction === "forward" && afterCursor) {
1398
- filter[orderByField] = {
1399
- [orderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
1400
- };
1401
- } else if (direction === "backward") {
1402
- if (beforeCursor) {
1403
- filter[orderByField] = {
1404
- [orderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
1405
- };
1406
- }
1407
- queryOptions.sort = { [orderByField]: orderDirection === "ASC" ? -1 : 1 };
1408
- }
1409
- const result = await targetModelClass.query(filter, queryOptions);
1410
- const results = result.data;
1411
- let nextCursor = null;
1412
- let prevCursor = null;
1413
- const isFirstPage = direction === "forward" && !afterCursor;
1414
- if (limit !== void 0) {
1415
- if (direction === "forward") {
1416
- if (results.length > limit) {
1417
- nextCursor = results[limit - 1][orderByField];
1418
- results.pop();
1419
- }
1420
- if (results.length > 0 && !isFirstPage) {
1421
- prevCursor = results[0][orderByField];
1422
- }
1423
- } else {
1424
- if (results.length > limit) {
1425
- prevCursor = results[limit - 1][orderByField];
1426
- results.pop();
1427
- }
1428
- results.reverse();
1429
- if (results.length > 0 && beforeCursor) {
1430
- nextCursor = results[results.length - 1][orderByField];
1431
- }
1432
- if (results.length > 0 && !beforeCursor) {
1433
- if (results.length === limit) {
1434
- prevCursor = results[0][orderByField];
1435
- }
1436
- }
1437
- }
1438
- }
1439
- return { data: results, nextCursor, prevCursor };
1667
+ if (limit !== void 0) queryOptions.limit = limit;
1668
+ if (cursor != null) queryOptions.uniqueStartKey = cursor;
1669
+ return targetModelClass.query(
1670
+ { [relatedIdField]: this.id },
1671
+ queryOptions
1672
+ );
1440
1673
  };
1441
1674
  }
1442
1675
  function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClass, joinModelClass, _dbEngine) {
1676
+ const {
1677
+ joinModelLocalField,
1678
+ joinModelRelatedField,
1679
+ joinModelOrderByField = "id",
1680
+ joinModelOrderDirection = "ASC"
1681
+ } = config;
1682
+ warnIfOrderFieldNullable(
1683
+ joinModelClass,
1684
+ joinModelOrderByField,
1685
+ `hasManyThrough(${targetModelClass.name} via ${joinModelClass.name})`
1686
+ );
1443
1687
  return async function(paginationArgs) {
1444
1688
  const {
1445
1689
  limit,
@@ -1453,87 +1697,42 @@ function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClas
1453
1697
  Logger.error(
1454
1698
  `[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
1455
1699
  );
1456
- return { data: [], nextCursor: null, prevCursor: null };
1700
+ return { data: [], hasMore: false };
1457
1701
  }
1458
1702
  if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
1459
1703
  Logger.error(
1460
1704
  `[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
1461
1705
  );
1462
- return { data: [], nextCursor: null, prevCursor: null };
1706
+ return { data: [], hasMore: false };
1463
1707
  }
1464
- const {
1465
- joinModelLocalField,
1466
- joinModelRelatedField,
1467
- joinModelOrderByField = "id",
1468
- joinModelOrderDirection = "ASC"
1469
- } = config;
1470
- const joinFilter = { [joinModelLocalField]: this.id };
1708
+ const forward = direction !== "backward";
1709
+ const cursor = forward ? afterCursor : beforeCursor;
1471
1710
  const joinQueryOptions = {
1472
1711
  sort: {
1473
1712
  [joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
1474
1713
  },
1475
- projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
1714
+ projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 },
1715
+ direction: forward ? 1 : -1
1476
1716
  };
1477
- if (limit !== void 0) {
1478
- joinQueryOptions.limit = limit + 1;
1479
- }
1480
- if (direction === "forward" && afterCursor) {
1481
- joinFilter[joinModelOrderByField] = {
1482
- [joinModelOrderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
1483
- };
1484
- } else if (direction === "backward") {
1485
- if (beforeCursor) {
1486
- joinFilter[joinModelOrderByField] = {
1487
- [joinModelOrderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
1488
- };
1489
- }
1490
- joinQueryOptions.sort = {
1491
- [joinModelOrderByField]: joinModelOrderDirection === "ASC" ? -1 : 1
1492
- };
1493
- }
1717
+ if (limit !== void 0) joinQueryOptions.limit = limit;
1718
+ if (cursor != null) joinQueryOptions.uniqueStartKey = cursor;
1494
1719
  const joinResult = await joinModelClass.query(
1495
- joinFilter,
1720
+ { [joinModelLocalField]: this.id },
1496
1721
  joinQueryOptions
1497
1722
  );
1498
- const joinResults = joinResult.data;
1499
- let nextCursorFromJoin = null;
1500
- let prevCursorFromJoin = null;
1501
- const isFirstPage = direction === "forward" && !afterCursor;
1502
- if (limit !== void 0) {
1503
- if (direction === "forward") {
1504
- if (joinResults.length > limit) {
1505
- nextCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
1506
- joinResults.pop();
1507
- }
1508
- if (joinResults.length > 0 && !isFirstPage) {
1509
- prevCursorFromJoin = joinResults[0][joinModelOrderByField];
1510
- }
1511
- } else {
1512
- if (joinResults.length > limit) {
1513
- prevCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
1514
- joinResults.pop();
1515
- }
1516
- joinResults.reverse();
1517
- if (joinResults.length > 0 && beforeCursor) {
1518
- nextCursorFromJoin = joinResults[joinResults.length - 1][joinModelOrderByField];
1519
- }
1520
- if (joinResults.length > 0 && !beforeCursor) {
1521
- if (joinResults.length === limit) {
1522
- prevCursorFromJoin = joinResults[0][joinModelOrderByField];
1523
- }
1524
- }
1525
- }
1723
+ const relatedIds = joinResult.data.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
1724
+ let targetData = [];
1725
+ if (relatedIds.length > 0) {
1726
+ const targetResult = await targetModelClass.query({
1727
+ id: { $in: relatedIds }
1728
+ });
1729
+ targetData = targetResult.data;
1526
1730
  }
1527
- const relatedIds = joinResults.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
1528
- if (relatedIds.length === 0)
1529
- return { data: [], nextCursor: null, prevCursor: null };
1530
- const targetResult = await targetModelClass.query({
1531
- id: { $in: relatedIds }
1532
- });
1533
1731
  return {
1534
- data: targetResult.data,
1535
- nextCursor: nextCursorFromJoin,
1536
- prevCursor: prevCursorFromJoin
1732
+ data: targetData,
1733
+ nextCursor: joinResult.nextCursor,
1734
+ prevCursor: joinResult.prevCursor,
1735
+ hasMore: joinResult.hasMore
1537
1736
  };
1538
1737
  };
1539
1738
  }
@@ -1657,10 +1856,12 @@ function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetMo
1657
1856
  }, `remove-join-${config.joinModel}-${this.id}-${targetId}`);
1658
1857
  };
1659
1858
  }
1859
+ var ALWAYS_PRESENT_ORDER_FIELDS;
1660
1860
  var init_relationshipManager = __esm({
1661
1861
  "src/models/relationshipManager.ts"() {
1662
1862
  "use strict";
1663
1863
  init_BaseModel();
1864
+ ALWAYS_PRESENT_ORDER_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
1664
1865
  }
1665
1866
  });
1666
1867
 
@@ -2431,7 +2632,13 @@ var init_IncludeResolver = __esm({
2431
2632
  }
2432
2633
  return null;
2433
2634
  }
2434
- /** Apply projection to a single record. */
2635
+ /**
2636
+ * Apply a projection to a single record. Assumes the projection was already
2637
+ * validated upstream (mixed inclusion+exclusion rejected by the translator):
2638
+ * `isInclusion` treats "any value === 1" as inclusion mode. `id` and `type`
2639
+ * are system-identity fields and are always kept in BOTH modes (mirrors the
2640
+ * DatabaseDO `_applyProjection`); `_meta` stays excludable.
2641
+ */
2435
2642
  _applyProjection(record, projection) {
2436
2643
  const fields = Object.entries(projection);
2437
2644
  if (fields.length === 0) return record;
@@ -2450,7 +2657,7 @@ var init_IncludeResolver = __esm({
2450
2657
  } else {
2451
2658
  const result = { ...record };
2452
2659
  for (const [f, v] of fields) {
2453
- if (v === 0) delete result[f];
2660
+ if (v === 0 && f !== "id" && f !== "type") delete result[f];
2454
2661
  }
2455
2662
  return result;
2456
2663
  }
@@ -2470,21 +2677,23 @@ __export(BaseModel_exports, {
2470
2677
  generateULID: () => generateULID
2471
2678
  });
2472
2679
  function generateULID() {
2473
- return (0, import_ulid.ulid)();
2680
+ return (0, import_ulid2.ulid)();
2474
2681
  }
2475
- var Y3, import_ulid, LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
2682
+ var Y5, import_ulid2, LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
2476
2683
  var init_BaseModel = __esm({
2477
2684
  "src/models/BaseModel.ts"() {
2478
2685
  "use strict";
2479
- Y3 = __toESM(require("yjs"), 1);
2480
- import_ulid = require("ulid");
2686
+ Y5 = __toESM(require("yjs"), 1);
2687
+ import_ulid2 = require("ulid");
2481
2688
  init_StringSet();
2482
2689
  init_documentTypes();
2483
2690
  init_DocumentQueryTranslator();
2484
2691
  init_CursorManager();
2485
2692
  init_sql();
2486
2693
  init_metaSync();
2694
+ init_writeLifecycle();
2487
2695
  init_yjsValues();
2696
+ init_uniqueIndex();
2488
2697
  LogLevel = /* @__PURE__ */ ((LogLevel2) => {
2489
2698
  LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
2490
2699
  LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
@@ -2869,7 +3078,9 @@ var init_BaseModel = __esm({
2869
3078
  this.setValue(fieldKey, data[fieldKey]);
2870
3079
  this._constructorProvidedFields.add(fieldKey);
2871
3080
  } else if (fieldOptions.default !== void 0) {
2872
- const defaultValue = typeof fieldOptions.default === "function" ? fieldOptions.default() : fieldOptions.default;
3081
+ const defaultValue = resolveLiteralOrFunctionDefault(
3082
+ fieldOptions.default
3083
+ );
2873
3084
  this.setValue(fieldKey, defaultValue);
2874
3085
  }
2875
3086
  }
@@ -3161,7 +3372,7 @@ var init_BaseModel = __esm({
3161
3372
  }
3162
3373
  getStringSetFromYjs(fieldName) {
3163
3374
  const yjsData = this.getFromYjs(fieldName);
3164
- if (yjsData instanceof Y3.Map) {
3375
+ if (yjsData instanceof Y5.Map) {
3165
3376
  return Array.from(yjsData.keys());
3166
3377
  }
3167
3378
  if (yjsData && typeof yjsData === "object") {
@@ -3269,7 +3480,7 @@ var init_BaseModel = __esm({
3269
3480
  }
3270
3481
  const extractStringSetValues = (value) => {
3271
3482
  if (!value) return [];
3272
- if (value instanceof Y3.Map) {
3483
+ if (value instanceof Y5.Map) {
3273
3484
  return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
3274
3485
  }
3275
3486
  if (Array.isArray(value)) {
@@ -3286,7 +3497,7 @@ var init_BaseModel = __esm({
3286
3497
  if (!recordId) continue;
3287
3498
  let itemData;
3288
3499
  const stringSetValuesByField = {};
3289
- if (recordData instanceof Y3.Map) {
3500
+ if (recordData instanceof Y5.Map) {
3290
3501
  itemData = {};
3291
3502
  const unknownFields = [];
3292
3503
  for (const [fieldKey, value] of recordData.entries()) {
@@ -3402,20 +3613,10 @@ var init_BaseModel = __esm({
3402
3613
  Logger.verbose(
3403
3614
  `[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
3404
3615
  );
3405
- const buildUniqueKey = (recordData, fields) => {
3406
- const keyParts = [];
3407
- for (const field of fields) {
3408
- const value = recordData instanceof Y3.Map ? recordData.get(field) : recordData[field];
3409
- if (value === null || value === void 0) {
3410
- return null;
3411
- }
3412
- keyParts.push(String(value));
3413
- }
3414
- return keyParts.join("|");
3415
- };
3616
+ const buildUniqueKey2 = (recordData, fields) => buildUniqueKey(recordData, fields);
3416
3617
  const extractItemData = (key, recordData) => {
3417
3618
  let itemData;
3418
- if (recordData instanceof Y3.Map) {
3619
+ if (recordData instanceof Y5.Map) {
3419
3620
  itemData = {};
3420
3621
  const unknownFields = [];
3421
3622
  for (const [fieldKey, value] of recordData.entries()) {
@@ -3499,7 +3700,7 @@ var init_BaseModel = __esm({
3499
3700
  const recordsByUniqueKey = /* @__PURE__ */ new Map();
3500
3701
  for (const [recordId, recordData] of allRecords.entries()) {
3501
3702
  if (recordIdsToDiscard.has(recordId)) continue;
3502
- const uniqueKey = buildUniqueKey(recordData, constraint.fields);
3703
+ const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
3503
3704
  if (uniqueKey === null) continue;
3504
3705
  if (!recordsByUniqueKey.has(uniqueKey)) {
3505
3706
  recordsByUniqueKey.set(uniqueKey, []);
@@ -3545,7 +3746,7 @@ var init_BaseModel = __esm({
3545
3746
  if (!recordData || !key) continue;
3546
3747
  const itemData = extractItemData(key, recordData);
3547
3748
  if (!itemData) continue;
3548
- if (change.action === "add" && recordData instanceof Y3.Map) {
3749
+ if (change.action === "add" && recordData instanceof Y5.Map) {
3549
3750
  Logger.verbose(
3550
3751
  `[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
3551
3752
  );
@@ -3643,9 +3844,12 @@ var init_BaseModel = __esm({
3643
3844
  const recordData = documentYMap.get(idToDiscard);
3644
3845
  if (!recordData) continue;
3645
3846
  for (const constraint of schema.resolvedUniqueConstraints) {
3646
- const uniqueKey = buildUniqueKey(recordData, constraint.fields);
3847
+ const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
3647
3848
  if (uniqueKey === null) continue;
3648
- const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
3849
+ const constraintMapName = uniqueIndexMapName(
3850
+ modelName,
3851
+ constraint.name
3852
+ );
3649
3853
  const constraintMap = yDoc.getMap(constraintMapName);
3650
3854
  const currentIndexValue = constraintMap.get(uniqueKey);
3651
3855
  if (currentIndexValue === idToDiscard) {
@@ -3663,11 +3867,14 @@ var init_BaseModel = __esm({
3663
3867
  }, `conflict-resolution-${modelName}-${docId}`);
3664
3868
  yDoc.transact(() => {
3665
3869
  for (const constraint of schema.resolvedUniqueConstraints) {
3666
- const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
3870
+ const constraintMapName = uniqueIndexMapName(
3871
+ modelName,
3872
+ constraint.name
3873
+ );
3667
3874
  const constraintMap = yDoc.getMap(constraintMapName);
3668
3875
  for (const [recordId, recordData] of documentYMap.entries()) {
3669
3876
  if (!recordData) continue;
3670
- const uniqueKey = buildUniqueKey(recordData, constraint.fields);
3877
+ const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
3671
3878
  if (uniqueKey === null) continue;
3672
3879
  const currentIndexValue = constraintMap.get(uniqueKey);
3673
3880
  if (currentIndexValue !== recordId) {
@@ -3690,7 +3897,7 @@ var init_BaseModel = __esm({
3690
3897
  `[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
3691
3898
  );
3692
3899
  for (const [recordId, recordData] of documentYMap.entries()) {
3693
- if (recordData instanceof Y3.Map) {
3900
+ if (recordData instanceof Y5.Map) {
3694
3901
  Logger.verbose(
3695
3902
  `[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
3696
3903
  );
@@ -3916,26 +4123,7 @@ var init_BaseModel = __esm({
3916
4123
  * if the field hasn't been touched since the wire-format change in #561.
3917
4124
  */
3918
4125
  applyStringSetChangeToYMap(recordYMap, fieldName, change) {
3919
- let nested = recordYMap.get(fieldName);
3920
- if (!(nested instanceof Y3.Map)) {
3921
- const migrated = new Y3.Map();
3922
- if (nested && typeof nested === "object") {
3923
- for (const [member, marker] of Object.entries(
3924
- nested
3925
- )) {
3926
- if (Boolean(marker)) migrated.set(member, true);
3927
- }
3928
- }
3929
- recordYMap.set(fieldName, migrated);
3930
- nested = migrated;
3931
- }
3932
- const target = nested;
3933
- for (const member of change.additions) {
3934
- target.set(member, true);
3935
- }
3936
- for (const member of change.removals) {
3937
- target.delete(member);
3938
- }
4126
+ applyStringSetChange(recordYMap, fieldName, change);
3939
4127
  }
3940
4128
  /**
3941
4129
  * Deep equality check for comparing field values
@@ -4137,6 +4325,34 @@ var init_BaseModel = __esm({
4137
4325
  `[${modelName}.save] getCurrentJSState() returned:`,
4138
4326
  dataToSave
4139
4327
  );
4328
+ const uniqueMissConflicts = /* @__PURE__ */ new Map();
4329
+ for (const constraint of schema.resolvedUniqueConstraints) {
4330
+ const newUniqueKey = this._buildUniqueKey(
4331
+ constraint.fields,
4332
+ dataToSave,
4333
+ modelName,
4334
+ constraint.name
4335
+ );
4336
+ if (newUniqueKey === null) {
4337
+ continue;
4338
+ }
4339
+ const constraintMap = yDoc.getMap(
4340
+ uniqueIndexMapName(modelName, constraint.name)
4341
+ );
4342
+ if (!constraintMap.get(newUniqueKey)) {
4343
+ const constraintKeyValues = constraint.fields.map(
4344
+ (field) => dataToSave[field]
4345
+ );
4346
+ const conflictingRecord = await modelConstructor._resolveByUniqueFallback(
4347
+ constraint,
4348
+ constraintKeyValues,
4349
+ { excludeId: this.id }
4350
+ );
4351
+ if (conflictingRecord) {
4352
+ uniqueMissConflicts.set(constraint.name, conflictingRecord.id);
4353
+ }
4354
+ }
4355
+ }
4140
4356
  await yDoc.transact(async () => {
4141
4357
  if (upsertOnConstraint && upsertOnKey) {
4142
4358
  const constraintMapName = `_uniqueIdx_${modelName}_${upsertOnConstraint.name}`;
@@ -4178,7 +4394,7 @@ var init_BaseModel = __esm({
4178
4394
  Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
4179
4395
  Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
4180
4396
  if (!recordYMap) {
4181
- recordYMap = new Y3.Map();
4397
+ recordYMap = new Y5.Map();
4182
4398
  Logger.verbose(
4183
4399
  `[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
4184
4400
  );
@@ -4230,7 +4446,10 @@ var init_BaseModel = __esm({
4230
4446
  );
4231
4447
  continue;
4232
4448
  }
4233
- const constraintMapName = `_uniqueIdx_${modelName}_${constraint.name}`;
4449
+ const constraintMapName = uniqueIndexMapName(
4450
+ modelName,
4451
+ constraint.name
4452
+ );
4234
4453
  const constraintMap = yDoc.getMap(constraintMapName);
4235
4454
  Logger.verbose(
4236
4455
  `[${modelName}] Save Check (Item '${this.id}'): GET from '${constraintMapName}' with Key '${newUniqueKey.substring(
@@ -4259,6 +4478,26 @@ var init_BaseModel = __esm({
4259
4478
  this.id,
4260
4479
  existingRecordIdWithNewKey
4261
4480
  );
4481
+ } else if (!existingRecordIdWithNewKey) {
4482
+ const conflictingRecordId = uniqueMissConflicts.get(constraint.name);
4483
+ if (conflictingRecordId && conflictingRecordId !== this.id) {
4484
+ Logger.verbose(
4485
+ `[${modelName}] Save Check (Item '${this.id}'): index miss for '${constraint.name}', but engine fallback found conflicting index-less record ${conflictingRecordId}.`
4486
+ );
4487
+ throw new UniqueConstraintViolationError(
4488
+ `Unique constraint '${constraint.name}' violated for model '${modelName}' on fields [${constraint.fields.join(
4489
+ ", "
4490
+ )}]. Attempted ID: ${this.id}. Value(s) starting with '${newUniqueKey.substring(
4491
+ 0,
4492
+ 50
4493
+ )}...' already exist for index-less record ID ${conflictingRecordId}.`,
4494
+ modelName,
4495
+ constraint.name,
4496
+ constraint.fields,
4497
+ this.id,
4498
+ conflictingRecordId
4499
+ );
4500
+ }
4262
4501
  }
4263
4502
  }
4264
4503
  if (isUpdate && oldData) {
@@ -4602,34 +4841,46 @@ var init_BaseModel = __esm({
4602
4841
  const modelName = schema.options.name;
4603
4842
  Logger.verbose(`[${modelName}] Executing query:`, { filter, options });
4604
4843
  const hasIncludes = options?.include && options.include.length > 0;
4605
- const translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
4844
+ const requestedLimit = options?.limit;
4845
+ const overFetch = typeof requestedLimit === "number" && requestedLimit > 0;
4846
+ let translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
4847
+ if (overFetch) {
4848
+ translatorOptions = {
4849
+ ...translatorOptions || {},
4850
+ limit: requestedLimit + 1
4851
+ };
4852
+ }
4606
4853
  const translator = new DocumentQueryTranslator(modelName, schema.fields);
4607
4854
  const translatedQuery = translator.translateFind(filter, translatorOptions);
4608
4855
  Logger.verbose(`[${modelName}] Translated SQL:`, translatedQuery.sql);
4609
4856
  Logger.verbose(`[${modelName}] SQL params:`, translatedQuery.params);
4610
- const results = await _BaseModel.dbInstance.query(
4857
+ let results = await _BaseModel.dbInstance.query(
4611
4858
  translatedQuery.sql,
4612
4859
  translatedQuery.params
4613
4860
  );
4614
4861
  Logger.verbose(`[${modelName}] Query returned ${results.length} results`);
4862
+ let hasMore = false;
4863
+ if (overFetch && results.length > requestedLimit) {
4864
+ hasMore = true;
4865
+ results = results.slice(0, requestedLimit);
4866
+ }
4615
4867
  if (options?.include && options.include.length > 0) {
4616
4868
  const { IncludeResolver: IncludeResolver2 } = await Promise.resolve().then(() => (init_IncludeResolver(), IncludeResolver_exports));
4617
4869
  const resolver = new IncludeResolver2(_BaseModel.dbInstance);
4618
4870
  await resolver.resolve(results, options.include, 0, modelName);
4619
4871
  }
4620
- const requestedLimit = options?.limit;
4621
- const hasMore = CursorManager.hasMoreResults(
4622
- requestedLimit,
4623
- results.length
4624
- );
4625
4872
  const isFirstPage = !options?.uniqueStartKey;
4873
+ const direction = options?.direction || 1;
4626
4874
  const cursors = CursorManager.generateResultCursors(
4627
4875
  results,
4628
4876
  translatedQuery.sortFields,
4629
- options?.direction || 1,
4877
+ direction,
4630
4878
  hasMore,
4631
4879
  isFirstPage
4632
4880
  );
4881
+ if (direction === -1) {
4882
+ results.reverse();
4883
+ }
4633
4884
  let transformedData;
4634
4885
  if (options?.projection && Object.keys(options.projection).length > 0) {
4635
4886
  transformedData = results.map((row) => {
@@ -5205,7 +5456,7 @@ var init_BaseModel = __esm({
5205
5456
  );
5206
5457
  }
5207
5458
  for (const [recordId, recordYMap] of documentYMap.entries()) {
5208
- if (recordYMap instanceof Y3.Map) {
5459
+ if (recordYMap instanceof Y5.Map) {
5209
5460
  const instance = new this({ id: recordId });
5210
5461
  instance._metaDocId = docId;
5211
5462
  const connectedDoc = modelConstructor.connectedDocuments.get(docId);
@@ -5230,6 +5481,50 @@ var init_BaseModel = __esm({
5230
5481
  }
5231
5482
  return allInstances;
5232
5483
  }
5484
+ /**
5485
+ * Resolve a record by its unique-constraint field values via an engine query.
5486
+ * Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
5487
+ * entry for a key.
5488
+ *
5489
+ * The index map is a cache over the SQLite projection, not the source of
5490
+ * truth: every record is loaded into the projection on `initializeForDocument`
5491
+ * regardless of index presence, but a record written by a client predating
5492
+ * index maintenance (legacy data) is missing from the index. Such a record
5493
+ * silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
5494
+ * check. A query over the constraint fields finds it reliably.
5495
+ *
5496
+ * Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
5497
+ * the document that holds it, via `queryOne`) or `null`. Callers decide
5498
+ * whether to heal the missing index entry (write paths) and do their own
5499
+ * logging — this method never mutates the index, so read paths stay
5500
+ * read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
5501
+ * constraint field means the record does not participate in the constraint,
5502
+ * so no query runs.
5503
+ */
5504
+ static async _resolveByUniqueFallback(constraint, keyValues, options) {
5505
+ if (!_BaseModel.dbInstance) {
5506
+ return null;
5507
+ }
5508
+ const filter = {};
5509
+ for (let i = 0; i < constraint.fields.length; i++) {
5510
+ const value = keyValues[i];
5511
+ if (value === null || value === void 0) {
5512
+ return null;
5513
+ }
5514
+ filter[constraint.fields[i]] = value;
5515
+ }
5516
+ if (options?.excludeId) {
5517
+ if ("id" in filter) {
5518
+ if (filter.id === options.excludeId) {
5519
+ return null;
5520
+ }
5521
+ } else {
5522
+ filter.id = { $ne: options.excludeId };
5523
+ }
5524
+ }
5525
+ const found = await this.queryOne(filter);
5526
+ return found ?? null;
5527
+ }
5233
5528
  static async findByUnique(constraintName, value) {
5234
5529
  const modelConstructor = this;
5235
5530
  const schema = modelConstructor.getSchema();
@@ -5268,7 +5563,10 @@ var init_BaseModel = __esm({
5268
5563
  const documentInfo = modelConstructor.connectedDocuments.get(docId);
5269
5564
  if (!documentInfo) continue;
5270
5565
  const yDoc = documentInfo.yDoc;
5271
- const constraintMapName2 = `_uniqueIdx_${currentModelName}_${constraint.name}`;
5566
+ const constraintMapName2 = uniqueIndexMapName(
5567
+ currentModelName,
5568
+ constraint.name
5569
+ );
5272
5570
  const constraintMap2 = yDoc.getMap(constraintMapName2);
5273
5571
  const recordId2 = constraintMap2.get(uniqueKeyString);
5274
5572
  if (recordId2) {
@@ -5276,7 +5574,7 @@ var init_BaseModel = __esm({
5276
5574
  const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
5277
5575
  if (documentYMap) {
5278
5576
  const recordYMap2 = documentYMap.get(recordId2);
5279
- if (recordYMap2 && recordYMap2 instanceof Y3.Map) {
5577
+ if (recordYMap2 && recordYMap2 instanceof Y5.Map) {
5280
5578
  const instance = new modelConstructor({
5281
5579
  id: recordId2
5282
5580
  });
@@ -5290,6 +5588,16 @@ var init_BaseModel = __esm({
5290
5588
  }
5291
5589
  }
5292
5590
  }
5591
+ const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
5592
+ constraint,
5593
+ keyValues
5594
+ );
5595
+ if (fallbackRecord) {
5596
+ Logger.verbose(
5597
+ `[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
5598
+ );
5599
+ return fallbackRecord;
5600
+ }
5293
5601
  Logger.verbose(
5294
5602
  `[${currentModelName}.findByUnique] Record not found in any connected document`
5295
5603
  );
@@ -5303,17 +5611,30 @@ var init_BaseModel = __esm({
5303
5611
  `[${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.`
5304
5612
  );
5305
5613
  }
5306
- const constraintMapName = `_uniqueIdx_${currentModelName}_${constraint.name}`;
5614
+ const constraintMapName = uniqueIndexMapName(
5615
+ currentModelName,
5616
+ constraint.name
5617
+ );
5307
5618
  const constraintMap = legacyDocInfo.yDoc.getMap(constraintMapName);
5308
5619
  const recordId = constraintMap.get(uniqueKeyString);
5309
5620
  if (!recordId) {
5621
+ const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
5622
+ constraint,
5623
+ keyValues
5624
+ );
5625
+ if (fallbackRecord) {
5626
+ Logger.verbose(
5627
+ `[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
5628
+ );
5629
+ return fallbackRecord;
5630
+ }
5310
5631
  return null;
5311
5632
  }
5312
5633
  const recordYMap = legacyDocumentMap.get(recordId);
5313
5634
  if (!recordYMap) {
5314
5635
  return null;
5315
5636
  }
5316
- if (recordYMap instanceof Y3.Map) {
5637
+ if (recordYMap instanceof Y5.Map) {
5317
5638
  const instance = new modelConstructor({
5318
5639
  id: recordId
5319
5640
  });
@@ -5371,7 +5692,10 @@ var init_BaseModel = __esm({
5371
5692
  `[${currentModelName}] upsertByUnique for '${constraintName}': unique key cannot be built due to null/undefined values. Upsert cannot proceed reliably.`
5372
5693
  );
5373
5694
  }
5374
- const constraintMapName = `_uniqueIdx_${currentModelName}_${constraint.name}`;
5695
+ const constraintMapName = uniqueIndexMapName(
5696
+ currentModelName,
5697
+ constraint.name
5698
+ );
5375
5699
  let existingRecord = null;
5376
5700
  if (connectedDocuments && connectedDocuments.size > 0) {
5377
5701
  for (const docId of connectedDocuments.keys()) {
@@ -5389,6 +5713,33 @@ var init_BaseModel = __esm({
5389
5713
  }
5390
5714
  } else {
5391
5715
  }
5716
+ if (!existingRecord) {
5717
+ const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
5718
+ constraint,
5719
+ keyValuesForLookup
5720
+ );
5721
+ if (fallbackRecord) {
5722
+ existingRecord = fallbackRecord;
5723
+ const healDocId = fallbackRecord._metaDocId;
5724
+ const healDocInfo = healDocId ? connectedDocuments.get(healDocId) : void 0;
5725
+ const canHeal = !!healDocInfo && healDocInfo.permissionHint !== "read";
5726
+ if (healDocInfo && canHeal) {
5727
+ healDocInfo.yDoc.transact(() => {
5728
+ const healMap = healDocInfo.yDoc.getMap(constraintMapName);
5729
+ if (healMap.get(uniqueKeyString) !== fallbackRecord.id) {
5730
+ healMap.set(uniqueKeyString, fallbackRecord.id);
5731
+ }
5732
+ }, `heal-unique-index-${currentModelName}-${healDocId}`);
5733
+ Logger.verbose(
5734
+ `[${currentModelName}] upsertByUnique: index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id}; healed missing index entry (legacy index-less record).`
5735
+ );
5736
+ } else {
5737
+ Logger.verbose(
5738
+ `[${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).`
5739
+ );
5740
+ }
5741
+ }
5742
+ }
5392
5743
  if (options?.objectMustExist) {
5393
5744
  if (!existingRecord) {
5394
5745
  throw new RecordNotFoundError(
@@ -5545,7 +5896,7 @@ var init_BaseModel = __esm({
5545
5896
  for (const [fieldKey, fieldOptions] of schema.fields) {
5546
5897
  if (fieldOptions?.type !== "stringset") continue;
5547
5898
  const value = recordYMap.get(fieldKey);
5548
- if (value instanceof Y3.Map) {
5899
+ if (value instanceof Y5.Map) {
5549
5900
  this.observeStringSetMapOnce(value);
5550
5901
  }
5551
5902
  }
@@ -5730,8 +6081,160 @@ var init_BaseModel = __esm({
5730
6081
  }
5731
6082
  });
5732
6083
 
6084
+ // src/utils/environment.ts
6085
+ var environment_exports = {};
6086
+ __export(environment_exports, {
6087
+ constants: () => constants,
6088
+ detectEnvironment: () => detectEnvironment,
6089
+ features: () => features,
6090
+ isBrowser: () => isBrowser,
6091
+ isNode: () => isNode,
6092
+ isNodeModuleAvailable: () => isNodeModuleAvailable
6093
+ });
6094
+ function detectEnvironment() {
6095
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
6096
+ return "node";
6097
+ }
6098
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
6099
+ return "browser";
6100
+ }
6101
+ if (typeof self !== "undefined" && typeof self.importScripts === "function") {
6102
+ return "browser";
6103
+ }
6104
+ return "unknown";
6105
+ }
6106
+ function isNode() {
6107
+ return detectEnvironment() === "node";
6108
+ }
6109
+ function isBrowser() {
6110
+ return detectEnvironment() === "browser";
6111
+ }
6112
+ async function isNodeModuleAvailable(moduleName) {
6113
+ if (!isNode()) {
6114
+ return false;
6115
+ }
6116
+ try {
6117
+ await import(
6118
+ /* @vite-ignore */
6119
+ moduleName
6120
+ );
6121
+ return true;
6122
+ } catch {
6123
+ return false;
6124
+ }
6125
+ }
6126
+ var features, constants;
6127
+ var init_environment = __esm({
6128
+ "src/utils/environment.ts"() {
6129
+ "use strict";
6130
+ features = {
6131
+ get hasWebWorkers() {
6132
+ return isBrowser() && typeof Worker !== "undefined";
6133
+ },
6134
+ get hasFileSystem() {
6135
+ return isNode();
6136
+ },
6137
+ get hasWebAssembly() {
6138
+ return typeof WebAssembly !== "undefined";
6139
+ },
6140
+ async hasBetterSqlite3() {
6141
+ return await isNodeModuleAvailable("better-sqlite3");
6142
+ }
6143
+ };
6144
+ constants = {
6145
+ DEFAULT_NODE_SQLITE_PATH: ":memory:",
6146
+ DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
6147
+ };
6148
+ }
6149
+ });
6150
+
5733
6151
  // src/engines/SqljsEngine.ts
5734
- var import_sql4, import_async_mutex, SQL_WASM_URL, TransactionalSQLJSOperations, SqljsEngine;
6152
+ async function resolveNodeWasmPath() {
6153
+ if (!isNode()) {
6154
+ return null;
6155
+ }
6156
+ try {
6157
+ const cjsRequire = typeof require !== "undefined" ? require : void 0;
6158
+ if (cjsRequire) {
6159
+ return cjsRequire.resolve("sql.js/dist/sql-wasm.wasm");
6160
+ }
6161
+ } catch {
6162
+ }
6163
+ try {
6164
+ const metaUrl = import_meta.url;
6165
+ if (metaUrl) {
6166
+ const { createRequire } = await import(
6167
+ /* @vite-ignore */
6168
+ "module"
6169
+ );
6170
+ const req = createRequire(metaUrl);
6171
+ return req.resolve("sql.js/dist/sql-wasm.wasm");
6172
+ }
6173
+ } catch {
6174
+ }
6175
+ return null;
6176
+ }
6177
+ async function preflightNodeWasm(wasmPath) {
6178
+ if (!isNode()) {
6179
+ return;
6180
+ }
6181
+ if (/^[a-z]+:\/\//i.test(wasmPath)) {
6182
+ return;
6183
+ }
6184
+ const { existsSync } = await import(
6185
+ /* @vite-ignore */
6186
+ "fs"
6187
+ );
6188
+ if (!existsSync(wasmPath)) {
6189
+ throw new Error(
6190
+ `[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()).`
6191
+ );
6192
+ }
6193
+ }
6194
+ async function loadSqlJsModule(locateFile) {
6195
+ let abortReject;
6196
+ const toError = (reason) => reason instanceof Error ? reason : new Error(
6197
+ `[SqljsEngine] sql.js aborted during initialization: ${String(
6198
+ reason
6199
+ )}`
6200
+ );
6201
+ const nodeProcess = isNode() && typeof process !== "undefined" ? process : void 0;
6202
+ const onUnhandled = (reason) => {
6203
+ const err = toError(reason);
6204
+ if (/Aborted/.test(err.message) || /Aborted/.test(String(reason))) {
6205
+ abortReject?.(err);
6206
+ return;
6207
+ }
6208
+ if (nodeProcess && nodeProcess.listenerCount("unhandledRejection") === 1) {
6209
+ nodeProcess.removeListener("unhandledRejection", onUnhandled);
6210
+ void Promise.reject(reason);
6211
+ }
6212
+ };
6213
+ if (nodeProcess) {
6214
+ nodeProcess.on("unhandledRejection", onUnhandled);
6215
+ }
6216
+ const abortGuard = new Promise((_resolve, reject) => {
6217
+ abortReject = reject;
6218
+ });
6219
+ abortGuard.catch(() => {
6220
+ });
6221
+ try {
6222
+ const initPromise = (0, import_sql4.default)({
6223
+ locateFile,
6224
+ // Layer 1: emscripten onAbort.
6225
+ onAbort: (reason) => abortReject?.(toError(reason))
6226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6227
+ });
6228
+ return await Promise.race([initPromise, abortGuard]);
6229
+ } finally {
6230
+ if (nodeProcess) {
6231
+ setTimeout(() => {
6232
+ nodeProcess.removeListener("unhandledRejection", onUnhandled);
6233
+ }, 0);
6234
+ }
6235
+ }
6236
+ }
6237
+ var import_sql4, import_async_mutex, import_meta, SQL_WASM_URL, SQL_WASM_FILENAME, TransactionalSQLJSOperations, SqljsEngine;
5735
6238
  var init_SqljsEngine = __esm({
5736
6239
  "src/engines/SqljsEngine.ts"() {
5737
6240
  "use strict";
@@ -5739,7 +6242,10 @@ var init_SqljsEngine = __esm({
5739
6242
  init_BaseModel();
5740
6243
  import_async_mutex = require("async-mutex");
5741
6244
  init_sql();
6245
+ init_environment();
6246
+ import_meta = {};
5742
6247
  SQL_WASM_URL = "/sql-wasm.wasm";
6248
+ SQL_WASM_FILENAME = "sql-wasm.wasm";
5743
6249
  TransactionalSQLJSOperations = class {
5744
6250
  db;
5745
6251
  engine;
@@ -5805,22 +6311,37 @@ var init_SqljsEngine = __esm({
5805
6311
  }
5806
6312
  async initialize(engineOptions) {
5807
6313
  Logger.info("[SqljsEngine] Initializing SQL.js engine...");
6314
+ const loadingModuleThisCall = !_SqljsEngine.SQL;
5808
6315
  try {
5809
6316
  if (!_SqljsEngine.SQL) {
5810
- const locateFileConfig = engineOptions?.locateFile || ((_file) => engineOptions?.wasmURL || SQL_WASM_URL);
6317
+ let locateFileConfig;
6318
+ if (engineOptions?.locateFile) {
6319
+ locateFileConfig = engineOptions.locateFile;
6320
+ } else if (engineOptions?.wasmURL) {
6321
+ const url = engineOptions.wasmURL;
6322
+ locateFileConfig = (_file) => url;
6323
+ } else {
6324
+ const nodeWasmPath = await resolveNodeWasmPath();
6325
+ const resolved = nodeWasmPath ?? SQL_WASM_URL;
6326
+ locateFileConfig = (_file) => resolved;
6327
+ }
6328
+ const wasmPath = locateFileConfig(SQL_WASM_FILENAME);
5811
6329
  Logger.debug(
5812
6330
  "[SqljsEngine] Attempting to load SQL.js WASM from:",
5813
- locateFileConfig("sql-wasm.wasm")
6331
+ wasmPath
5814
6332
  );
5815
- _SqljsEngine.SQL = await (0, import_sql4.default)({
5816
- locateFile: locateFileConfig
5817
- });
6333
+ await preflightNodeWasm(wasmPath);
6334
+ const SQL = await loadSqlJsModule(locateFileConfig);
6335
+ _SqljsEngine.SQL = SQL;
5818
6336
  Logger.info("[SqljsEngine] SQL.js WASM loaded successfully.");
5819
6337
  }
5820
6338
  this.db = new _SqljsEngine.SQL.Database();
5821
6339
  Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
5822
6340
  this.updateTableNames();
5823
6341
  } catch (error) {
6342
+ if (loadingModuleThisCall) {
6343
+ _SqljsEngine.SQL = null;
6344
+ }
5824
6345
  Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
5825
6346
  throw error;
5826
6347
  }
@@ -6108,73 +6629,6 @@ var init_SqljsEngine = __esm({
6108
6629
  }
6109
6630
  });
6110
6631
 
6111
- // src/utils/environment.ts
6112
- var environment_exports = {};
6113
- __export(environment_exports, {
6114
- constants: () => constants,
6115
- detectEnvironment: () => detectEnvironment,
6116
- features: () => features,
6117
- isBrowser: () => isBrowser,
6118
- isNode: () => isNode,
6119
- isNodeModuleAvailable: () => isNodeModuleAvailable
6120
- });
6121
- function detectEnvironment() {
6122
- if (typeof process !== "undefined" && process.versions && process.versions.node) {
6123
- return "node";
6124
- }
6125
- if (typeof window !== "undefined" && typeof document !== "undefined") {
6126
- return "browser";
6127
- }
6128
- if (typeof self !== "undefined" && typeof self.importScripts === "function") {
6129
- return "browser";
6130
- }
6131
- return "unknown";
6132
- }
6133
- function isNode() {
6134
- return detectEnvironment() === "node";
6135
- }
6136
- function isBrowser() {
6137
- return detectEnvironment() === "browser";
6138
- }
6139
- async function isNodeModuleAvailable(moduleName) {
6140
- if (!isNode()) {
6141
- return false;
6142
- }
6143
- try {
6144
- await import(
6145
- /* @vite-ignore */
6146
- moduleName
6147
- );
6148
- return true;
6149
- } catch {
6150
- return false;
6151
- }
6152
- }
6153
- var features, constants;
6154
- var init_environment = __esm({
6155
- "src/utils/environment.ts"() {
6156
- "use strict";
6157
- features = {
6158
- get hasWebWorkers() {
6159
- return isBrowser() && typeof Worker !== "undefined";
6160
- },
6161
- get hasFileSystem() {
6162
- return isNode();
6163
- },
6164
- get hasWebAssembly() {
6165
- return typeof WebAssembly !== "undefined";
6166
- },
6167
- async hasBetterSqlite3() {
6168
- return await isNodeModuleAvailable("better-sqlite3");
6169
- }
6170
- };
6171
- constants = {
6172
- DEFAULT_NODE_SQLITE_PATH: ":memory:",
6173
- DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
6174
- };
6175
- }
6176
- });
6177
-
6178
6632
  // src/engines/node/NodeSqliteEngine.ts
6179
6633
  var NodeSqliteEngine_exports = {};
6180
6634
  __export(NodeSqliteEngine_exports, {
@@ -6866,33 +7320,47 @@ __export(node_exports, {
6866
7320
  SqljsEngine: () => SqljsEngine,
6867
7321
  StringSet: () => StringSet,
6868
7322
  UniqueConstraintViolationError: () => UniqueConstraintViolationError,
7323
+ applyAutoStampsFromSchema: () => applyAutoStampsFromSchema,
7324
+ applyDefaultsFromSchema: () => applyDefaultsFromSchema,
7325
+ applyStringSetChange: () => applyStringSetChange,
7326
+ applyStringSetDelta: () => applyStringSetDelta,
7327
+ assertNever: () => assertNever,
6869
7328
  attachAndRegisterModel: () => attachAndRegisterModel,
6870
7329
  attachSchemaToClass: () => attachSchemaToClass,
6871
7330
  autoRegisterModel: () => autoRegisterModel,
6872
7331
  clearMetaSyncCache: () => clearMetaSyncCache,
6873
7332
  constants: () => constants,
6874
7333
  createModelClass: () => createModelClass,
7334
+ decodeFunctionDefault: () => decodeFunctionDefault,
6875
7335
  defineModelSchema: () => defineModelSchema,
6876
7336
  detectEnvironment: () => detectEnvironment,
6877
7337
  discoverModelNames: () => discoverModelNames,
6878
7338
  discoverSchema: () => discoverSchema,
6879
7339
  dumpYDocToPlain: () => dumpYDocToPlain,
7340
+ ensureStringSetYMap: () => ensureStringSetYMap,
6880
7341
  features: () => features,
7342
+ fieldWriteKind: () => fieldWriteKind,
6881
7343
  generateULID: () => generateULID,
6882
7344
  getRecommendedNodeEngine: () => getRecommendedNodeEngine,
6883
7345
  inferFieldType: () => inferFieldType,
6884
7346
  initJsBao: () => initJsBao,
6885
7347
  isBrowser: () => isBrowser,
7348
+ isKnownFieldType: () => isKnownFieldType,
6886
7349
  isNode: () => isNode,
6887
7350
  isNodeModuleAvailable: () => isNodeModuleAvailable,
6888
7351
  loadSchemaFromToml: () => loadSchemaFromToml,
6889
7352
  loadSchemaFromTomlString: () => loadSchemaFromTomlString,
7353
+ parseStringSetOp: () => parseStringSetOp,
6890
7354
  registerFunctionDefault: () => registerFunctionDefault,
7355
+ registerFunctionDefaultGenerator: () => registerFunctionDefaultGenerator,
7356
+ replaceStringSet: () => replaceStringSet,
6891
7357
  resetJsBao: () => resetJsBao,
7358
+ resolveDefaultValue: () => resolveDefaultValue,
6892
7359
  schemaToToml: () => schemaToToml,
6893
7360
  summarizePlainYDoc: () => summarizePlainYDoc,
6894
7361
  syncInferredMeta: () => syncInferredMeta,
6895
- syncModelMeta: () => syncModelMeta
7362
+ syncModelMeta: () => syncModelMeta,
7363
+ writeField: () => writeField
6896
7364
  });
6897
7365
  module.exports = __toCommonJS(node_exports);
6898
7366
  init_BaseModel();
@@ -7159,6 +7627,7 @@ init_StringSet();
7159
7627
  // src/models/schema.ts
7160
7628
  init_BaseModel();
7161
7629
  init_ModelRegistry();
7630
+ init_uniqueIndex();
7162
7631
  function defineModelSchema(input) {
7163
7632
  const { name, fields } = input;
7164
7633
  const options = {
@@ -7231,7 +7700,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
7231
7700
  for (const [fieldName, options] of fields.entries()) {
7232
7701
  if (options.unique) {
7233
7702
  resolved.push({
7234
- name: `${modelName}_${fieldName}_unique`,
7703
+ name: fieldUniqueConstraintName(modelName, fieldName),
7235
7704
  fields: [fieldName]
7236
7705
  });
7237
7706
  }
@@ -7295,7 +7764,7 @@ function attachAndRegisterModel(modelClass, schema) {
7295
7764
  }
7296
7765
 
7297
7766
  // src/utils/yDocSchema.ts
7298
- var Y4 = __toESM(require("yjs"), 1);
7767
+ var Y6 = __toESM(require("yjs"), 1);
7299
7768
  init_yjsValues();
7300
7769
  function discoverSchema(yDoc) {
7301
7770
  const models = {};
@@ -7330,7 +7799,7 @@ function discoverModelNames(yDoc) {
7330
7799
  function materializeMap(yDoc, key) {
7331
7800
  try {
7332
7801
  const map = yDoc.getMap(key);
7333
- return map instanceof Y4.Map ? map : null;
7802
+ return map instanceof Y6.Map ? map : null;
7334
7803
  } catch {
7335
7804
  return null;
7336
7805
  }
@@ -7340,11 +7809,11 @@ function readModelMeta(metaMap) {
7340
7809
  let constraints;
7341
7810
  let relationships;
7342
7811
  for (const [key, value] of metaMap.entries()) {
7343
- if (key === "_constraints" && value instanceof Y4.Map) {
7812
+ if (key === "_constraints" && value instanceof Y6.Map) {
7344
7813
  constraints = readConstraints(value);
7345
- } else if (key === "_relationships" && value instanceof Y4.Map) {
7814
+ } else if (key === "_relationships" && value instanceof Y6.Map) {
7346
7815
  relationships = readRelationships(value);
7347
- } else if (value instanceof Y4.Map) {
7816
+ } else if (value instanceof Y6.Map) {
7348
7817
  fields[key] = readFieldMeta(value);
7349
7818
  }
7350
7819
  }
@@ -7379,7 +7848,7 @@ function inferModelFromData(dataMap) {
7379
7848
  const fields = {};
7380
7849
  let sampled = 0;
7381
7850
  for (const [_recordId, recordValue] of dataMap.entries()) {
7382
- if (!(recordValue instanceof Y4.Map)) continue;
7851
+ if (!(recordValue instanceof Y6.Map)) continue;
7383
7852
  if (++sampled > 5) break;
7384
7853
  for (const [fieldName, value] of recordValue.entries()) {
7385
7854
  if (fieldName.startsWith("_")) continue;
@@ -7407,7 +7876,7 @@ function inferTypeFromValue(value) {
7407
7876
  function readConstraints(constraintsMap) {
7408
7877
  const out = {};
7409
7878
  for (const [name, value] of constraintsMap.entries()) {
7410
- if (!(value instanceof Y4.Map)) continue;
7879
+ if (!(value instanceof Y6.Map)) continue;
7411
7880
  let fields = [];
7412
7881
  const rawFields = value.get("fields");
7413
7882
  if (typeof rawFields === "string") {
@@ -7426,7 +7895,7 @@ function readConstraints(constraintsMap) {
7426
7895
  function readRelationships(relsMap) {
7427
7896
  const out = {};
7428
7897
  for (const [name, value] of relsMap.entries()) {
7429
- if (!(value instanceof Y4.Map)) continue;
7898
+ if (!(value instanceof Y6.Map)) continue;
7430
7899
  const rel = {};
7431
7900
  for (const [k, v] of value.entries()) {
7432
7901
  rel[k] = v;
@@ -7722,18 +8191,19 @@ async function loadSchemaFromToml(filePath, options = {}) {
7722
8191
 
7723
8192
  // src/node.ts
7724
8193
  init_metaSync();
8194
+ init_writeLifecycle();
7725
8195
 
7726
8196
  // src/utils/yDocDump.ts
7727
- var Y5 = __toESM(require("yjs"), 1);
8197
+ var Y7 = __toESM(require("yjs"), 1);
7728
8198
  function toPlain(value) {
7729
- if (value instanceof Y5.Map) {
8199
+ if (value instanceof Y7.Map) {
7730
8200
  const obj = {};
7731
8201
  value.forEach((v, k) => {
7732
8202
  obj[k] = toPlain(v);
7733
8203
  });
7734
8204
  return obj;
7735
8205
  }
7736
- if (value instanceof Y5.Array) {
8206
+ if (value instanceof Y7.Array) {
7737
8207
  return value.toArray().map((item) => toPlain(item));
7738
8208
  }
7739
8209
  return value;
@@ -7742,7 +8212,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
7742
8212
  const result = {};
7743
8213
  const includeIndexes = !!options.includeIndexes;
7744
8214
  yDoc.share.forEach((type, name) => {
7745
- if (!(type instanceof Y5.Map)) return;
8215
+ if (!(type instanceof Y7.Map)) return;
7746
8216
  if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
7747
8217
  const records = {};
7748
8218
  type.forEach((recordValue, recordId) => {
@@ -7803,31 +8273,45 @@ async function getRecommendedNodeEngine() {
7803
8273
  SqljsEngine,
7804
8274
  StringSet,
7805
8275
  UniqueConstraintViolationError,
8276
+ applyAutoStampsFromSchema,
8277
+ applyDefaultsFromSchema,
8278
+ applyStringSetChange,
8279
+ applyStringSetDelta,
8280
+ assertNever,
7806
8281
  attachAndRegisterModel,
7807
8282
  attachSchemaToClass,
7808
8283
  autoRegisterModel,
7809
8284
  clearMetaSyncCache,
7810
8285
  constants,
7811
8286
  createModelClass,
8287
+ decodeFunctionDefault,
7812
8288
  defineModelSchema,
7813
8289
  detectEnvironment,
7814
8290
  discoverModelNames,
7815
8291
  discoverSchema,
7816
8292
  dumpYDocToPlain,
8293
+ ensureStringSetYMap,
7817
8294
  features,
8295
+ fieldWriteKind,
7818
8296
  generateULID,
7819
8297
  getRecommendedNodeEngine,
7820
8298
  inferFieldType,
7821
8299
  initJsBao,
7822
8300
  isBrowser,
8301
+ isKnownFieldType,
7823
8302
  isNode,
7824
8303
  isNodeModuleAvailable,
7825
8304
  loadSchemaFromToml,
7826
8305
  loadSchemaFromTomlString,
8306
+ parseStringSetOp,
7827
8307
  registerFunctionDefault,
8308
+ registerFunctionDefaultGenerator,
8309
+ replaceStringSet,
7828
8310
  resetJsBao,
8311
+ resolveDefaultValue,
7829
8312
  schemaToToml,
7830
8313
  summarizePlainYDoc,
7831
8314
  syncInferredMeta,
7832
- syncModelMeta
8315
+ syncModelMeta,
8316
+ writeField
7833
8317
  });