js-bao 0.5.0 → 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/README.md +124 -30
- package/dist/browser.cjs +759 -273
- package/dist/browser.d.cts +62 -0
- package/dist/browser.d.ts +62 -0
- package/dist/browser.js +760 -271
- package/dist/client.cjs +53 -6
- package/dist/client.d.cts +149 -0
- package/dist/client.d.ts +149 -0
- package/dist/client.js +49 -4
- package/dist/cloudflare-do.cjs +643 -231
- package/dist/cloudflare-do.d.cts +237 -1
- package/dist/cloudflare-do.d.ts +237 -1
- package/dist/cloudflare-do.js +639 -229
- package/dist/cloudflare.cjs +330 -26
- package/dist/cloudflare.d.cts +360 -1
- package/dist/cloudflare.d.ts +360 -1
- package/dist/cloudflare.js +326 -24
- package/dist/codegen-v2.cjs +139 -21
- package/dist/codegen.cjs +7 -1
- package/dist/index.cjs +724 -232
- package/dist/index.d.cts +98 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +721 -229
- package/dist/node.cjs +948 -299
- package/dist/node.d.cts +220 -1
- package/dist/node.d.ts +220 -1
- package/dist/node.js +934 -296
- package/package.json +2 -1
package/dist/browser.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
|
-
|
|
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")}
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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(
|
|
@@ -1161,10 +1192,109 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
1161
1192
|
}
|
|
1162
1193
|
});
|
|
1163
1194
|
|
|
1164
|
-
// src/
|
|
1195
|
+
// src/utils/yjsValues.ts
|
|
1165
1196
|
import * as Y from "yjs";
|
|
1197
|
+
function isYjsComposite(value) {
|
|
1198
|
+
return value instanceof Y.Map || value instanceof Y.Array || value instanceof Y.Text;
|
|
1199
|
+
}
|
|
1200
|
+
function isStringSetYMap(value) {
|
|
1201
|
+
if (!(value instanceof Y.Map)) return false;
|
|
1202
|
+
for (const v of value.values()) {
|
|
1203
|
+
if (v !== true) return false;
|
|
1204
|
+
}
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
function normalizeYjsValue(value) {
|
|
1208
|
+
if (value instanceof Y.Text) {
|
|
1209
|
+
return value.toString();
|
|
1210
|
+
}
|
|
1211
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1212
|
+
return value.toJSON();
|
|
1213
|
+
}
|
|
1214
|
+
return value;
|
|
1215
|
+
}
|
|
1216
|
+
function normalizeYjsValueForStorage(value) {
|
|
1217
|
+
if (value instanceof Y.Text) {
|
|
1218
|
+
return value.toString();
|
|
1219
|
+
}
|
|
1220
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1221
|
+
return JSON.stringify(value.toJSON());
|
|
1222
|
+
}
|
|
1223
|
+
return value;
|
|
1224
|
+
}
|
|
1225
|
+
var init_yjsValues = __esm({
|
|
1226
|
+
"src/utils/yjsValues.ts"() {
|
|
1227
|
+
"use strict";
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
// src/models/writeLifecycle.ts
|
|
1232
|
+
import * as Y2 from "yjs";
|
|
1233
|
+
import { ulid } from "ulid";
|
|
1234
|
+
function ensureStringSetYMap(rm, fieldName) {
|
|
1235
|
+
const nested = rm.get(fieldName);
|
|
1236
|
+
if (nested instanceof Y2.Map) return nested;
|
|
1237
|
+
const fresh = new Y2.Map();
|
|
1238
|
+
if (nested && typeof nested === "object") {
|
|
1239
|
+
for (const [member, marker] of Object.entries(
|
|
1240
|
+
nested
|
|
1241
|
+
)) {
|
|
1242
|
+
if (Boolean(marker)) fresh.set(member, true);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
rm.set(fieldName, fresh);
|
|
1246
|
+
return fresh;
|
|
1247
|
+
}
|
|
1248
|
+
function applyStringSetDelta(target, delta) {
|
|
1249
|
+
if (delta.clear) {
|
|
1250
|
+
for (const member of Array.from(target.keys())) target.delete(member);
|
|
1251
|
+
}
|
|
1252
|
+
if (delta.remove) {
|
|
1253
|
+
for (const member of delta.remove) target.delete(String(member));
|
|
1254
|
+
}
|
|
1255
|
+
if (delta.add) {
|
|
1256
|
+
for (const member of delta.add) {
|
|
1257
|
+
const key = String(member);
|
|
1258
|
+
if (target.get(key) !== true) target.set(key, true);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
function applyStringSetChange(recordYMap, fieldName, change) {
|
|
1263
|
+
const target = ensureStringSetYMap(recordYMap, fieldName);
|
|
1264
|
+
applyStringSetDelta(target, {
|
|
1265
|
+
add: change.additions,
|
|
1266
|
+
remove: change.removals
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
function registerFunctionDefaultGenerator(name, fn) {
|
|
1270
|
+
FUNCTION_DEFAULT_GENERATORS.set(name, fn);
|
|
1271
|
+
}
|
|
1272
|
+
function resolveLiteralOrFunctionDefault(rawDefault) {
|
|
1273
|
+
return typeof rawDefault === "function" ? rawDefault() : rawDefault;
|
|
1274
|
+
}
|
|
1275
|
+
var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
|
|
1276
|
+
var init_writeLifecycle = __esm({
|
|
1277
|
+
"src/models/writeLifecycle.ts"() {
|
|
1278
|
+
"use strict";
|
|
1279
|
+
KNOWN_FIELD_TYPE_MAP = {
|
|
1280
|
+
string: true,
|
|
1281
|
+
number: true,
|
|
1282
|
+
boolean: true,
|
|
1283
|
+
date: true,
|
|
1284
|
+
id: true,
|
|
1285
|
+
stringset: true
|
|
1286
|
+
};
|
|
1287
|
+
KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
|
|
1288
|
+
FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
|
|
1289
|
+
registerFunctionDefaultGenerator("generate_ulid", () => ulid());
|
|
1290
|
+
UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
// src/models/metaSync.ts
|
|
1295
|
+
import * as Y3 from "yjs";
|
|
1166
1296
|
function inferFieldType(value) {
|
|
1167
|
-
if (value
|
|
1297
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
1168
1298
|
switch (typeof value) {
|
|
1169
1299
|
case "string":
|
|
1170
1300
|
return "string";
|
|
@@ -1183,7 +1313,10 @@ function encodeDefault(value) {
|
|
|
1183
1313
|
if (value === void 0 || value === null) return void 0;
|
|
1184
1314
|
if (typeof value === "function") {
|
|
1185
1315
|
const name = KNOWN_FUNCTION_DEFAULTS.get(value);
|
|
1186
|
-
return name ? `$${name}` :
|
|
1316
|
+
return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
|
|
1317
|
+
}
|
|
1318
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
1319
|
+
return `$${value}`;
|
|
1187
1320
|
}
|
|
1188
1321
|
return value;
|
|
1189
1322
|
}
|
|
@@ -1209,7 +1342,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1209
1342
|
if (compoundConstraints.length > 0) {
|
|
1210
1343
|
let constraints = meta.get("_constraints");
|
|
1211
1344
|
if (!constraints) {
|
|
1212
|
-
constraints = new
|
|
1345
|
+
constraints = new Y3.Map();
|
|
1213
1346
|
meta.set("_constraints", constraints);
|
|
1214
1347
|
}
|
|
1215
1348
|
for (const constraint of compoundConstraints) {
|
|
@@ -1220,7 +1353,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1220
1353
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
1221
1354
|
let rels = meta.get("_relationships");
|
|
1222
1355
|
if (!rels) {
|
|
1223
|
-
rels = new
|
|
1356
|
+
rels = new Y3.Map();
|
|
1224
1357
|
meta.set("_relationships", rels);
|
|
1225
1358
|
}
|
|
1226
1359
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -1232,7 +1365,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1232
1365
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
1233
1366
|
let fieldMeta = metaMap.get(fieldName);
|
|
1234
1367
|
if (!fieldMeta) {
|
|
1235
|
-
fieldMeta = new
|
|
1368
|
+
fieldMeta = new Y3.Map();
|
|
1236
1369
|
metaMap.set(fieldName, fieldMeta);
|
|
1237
1370
|
}
|
|
1238
1371
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -1240,6 +1373,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1240
1373
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
1241
1374
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
1242
1375
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
1376
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
1243
1377
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
1244
1378
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
1245
1379
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -1248,7 +1382,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1248
1382
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
1249
1383
|
let cMeta = constraintsMap.get(constraint.name);
|
|
1250
1384
|
if (!cMeta) {
|
|
1251
|
-
cMeta = new
|
|
1385
|
+
cMeta = new Y3.Map();
|
|
1252
1386
|
constraintsMap.set(constraint.name, cMeta);
|
|
1253
1387
|
}
|
|
1254
1388
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -1258,7 +1392,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
1258
1392
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
1259
1393
|
let relMeta = relsMap.get(relName);
|
|
1260
1394
|
if (!relMeta) {
|
|
1261
|
-
relMeta = new
|
|
1395
|
+
relMeta = new Y3.Map();
|
|
1262
1396
|
relsMap.set(relName, relMeta);
|
|
1263
1397
|
}
|
|
1264
1398
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -1273,7 +1407,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
1273
1407
|
if (fieldName.startsWith("_")) continue;
|
|
1274
1408
|
let fieldMeta = meta.get(fieldName);
|
|
1275
1409
|
if (!fieldMeta) {
|
|
1276
|
-
fieldMeta = new
|
|
1410
|
+
fieldMeta = new Y3.Map();
|
|
1277
1411
|
meta.set(fieldName, fieldMeta);
|
|
1278
1412
|
}
|
|
1279
1413
|
if (!fieldMeta.has("type")) {
|
|
@@ -1293,12 +1427,52 @@ var KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
|
1293
1427
|
var init_metaSync = __esm({
|
|
1294
1428
|
"src/models/metaSync.ts"() {
|
|
1295
1429
|
"use strict";
|
|
1430
|
+
init_yjsValues();
|
|
1431
|
+
init_writeLifecycle();
|
|
1296
1432
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
1297
1433
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
1298
1434
|
}
|
|
1299
1435
|
});
|
|
1300
1436
|
|
|
1437
|
+
// src/utils/uniqueIndex.ts
|
|
1438
|
+
import * as Y4 from "yjs";
|
|
1439
|
+
function buildUniqueKey(recordData, fields) {
|
|
1440
|
+
const keyParts = [];
|
|
1441
|
+
for (const field of fields) {
|
|
1442
|
+
const value = recordData instanceof Y4.Map ? recordData.get(field) : recordData[field];
|
|
1443
|
+
if (value === null || value === void 0) {
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
keyParts.push(String(value));
|
|
1447
|
+
}
|
|
1448
|
+
return keyParts.join("|");
|
|
1449
|
+
}
|
|
1450
|
+
function uniqueIndexMapName(modelName, constraintName) {
|
|
1451
|
+
return `_uniqueIdx_${modelName}_${constraintName}`;
|
|
1452
|
+
}
|
|
1453
|
+
function fieldUniqueConstraintName(modelName, fieldName) {
|
|
1454
|
+
return `${modelName}_${fieldName}_unique`;
|
|
1455
|
+
}
|
|
1456
|
+
var init_uniqueIndex = __esm({
|
|
1457
|
+
"src/utils/uniqueIndex.ts"() {
|
|
1458
|
+
"use strict";
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1301
1462
|
// src/models/relationshipManager.ts
|
|
1463
|
+
function warnIfOrderFieldNullable(modelClass, orderByField, relationshipDescription) {
|
|
1464
|
+
if (ALWAYS_PRESENT_ORDER_FIELDS.has(orderByField)) return;
|
|
1465
|
+
const schema = modelClass.getSchema?.();
|
|
1466
|
+
const fields = schema?.fields;
|
|
1467
|
+
if (!fields || typeof fields.get !== "function") return;
|
|
1468
|
+
const fieldOptions = fields.get(orderByField);
|
|
1469
|
+
if (!fieldOptions) return;
|
|
1470
|
+
if (fieldOptions.required !== true) {
|
|
1471
|
+
Logger.warn(
|
|
1472
|
+
`[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.)`
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1302
1476
|
function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
1303
1477
|
return async function() {
|
|
1304
1478
|
const relatedId = this[config.relatedIdField];
|
|
@@ -1307,6 +1481,16 @@ function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
|
1307
1481
|
};
|
|
1308
1482
|
}
|
|
1309
1483
|
function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbEngine) {
|
|
1484
|
+
const {
|
|
1485
|
+
relatedIdField,
|
|
1486
|
+
orderByField = "id",
|
|
1487
|
+
orderDirection = "ASC"
|
|
1488
|
+
} = config;
|
|
1489
|
+
warnIfOrderFieldNullable(
|
|
1490
|
+
targetModelClass,
|
|
1491
|
+
orderByField,
|
|
1492
|
+
`hasMany(${targetModelClass.name})`
|
|
1493
|
+
);
|
|
1310
1494
|
return async function(paginationArgs) {
|
|
1311
1495
|
const {
|
|
1312
1496
|
limit,
|
|
@@ -1314,71 +1498,39 @@ function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbE
|
|
|
1314
1498
|
beforeCursor,
|
|
1315
1499
|
direction = "forward"
|
|
1316
1500
|
} = paginationArgs || {};
|
|
1317
|
-
const {
|
|
1318
|
-
relatedIdField,
|
|
1319
|
-
orderByField = "id",
|
|
1320
|
-
orderDirection = "ASC"
|
|
1321
|
-
} = config;
|
|
1322
1501
|
const targetSchema = targetModelClass.getSchema();
|
|
1323
1502
|
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
1324
1503
|
Logger.error(
|
|
1325
1504
|
`[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
|
|
1326
1505
|
);
|
|
1327
|
-
return { data: [],
|
|
1506
|
+
return { data: [], hasMore: false };
|
|
1328
1507
|
}
|
|
1329
|
-
const
|
|
1508
|
+
const forward = direction !== "backward";
|
|
1509
|
+
const cursor = forward ? afterCursor : beforeCursor;
|
|
1330
1510
|
const queryOptions = {
|
|
1331
|
-
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
|
|
1511
|
+
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 },
|
|
1512
|
+
direction: forward ? 1 : -1
|
|
1332
1513
|
};
|
|
1333
|
-
if (limit !== void 0)
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
};
|
|
1340
|
-
} else if (direction === "backward") {
|
|
1341
|
-
if (beforeCursor) {
|
|
1342
|
-
filter[orderByField] = {
|
|
1343
|
-
[orderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
queryOptions.sort = { [orderByField]: orderDirection === "ASC" ? -1 : 1 };
|
|
1347
|
-
}
|
|
1348
|
-
const result = await targetModelClass.query(filter, queryOptions);
|
|
1349
|
-
const results = result.data;
|
|
1350
|
-
let nextCursor = null;
|
|
1351
|
-
let prevCursor = null;
|
|
1352
|
-
const isFirstPage = direction === "forward" && !afterCursor;
|
|
1353
|
-
if (limit !== void 0) {
|
|
1354
|
-
if (direction === "forward") {
|
|
1355
|
-
if (results.length > limit) {
|
|
1356
|
-
nextCursor = results[limit - 1][orderByField];
|
|
1357
|
-
results.pop();
|
|
1358
|
-
}
|
|
1359
|
-
if (results.length > 0 && !isFirstPage) {
|
|
1360
|
-
prevCursor = results[0][orderByField];
|
|
1361
|
-
}
|
|
1362
|
-
} else {
|
|
1363
|
-
if (results.length > limit) {
|
|
1364
|
-
prevCursor = results[limit - 1][orderByField];
|
|
1365
|
-
results.pop();
|
|
1366
|
-
}
|
|
1367
|
-
results.reverse();
|
|
1368
|
-
if (results.length > 0 && beforeCursor) {
|
|
1369
|
-
nextCursor = results[results.length - 1][orderByField];
|
|
1370
|
-
}
|
|
1371
|
-
if (results.length > 0 && !beforeCursor) {
|
|
1372
|
-
if (results.length === limit) {
|
|
1373
|
-
prevCursor = results[0][orderByField];
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
return { data: results, nextCursor, prevCursor };
|
|
1514
|
+
if (limit !== void 0) queryOptions.limit = limit;
|
|
1515
|
+
if (cursor != null) queryOptions.uniqueStartKey = cursor;
|
|
1516
|
+
return targetModelClass.query(
|
|
1517
|
+
{ [relatedIdField]: this.id },
|
|
1518
|
+
queryOptions
|
|
1519
|
+
);
|
|
1379
1520
|
};
|
|
1380
1521
|
}
|
|
1381
1522
|
function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClass, joinModelClass, _dbEngine) {
|
|
1523
|
+
const {
|
|
1524
|
+
joinModelLocalField,
|
|
1525
|
+
joinModelRelatedField,
|
|
1526
|
+
joinModelOrderByField = "id",
|
|
1527
|
+
joinModelOrderDirection = "ASC"
|
|
1528
|
+
} = config;
|
|
1529
|
+
warnIfOrderFieldNullable(
|
|
1530
|
+
joinModelClass,
|
|
1531
|
+
joinModelOrderByField,
|
|
1532
|
+
`hasManyThrough(${targetModelClass.name} via ${joinModelClass.name})`
|
|
1533
|
+
);
|
|
1382
1534
|
return async function(paginationArgs) {
|
|
1383
1535
|
const {
|
|
1384
1536
|
limit,
|
|
@@ -1392,87 +1544,42 @@ function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClas
|
|
|
1392
1544
|
Logger.error(
|
|
1393
1545
|
`[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
1394
1546
|
);
|
|
1395
|
-
return { data: [],
|
|
1547
|
+
return { data: [], hasMore: false };
|
|
1396
1548
|
}
|
|
1397
1549
|
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
1398
1550
|
Logger.error(
|
|
1399
1551
|
`[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
1400
1552
|
);
|
|
1401
|
-
return { data: [],
|
|
1553
|
+
return { data: [], hasMore: false };
|
|
1402
1554
|
}
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
joinModelRelatedField,
|
|
1406
|
-
joinModelOrderByField = "id",
|
|
1407
|
-
joinModelOrderDirection = "ASC"
|
|
1408
|
-
} = config;
|
|
1409
|
-
const joinFilter = { [joinModelLocalField]: this.id };
|
|
1555
|
+
const forward = direction !== "backward";
|
|
1556
|
+
const cursor = forward ? afterCursor : beforeCursor;
|
|
1410
1557
|
const joinQueryOptions = {
|
|
1411
1558
|
sort: {
|
|
1412
1559
|
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
|
|
1413
1560
|
},
|
|
1414
|
-
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
|
|
1561
|
+
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 },
|
|
1562
|
+
direction: forward ? 1 : -1
|
|
1415
1563
|
};
|
|
1416
|
-
if (limit !== void 0)
|
|
1417
|
-
|
|
1418
|
-
}
|
|
1419
|
-
if (direction === "forward" && afterCursor) {
|
|
1420
|
-
joinFilter[joinModelOrderByField] = {
|
|
1421
|
-
[joinModelOrderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
|
|
1422
|
-
};
|
|
1423
|
-
} else if (direction === "backward") {
|
|
1424
|
-
if (beforeCursor) {
|
|
1425
|
-
joinFilter[joinModelOrderByField] = {
|
|
1426
|
-
[joinModelOrderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
1427
|
-
};
|
|
1428
|
-
}
|
|
1429
|
-
joinQueryOptions.sort = {
|
|
1430
|
-
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? -1 : 1
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1564
|
+
if (limit !== void 0) joinQueryOptions.limit = limit;
|
|
1565
|
+
if (cursor != null) joinQueryOptions.uniqueStartKey = cursor;
|
|
1433
1566
|
const joinResult = await joinModelClass.query(
|
|
1434
|
-
|
|
1567
|
+
{ [joinModelLocalField]: this.id },
|
|
1435
1568
|
joinQueryOptions
|
|
1436
1569
|
);
|
|
1437
|
-
const
|
|
1438
|
-
let
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
nextCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
1445
|
-
joinResults.pop();
|
|
1446
|
-
}
|
|
1447
|
-
if (joinResults.length > 0 && !isFirstPage) {
|
|
1448
|
-
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
1449
|
-
}
|
|
1450
|
-
} else {
|
|
1451
|
-
if (joinResults.length > limit) {
|
|
1452
|
-
prevCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
1453
|
-
joinResults.pop();
|
|
1454
|
-
}
|
|
1455
|
-
joinResults.reverse();
|
|
1456
|
-
if (joinResults.length > 0 && beforeCursor) {
|
|
1457
|
-
nextCursorFromJoin = joinResults[joinResults.length - 1][joinModelOrderByField];
|
|
1458
|
-
}
|
|
1459
|
-
if (joinResults.length > 0 && !beforeCursor) {
|
|
1460
|
-
if (joinResults.length === limit) {
|
|
1461
|
-
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1570
|
+
const relatedIds = joinResult.data.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
|
|
1571
|
+
let targetData = [];
|
|
1572
|
+
if (relatedIds.length > 0) {
|
|
1573
|
+
const targetResult = await targetModelClass.query({
|
|
1574
|
+
id: { $in: relatedIds }
|
|
1575
|
+
});
|
|
1576
|
+
targetData = targetResult.data;
|
|
1465
1577
|
}
|
|
1466
|
-
const relatedIds = joinResults.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
|
|
1467
|
-
if (relatedIds.length === 0)
|
|
1468
|
-
return { data: [], nextCursor: null, prevCursor: null };
|
|
1469
|
-
const targetResult = await targetModelClass.query({
|
|
1470
|
-
id: { $in: relatedIds }
|
|
1471
|
-
});
|
|
1472
1578
|
return {
|
|
1473
|
-
data:
|
|
1474
|
-
nextCursor:
|
|
1475
|
-
prevCursor:
|
|
1579
|
+
data: targetData,
|
|
1580
|
+
nextCursor: joinResult.nextCursor,
|
|
1581
|
+
prevCursor: joinResult.prevCursor,
|
|
1582
|
+
hasMore: joinResult.hasMore
|
|
1476
1583
|
};
|
|
1477
1584
|
};
|
|
1478
1585
|
}
|
|
@@ -1596,10 +1703,12 @@ function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetMo
|
|
|
1596
1703
|
}, `remove-join-${config.joinModel}-${this.id}-${targetId}`);
|
|
1597
1704
|
};
|
|
1598
1705
|
}
|
|
1706
|
+
var ALWAYS_PRESENT_ORDER_FIELDS;
|
|
1599
1707
|
var init_relationshipManager = __esm({
|
|
1600
1708
|
"src/models/relationshipManager.ts"() {
|
|
1601
1709
|
"use strict";
|
|
1602
1710
|
init_BaseModel();
|
|
1711
|
+
ALWAYS_PRESENT_ORDER_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
|
|
1603
1712
|
}
|
|
1604
1713
|
});
|
|
1605
1714
|
|
|
@@ -2370,7 +2479,13 @@ var init_IncludeResolver = __esm({
|
|
|
2370
2479
|
}
|
|
2371
2480
|
return null;
|
|
2372
2481
|
}
|
|
2373
|
-
/**
|
|
2482
|
+
/**
|
|
2483
|
+
* Apply a projection to a single record. Assumes the projection was already
|
|
2484
|
+
* validated upstream (mixed inclusion+exclusion rejected by the translator):
|
|
2485
|
+
* `isInclusion` treats "any value === 1" as inclusion mode. `id` and `type`
|
|
2486
|
+
* are system-identity fields and are always kept in BOTH modes (mirrors the
|
|
2487
|
+
* DatabaseDO `_applyProjection`); `_meta` stays excludable.
|
|
2488
|
+
*/
|
|
2374
2489
|
_applyProjection(record, projection) {
|
|
2375
2490
|
const fields = Object.entries(projection);
|
|
2376
2491
|
if (fields.length === 0) return record;
|
|
@@ -2389,7 +2504,7 @@ var init_IncludeResolver = __esm({
|
|
|
2389
2504
|
} else {
|
|
2390
2505
|
const result = { ...record };
|
|
2391
2506
|
for (const [f, v] of fields) {
|
|
2392
|
-
if (v === 0) delete result[f];
|
|
2507
|
+
if (v === 0 && f !== "id" && f !== "type") delete result[f];
|
|
2393
2508
|
}
|
|
2394
2509
|
return result;
|
|
2395
2510
|
}
|
|
@@ -2408,10 +2523,10 @@ __export(BaseModel_exports, {
|
|
|
2408
2523
|
UniqueConstraintViolationError: () => UniqueConstraintViolationError,
|
|
2409
2524
|
generateULID: () => generateULID
|
|
2410
2525
|
});
|
|
2411
|
-
import * as
|
|
2412
|
-
import { ulid } from "ulid";
|
|
2526
|
+
import * as Y5 from "yjs";
|
|
2527
|
+
import { ulid as ulid2 } from "ulid";
|
|
2413
2528
|
function generateULID() {
|
|
2414
|
-
return
|
|
2529
|
+
return ulid2();
|
|
2415
2530
|
}
|
|
2416
2531
|
var LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
|
|
2417
2532
|
var init_BaseModel = __esm({
|
|
@@ -2423,6 +2538,9 @@ var init_BaseModel = __esm({
|
|
|
2423
2538
|
init_CursorManager();
|
|
2424
2539
|
init_sql();
|
|
2425
2540
|
init_metaSync();
|
|
2541
|
+
init_writeLifecycle();
|
|
2542
|
+
init_yjsValues();
|
|
2543
|
+
init_uniqueIndex();
|
|
2426
2544
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
2427
2545
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
2428
2546
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2807,7 +2925,9 @@ var init_BaseModel = __esm({
|
|
|
2807
2925
|
this.setValue(fieldKey, data[fieldKey]);
|
|
2808
2926
|
this._constructorProvidedFields.add(fieldKey);
|
|
2809
2927
|
} else if (fieldOptions.default !== void 0) {
|
|
2810
|
-
const defaultValue =
|
|
2928
|
+
const defaultValue = resolveLiteralOrFunctionDefault(
|
|
2929
|
+
fieldOptions.default
|
|
2930
|
+
);
|
|
2811
2931
|
this.setValue(fieldKey, defaultValue);
|
|
2812
2932
|
}
|
|
2813
2933
|
}
|
|
@@ -2931,7 +3051,30 @@ var init_BaseModel = __esm({
|
|
|
2931
3051
|
if (verboseEnabled) {
|
|
2932
3052
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2933
3053
|
}
|
|
2934
|
-
return fieldValue;
|
|
3054
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
3058
|
+
*
|
|
3059
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
3060
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
3061
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
3062
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
3063
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
3064
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
3065
|
+
*/
|
|
3066
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
3067
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
3068
|
+
if (fieldOptions?.type === "stringset") {
|
|
3069
|
+
return value;
|
|
3070
|
+
}
|
|
3071
|
+
if (isYjsComposite(value)) {
|
|
3072
|
+
Logger.warn(
|
|
3073
|
+
`[${modelName ?? this.constructor.name}] Field '${fieldKey}' holds a composite Yjs value (${value?.constructor?.name}) but its declared type is '${fieldOptions?.type ?? "unknown"}'. Normalizing to plain JS \u2014 the writer disagreed with the schema.`
|
|
3074
|
+
);
|
|
3075
|
+
return normalizeYjsValue(value);
|
|
3076
|
+
}
|
|
3077
|
+
return value;
|
|
2935
3078
|
}
|
|
2936
3079
|
getValue(fieldKey) {
|
|
2937
3080
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -3076,7 +3219,7 @@ var init_BaseModel = __esm({
|
|
|
3076
3219
|
}
|
|
3077
3220
|
getStringSetFromYjs(fieldName) {
|
|
3078
3221
|
const yjsData = this.getFromYjs(fieldName);
|
|
3079
|
-
if (yjsData instanceof
|
|
3222
|
+
if (yjsData instanceof Y5.Map) {
|
|
3080
3223
|
return Array.from(yjsData.keys());
|
|
3081
3224
|
}
|
|
3082
3225
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -3184,7 +3327,7 @@ var init_BaseModel = __esm({
|
|
|
3184
3327
|
}
|
|
3185
3328
|
const extractStringSetValues = (value) => {
|
|
3186
3329
|
if (!value) return [];
|
|
3187
|
-
if (value instanceof
|
|
3330
|
+
if (value instanceof Y5.Map) {
|
|
3188
3331
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
3189
3332
|
}
|
|
3190
3333
|
if (Array.isArray(value)) {
|
|
@@ -3201,7 +3344,7 @@ var init_BaseModel = __esm({
|
|
|
3201
3344
|
if (!recordId) continue;
|
|
3202
3345
|
let itemData;
|
|
3203
3346
|
const stringSetValuesByField = {};
|
|
3204
|
-
if (recordData instanceof
|
|
3347
|
+
if (recordData instanceof Y5.Map) {
|
|
3205
3348
|
itemData = {};
|
|
3206
3349
|
const unknownFields = [];
|
|
3207
3350
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3317,20 +3460,10 @@ var init_BaseModel = __esm({
|
|
|
3317
3460
|
Logger.verbose(
|
|
3318
3461
|
`[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
|
|
3319
3462
|
);
|
|
3320
|
-
const
|
|
3321
|
-
const keyParts = [];
|
|
3322
|
-
for (const field of fields) {
|
|
3323
|
-
const value = recordData instanceof Y2.Map ? recordData.get(field) : recordData[field];
|
|
3324
|
-
if (value === null || value === void 0) {
|
|
3325
|
-
return null;
|
|
3326
|
-
}
|
|
3327
|
-
keyParts.push(String(value));
|
|
3328
|
-
}
|
|
3329
|
-
return keyParts.join("|");
|
|
3330
|
-
};
|
|
3463
|
+
const buildUniqueKey2 = (recordData, fields) => buildUniqueKey(recordData, fields);
|
|
3331
3464
|
const extractItemData = (key, recordData) => {
|
|
3332
3465
|
let itemData;
|
|
3333
|
-
if (recordData instanceof
|
|
3466
|
+
if (recordData instanceof Y5.Map) {
|
|
3334
3467
|
itemData = {};
|
|
3335
3468
|
const unknownFields = [];
|
|
3336
3469
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3343,7 +3476,14 @@ var init_BaseModel = __esm({
|
|
|
3343
3476
|
continue;
|
|
3344
3477
|
}
|
|
3345
3478
|
if (value !== void 0) {
|
|
3346
|
-
|
|
3479
|
+
if (isYjsComposite(value)) {
|
|
3480
|
+
Logger.warn(
|
|
3481
|
+
`[${this.name}] Field '${fieldKey}' on record ${key} holds a composite Yjs value (${value?.constructor?.name}) but its declared type is '${fieldOptions.type}'. Normalizing to plain JS before persisting to SQLite.`
|
|
3482
|
+
);
|
|
3483
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3484
|
+
} else {
|
|
3485
|
+
itemData[fieldKey] = value;
|
|
3486
|
+
}
|
|
3347
3487
|
}
|
|
3348
3488
|
}
|
|
3349
3489
|
if (unknownFields.length > 0) {
|
|
@@ -3366,7 +3506,14 @@ var init_BaseModel = __esm({
|
|
|
3366
3506
|
continue;
|
|
3367
3507
|
}
|
|
3368
3508
|
if (value !== void 0) {
|
|
3369
|
-
|
|
3509
|
+
if (isYjsComposite(value)) {
|
|
3510
|
+
Logger.warn(
|
|
3511
|
+
`[${this.name}] Field '${fieldKey}' on legacy record ${key} holds a composite Yjs value (${value?.constructor?.name}) but its declared type is '${fieldOptions.type}'. Normalizing to plain JS before persisting to SQLite.`
|
|
3512
|
+
);
|
|
3513
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3514
|
+
} else {
|
|
3515
|
+
filteredData[fieldKey] = value;
|
|
3516
|
+
}
|
|
3370
3517
|
}
|
|
3371
3518
|
}
|
|
3372
3519
|
if (unknownFields.length > 0) {
|
|
@@ -3400,7 +3547,7 @@ var init_BaseModel = __esm({
|
|
|
3400
3547
|
const recordsByUniqueKey = /* @__PURE__ */ new Map();
|
|
3401
3548
|
for (const [recordId, recordData] of allRecords.entries()) {
|
|
3402
3549
|
if (recordIdsToDiscard.has(recordId)) continue;
|
|
3403
|
-
const uniqueKey =
|
|
3550
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3404
3551
|
if (uniqueKey === null) continue;
|
|
3405
3552
|
if (!recordsByUniqueKey.has(uniqueKey)) {
|
|
3406
3553
|
recordsByUniqueKey.set(uniqueKey, []);
|
|
@@ -3446,7 +3593,7 @@ var init_BaseModel = __esm({
|
|
|
3446
3593
|
if (!recordData || !key) continue;
|
|
3447
3594
|
const itemData = extractItemData(key, recordData);
|
|
3448
3595
|
if (!itemData) continue;
|
|
3449
|
-
if (change.action === "add" && recordData instanceof
|
|
3596
|
+
if (change.action === "add" && recordData instanceof Y5.Map) {
|
|
3450
3597
|
Logger.verbose(
|
|
3451
3598
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
3452
3599
|
);
|
|
@@ -3544,9 +3691,12 @@ var init_BaseModel = __esm({
|
|
|
3544
3691
|
const recordData = documentYMap.get(idToDiscard);
|
|
3545
3692
|
if (!recordData) continue;
|
|
3546
3693
|
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
3547
|
-
const uniqueKey =
|
|
3694
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3548
3695
|
if (uniqueKey === null) continue;
|
|
3549
|
-
const constraintMapName =
|
|
3696
|
+
const constraintMapName = uniqueIndexMapName(
|
|
3697
|
+
modelName,
|
|
3698
|
+
constraint.name
|
|
3699
|
+
);
|
|
3550
3700
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
3551
3701
|
const currentIndexValue = constraintMap.get(uniqueKey);
|
|
3552
3702
|
if (currentIndexValue === idToDiscard) {
|
|
@@ -3564,11 +3714,14 @@ var init_BaseModel = __esm({
|
|
|
3564
3714
|
}, `conflict-resolution-${modelName}-${docId}`);
|
|
3565
3715
|
yDoc.transact(() => {
|
|
3566
3716
|
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
3567
|
-
const constraintMapName =
|
|
3717
|
+
const constraintMapName = uniqueIndexMapName(
|
|
3718
|
+
modelName,
|
|
3719
|
+
constraint.name
|
|
3720
|
+
);
|
|
3568
3721
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
3569
3722
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3570
3723
|
if (!recordData) continue;
|
|
3571
|
-
const uniqueKey =
|
|
3724
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3572
3725
|
if (uniqueKey === null) continue;
|
|
3573
3726
|
const currentIndexValue = constraintMap.get(uniqueKey);
|
|
3574
3727
|
if (currentIndexValue !== recordId) {
|
|
@@ -3591,7 +3744,7 @@ var init_BaseModel = __esm({
|
|
|
3591
3744
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
3592
3745
|
);
|
|
3593
3746
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3594
|
-
if (recordData instanceof
|
|
3747
|
+
if (recordData instanceof Y5.Map) {
|
|
3595
3748
|
Logger.verbose(
|
|
3596
3749
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
3597
3750
|
);
|
|
@@ -3776,6 +3929,11 @@ var init_BaseModel = __esm({
|
|
|
3776
3929
|
Logger.debug(
|
|
3777
3930
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3778
3931
|
);
|
|
3932
|
+
if (isYjsComposite(yjsValue)) {
|
|
3933
|
+
Logger.warn(
|
|
3934
|
+
`[${modelConstructor.name}] Field '${key}' holds a composite Yjs value (${yjsValue?.constructor?.name}) in a non-stringset slot; the local scalar change will overwrite it (last-writer-wins).`
|
|
3935
|
+
);
|
|
3936
|
+
}
|
|
3779
3937
|
if (yjsValue === void 0) {
|
|
3780
3938
|
Logger.debug(
|
|
3781
3939
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3812,26 +3970,7 @@ var init_BaseModel = __esm({
|
|
|
3812
3970
|
* if the field hasn't been touched since the wire-format change in #561.
|
|
3813
3971
|
*/
|
|
3814
3972
|
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3815
|
-
|
|
3816
|
-
if (!(nested instanceof Y2.Map)) {
|
|
3817
|
-
const migrated = new Y2.Map();
|
|
3818
|
-
if (nested && typeof nested === "object") {
|
|
3819
|
-
for (const [member, marker] of Object.entries(
|
|
3820
|
-
nested
|
|
3821
|
-
)) {
|
|
3822
|
-
if (Boolean(marker)) migrated.set(member, true);
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
recordYMap.set(fieldName, migrated);
|
|
3826
|
-
nested = migrated;
|
|
3827
|
-
}
|
|
3828
|
-
const target = nested;
|
|
3829
|
-
for (const member of change.additions) {
|
|
3830
|
-
target.set(member, true);
|
|
3831
|
-
}
|
|
3832
|
-
for (const member of change.removals) {
|
|
3833
|
-
target.delete(member);
|
|
3834
|
-
}
|
|
3973
|
+
applyStringSetChange(recordYMap, fieldName, change);
|
|
3835
3974
|
}
|
|
3836
3975
|
/**
|
|
3837
3976
|
* Deep equality check for comparing field values
|
|
@@ -4001,6 +4140,29 @@ var init_BaseModel = __esm({
|
|
|
4001
4140
|
);
|
|
4002
4141
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
4003
4142
|
}
|
|
4143
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
4144
|
+
{
|
|
4145
|
+
const existingRecord = targetYMap.get(this.id);
|
|
4146
|
+
const isNew = !existingRecord;
|
|
4147
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
4148
|
+
const stamp = fieldOptions.autoStamp;
|
|
4149
|
+
if (!stamp) continue;
|
|
4150
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
4151
|
+
if (callerExplicit) {
|
|
4152
|
+
const localValue = this._localChanges[fieldKey];
|
|
4153
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
4154
|
+
}
|
|
4155
|
+
if (stamp === "create") {
|
|
4156
|
+
if (!isNew) continue;
|
|
4157
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
4158
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
4159
|
+
}
|
|
4160
|
+
this[fieldKey] = Date.now();
|
|
4161
|
+
if (stamp === "create") {
|
|
4162
|
+
preTransactCreateStamps.add(fieldKey);
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4004
4166
|
this.validateBeforeSave();
|
|
4005
4167
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
4006
4168
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -4010,6 +4172,34 @@ var init_BaseModel = __esm({
|
|
|
4010
4172
|
`[${modelName}.save] getCurrentJSState() returned:`,
|
|
4011
4173
|
dataToSave
|
|
4012
4174
|
);
|
|
4175
|
+
const uniqueMissConflicts = /* @__PURE__ */ new Map();
|
|
4176
|
+
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
4177
|
+
const newUniqueKey = this._buildUniqueKey(
|
|
4178
|
+
constraint.fields,
|
|
4179
|
+
dataToSave,
|
|
4180
|
+
modelName,
|
|
4181
|
+
constraint.name
|
|
4182
|
+
);
|
|
4183
|
+
if (newUniqueKey === null) {
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
const constraintMap = yDoc.getMap(
|
|
4187
|
+
uniqueIndexMapName(modelName, constraint.name)
|
|
4188
|
+
);
|
|
4189
|
+
if (!constraintMap.get(newUniqueKey)) {
|
|
4190
|
+
const constraintKeyValues = constraint.fields.map(
|
|
4191
|
+
(field) => dataToSave[field]
|
|
4192
|
+
);
|
|
4193
|
+
const conflictingRecord = await modelConstructor._resolveByUniqueFallback(
|
|
4194
|
+
constraint,
|
|
4195
|
+
constraintKeyValues,
|
|
4196
|
+
{ excludeId: this.id }
|
|
4197
|
+
);
|
|
4198
|
+
if (conflictingRecord) {
|
|
4199
|
+
uniqueMissConflicts.set(constraint.name, conflictingRecord.id);
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4013
4203
|
await yDoc.transact(async () => {
|
|
4014
4204
|
if (upsertOnConstraint && upsertOnKey) {
|
|
4015
4205
|
const constraintMapName = `_uniqueIdx_${modelName}_${upsertOnConstraint.name}`;
|
|
@@ -4031,6 +4221,14 @@ var init_BaseModel = __esm({
|
|
|
4031
4221
|
}
|
|
4032
4222
|
}
|
|
4033
4223
|
}
|
|
4224
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
4225
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
4226
|
+
delete this._localChanges[fieldKey];
|
|
4227
|
+
}
|
|
4228
|
+
if (fieldKey in dataToSave) {
|
|
4229
|
+
delete dataToSave[fieldKey];
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4034
4232
|
}
|
|
4035
4233
|
}
|
|
4036
4234
|
if (!targetYMap) {
|
|
@@ -4043,7 +4241,7 @@ var init_BaseModel = __esm({
|
|
|
4043
4241
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
4044
4242
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
4045
4243
|
if (!recordYMap) {
|
|
4046
|
-
recordYMap = new
|
|
4244
|
+
recordYMap = new Y5.Map();
|
|
4047
4245
|
Logger.verbose(
|
|
4048
4246
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
4049
4247
|
);
|
|
@@ -4095,7 +4293,10 @@ var init_BaseModel = __esm({
|
|
|
4095
4293
|
);
|
|
4096
4294
|
continue;
|
|
4097
4295
|
}
|
|
4098
|
-
const constraintMapName =
|
|
4296
|
+
const constraintMapName = uniqueIndexMapName(
|
|
4297
|
+
modelName,
|
|
4298
|
+
constraint.name
|
|
4299
|
+
);
|
|
4099
4300
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
4100
4301
|
Logger.verbose(
|
|
4101
4302
|
`[${modelName}] Save Check (Item '${this.id}'): GET from '${constraintMapName}' with Key '${newUniqueKey.substring(
|
|
@@ -4124,6 +4325,26 @@ var init_BaseModel = __esm({
|
|
|
4124
4325
|
this.id,
|
|
4125
4326
|
existingRecordIdWithNewKey
|
|
4126
4327
|
);
|
|
4328
|
+
} else if (!existingRecordIdWithNewKey) {
|
|
4329
|
+
const conflictingRecordId = uniqueMissConflicts.get(constraint.name);
|
|
4330
|
+
if (conflictingRecordId && conflictingRecordId !== this.id) {
|
|
4331
|
+
Logger.verbose(
|
|
4332
|
+
`[${modelName}] Save Check (Item '${this.id}'): index miss for '${constraint.name}', but engine fallback found conflicting index-less record ${conflictingRecordId}.`
|
|
4333
|
+
);
|
|
4334
|
+
throw new UniqueConstraintViolationError(
|
|
4335
|
+
`Unique constraint '${constraint.name}' violated for model '${modelName}' on fields [${constraint.fields.join(
|
|
4336
|
+
", "
|
|
4337
|
+
)}]. Attempted ID: ${this.id}. Value(s) starting with '${newUniqueKey.substring(
|
|
4338
|
+
0,
|
|
4339
|
+
50
|
|
4340
|
+
)}...' already exist for index-less record ID ${conflictingRecordId}.`,
|
|
4341
|
+
modelName,
|
|
4342
|
+
constraint.name,
|
|
4343
|
+
constraint.fields,
|
|
4344
|
+
this.id,
|
|
4345
|
+
conflictingRecordId
|
|
4346
|
+
);
|
|
4347
|
+
}
|
|
4127
4348
|
}
|
|
4128
4349
|
}
|
|
4129
4350
|
if (isUpdate && oldData) {
|
|
@@ -4467,34 +4688,46 @@ var init_BaseModel = __esm({
|
|
|
4467
4688
|
const modelName = schema.options.name;
|
|
4468
4689
|
Logger.verbose(`[${modelName}] Executing query:`, { filter, options });
|
|
4469
4690
|
const hasIncludes = options?.include && options.include.length > 0;
|
|
4470
|
-
const
|
|
4691
|
+
const requestedLimit = options?.limit;
|
|
4692
|
+
const overFetch = typeof requestedLimit === "number" && requestedLimit > 0;
|
|
4693
|
+
let translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
|
|
4694
|
+
if (overFetch) {
|
|
4695
|
+
translatorOptions = {
|
|
4696
|
+
...translatorOptions || {},
|
|
4697
|
+
limit: requestedLimit + 1
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4471
4700
|
const translator = new DocumentQueryTranslator(modelName, schema.fields);
|
|
4472
4701
|
const translatedQuery = translator.translateFind(filter, translatorOptions);
|
|
4473
4702
|
Logger.verbose(`[${modelName}] Translated SQL:`, translatedQuery.sql);
|
|
4474
4703
|
Logger.verbose(`[${modelName}] SQL params:`, translatedQuery.params);
|
|
4475
|
-
|
|
4704
|
+
let results = await _BaseModel.dbInstance.query(
|
|
4476
4705
|
translatedQuery.sql,
|
|
4477
4706
|
translatedQuery.params
|
|
4478
4707
|
);
|
|
4479
4708
|
Logger.verbose(`[${modelName}] Query returned ${results.length} results`);
|
|
4709
|
+
let hasMore = false;
|
|
4710
|
+
if (overFetch && results.length > requestedLimit) {
|
|
4711
|
+
hasMore = true;
|
|
4712
|
+
results = results.slice(0, requestedLimit);
|
|
4713
|
+
}
|
|
4480
4714
|
if (options?.include && options.include.length > 0) {
|
|
4481
4715
|
const { IncludeResolver: IncludeResolver2 } = await Promise.resolve().then(() => (init_IncludeResolver(), IncludeResolver_exports));
|
|
4482
4716
|
const resolver = new IncludeResolver2(_BaseModel.dbInstance);
|
|
4483
4717
|
await resolver.resolve(results, options.include, 0, modelName);
|
|
4484
4718
|
}
|
|
4485
|
-
const requestedLimit = options?.limit;
|
|
4486
|
-
const hasMore = CursorManager.hasMoreResults(
|
|
4487
|
-
requestedLimit,
|
|
4488
|
-
results.length
|
|
4489
|
-
);
|
|
4490
4719
|
const isFirstPage = !options?.uniqueStartKey;
|
|
4720
|
+
const direction = options?.direction || 1;
|
|
4491
4721
|
const cursors = CursorManager.generateResultCursors(
|
|
4492
4722
|
results,
|
|
4493
4723
|
translatedQuery.sortFields,
|
|
4494
|
-
|
|
4724
|
+
direction,
|
|
4495
4725
|
hasMore,
|
|
4496
4726
|
isFirstPage
|
|
4497
4727
|
);
|
|
4728
|
+
if (direction === -1) {
|
|
4729
|
+
results.reverse();
|
|
4730
|
+
}
|
|
4498
4731
|
let transformedData;
|
|
4499
4732
|
if (options?.projection && Object.keys(options.projection).length > 0) {
|
|
4500
4733
|
transformedData = results.map((row) => {
|
|
@@ -5070,7 +5303,7 @@ var init_BaseModel = __esm({
|
|
|
5070
5303
|
);
|
|
5071
5304
|
}
|
|
5072
5305
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
5073
|
-
if (recordYMap instanceof
|
|
5306
|
+
if (recordYMap instanceof Y5.Map) {
|
|
5074
5307
|
const instance = new this({ id: recordId });
|
|
5075
5308
|
instance._metaDocId = docId;
|
|
5076
5309
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -5095,6 +5328,50 @@ var init_BaseModel = __esm({
|
|
|
5095
5328
|
}
|
|
5096
5329
|
return allInstances;
|
|
5097
5330
|
}
|
|
5331
|
+
/**
|
|
5332
|
+
* Resolve a record by its unique-constraint field values via an engine query.
|
|
5333
|
+
* Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
|
|
5334
|
+
* entry for a key.
|
|
5335
|
+
*
|
|
5336
|
+
* The index map is a cache over the SQLite projection, not the source of
|
|
5337
|
+
* truth: every record is loaded into the projection on `initializeForDocument`
|
|
5338
|
+
* regardless of index presence, but a record written by a client predating
|
|
5339
|
+
* index maintenance (legacy data) is missing from the index. Such a record
|
|
5340
|
+
* silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
|
|
5341
|
+
* check. A query over the constraint fields finds it reliably.
|
|
5342
|
+
*
|
|
5343
|
+
* Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
|
|
5344
|
+
* the document that holds it, via `queryOne`) or `null`. Callers decide
|
|
5345
|
+
* whether to heal the missing index entry (write paths) and do their own
|
|
5346
|
+
* logging — this method never mutates the index, so read paths stay
|
|
5347
|
+
* read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
|
|
5348
|
+
* constraint field means the record does not participate in the constraint,
|
|
5349
|
+
* so no query runs.
|
|
5350
|
+
*/
|
|
5351
|
+
static async _resolveByUniqueFallback(constraint, keyValues, options) {
|
|
5352
|
+
if (!_BaseModel.dbInstance) {
|
|
5353
|
+
return null;
|
|
5354
|
+
}
|
|
5355
|
+
const filter = {};
|
|
5356
|
+
for (let i = 0; i < constraint.fields.length; i++) {
|
|
5357
|
+
const value = keyValues[i];
|
|
5358
|
+
if (value === null || value === void 0) {
|
|
5359
|
+
return null;
|
|
5360
|
+
}
|
|
5361
|
+
filter[constraint.fields[i]] = value;
|
|
5362
|
+
}
|
|
5363
|
+
if (options?.excludeId) {
|
|
5364
|
+
if ("id" in filter) {
|
|
5365
|
+
if (filter.id === options.excludeId) {
|
|
5366
|
+
return null;
|
|
5367
|
+
}
|
|
5368
|
+
} else {
|
|
5369
|
+
filter.id = { $ne: options.excludeId };
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
const found = await this.queryOne(filter);
|
|
5373
|
+
return found ?? null;
|
|
5374
|
+
}
|
|
5098
5375
|
static async findByUnique(constraintName, value) {
|
|
5099
5376
|
const modelConstructor = this;
|
|
5100
5377
|
const schema = modelConstructor.getSchema();
|
|
@@ -5133,7 +5410,10 @@ var init_BaseModel = __esm({
|
|
|
5133
5410
|
const documentInfo = modelConstructor.connectedDocuments.get(docId);
|
|
5134
5411
|
if (!documentInfo) continue;
|
|
5135
5412
|
const yDoc = documentInfo.yDoc;
|
|
5136
|
-
const constraintMapName2 =
|
|
5413
|
+
const constraintMapName2 = uniqueIndexMapName(
|
|
5414
|
+
currentModelName,
|
|
5415
|
+
constraint.name
|
|
5416
|
+
);
|
|
5137
5417
|
const constraintMap2 = yDoc.getMap(constraintMapName2);
|
|
5138
5418
|
const recordId2 = constraintMap2.get(uniqueKeyString);
|
|
5139
5419
|
if (recordId2) {
|
|
@@ -5141,7 +5421,7 @@ var init_BaseModel = __esm({
|
|
|
5141
5421
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
5142
5422
|
if (documentYMap) {
|
|
5143
5423
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
5144
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
5424
|
+
if (recordYMap2 && recordYMap2 instanceof Y5.Map) {
|
|
5145
5425
|
const instance = new modelConstructor({
|
|
5146
5426
|
id: recordId2
|
|
5147
5427
|
});
|
|
@@ -5155,6 +5435,16 @@ var init_BaseModel = __esm({
|
|
|
5155
5435
|
}
|
|
5156
5436
|
}
|
|
5157
5437
|
}
|
|
5438
|
+
const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
|
|
5439
|
+
constraint,
|
|
5440
|
+
keyValues
|
|
5441
|
+
);
|
|
5442
|
+
if (fallbackRecord) {
|
|
5443
|
+
Logger.verbose(
|
|
5444
|
+
`[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
|
|
5445
|
+
);
|
|
5446
|
+
return fallbackRecord;
|
|
5447
|
+
}
|
|
5158
5448
|
Logger.verbose(
|
|
5159
5449
|
`[${currentModelName}.findByUnique] Record not found in any connected document`
|
|
5160
5450
|
);
|
|
@@ -5168,17 +5458,30 @@ var init_BaseModel = __esm({
|
|
|
5168
5458
|
`[${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.`
|
|
5169
5459
|
);
|
|
5170
5460
|
}
|
|
5171
|
-
const constraintMapName =
|
|
5461
|
+
const constraintMapName = uniqueIndexMapName(
|
|
5462
|
+
currentModelName,
|
|
5463
|
+
constraint.name
|
|
5464
|
+
);
|
|
5172
5465
|
const constraintMap = legacyDocInfo.yDoc.getMap(constraintMapName);
|
|
5173
5466
|
const recordId = constraintMap.get(uniqueKeyString);
|
|
5174
5467
|
if (!recordId) {
|
|
5468
|
+
const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
|
|
5469
|
+
constraint,
|
|
5470
|
+
keyValues
|
|
5471
|
+
);
|
|
5472
|
+
if (fallbackRecord) {
|
|
5473
|
+
Logger.verbose(
|
|
5474
|
+
`[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
|
|
5475
|
+
);
|
|
5476
|
+
return fallbackRecord;
|
|
5477
|
+
}
|
|
5175
5478
|
return null;
|
|
5176
5479
|
}
|
|
5177
5480
|
const recordYMap = legacyDocumentMap.get(recordId);
|
|
5178
5481
|
if (!recordYMap) {
|
|
5179
5482
|
return null;
|
|
5180
5483
|
}
|
|
5181
|
-
if (recordYMap instanceof
|
|
5484
|
+
if (recordYMap instanceof Y5.Map) {
|
|
5182
5485
|
const instance = new modelConstructor({
|
|
5183
5486
|
id: recordId
|
|
5184
5487
|
});
|
|
@@ -5236,7 +5539,10 @@ var init_BaseModel = __esm({
|
|
|
5236
5539
|
`[${currentModelName}] upsertByUnique for '${constraintName}': unique key cannot be built due to null/undefined values. Upsert cannot proceed reliably.`
|
|
5237
5540
|
);
|
|
5238
5541
|
}
|
|
5239
|
-
const constraintMapName =
|
|
5542
|
+
const constraintMapName = uniqueIndexMapName(
|
|
5543
|
+
currentModelName,
|
|
5544
|
+
constraint.name
|
|
5545
|
+
);
|
|
5240
5546
|
let existingRecord = null;
|
|
5241
5547
|
if (connectedDocuments && connectedDocuments.size > 0) {
|
|
5242
5548
|
for (const docId of connectedDocuments.keys()) {
|
|
@@ -5254,6 +5560,33 @@ var init_BaseModel = __esm({
|
|
|
5254
5560
|
}
|
|
5255
5561
|
} else {
|
|
5256
5562
|
}
|
|
5563
|
+
if (!existingRecord) {
|
|
5564
|
+
const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
|
|
5565
|
+
constraint,
|
|
5566
|
+
keyValuesForLookup
|
|
5567
|
+
);
|
|
5568
|
+
if (fallbackRecord) {
|
|
5569
|
+
existingRecord = fallbackRecord;
|
|
5570
|
+
const healDocId = fallbackRecord._metaDocId;
|
|
5571
|
+
const healDocInfo = healDocId ? connectedDocuments.get(healDocId) : void 0;
|
|
5572
|
+
const canHeal = !!healDocInfo && healDocInfo.permissionHint !== "read";
|
|
5573
|
+
if (healDocInfo && canHeal) {
|
|
5574
|
+
healDocInfo.yDoc.transact(() => {
|
|
5575
|
+
const healMap = healDocInfo.yDoc.getMap(constraintMapName);
|
|
5576
|
+
if (healMap.get(uniqueKeyString) !== fallbackRecord.id) {
|
|
5577
|
+
healMap.set(uniqueKeyString, fallbackRecord.id);
|
|
5578
|
+
}
|
|
5579
|
+
}, `heal-unique-index-${currentModelName}-${healDocId}`);
|
|
5580
|
+
Logger.verbose(
|
|
5581
|
+
`[${currentModelName}] upsertByUnique: index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id}; healed missing index entry (legacy index-less record).`
|
|
5582
|
+
);
|
|
5583
|
+
} else {
|
|
5584
|
+
Logger.verbose(
|
|
5585
|
+
`[${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).`
|
|
5586
|
+
);
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5257
5590
|
if (options?.objectMustExist) {
|
|
5258
5591
|
if (!existingRecord) {
|
|
5259
5592
|
throw new RecordNotFoundError(
|
|
@@ -5410,7 +5743,7 @@ var init_BaseModel = __esm({
|
|
|
5410
5743
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5411
5744
|
if (fieldOptions?.type !== "stringset") continue;
|
|
5412
5745
|
const value = recordYMap.get(fieldKey);
|
|
5413
|
-
if (value instanceof
|
|
5746
|
+
if (value instanceof Y5.Map) {
|
|
5414
5747
|
this.observeStringSetMapOnce(value);
|
|
5415
5748
|
}
|
|
5416
5749
|
}
|
|
@@ -5457,7 +5790,14 @@ var init_BaseModel = __esm({
|
|
|
5457
5790
|
continue;
|
|
5458
5791
|
}
|
|
5459
5792
|
if (value !== void 0) {
|
|
5460
|
-
|
|
5793
|
+
if (isYjsComposite(value)) {
|
|
5794
|
+
Logger.warn(
|
|
5795
|
+
`[${modelName}] Field '${key}' on record ${recordId} holds a composite Yjs value (${value?.constructor?.name}) but its declared type is '${fieldOptions.type}'. Normalizing to plain JS before persisting to SQLite.`
|
|
5796
|
+
);
|
|
5797
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5798
|
+
} else {
|
|
5799
|
+
updatedData[key] = value;
|
|
5800
|
+
}
|
|
5461
5801
|
}
|
|
5462
5802
|
}
|
|
5463
5803
|
if (unknownFields.length > 0) {
|
|
@@ -5537,7 +5877,14 @@ var init_BaseModel = __esm({
|
|
|
5537
5877
|
continue;
|
|
5538
5878
|
}
|
|
5539
5879
|
if (value !== void 0) {
|
|
5540
|
-
|
|
5880
|
+
if (isYjsComposite(value)) {
|
|
5881
|
+
Logger.warn(
|
|
5882
|
+
`[${modelName}] Field '${key}' on record ${recordId} in document ${docId} holds a composite Yjs value (${value?.constructor?.name}) but its declared type is '${fieldOptions.type}'. Normalizing to plain JS before persisting to SQLite.`
|
|
5883
|
+
);
|
|
5884
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5885
|
+
} else {
|
|
5886
|
+
updatedData[key] = value;
|
|
5887
|
+
}
|
|
5541
5888
|
}
|
|
5542
5889
|
}
|
|
5543
5890
|
if (unknownFields.length > 0) {
|
|
@@ -5628,7 +5975,143 @@ init_BaseModel();
|
|
|
5628
5975
|
init_sql();
|
|
5629
5976
|
import initSqlJs from "sql.js";
|
|
5630
5977
|
import { Mutex } from "async-mutex";
|
|
5978
|
+
|
|
5979
|
+
// src/utils/environment.ts
|
|
5980
|
+
function detectEnvironment() {
|
|
5981
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
5982
|
+
return "node";
|
|
5983
|
+
}
|
|
5984
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
5985
|
+
return "browser";
|
|
5986
|
+
}
|
|
5987
|
+
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
5988
|
+
return "browser";
|
|
5989
|
+
}
|
|
5990
|
+
return "unknown";
|
|
5991
|
+
}
|
|
5992
|
+
function isNode() {
|
|
5993
|
+
return detectEnvironment() === "node";
|
|
5994
|
+
}
|
|
5995
|
+
function isBrowser() {
|
|
5996
|
+
return detectEnvironment() === "browser";
|
|
5997
|
+
}
|
|
5998
|
+
async function isNodeModuleAvailable(moduleName) {
|
|
5999
|
+
if (!isNode()) {
|
|
6000
|
+
return false;
|
|
6001
|
+
}
|
|
6002
|
+
try {
|
|
6003
|
+
await import(
|
|
6004
|
+
/* @vite-ignore */
|
|
6005
|
+
moduleName
|
|
6006
|
+
);
|
|
6007
|
+
return true;
|
|
6008
|
+
} catch {
|
|
6009
|
+
return false;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
var features = {
|
|
6013
|
+
get hasWebWorkers() {
|
|
6014
|
+
return isBrowser() && typeof Worker !== "undefined";
|
|
6015
|
+
},
|
|
6016
|
+
get hasFileSystem() {
|
|
6017
|
+
return isNode();
|
|
6018
|
+
},
|
|
6019
|
+
get hasWebAssembly() {
|
|
6020
|
+
return typeof WebAssembly !== "undefined";
|
|
6021
|
+
},
|
|
6022
|
+
async hasBetterSqlite3() {
|
|
6023
|
+
return await isNodeModuleAvailable("better-sqlite3");
|
|
6024
|
+
}
|
|
6025
|
+
};
|
|
6026
|
+
|
|
6027
|
+
// src/engines/SqljsEngine.ts
|
|
5631
6028
|
var SQL_WASM_URL = "/sql-wasm.wasm";
|
|
6029
|
+
var SQL_WASM_FILENAME = "sql-wasm.wasm";
|
|
6030
|
+
async function resolveNodeWasmPath() {
|
|
6031
|
+
if (!isNode()) {
|
|
6032
|
+
return null;
|
|
6033
|
+
}
|
|
6034
|
+
try {
|
|
6035
|
+
const cjsRequire = typeof __require !== "undefined" ? __require : void 0;
|
|
6036
|
+
if (cjsRequire) {
|
|
6037
|
+
return cjsRequire.resolve("sql.js/dist/sql-wasm.wasm");
|
|
6038
|
+
}
|
|
6039
|
+
} catch {
|
|
6040
|
+
}
|
|
6041
|
+
try {
|
|
6042
|
+
const metaUrl = import.meta.url;
|
|
6043
|
+
if (metaUrl) {
|
|
6044
|
+
const { createRequire } = await import(
|
|
6045
|
+
/* @vite-ignore */
|
|
6046
|
+
"module"
|
|
6047
|
+
);
|
|
6048
|
+
const req = createRequire(metaUrl);
|
|
6049
|
+
return req.resolve("sql.js/dist/sql-wasm.wasm");
|
|
6050
|
+
}
|
|
6051
|
+
} catch {
|
|
6052
|
+
}
|
|
6053
|
+
return null;
|
|
6054
|
+
}
|
|
6055
|
+
async function preflightNodeWasm(wasmPath) {
|
|
6056
|
+
if (!isNode()) {
|
|
6057
|
+
return;
|
|
6058
|
+
}
|
|
6059
|
+
if (/^[a-z]+:\/\//i.test(wasmPath)) {
|
|
6060
|
+
return;
|
|
6061
|
+
}
|
|
6062
|
+
const { existsSync } = await import(
|
|
6063
|
+
/* @vite-ignore */
|
|
6064
|
+
"fs"
|
|
6065
|
+
);
|
|
6066
|
+
if (!existsSync(wasmPath)) {
|
|
6067
|
+
throw new Error(
|
|
6068
|
+
`[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()).`
|
|
6069
|
+
);
|
|
6070
|
+
}
|
|
6071
|
+
}
|
|
6072
|
+
async function loadSqlJsModule(locateFile) {
|
|
6073
|
+
let abortReject;
|
|
6074
|
+
const toError = (reason) => reason instanceof Error ? reason : new Error(
|
|
6075
|
+
`[SqljsEngine] sql.js aborted during initialization: ${String(
|
|
6076
|
+
reason
|
|
6077
|
+
)}`
|
|
6078
|
+
);
|
|
6079
|
+
const nodeProcess = isNode() && typeof process !== "undefined" ? process : void 0;
|
|
6080
|
+
const onUnhandled = (reason) => {
|
|
6081
|
+
const err = toError(reason);
|
|
6082
|
+
if (/Aborted/.test(err.message) || /Aborted/.test(String(reason))) {
|
|
6083
|
+
abortReject?.(err);
|
|
6084
|
+
return;
|
|
6085
|
+
}
|
|
6086
|
+
if (nodeProcess && nodeProcess.listenerCount("unhandledRejection") === 1) {
|
|
6087
|
+
nodeProcess.removeListener("unhandledRejection", onUnhandled);
|
|
6088
|
+
void Promise.reject(reason);
|
|
6089
|
+
}
|
|
6090
|
+
};
|
|
6091
|
+
if (nodeProcess) {
|
|
6092
|
+
nodeProcess.on("unhandledRejection", onUnhandled);
|
|
6093
|
+
}
|
|
6094
|
+
const abortGuard = new Promise((_resolve, reject) => {
|
|
6095
|
+
abortReject = reject;
|
|
6096
|
+
});
|
|
6097
|
+
abortGuard.catch(() => {
|
|
6098
|
+
});
|
|
6099
|
+
try {
|
|
6100
|
+
const initPromise = initSqlJs({
|
|
6101
|
+
locateFile,
|
|
6102
|
+
// Layer 1: emscripten onAbort.
|
|
6103
|
+
onAbort: (reason) => abortReject?.(toError(reason))
|
|
6104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6105
|
+
});
|
|
6106
|
+
return await Promise.race([initPromise, abortGuard]);
|
|
6107
|
+
} finally {
|
|
6108
|
+
if (nodeProcess) {
|
|
6109
|
+
setTimeout(() => {
|
|
6110
|
+
nodeProcess.removeListener("unhandledRejection", onUnhandled);
|
|
6111
|
+
}, 0);
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
5632
6115
|
var TransactionalSQLJSOperations = class {
|
|
5633
6116
|
db;
|
|
5634
6117
|
engine;
|
|
@@ -5694,22 +6177,37 @@ var SqljsEngine = class _SqljsEngine {
|
|
|
5694
6177
|
}
|
|
5695
6178
|
async initialize(engineOptions) {
|
|
5696
6179
|
Logger.info("[SqljsEngine] Initializing SQL.js engine...");
|
|
6180
|
+
const loadingModuleThisCall = !_SqljsEngine.SQL;
|
|
5697
6181
|
try {
|
|
5698
6182
|
if (!_SqljsEngine.SQL) {
|
|
5699
|
-
|
|
6183
|
+
let locateFileConfig;
|
|
6184
|
+
if (engineOptions?.locateFile) {
|
|
6185
|
+
locateFileConfig = engineOptions.locateFile;
|
|
6186
|
+
} else if (engineOptions?.wasmURL) {
|
|
6187
|
+
const url = engineOptions.wasmURL;
|
|
6188
|
+
locateFileConfig = (_file) => url;
|
|
6189
|
+
} else {
|
|
6190
|
+
const nodeWasmPath = await resolveNodeWasmPath();
|
|
6191
|
+
const resolved = nodeWasmPath ?? SQL_WASM_URL;
|
|
6192
|
+
locateFileConfig = (_file) => resolved;
|
|
6193
|
+
}
|
|
6194
|
+
const wasmPath = locateFileConfig(SQL_WASM_FILENAME);
|
|
5700
6195
|
Logger.debug(
|
|
5701
6196
|
"[SqljsEngine] Attempting to load SQL.js WASM from:",
|
|
5702
|
-
|
|
6197
|
+
wasmPath
|
|
5703
6198
|
);
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
6199
|
+
await preflightNodeWasm(wasmPath);
|
|
6200
|
+
const SQL = await loadSqlJsModule(locateFileConfig);
|
|
6201
|
+
_SqljsEngine.SQL = SQL;
|
|
5707
6202
|
Logger.info("[SqljsEngine] SQL.js WASM loaded successfully.");
|
|
5708
6203
|
}
|
|
5709
6204
|
this.db = new _SqljsEngine.SQL.Database();
|
|
5710
6205
|
Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
|
|
5711
6206
|
this.updateTableNames();
|
|
5712
6207
|
} catch (error) {
|
|
6208
|
+
if (loadingModuleThisCall) {
|
|
6209
|
+
_SqljsEngine.SQL = null;
|
|
6210
|
+
}
|
|
5713
6211
|
Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
|
|
5714
6212
|
throw error;
|
|
5715
6213
|
}
|
|
@@ -5995,54 +6493,6 @@ var SqljsEngine = class _SqljsEngine {
|
|
|
5995
6493
|
}
|
|
5996
6494
|
};
|
|
5997
6495
|
|
|
5998
|
-
// src/utils/environment.ts
|
|
5999
|
-
function detectEnvironment() {
|
|
6000
|
-
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
6001
|
-
return "node";
|
|
6002
|
-
}
|
|
6003
|
-
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
6004
|
-
return "browser";
|
|
6005
|
-
}
|
|
6006
|
-
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
6007
|
-
return "browser";
|
|
6008
|
-
}
|
|
6009
|
-
return "unknown";
|
|
6010
|
-
}
|
|
6011
|
-
function isNode() {
|
|
6012
|
-
return detectEnvironment() === "node";
|
|
6013
|
-
}
|
|
6014
|
-
function isBrowser() {
|
|
6015
|
-
return detectEnvironment() === "browser";
|
|
6016
|
-
}
|
|
6017
|
-
async function isNodeModuleAvailable(moduleName) {
|
|
6018
|
-
if (!isNode()) {
|
|
6019
|
-
return false;
|
|
6020
|
-
}
|
|
6021
|
-
try {
|
|
6022
|
-
await import(
|
|
6023
|
-
/* @vite-ignore */
|
|
6024
|
-
moduleName
|
|
6025
|
-
);
|
|
6026
|
-
return true;
|
|
6027
|
-
} catch {
|
|
6028
|
-
return false;
|
|
6029
|
-
}
|
|
6030
|
-
}
|
|
6031
|
-
var features = {
|
|
6032
|
-
get hasWebWorkers() {
|
|
6033
|
-
return isBrowser() && typeof Worker !== "undefined";
|
|
6034
|
-
},
|
|
6035
|
-
get hasFileSystem() {
|
|
6036
|
-
return isNode();
|
|
6037
|
-
},
|
|
6038
|
-
get hasWebAssembly() {
|
|
6039
|
-
return typeof WebAssembly !== "undefined";
|
|
6040
|
-
},
|
|
6041
|
-
async hasBetterSqlite3() {
|
|
6042
|
-
return await isNodeModuleAvailable("better-sqlite3");
|
|
6043
|
-
}
|
|
6044
|
-
};
|
|
6045
|
-
|
|
6046
6496
|
// src/engines/BrowserDatabaseFactory.ts
|
|
6047
6497
|
init_BaseModel();
|
|
6048
6498
|
var BrowserDatabaseFactory = class {
|
|
@@ -6353,6 +6803,7 @@ init_StringSet();
|
|
|
6353
6803
|
// src/models/schema.ts
|
|
6354
6804
|
init_BaseModel();
|
|
6355
6805
|
init_ModelRegistry();
|
|
6806
|
+
init_uniqueIndex();
|
|
6356
6807
|
function defineModelSchema(input) {
|
|
6357
6808
|
const { name, fields } = input;
|
|
6358
6809
|
const options = {
|
|
@@ -6425,7 +6876,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
6425
6876
|
for (const [fieldName, options] of fields.entries()) {
|
|
6426
6877
|
if (options.unique) {
|
|
6427
6878
|
resolved.push({
|
|
6428
|
-
name:
|
|
6879
|
+
name: fieldUniqueConstraintName(modelName, fieldName),
|
|
6429
6880
|
fields: [fieldName]
|
|
6430
6881
|
});
|
|
6431
6882
|
}
|
|
@@ -6489,7 +6940,8 @@ function attachAndRegisterModel(modelClass, schema) {
|
|
|
6489
6940
|
}
|
|
6490
6941
|
|
|
6491
6942
|
// src/utils/yDocSchema.ts
|
|
6492
|
-
|
|
6943
|
+
init_yjsValues();
|
|
6944
|
+
import * as Y6 from "yjs";
|
|
6493
6945
|
function discoverSchema(yDoc) {
|
|
6494
6946
|
const models = {};
|
|
6495
6947
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -6523,7 +6975,7 @@ function discoverModelNames(yDoc) {
|
|
|
6523
6975
|
function materializeMap(yDoc, key) {
|
|
6524
6976
|
try {
|
|
6525
6977
|
const map = yDoc.getMap(key);
|
|
6526
|
-
return map instanceof
|
|
6978
|
+
return map instanceof Y6.Map ? map : null;
|
|
6527
6979
|
} catch {
|
|
6528
6980
|
return null;
|
|
6529
6981
|
}
|
|
@@ -6533,11 +6985,11 @@ function readModelMeta(metaMap) {
|
|
|
6533
6985
|
let constraints;
|
|
6534
6986
|
let relationships;
|
|
6535
6987
|
for (const [key, value] of metaMap.entries()) {
|
|
6536
|
-
if (key === "_constraints" && value instanceof
|
|
6988
|
+
if (key === "_constraints" && value instanceof Y6.Map) {
|
|
6537
6989
|
constraints = readConstraints(value);
|
|
6538
|
-
} else if (key === "_relationships" && value instanceof
|
|
6990
|
+
} else if (key === "_relationships" && value instanceof Y6.Map) {
|
|
6539
6991
|
relationships = readRelationships(value);
|
|
6540
|
-
} else if (value instanceof
|
|
6992
|
+
} else if (value instanceof Y6.Map) {
|
|
6541
6993
|
fields[key] = readFieldMeta(value);
|
|
6542
6994
|
}
|
|
6543
6995
|
}
|
|
@@ -6556,6 +7008,10 @@ function readFieldMeta(fieldMap) {
|
|
|
6556
7008
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
6557
7009
|
if (fieldMap.get("required") === true) field.required = true;
|
|
6558
7010
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7011
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7012
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7013
|
+
field.autoStamp = autoStamp;
|
|
7014
|
+
}
|
|
6559
7015
|
const def = fieldMap.get("default");
|
|
6560
7016
|
if (def !== void 0) field.default = def;
|
|
6561
7017
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -6568,7 +7024,7 @@ function inferModelFromData(dataMap) {
|
|
|
6568
7024
|
const fields = {};
|
|
6569
7025
|
let sampled = 0;
|
|
6570
7026
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
6571
|
-
if (!(recordValue instanceof
|
|
7027
|
+
if (!(recordValue instanceof Y6.Map)) continue;
|
|
6572
7028
|
if (++sampled > 5) break;
|
|
6573
7029
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
6574
7030
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -6581,7 +7037,7 @@ function inferModelFromData(dataMap) {
|
|
|
6581
7037
|
return { fields };
|
|
6582
7038
|
}
|
|
6583
7039
|
function inferTypeFromValue(value) {
|
|
6584
|
-
if (value
|
|
7040
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
6585
7041
|
switch (typeof value) {
|
|
6586
7042
|
case "string":
|
|
6587
7043
|
return "string";
|
|
@@ -6596,7 +7052,7 @@ function inferTypeFromValue(value) {
|
|
|
6596
7052
|
function readConstraints(constraintsMap) {
|
|
6597
7053
|
const out = {};
|
|
6598
7054
|
for (const [name, value] of constraintsMap.entries()) {
|
|
6599
|
-
if (!(value instanceof
|
|
7055
|
+
if (!(value instanceof Y6.Map)) continue;
|
|
6600
7056
|
let fields = [];
|
|
6601
7057
|
const rawFields = value.get("fields");
|
|
6602
7058
|
if (typeof rawFields === "string") {
|
|
@@ -6615,7 +7071,7 @@ function readConstraints(constraintsMap) {
|
|
|
6615
7071
|
function readRelationships(relsMap) {
|
|
6616
7072
|
const out = {};
|
|
6617
7073
|
for (const [name, value] of relsMap.entries()) {
|
|
6618
|
-
if (!(value instanceof
|
|
7074
|
+
if (!(value instanceof Y6.Map)) continue;
|
|
6619
7075
|
const rel = {};
|
|
6620
7076
|
for (const [k, v] of value.entries()) {
|
|
6621
7077
|
rel[k] = v;
|
|
@@ -6626,6 +7082,7 @@ function readRelationships(relsMap) {
|
|
|
6626
7082
|
}
|
|
6627
7083
|
var CAMEL_TO_SNAKE = {
|
|
6628
7084
|
autoAssign: "auto_assign",
|
|
7085
|
+
autoStamp: "auto_stamp",
|
|
6629
7086
|
maxLength: "max_length",
|
|
6630
7087
|
maxCount: "max_count",
|
|
6631
7088
|
relatedIdField: "related_id_field",
|
|
@@ -6656,6 +7113,7 @@ function schemaToToml(schema) {
|
|
|
6656
7113
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
6657
7114
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
6658
7115
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7116
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
6659
7117
|
if (field.indexed) lines.push("indexed = true");
|
|
6660
7118
|
if (field.unique) lines.push("unique = true");
|
|
6661
7119
|
if (field.required) lines.push("required = true");
|
|
@@ -6702,10 +7160,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
6702
7160
|
"unique",
|
|
6703
7161
|
"required",
|
|
6704
7162
|
"auto_assign",
|
|
7163
|
+
"auto_stamp",
|
|
6705
7164
|
"max_length",
|
|
6706
7165
|
"max_count",
|
|
6707
|
-
"default"
|
|
7166
|
+
"default",
|
|
7167
|
+
"enum"
|
|
6708
7168
|
]);
|
|
7169
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
6709
7170
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
6710
7171
|
"fields",
|
|
6711
7172
|
"relationships",
|
|
@@ -6747,9 +7208,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
6747
7208
|
if (raw.unique === true) opts.unique = true;
|
|
6748
7209
|
if (raw.required === true) opts.required = true;
|
|
6749
7210
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
7211
|
+
if (raw.auto_stamp !== void 0) {
|
|
7212
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
7213
|
+
throw new Error(
|
|
7214
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
7215
|
+
...VALID_AUTO_STAMP_VALUES
|
|
7216
|
+
].join(", ")}`
|
|
7217
|
+
);
|
|
7218
|
+
}
|
|
7219
|
+
opts.autoStamp = raw.auto_stamp;
|
|
7220
|
+
}
|
|
6750
7221
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
6751
7222
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
6752
7223
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
7224
|
+
if (raw.enum !== void 0) {
|
|
7225
|
+
if (raw.type !== "string") {
|
|
7226
|
+
throw new Error(
|
|
7227
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
7228
|
+
);
|
|
7229
|
+
}
|
|
7230
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
7231
|
+
throw new Error(
|
|
7232
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
7233
|
+
);
|
|
7234
|
+
}
|
|
7235
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
7236
|
+
throw new Error(
|
|
7237
|
+
`${context}: \`enum\` values must all be strings.`
|
|
7238
|
+
);
|
|
7239
|
+
}
|
|
7240
|
+
opts.enum = [...raw.enum];
|
|
7241
|
+
}
|
|
6753
7242
|
return opts;
|
|
6754
7243
|
}
|
|
6755
7244
|
function requireField(raw, field, context) {
|