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/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
|
-
|
|
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")}
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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(
|
|
@@ -1183,9 +1208,244 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
1183
1208
|
}
|
|
1184
1209
|
});
|
|
1185
1210
|
|
|
1211
|
+
// src/utils/yjsValues.ts
|
|
1212
|
+
function isYjsComposite(value) {
|
|
1213
|
+
return value instanceof Y.Map || value instanceof Y.Array || value instanceof Y.Text;
|
|
1214
|
+
}
|
|
1215
|
+
function isStringSetYMap(value) {
|
|
1216
|
+
if (!(value instanceof Y.Map)) return false;
|
|
1217
|
+
for (const v of value.values()) {
|
|
1218
|
+
if (v !== true) return false;
|
|
1219
|
+
}
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
function normalizeYjsValue(value) {
|
|
1223
|
+
if (value instanceof Y.Text) {
|
|
1224
|
+
return value.toString();
|
|
1225
|
+
}
|
|
1226
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1227
|
+
return value.toJSON();
|
|
1228
|
+
}
|
|
1229
|
+
return value;
|
|
1230
|
+
}
|
|
1231
|
+
function normalizeYjsValueForStorage(value) {
|
|
1232
|
+
if (value instanceof Y.Text) {
|
|
1233
|
+
return value.toString();
|
|
1234
|
+
}
|
|
1235
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1236
|
+
return JSON.stringify(value.toJSON());
|
|
1237
|
+
}
|
|
1238
|
+
return value;
|
|
1239
|
+
}
|
|
1240
|
+
var Y;
|
|
1241
|
+
var init_yjsValues = __esm({
|
|
1242
|
+
"src/utils/yjsValues.ts"() {
|
|
1243
|
+
"use strict";
|
|
1244
|
+
Y = __toESM(require("yjs"), 1);
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
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
|
+
|
|
1186
1446
|
// src/models/metaSync.ts
|
|
1187
1447
|
function inferFieldType(value) {
|
|
1188
|
-
if (value
|
|
1448
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
1189
1449
|
switch (typeof value) {
|
|
1190
1450
|
case "string":
|
|
1191
1451
|
return "string";
|
|
@@ -1204,7 +1464,10 @@ function encodeDefault(value) {
|
|
|
1204
1464
|
if (value === void 0 || value === null) return void 0;
|
|
1205
1465
|
if (typeof value === "function") {
|
|
1206
1466
|
const name = KNOWN_FUNCTION_DEFAULTS.get(value);
|
|
1207
|
-
return name ? `$${name}` :
|
|
1467
|
+
return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
|
|
1468
|
+
}
|
|
1469
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
1470
|
+
return `$${value}`;
|
|
1208
1471
|
}
|
|
1209
1472
|
return value;
|
|
1210
1473
|
}
|
|
@@ -1230,7 +1493,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1230
1493
|
if (compoundConstraints.length > 0) {
|
|
1231
1494
|
let constraints = meta.get("_constraints");
|
|
1232
1495
|
if (!constraints) {
|
|
1233
|
-
constraints = new
|
|
1496
|
+
constraints = new Y3.Map();
|
|
1234
1497
|
meta.set("_constraints", constraints);
|
|
1235
1498
|
}
|
|
1236
1499
|
for (const constraint of compoundConstraints) {
|
|
@@ -1241,7 +1504,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1241
1504
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
1242
1505
|
let rels = meta.get("_relationships");
|
|
1243
1506
|
if (!rels) {
|
|
1244
|
-
rels = new
|
|
1507
|
+
rels = new Y3.Map();
|
|
1245
1508
|
meta.set("_relationships", rels);
|
|
1246
1509
|
}
|
|
1247
1510
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -1253,7 +1516,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1253
1516
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
1254
1517
|
let fieldMeta = metaMap.get(fieldName);
|
|
1255
1518
|
if (!fieldMeta) {
|
|
1256
|
-
fieldMeta = new
|
|
1519
|
+
fieldMeta = new Y3.Map();
|
|
1257
1520
|
metaMap.set(fieldName, fieldMeta);
|
|
1258
1521
|
}
|
|
1259
1522
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -1261,6 +1524,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1261
1524
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
1262
1525
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
1263
1526
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
1527
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
1264
1528
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
1265
1529
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
1266
1530
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -1269,7 +1533,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1269
1533
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
1270
1534
|
let cMeta = constraintsMap.get(constraint.name);
|
|
1271
1535
|
if (!cMeta) {
|
|
1272
|
-
cMeta = new
|
|
1536
|
+
cMeta = new Y3.Map();
|
|
1273
1537
|
constraintsMap.set(constraint.name, cMeta);
|
|
1274
1538
|
}
|
|
1275
1539
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -1279,7 +1543,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
1279
1543
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
1280
1544
|
let relMeta = relsMap.get(relName);
|
|
1281
1545
|
if (!relMeta) {
|
|
1282
|
-
relMeta = new
|
|
1546
|
+
relMeta = new Y3.Map();
|
|
1283
1547
|
relsMap.set(relName, relMeta);
|
|
1284
1548
|
}
|
|
1285
1549
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -1294,7 +1558,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
1294
1558
|
if (fieldName.startsWith("_")) continue;
|
|
1295
1559
|
let fieldMeta = meta.get(fieldName);
|
|
1296
1560
|
if (!fieldMeta) {
|
|
1297
|
-
fieldMeta = new
|
|
1561
|
+
fieldMeta = new Y3.Map();
|
|
1298
1562
|
meta.set(fieldName, fieldMeta);
|
|
1299
1563
|
}
|
|
1300
1564
|
if (!fieldMeta.has("type")) {
|
|
@@ -1310,17 +1574,58 @@ function setIfChanged(map, key, value) {
|
|
|
1310
1574
|
map.set(key, value);
|
|
1311
1575
|
}
|
|
1312
1576
|
}
|
|
1313
|
-
var
|
|
1577
|
+
var Y3, KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
1314
1578
|
var init_metaSync = __esm({
|
|
1315
1579
|
"src/models/metaSync.ts"() {
|
|
1316
1580
|
"use strict";
|
|
1317
|
-
|
|
1581
|
+
Y3 = __toESM(require("yjs"), 1);
|
|
1582
|
+
init_yjsValues();
|
|
1583
|
+
init_writeLifecycle();
|
|
1318
1584
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
1319
1585
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
1320
1586
|
}
|
|
1321
1587
|
});
|
|
1322
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
|
+
|
|
1323
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
|
+
}
|
|
1324
1629
|
function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
1325
1630
|
return async function() {
|
|
1326
1631
|
const relatedId = this[config.relatedIdField];
|
|
@@ -1329,6 +1634,16 @@ function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
|
1329
1634
|
};
|
|
1330
1635
|
}
|
|
1331
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
|
+
);
|
|
1332
1647
|
return async function(paginationArgs) {
|
|
1333
1648
|
const {
|
|
1334
1649
|
limit,
|
|
@@ -1336,71 +1651,39 @@ function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbE
|
|
|
1336
1651
|
beforeCursor,
|
|
1337
1652
|
direction = "forward"
|
|
1338
1653
|
} = paginationArgs || {};
|
|
1339
|
-
const {
|
|
1340
|
-
relatedIdField,
|
|
1341
|
-
orderByField = "id",
|
|
1342
|
-
orderDirection = "ASC"
|
|
1343
|
-
} = config;
|
|
1344
1654
|
const targetSchema = targetModelClass.getSchema();
|
|
1345
1655
|
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
1346
1656
|
Logger.error(
|
|
1347
1657
|
`[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
|
|
1348
1658
|
);
|
|
1349
|
-
return { data: [],
|
|
1659
|
+
return { data: [], hasMore: false };
|
|
1350
1660
|
}
|
|
1351
|
-
const
|
|
1661
|
+
const forward = direction !== "backward";
|
|
1662
|
+
const cursor = forward ? afterCursor : beforeCursor;
|
|
1352
1663
|
const queryOptions = {
|
|
1353
|
-
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
|
|
1664
|
+
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 },
|
|
1665
|
+
direction: forward ? 1 : -1
|
|
1354
1666
|
};
|
|
1355
|
-
if (limit !== void 0)
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
};
|
|
1362
|
-
} else if (direction === "backward") {
|
|
1363
|
-
if (beforeCursor) {
|
|
1364
|
-
filter[orderByField] = {
|
|
1365
|
-
[orderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
queryOptions.sort = { [orderByField]: orderDirection === "ASC" ? -1 : 1 };
|
|
1369
|
-
}
|
|
1370
|
-
const result = await targetModelClass.query(filter, queryOptions);
|
|
1371
|
-
const results = result.data;
|
|
1372
|
-
let nextCursor = null;
|
|
1373
|
-
let prevCursor = null;
|
|
1374
|
-
const isFirstPage = direction === "forward" && !afterCursor;
|
|
1375
|
-
if (limit !== void 0) {
|
|
1376
|
-
if (direction === "forward") {
|
|
1377
|
-
if (results.length > limit) {
|
|
1378
|
-
nextCursor = results[limit - 1][orderByField];
|
|
1379
|
-
results.pop();
|
|
1380
|
-
}
|
|
1381
|
-
if (results.length > 0 && !isFirstPage) {
|
|
1382
|
-
prevCursor = results[0][orderByField];
|
|
1383
|
-
}
|
|
1384
|
-
} else {
|
|
1385
|
-
if (results.length > limit) {
|
|
1386
|
-
prevCursor = results[limit - 1][orderByField];
|
|
1387
|
-
results.pop();
|
|
1388
|
-
}
|
|
1389
|
-
results.reverse();
|
|
1390
|
-
if (results.length > 0 && beforeCursor) {
|
|
1391
|
-
nextCursor = results[results.length - 1][orderByField];
|
|
1392
|
-
}
|
|
1393
|
-
if (results.length > 0 && !beforeCursor) {
|
|
1394
|
-
if (results.length === limit) {
|
|
1395
|
-
prevCursor = results[0][orderByField];
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
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
|
+
);
|
|
1401
1673
|
};
|
|
1402
1674
|
}
|
|
1403
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
|
+
);
|
|
1404
1687
|
return async function(paginationArgs) {
|
|
1405
1688
|
const {
|
|
1406
1689
|
limit,
|
|
@@ -1414,87 +1697,42 @@ function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClas
|
|
|
1414
1697
|
Logger.error(
|
|
1415
1698
|
`[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
1416
1699
|
);
|
|
1417
|
-
return { data: [],
|
|
1700
|
+
return { data: [], hasMore: false };
|
|
1418
1701
|
}
|
|
1419
1702
|
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
1420
1703
|
Logger.error(
|
|
1421
1704
|
`[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
1422
1705
|
);
|
|
1423
|
-
return { data: [],
|
|
1706
|
+
return { data: [], hasMore: false };
|
|
1424
1707
|
}
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
joinModelRelatedField,
|
|
1428
|
-
joinModelOrderByField = "id",
|
|
1429
|
-
joinModelOrderDirection = "ASC"
|
|
1430
|
-
} = config;
|
|
1431
|
-
const joinFilter = { [joinModelLocalField]: this.id };
|
|
1708
|
+
const forward = direction !== "backward";
|
|
1709
|
+
const cursor = forward ? afterCursor : beforeCursor;
|
|
1432
1710
|
const joinQueryOptions = {
|
|
1433
1711
|
sort: {
|
|
1434
1712
|
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
|
|
1435
1713
|
},
|
|
1436
|
-
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
|
|
1714
|
+
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 },
|
|
1715
|
+
direction: forward ? 1 : -1
|
|
1437
1716
|
};
|
|
1438
|
-
if (limit !== void 0)
|
|
1439
|
-
|
|
1440
|
-
}
|
|
1441
|
-
if (direction === "forward" && afterCursor) {
|
|
1442
|
-
joinFilter[joinModelOrderByField] = {
|
|
1443
|
-
[joinModelOrderDirection === "ASC" ? "$gt" : "$lt"]: afterCursor
|
|
1444
|
-
};
|
|
1445
|
-
} else if (direction === "backward") {
|
|
1446
|
-
if (beforeCursor) {
|
|
1447
|
-
joinFilter[joinModelOrderByField] = {
|
|
1448
|
-
[joinModelOrderDirection === "ASC" ? "$lt" : "$gt"]: beforeCursor
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
joinQueryOptions.sort = {
|
|
1452
|
-
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? -1 : 1
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1717
|
+
if (limit !== void 0) joinQueryOptions.limit = limit;
|
|
1718
|
+
if (cursor != null) joinQueryOptions.uniqueStartKey = cursor;
|
|
1455
1719
|
const joinResult = await joinModelClass.query(
|
|
1456
|
-
|
|
1720
|
+
{ [joinModelLocalField]: this.id },
|
|
1457
1721
|
joinQueryOptions
|
|
1458
1722
|
);
|
|
1459
|
-
const
|
|
1460
|
-
let
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
nextCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
1467
|
-
joinResults.pop();
|
|
1468
|
-
}
|
|
1469
|
-
if (joinResults.length > 0 && !isFirstPage) {
|
|
1470
|
-
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
1471
|
-
}
|
|
1472
|
-
} else {
|
|
1473
|
-
if (joinResults.length > limit) {
|
|
1474
|
-
prevCursorFromJoin = joinResults[limit - 1][joinModelOrderByField];
|
|
1475
|
-
joinResults.pop();
|
|
1476
|
-
}
|
|
1477
|
-
joinResults.reverse();
|
|
1478
|
-
if (joinResults.length > 0 && beforeCursor) {
|
|
1479
|
-
nextCursorFromJoin = joinResults[joinResults.length - 1][joinModelOrderByField];
|
|
1480
|
-
}
|
|
1481
|
-
if (joinResults.length > 0 && !beforeCursor) {
|
|
1482
|
-
if (joinResults.length === limit) {
|
|
1483
|
-
prevCursorFromJoin = joinResults[0][joinModelOrderByField];
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
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;
|
|
1487
1730
|
}
|
|
1488
|
-
const relatedIds = joinResults.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
|
|
1489
|
-
if (relatedIds.length === 0)
|
|
1490
|
-
return { data: [], nextCursor: null, prevCursor: null };
|
|
1491
|
-
const targetResult = await targetModelClass.query({
|
|
1492
|
-
id: { $in: relatedIds }
|
|
1493
|
-
});
|
|
1494
1731
|
return {
|
|
1495
|
-
data:
|
|
1496
|
-
nextCursor:
|
|
1497
|
-
prevCursor:
|
|
1732
|
+
data: targetData,
|
|
1733
|
+
nextCursor: joinResult.nextCursor,
|
|
1734
|
+
prevCursor: joinResult.prevCursor,
|
|
1735
|
+
hasMore: joinResult.hasMore
|
|
1498
1736
|
};
|
|
1499
1737
|
};
|
|
1500
1738
|
}
|
|
@@ -1618,10 +1856,12 @@ function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetMo
|
|
|
1618
1856
|
}, `remove-join-${config.joinModel}-${this.id}-${targetId}`);
|
|
1619
1857
|
};
|
|
1620
1858
|
}
|
|
1859
|
+
var ALWAYS_PRESENT_ORDER_FIELDS;
|
|
1621
1860
|
var init_relationshipManager = __esm({
|
|
1622
1861
|
"src/models/relationshipManager.ts"() {
|
|
1623
1862
|
"use strict";
|
|
1624
1863
|
init_BaseModel();
|
|
1864
|
+
ALWAYS_PRESENT_ORDER_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
|
|
1625
1865
|
}
|
|
1626
1866
|
});
|
|
1627
1867
|
|
|
@@ -2392,7 +2632,13 @@ var init_IncludeResolver = __esm({
|
|
|
2392
2632
|
}
|
|
2393
2633
|
return null;
|
|
2394
2634
|
}
|
|
2395
|
-
/**
|
|
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
|
+
*/
|
|
2396
2642
|
_applyProjection(record, projection) {
|
|
2397
2643
|
const fields = Object.entries(projection);
|
|
2398
2644
|
if (fields.length === 0) return record;
|
|
@@ -2411,7 +2657,7 @@ var init_IncludeResolver = __esm({
|
|
|
2411
2657
|
} else {
|
|
2412
2658
|
const result = { ...record };
|
|
2413
2659
|
for (const [f, v] of fields) {
|
|
2414
|
-
if (v === 0) delete result[f];
|
|
2660
|
+
if (v === 0 && f !== "id" && f !== "type") delete result[f];
|
|
2415
2661
|
}
|
|
2416
2662
|
return result;
|
|
2417
2663
|
}
|
|
@@ -2431,20 +2677,23 @@ __export(BaseModel_exports, {
|
|
|
2431
2677
|
generateULID: () => generateULID
|
|
2432
2678
|
});
|
|
2433
2679
|
function generateULID() {
|
|
2434
|
-
return (0,
|
|
2680
|
+
return (0, import_ulid2.ulid)();
|
|
2435
2681
|
}
|
|
2436
|
-
var
|
|
2682
|
+
var Y5, import_ulid2, LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
|
|
2437
2683
|
var init_BaseModel = __esm({
|
|
2438
2684
|
"src/models/BaseModel.ts"() {
|
|
2439
2685
|
"use strict";
|
|
2440
|
-
|
|
2441
|
-
|
|
2686
|
+
Y5 = __toESM(require("yjs"), 1);
|
|
2687
|
+
import_ulid2 = require("ulid");
|
|
2442
2688
|
init_StringSet();
|
|
2443
2689
|
init_documentTypes();
|
|
2444
2690
|
init_DocumentQueryTranslator();
|
|
2445
2691
|
init_CursorManager();
|
|
2446
2692
|
init_sql();
|
|
2447
2693
|
init_metaSync();
|
|
2694
|
+
init_writeLifecycle();
|
|
2695
|
+
init_yjsValues();
|
|
2696
|
+
init_uniqueIndex();
|
|
2448
2697
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
2449
2698
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
2450
2699
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2829,7 +3078,9 @@ var init_BaseModel = __esm({
|
|
|
2829
3078
|
this.setValue(fieldKey, data[fieldKey]);
|
|
2830
3079
|
this._constructorProvidedFields.add(fieldKey);
|
|
2831
3080
|
} else if (fieldOptions.default !== void 0) {
|
|
2832
|
-
const defaultValue =
|
|
3081
|
+
const defaultValue = resolveLiteralOrFunctionDefault(
|
|
3082
|
+
fieldOptions.default
|
|
3083
|
+
);
|
|
2833
3084
|
this.setValue(fieldKey, defaultValue);
|
|
2834
3085
|
}
|
|
2835
3086
|
}
|
|
@@ -2953,7 +3204,30 @@ var init_BaseModel = __esm({
|
|
|
2953
3204
|
if (verboseEnabled) {
|
|
2954
3205
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2955
3206
|
}
|
|
2956
|
-
return fieldValue;
|
|
3207
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
3211
|
+
*
|
|
3212
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
3213
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
3214
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
3215
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
3216
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
3217
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
3218
|
+
*/
|
|
3219
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
3220
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
3221
|
+
if (fieldOptions?.type === "stringset") {
|
|
3222
|
+
return value;
|
|
3223
|
+
}
|
|
3224
|
+
if (isYjsComposite(value)) {
|
|
3225
|
+
Logger.warn(
|
|
3226
|
+
`[${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.`
|
|
3227
|
+
);
|
|
3228
|
+
return normalizeYjsValue(value);
|
|
3229
|
+
}
|
|
3230
|
+
return value;
|
|
2957
3231
|
}
|
|
2958
3232
|
getValue(fieldKey) {
|
|
2959
3233
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -3098,7 +3372,7 @@ var init_BaseModel = __esm({
|
|
|
3098
3372
|
}
|
|
3099
3373
|
getStringSetFromYjs(fieldName) {
|
|
3100
3374
|
const yjsData = this.getFromYjs(fieldName);
|
|
3101
|
-
if (yjsData instanceof
|
|
3375
|
+
if (yjsData instanceof Y5.Map) {
|
|
3102
3376
|
return Array.from(yjsData.keys());
|
|
3103
3377
|
}
|
|
3104
3378
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -3206,7 +3480,7 @@ var init_BaseModel = __esm({
|
|
|
3206
3480
|
}
|
|
3207
3481
|
const extractStringSetValues = (value) => {
|
|
3208
3482
|
if (!value) return [];
|
|
3209
|
-
if (value instanceof
|
|
3483
|
+
if (value instanceof Y5.Map) {
|
|
3210
3484
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
3211
3485
|
}
|
|
3212
3486
|
if (Array.isArray(value)) {
|
|
@@ -3223,7 +3497,7 @@ var init_BaseModel = __esm({
|
|
|
3223
3497
|
if (!recordId) continue;
|
|
3224
3498
|
let itemData;
|
|
3225
3499
|
const stringSetValuesByField = {};
|
|
3226
|
-
if (recordData instanceof
|
|
3500
|
+
if (recordData instanceof Y5.Map) {
|
|
3227
3501
|
itemData = {};
|
|
3228
3502
|
const unknownFields = [];
|
|
3229
3503
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3339,20 +3613,10 @@ var init_BaseModel = __esm({
|
|
|
3339
3613
|
Logger.verbose(
|
|
3340
3614
|
`[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
|
|
3341
3615
|
);
|
|
3342
|
-
const
|
|
3343
|
-
const keyParts = [];
|
|
3344
|
-
for (const field of fields) {
|
|
3345
|
-
const value = recordData instanceof Y2.Map ? recordData.get(field) : recordData[field];
|
|
3346
|
-
if (value === null || value === void 0) {
|
|
3347
|
-
return null;
|
|
3348
|
-
}
|
|
3349
|
-
keyParts.push(String(value));
|
|
3350
|
-
}
|
|
3351
|
-
return keyParts.join("|");
|
|
3352
|
-
};
|
|
3616
|
+
const buildUniqueKey2 = (recordData, fields) => buildUniqueKey(recordData, fields);
|
|
3353
3617
|
const extractItemData = (key, recordData) => {
|
|
3354
3618
|
let itemData;
|
|
3355
|
-
if (recordData instanceof
|
|
3619
|
+
if (recordData instanceof Y5.Map) {
|
|
3356
3620
|
itemData = {};
|
|
3357
3621
|
const unknownFields = [];
|
|
3358
3622
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3365,7 +3629,14 @@ var init_BaseModel = __esm({
|
|
|
3365
3629
|
continue;
|
|
3366
3630
|
}
|
|
3367
3631
|
if (value !== void 0) {
|
|
3368
|
-
|
|
3632
|
+
if (isYjsComposite(value)) {
|
|
3633
|
+
Logger.warn(
|
|
3634
|
+
`[${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.`
|
|
3635
|
+
);
|
|
3636
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3637
|
+
} else {
|
|
3638
|
+
itemData[fieldKey] = value;
|
|
3639
|
+
}
|
|
3369
3640
|
}
|
|
3370
3641
|
}
|
|
3371
3642
|
if (unknownFields.length > 0) {
|
|
@@ -3388,7 +3659,14 @@ var init_BaseModel = __esm({
|
|
|
3388
3659
|
continue;
|
|
3389
3660
|
}
|
|
3390
3661
|
if (value !== void 0) {
|
|
3391
|
-
|
|
3662
|
+
if (isYjsComposite(value)) {
|
|
3663
|
+
Logger.warn(
|
|
3664
|
+
`[${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.`
|
|
3665
|
+
);
|
|
3666
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3667
|
+
} else {
|
|
3668
|
+
filteredData[fieldKey] = value;
|
|
3669
|
+
}
|
|
3392
3670
|
}
|
|
3393
3671
|
}
|
|
3394
3672
|
if (unknownFields.length > 0) {
|
|
@@ -3422,7 +3700,7 @@ var init_BaseModel = __esm({
|
|
|
3422
3700
|
const recordsByUniqueKey = /* @__PURE__ */ new Map();
|
|
3423
3701
|
for (const [recordId, recordData] of allRecords.entries()) {
|
|
3424
3702
|
if (recordIdsToDiscard.has(recordId)) continue;
|
|
3425
|
-
const uniqueKey =
|
|
3703
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3426
3704
|
if (uniqueKey === null) continue;
|
|
3427
3705
|
if (!recordsByUniqueKey.has(uniqueKey)) {
|
|
3428
3706
|
recordsByUniqueKey.set(uniqueKey, []);
|
|
@@ -3468,7 +3746,7 @@ var init_BaseModel = __esm({
|
|
|
3468
3746
|
if (!recordData || !key) continue;
|
|
3469
3747
|
const itemData = extractItemData(key, recordData);
|
|
3470
3748
|
if (!itemData) continue;
|
|
3471
|
-
if (change.action === "add" && recordData instanceof
|
|
3749
|
+
if (change.action === "add" && recordData instanceof Y5.Map) {
|
|
3472
3750
|
Logger.verbose(
|
|
3473
3751
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
3474
3752
|
);
|
|
@@ -3566,9 +3844,12 @@ var init_BaseModel = __esm({
|
|
|
3566
3844
|
const recordData = documentYMap.get(idToDiscard);
|
|
3567
3845
|
if (!recordData) continue;
|
|
3568
3846
|
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
3569
|
-
const uniqueKey =
|
|
3847
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3570
3848
|
if (uniqueKey === null) continue;
|
|
3571
|
-
const constraintMapName =
|
|
3849
|
+
const constraintMapName = uniqueIndexMapName(
|
|
3850
|
+
modelName,
|
|
3851
|
+
constraint.name
|
|
3852
|
+
);
|
|
3572
3853
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
3573
3854
|
const currentIndexValue = constraintMap.get(uniqueKey);
|
|
3574
3855
|
if (currentIndexValue === idToDiscard) {
|
|
@@ -3586,11 +3867,14 @@ var init_BaseModel = __esm({
|
|
|
3586
3867
|
}, `conflict-resolution-${modelName}-${docId}`);
|
|
3587
3868
|
yDoc.transact(() => {
|
|
3588
3869
|
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
3589
|
-
const constraintMapName =
|
|
3870
|
+
const constraintMapName = uniqueIndexMapName(
|
|
3871
|
+
modelName,
|
|
3872
|
+
constraint.name
|
|
3873
|
+
);
|
|
3590
3874
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
3591
3875
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3592
3876
|
if (!recordData) continue;
|
|
3593
|
-
const uniqueKey =
|
|
3877
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3594
3878
|
if (uniqueKey === null) continue;
|
|
3595
3879
|
const currentIndexValue = constraintMap.get(uniqueKey);
|
|
3596
3880
|
if (currentIndexValue !== recordId) {
|
|
@@ -3613,7 +3897,7 @@ var init_BaseModel = __esm({
|
|
|
3613
3897
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
3614
3898
|
);
|
|
3615
3899
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3616
|
-
if (recordData instanceof
|
|
3900
|
+
if (recordData instanceof Y5.Map) {
|
|
3617
3901
|
Logger.verbose(
|
|
3618
3902
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
3619
3903
|
);
|
|
@@ -3798,6 +4082,11 @@ var init_BaseModel = __esm({
|
|
|
3798
4082
|
Logger.debug(
|
|
3799
4083
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3800
4084
|
);
|
|
4085
|
+
if (isYjsComposite(yjsValue)) {
|
|
4086
|
+
Logger.warn(
|
|
4087
|
+
`[${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).`
|
|
4088
|
+
);
|
|
4089
|
+
}
|
|
3801
4090
|
if (yjsValue === void 0) {
|
|
3802
4091
|
Logger.debug(
|
|
3803
4092
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3834,26 +4123,7 @@ var init_BaseModel = __esm({
|
|
|
3834
4123
|
* if the field hasn't been touched since the wire-format change in #561.
|
|
3835
4124
|
*/
|
|
3836
4125
|
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3837
|
-
|
|
3838
|
-
if (!(nested instanceof Y2.Map)) {
|
|
3839
|
-
const migrated = new Y2.Map();
|
|
3840
|
-
if (nested && typeof nested === "object") {
|
|
3841
|
-
for (const [member, marker] of Object.entries(
|
|
3842
|
-
nested
|
|
3843
|
-
)) {
|
|
3844
|
-
if (Boolean(marker)) migrated.set(member, true);
|
|
3845
|
-
}
|
|
3846
|
-
}
|
|
3847
|
-
recordYMap.set(fieldName, migrated);
|
|
3848
|
-
nested = migrated;
|
|
3849
|
-
}
|
|
3850
|
-
const target = nested;
|
|
3851
|
-
for (const member of change.additions) {
|
|
3852
|
-
target.set(member, true);
|
|
3853
|
-
}
|
|
3854
|
-
for (const member of change.removals) {
|
|
3855
|
-
target.delete(member);
|
|
3856
|
-
}
|
|
4126
|
+
applyStringSetChange(recordYMap, fieldName, change);
|
|
3857
4127
|
}
|
|
3858
4128
|
/**
|
|
3859
4129
|
* Deep equality check for comparing field values
|
|
@@ -4023,6 +4293,29 @@ var init_BaseModel = __esm({
|
|
|
4023
4293
|
);
|
|
4024
4294
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
4025
4295
|
}
|
|
4296
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
4297
|
+
{
|
|
4298
|
+
const existingRecord = targetYMap.get(this.id);
|
|
4299
|
+
const isNew = !existingRecord;
|
|
4300
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
4301
|
+
const stamp = fieldOptions.autoStamp;
|
|
4302
|
+
if (!stamp) continue;
|
|
4303
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
4304
|
+
if (callerExplicit) {
|
|
4305
|
+
const localValue = this._localChanges[fieldKey];
|
|
4306
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
4307
|
+
}
|
|
4308
|
+
if (stamp === "create") {
|
|
4309
|
+
if (!isNew) continue;
|
|
4310
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
4311
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
4312
|
+
}
|
|
4313
|
+
this[fieldKey] = Date.now();
|
|
4314
|
+
if (stamp === "create") {
|
|
4315
|
+
preTransactCreateStamps.add(fieldKey);
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4026
4319
|
this.validateBeforeSave();
|
|
4027
4320
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
4028
4321
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -4032,6 +4325,34 @@ var init_BaseModel = __esm({
|
|
|
4032
4325
|
`[${modelName}.save] getCurrentJSState() returned:`,
|
|
4033
4326
|
dataToSave
|
|
4034
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
|
+
}
|
|
4035
4356
|
await yDoc.transact(async () => {
|
|
4036
4357
|
if (upsertOnConstraint && upsertOnKey) {
|
|
4037
4358
|
const constraintMapName = `_uniqueIdx_${modelName}_${upsertOnConstraint.name}`;
|
|
@@ -4053,6 +4374,14 @@ var init_BaseModel = __esm({
|
|
|
4053
4374
|
}
|
|
4054
4375
|
}
|
|
4055
4376
|
}
|
|
4377
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
4378
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
4379
|
+
delete this._localChanges[fieldKey];
|
|
4380
|
+
}
|
|
4381
|
+
if (fieldKey in dataToSave) {
|
|
4382
|
+
delete dataToSave[fieldKey];
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4056
4385
|
}
|
|
4057
4386
|
}
|
|
4058
4387
|
if (!targetYMap) {
|
|
@@ -4065,7 +4394,7 @@ var init_BaseModel = __esm({
|
|
|
4065
4394
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
4066
4395
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
4067
4396
|
if (!recordYMap) {
|
|
4068
|
-
recordYMap = new
|
|
4397
|
+
recordYMap = new Y5.Map();
|
|
4069
4398
|
Logger.verbose(
|
|
4070
4399
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
4071
4400
|
);
|
|
@@ -4117,7 +4446,10 @@ var init_BaseModel = __esm({
|
|
|
4117
4446
|
);
|
|
4118
4447
|
continue;
|
|
4119
4448
|
}
|
|
4120
|
-
const constraintMapName =
|
|
4449
|
+
const constraintMapName = uniqueIndexMapName(
|
|
4450
|
+
modelName,
|
|
4451
|
+
constraint.name
|
|
4452
|
+
);
|
|
4121
4453
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
4122
4454
|
Logger.verbose(
|
|
4123
4455
|
`[${modelName}] Save Check (Item '${this.id}'): GET from '${constraintMapName}' with Key '${newUniqueKey.substring(
|
|
@@ -4146,6 +4478,26 @@ var init_BaseModel = __esm({
|
|
|
4146
4478
|
this.id,
|
|
4147
4479
|
existingRecordIdWithNewKey
|
|
4148
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
|
+
}
|
|
4149
4501
|
}
|
|
4150
4502
|
}
|
|
4151
4503
|
if (isUpdate && oldData) {
|
|
@@ -4489,34 +4841,46 @@ var init_BaseModel = __esm({
|
|
|
4489
4841
|
const modelName = schema.options.name;
|
|
4490
4842
|
Logger.verbose(`[${modelName}] Executing query:`, { filter, options });
|
|
4491
4843
|
const hasIncludes = options?.include && options.include.length > 0;
|
|
4492
|
-
const
|
|
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
|
+
}
|
|
4493
4853
|
const translator = new DocumentQueryTranslator(modelName, schema.fields);
|
|
4494
4854
|
const translatedQuery = translator.translateFind(filter, translatorOptions);
|
|
4495
4855
|
Logger.verbose(`[${modelName}] Translated SQL:`, translatedQuery.sql);
|
|
4496
4856
|
Logger.verbose(`[${modelName}] SQL params:`, translatedQuery.params);
|
|
4497
|
-
|
|
4857
|
+
let results = await _BaseModel.dbInstance.query(
|
|
4498
4858
|
translatedQuery.sql,
|
|
4499
4859
|
translatedQuery.params
|
|
4500
4860
|
);
|
|
4501
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
|
+
}
|
|
4502
4867
|
if (options?.include && options.include.length > 0) {
|
|
4503
4868
|
const { IncludeResolver: IncludeResolver2 } = await Promise.resolve().then(() => (init_IncludeResolver(), IncludeResolver_exports));
|
|
4504
4869
|
const resolver = new IncludeResolver2(_BaseModel.dbInstance);
|
|
4505
4870
|
await resolver.resolve(results, options.include, 0, modelName);
|
|
4506
4871
|
}
|
|
4507
|
-
const requestedLimit = options?.limit;
|
|
4508
|
-
const hasMore = CursorManager.hasMoreResults(
|
|
4509
|
-
requestedLimit,
|
|
4510
|
-
results.length
|
|
4511
|
-
);
|
|
4512
4872
|
const isFirstPage = !options?.uniqueStartKey;
|
|
4873
|
+
const direction = options?.direction || 1;
|
|
4513
4874
|
const cursors = CursorManager.generateResultCursors(
|
|
4514
4875
|
results,
|
|
4515
4876
|
translatedQuery.sortFields,
|
|
4516
|
-
|
|
4877
|
+
direction,
|
|
4517
4878
|
hasMore,
|
|
4518
4879
|
isFirstPage
|
|
4519
4880
|
);
|
|
4881
|
+
if (direction === -1) {
|
|
4882
|
+
results.reverse();
|
|
4883
|
+
}
|
|
4520
4884
|
let transformedData;
|
|
4521
4885
|
if (options?.projection && Object.keys(options.projection).length > 0) {
|
|
4522
4886
|
transformedData = results.map((row) => {
|
|
@@ -5092,7 +5456,7 @@ var init_BaseModel = __esm({
|
|
|
5092
5456
|
);
|
|
5093
5457
|
}
|
|
5094
5458
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
5095
|
-
if (recordYMap instanceof
|
|
5459
|
+
if (recordYMap instanceof Y5.Map) {
|
|
5096
5460
|
const instance = new this({ id: recordId });
|
|
5097
5461
|
instance._metaDocId = docId;
|
|
5098
5462
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -5117,6 +5481,50 @@ var init_BaseModel = __esm({
|
|
|
5117
5481
|
}
|
|
5118
5482
|
return allInstances;
|
|
5119
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
|
+
}
|
|
5120
5528
|
static async findByUnique(constraintName, value) {
|
|
5121
5529
|
const modelConstructor = this;
|
|
5122
5530
|
const schema = modelConstructor.getSchema();
|
|
@@ -5155,7 +5563,10 @@ var init_BaseModel = __esm({
|
|
|
5155
5563
|
const documentInfo = modelConstructor.connectedDocuments.get(docId);
|
|
5156
5564
|
if (!documentInfo) continue;
|
|
5157
5565
|
const yDoc = documentInfo.yDoc;
|
|
5158
|
-
const constraintMapName2 =
|
|
5566
|
+
const constraintMapName2 = uniqueIndexMapName(
|
|
5567
|
+
currentModelName,
|
|
5568
|
+
constraint.name
|
|
5569
|
+
);
|
|
5159
5570
|
const constraintMap2 = yDoc.getMap(constraintMapName2);
|
|
5160
5571
|
const recordId2 = constraintMap2.get(uniqueKeyString);
|
|
5161
5572
|
if (recordId2) {
|
|
@@ -5163,7 +5574,7 @@ var init_BaseModel = __esm({
|
|
|
5163
5574
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
5164
5575
|
if (documentYMap) {
|
|
5165
5576
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
5166
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
5577
|
+
if (recordYMap2 && recordYMap2 instanceof Y5.Map) {
|
|
5167
5578
|
const instance = new modelConstructor({
|
|
5168
5579
|
id: recordId2
|
|
5169
5580
|
});
|
|
@@ -5177,6 +5588,16 @@ var init_BaseModel = __esm({
|
|
|
5177
5588
|
}
|
|
5178
5589
|
}
|
|
5179
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
|
+
}
|
|
5180
5601
|
Logger.verbose(
|
|
5181
5602
|
`[${currentModelName}.findByUnique] Record not found in any connected document`
|
|
5182
5603
|
);
|
|
@@ -5190,17 +5611,30 @@ var init_BaseModel = __esm({
|
|
|
5190
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.`
|
|
5191
5612
|
);
|
|
5192
5613
|
}
|
|
5193
|
-
const constraintMapName =
|
|
5614
|
+
const constraintMapName = uniqueIndexMapName(
|
|
5615
|
+
currentModelName,
|
|
5616
|
+
constraint.name
|
|
5617
|
+
);
|
|
5194
5618
|
const constraintMap = legacyDocInfo.yDoc.getMap(constraintMapName);
|
|
5195
5619
|
const recordId = constraintMap.get(uniqueKeyString);
|
|
5196
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
|
+
}
|
|
5197
5631
|
return null;
|
|
5198
5632
|
}
|
|
5199
5633
|
const recordYMap = legacyDocumentMap.get(recordId);
|
|
5200
5634
|
if (!recordYMap) {
|
|
5201
5635
|
return null;
|
|
5202
5636
|
}
|
|
5203
|
-
if (recordYMap instanceof
|
|
5637
|
+
if (recordYMap instanceof Y5.Map) {
|
|
5204
5638
|
const instance = new modelConstructor({
|
|
5205
5639
|
id: recordId
|
|
5206
5640
|
});
|
|
@@ -5258,7 +5692,10 @@ var init_BaseModel = __esm({
|
|
|
5258
5692
|
`[${currentModelName}] upsertByUnique for '${constraintName}': unique key cannot be built due to null/undefined values. Upsert cannot proceed reliably.`
|
|
5259
5693
|
);
|
|
5260
5694
|
}
|
|
5261
|
-
const constraintMapName =
|
|
5695
|
+
const constraintMapName = uniqueIndexMapName(
|
|
5696
|
+
currentModelName,
|
|
5697
|
+
constraint.name
|
|
5698
|
+
);
|
|
5262
5699
|
let existingRecord = null;
|
|
5263
5700
|
if (connectedDocuments && connectedDocuments.size > 0) {
|
|
5264
5701
|
for (const docId of connectedDocuments.keys()) {
|
|
@@ -5276,6 +5713,33 @@ var init_BaseModel = __esm({
|
|
|
5276
5713
|
}
|
|
5277
5714
|
} else {
|
|
5278
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
|
+
}
|
|
5279
5743
|
if (options?.objectMustExist) {
|
|
5280
5744
|
if (!existingRecord) {
|
|
5281
5745
|
throw new RecordNotFoundError(
|
|
@@ -5432,7 +5896,7 @@ var init_BaseModel = __esm({
|
|
|
5432
5896
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5433
5897
|
if (fieldOptions?.type !== "stringset") continue;
|
|
5434
5898
|
const value = recordYMap.get(fieldKey);
|
|
5435
|
-
if (value instanceof
|
|
5899
|
+
if (value instanceof Y5.Map) {
|
|
5436
5900
|
this.observeStringSetMapOnce(value);
|
|
5437
5901
|
}
|
|
5438
5902
|
}
|
|
@@ -5479,7 +5943,14 @@ var init_BaseModel = __esm({
|
|
|
5479
5943
|
continue;
|
|
5480
5944
|
}
|
|
5481
5945
|
if (value !== void 0) {
|
|
5482
|
-
|
|
5946
|
+
if (isYjsComposite(value)) {
|
|
5947
|
+
Logger.warn(
|
|
5948
|
+
`[${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.`
|
|
5949
|
+
);
|
|
5950
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5951
|
+
} else {
|
|
5952
|
+
updatedData[key] = value;
|
|
5953
|
+
}
|
|
5483
5954
|
}
|
|
5484
5955
|
}
|
|
5485
5956
|
if (unknownFields.length > 0) {
|
|
@@ -5559,7 +6030,14 @@ var init_BaseModel = __esm({
|
|
|
5559
6030
|
continue;
|
|
5560
6031
|
}
|
|
5561
6032
|
if (value !== void 0) {
|
|
5562
|
-
|
|
6033
|
+
if (isYjsComposite(value)) {
|
|
6034
|
+
Logger.warn(
|
|
6035
|
+
`[${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.`
|
|
6036
|
+
);
|
|
6037
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
6038
|
+
} else {
|
|
6039
|
+
updatedData[key] = value;
|
|
6040
|
+
}
|
|
5563
6041
|
}
|
|
5564
6042
|
}
|
|
5565
6043
|
if (unknownFields.length > 0) {
|
|
@@ -5603,8 +6081,160 @@ var init_BaseModel = __esm({
|
|
|
5603
6081
|
}
|
|
5604
6082
|
});
|
|
5605
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
|
+
|
|
5606
6151
|
// src/engines/SqljsEngine.ts
|
|
5607
|
-
|
|
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;
|
|
5608
6238
|
var init_SqljsEngine = __esm({
|
|
5609
6239
|
"src/engines/SqljsEngine.ts"() {
|
|
5610
6240
|
"use strict";
|
|
@@ -5612,7 +6242,10 @@ var init_SqljsEngine = __esm({
|
|
|
5612
6242
|
init_BaseModel();
|
|
5613
6243
|
import_async_mutex = require("async-mutex");
|
|
5614
6244
|
init_sql();
|
|
6245
|
+
init_environment();
|
|
6246
|
+
import_meta = {};
|
|
5615
6247
|
SQL_WASM_URL = "/sql-wasm.wasm";
|
|
6248
|
+
SQL_WASM_FILENAME = "sql-wasm.wasm";
|
|
5616
6249
|
TransactionalSQLJSOperations = class {
|
|
5617
6250
|
db;
|
|
5618
6251
|
engine;
|
|
@@ -5678,22 +6311,37 @@ var init_SqljsEngine = __esm({
|
|
|
5678
6311
|
}
|
|
5679
6312
|
async initialize(engineOptions) {
|
|
5680
6313
|
Logger.info("[SqljsEngine] Initializing SQL.js engine...");
|
|
6314
|
+
const loadingModuleThisCall = !_SqljsEngine.SQL;
|
|
5681
6315
|
try {
|
|
5682
6316
|
if (!_SqljsEngine.SQL) {
|
|
5683
|
-
|
|
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);
|
|
5684
6329
|
Logger.debug(
|
|
5685
6330
|
"[SqljsEngine] Attempting to load SQL.js WASM from:",
|
|
5686
|
-
|
|
6331
|
+
wasmPath
|
|
5687
6332
|
);
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
6333
|
+
await preflightNodeWasm(wasmPath);
|
|
6334
|
+
const SQL = await loadSqlJsModule(locateFileConfig);
|
|
6335
|
+
_SqljsEngine.SQL = SQL;
|
|
5691
6336
|
Logger.info("[SqljsEngine] SQL.js WASM loaded successfully.");
|
|
5692
6337
|
}
|
|
5693
6338
|
this.db = new _SqljsEngine.SQL.Database();
|
|
5694
6339
|
Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
|
|
5695
6340
|
this.updateTableNames();
|
|
5696
6341
|
} catch (error) {
|
|
6342
|
+
if (loadingModuleThisCall) {
|
|
6343
|
+
_SqljsEngine.SQL = null;
|
|
6344
|
+
}
|
|
5697
6345
|
Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
|
|
5698
6346
|
throw error;
|
|
5699
6347
|
}
|
|
@@ -5981,73 +6629,6 @@ var init_SqljsEngine = __esm({
|
|
|
5981
6629
|
}
|
|
5982
6630
|
});
|
|
5983
6631
|
|
|
5984
|
-
// src/utils/environment.ts
|
|
5985
|
-
var environment_exports = {};
|
|
5986
|
-
__export(environment_exports, {
|
|
5987
|
-
constants: () => constants,
|
|
5988
|
-
detectEnvironment: () => detectEnvironment,
|
|
5989
|
-
features: () => features,
|
|
5990
|
-
isBrowser: () => isBrowser,
|
|
5991
|
-
isNode: () => isNode,
|
|
5992
|
-
isNodeModuleAvailable: () => isNodeModuleAvailable
|
|
5993
|
-
});
|
|
5994
|
-
function detectEnvironment() {
|
|
5995
|
-
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
5996
|
-
return "node";
|
|
5997
|
-
}
|
|
5998
|
-
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
5999
|
-
return "browser";
|
|
6000
|
-
}
|
|
6001
|
-
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
6002
|
-
return "browser";
|
|
6003
|
-
}
|
|
6004
|
-
return "unknown";
|
|
6005
|
-
}
|
|
6006
|
-
function isNode() {
|
|
6007
|
-
return detectEnvironment() === "node";
|
|
6008
|
-
}
|
|
6009
|
-
function isBrowser() {
|
|
6010
|
-
return detectEnvironment() === "browser";
|
|
6011
|
-
}
|
|
6012
|
-
async function isNodeModuleAvailable(moduleName) {
|
|
6013
|
-
if (!isNode()) {
|
|
6014
|
-
return false;
|
|
6015
|
-
}
|
|
6016
|
-
try {
|
|
6017
|
-
await import(
|
|
6018
|
-
/* @vite-ignore */
|
|
6019
|
-
moduleName
|
|
6020
|
-
);
|
|
6021
|
-
return true;
|
|
6022
|
-
} catch {
|
|
6023
|
-
return false;
|
|
6024
|
-
}
|
|
6025
|
-
}
|
|
6026
|
-
var features, constants;
|
|
6027
|
-
var init_environment = __esm({
|
|
6028
|
-
"src/utils/environment.ts"() {
|
|
6029
|
-
"use strict";
|
|
6030
|
-
features = {
|
|
6031
|
-
get hasWebWorkers() {
|
|
6032
|
-
return isBrowser() && typeof Worker !== "undefined";
|
|
6033
|
-
},
|
|
6034
|
-
get hasFileSystem() {
|
|
6035
|
-
return isNode();
|
|
6036
|
-
},
|
|
6037
|
-
get hasWebAssembly() {
|
|
6038
|
-
return typeof WebAssembly !== "undefined";
|
|
6039
|
-
},
|
|
6040
|
-
async hasBetterSqlite3() {
|
|
6041
|
-
return await isNodeModuleAvailable("better-sqlite3");
|
|
6042
|
-
}
|
|
6043
|
-
};
|
|
6044
|
-
constants = {
|
|
6045
|
-
DEFAULT_NODE_SQLITE_PATH: ":memory:",
|
|
6046
|
-
DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
|
|
6047
|
-
};
|
|
6048
|
-
}
|
|
6049
|
-
});
|
|
6050
|
-
|
|
6051
6632
|
// src/engines/node/NodeSqliteEngine.ts
|
|
6052
6633
|
var NodeSqliteEngine_exports = {};
|
|
6053
6634
|
__export(NodeSqliteEngine_exports, {
|
|
@@ -6739,33 +7320,47 @@ __export(node_exports, {
|
|
|
6739
7320
|
SqljsEngine: () => SqljsEngine,
|
|
6740
7321
|
StringSet: () => StringSet,
|
|
6741
7322
|
UniqueConstraintViolationError: () => UniqueConstraintViolationError,
|
|
7323
|
+
applyAutoStampsFromSchema: () => applyAutoStampsFromSchema,
|
|
7324
|
+
applyDefaultsFromSchema: () => applyDefaultsFromSchema,
|
|
7325
|
+
applyStringSetChange: () => applyStringSetChange,
|
|
7326
|
+
applyStringSetDelta: () => applyStringSetDelta,
|
|
7327
|
+
assertNever: () => assertNever,
|
|
6742
7328
|
attachAndRegisterModel: () => attachAndRegisterModel,
|
|
6743
7329
|
attachSchemaToClass: () => attachSchemaToClass,
|
|
6744
7330
|
autoRegisterModel: () => autoRegisterModel,
|
|
6745
7331
|
clearMetaSyncCache: () => clearMetaSyncCache,
|
|
6746
7332
|
constants: () => constants,
|
|
6747
7333
|
createModelClass: () => createModelClass,
|
|
7334
|
+
decodeFunctionDefault: () => decodeFunctionDefault,
|
|
6748
7335
|
defineModelSchema: () => defineModelSchema,
|
|
6749
7336
|
detectEnvironment: () => detectEnvironment,
|
|
6750
7337
|
discoverModelNames: () => discoverModelNames,
|
|
6751
7338
|
discoverSchema: () => discoverSchema,
|
|
6752
7339
|
dumpYDocToPlain: () => dumpYDocToPlain,
|
|
7340
|
+
ensureStringSetYMap: () => ensureStringSetYMap,
|
|
6753
7341
|
features: () => features,
|
|
7342
|
+
fieldWriteKind: () => fieldWriteKind,
|
|
6754
7343
|
generateULID: () => generateULID,
|
|
6755
7344
|
getRecommendedNodeEngine: () => getRecommendedNodeEngine,
|
|
6756
7345
|
inferFieldType: () => inferFieldType,
|
|
6757
7346
|
initJsBao: () => initJsBao,
|
|
6758
7347
|
isBrowser: () => isBrowser,
|
|
7348
|
+
isKnownFieldType: () => isKnownFieldType,
|
|
6759
7349
|
isNode: () => isNode,
|
|
6760
7350
|
isNodeModuleAvailable: () => isNodeModuleAvailable,
|
|
6761
7351
|
loadSchemaFromToml: () => loadSchemaFromToml,
|
|
6762
7352
|
loadSchemaFromTomlString: () => loadSchemaFromTomlString,
|
|
7353
|
+
parseStringSetOp: () => parseStringSetOp,
|
|
6763
7354
|
registerFunctionDefault: () => registerFunctionDefault,
|
|
7355
|
+
registerFunctionDefaultGenerator: () => registerFunctionDefaultGenerator,
|
|
7356
|
+
replaceStringSet: () => replaceStringSet,
|
|
6764
7357
|
resetJsBao: () => resetJsBao,
|
|
7358
|
+
resolveDefaultValue: () => resolveDefaultValue,
|
|
6765
7359
|
schemaToToml: () => schemaToToml,
|
|
6766
7360
|
summarizePlainYDoc: () => summarizePlainYDoc,
|
|
6767
7361
|
syncInferredMeta: () => syncInferredMeta,
|
|
6768
|
-
syncModelMeta: () => syncModelMeta
|
|
7362
|
+
syncModelMeta: () => syncModelMeta,
|
|
7363
|
+
writeField: () => writeField
|
|
6769
7364
|
});
|
|
6770
7365
|
module.exports = __toCommonJS(node_exports);
|
|
6771
7366
|
init_BaseModel();
|
|
@@ -7032,6 +7627,7 @@ init_StringSet();
|
|
|
7032
7627
|
// src/models/schema.ts
|
|
7033
7628
|
init_BaseModel();
|
|
7034
7629
|
init_ModelRegistry();
|
|
7630
|
+
init_uniqueIndex();
|
|
7035
7631
|
function defineModelSchema(input) {
|
|
7036
7632
|
const { name, fields } = input;
|
|
7037
7633
|
const options = {
|
|
@@ -7104,7 +7700,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
7104
7700
|
for (const [fieldName, options] of fields.entries()) {
|
|
7105
7701
|
if (options.unique) {
|
|
7106
7702
|
resolved.push({
|
|
7107
|
-
name:
|
|
7703
|
+
name: fieldUniqueConstraintName(modelName, fieldName),
|
|
7108
7704
|
fields: [fieldName]
|
|
7109
7705
|
});
|
|
7110
7706
|
}
|
|
@@ -7168,7 +7764,8 @@ function attachAndRegisterModel(modelClass, schema) {
|
|
|
7168
7764
|
}
|
|
7169
7765
|
|
|
7170
7766
|
// src/utils/yDocSchema.ts
|
|
7171
|
-
var
|
|
7767
|
+
var Y6 = __toESM(require("yjs"), 1);
|
|
7768
|
+
init_yjsValues();
|
|
7172
7769
|
function discoverSchema(yDoc) {
|
|
7173
7770
|
const models = {};
|
|
7174
7771
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -7202,7 +7799,7 @@ function discoverModelNames(yDoc) {
|
|
|
7202
7799
|
function materializeMap(yDoc, key) {
|
|
7203
7800
|
try {
|
|
7204
7801
|
const map = yDoc.getMap(key);
|
|
7205
|
-
return map instanceof
|
|
7802
|
+
return map instanceof Y6.Map ? map : null;
|
|
7206
7803
|
} catch {
|
|
7207
7804
|
return null;
|
|
7208
7805
|
}
|
|
@@ -7212,11 +7809,11 @@ function readModelMeta(metaMap) {
|
|
|
7212
7809
|
let constraints;
|
|
7213
7810
|
let relationships;
|
|
7214
7811
|
for (const [key, value] of metaMap.entries()) {
|
|
7215
|
-
if (key === "_constraints" && value instanceof
|
|
7812
|
+
if (key === "_constraints" && value instanceof Y6.Map) {
|
|
7216
7813
|
constraints = readConstraints(value);
|
|
7217
|
-
} else if (key === "_relationships" && value instanceof
|
|
7814
|
+
} else if (key === "_relationships" && value instanceof Y6.Map) {
|
|
7218
7815
|
relationships = readRelationships(value);
|
|
7219
|
-
} else if (value instanceof
|
|
7816
|
+
} else if (value instanceof Y6.Map) {
|
|
7220
7817
|
fields[key] = readFieldMeta(value);
|
|
7221
7818
|
}
|
|
7222
7819
|
}
|
|
@@ -7235,6 +7832,10 @@ function readFieldMeta(fieldMap) {
|
|
|
7235
7832
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
7236
7833
|
if (fieldMap.get("required") === true) field.required = true;
|
|
7237
7834
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7835
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7836
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7837
|
+
field.autoStamp = autoStamp;
|
|
7838
|
+
}
|
|
7238
7839
|
const def = fieldMap.get("default");
|
|
7239
7840
|
if (def !== void 0) field.default = def;
|
|
7240
7841
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -7247,7 +7848,7 @@ function inferModelFromData(dataMap) {
|
|
|
7247
7848
|
const fields = {};
|
|
7248
7849
|
let sampled = 0;
|
|
7249
7850
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
7250
|
-
if (!(recordValue instanceof
|
|
7851
|
+
if (!(recordValue instanceof Y6.Map)) continue;
|
|
7251
7852
|
if (++sampled > 5) break;
|
|
7252
7853
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
7253
7854
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -7260,7 +7861,7 @@ function inferModelFromData(dataMap) {
|
|
|
7260
7861
|
return { fields };
|
|
7261
7862
|
}
|
|
7262
7863
|
function inferTypeFromValue(value) {
|
|
7263
|
-
if (value
|
|
7864
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
7264
7865
|
switch (typeof value) {
|
|
7265
7866
|
case "string":
|
|
7266
7867
|
return "string";
|
|
@@ -7275,7 +7876,7 @@ function inferTypeFromValue(value) {
|
|
|
7275
7876
|
function readConstraints(constraintsMap) {
|
|
7276
7877
|
const out = {};
|
|
7277
7878
|
for (const [name, value] of constraintsMap.entries()) {
|
|
7278
|
-
if (!(value instanceof
|
|
7879
|
+
if (!(value instanceof Y6.Map)) continue;
|
|
7279
7880
|
let fields = [];
|
|
7280
7881
|
const rawFields = value.get("fields");
|
|
7281
7882
|
if (typeof rawFields === "string") {
|
|
@@ -7294,7 +7895,7 @@ function readConstraints(constraintsMap) {
|
|
|
7294
7895
|
function readRelationships(relsMap) {
|
|
7295
7896
|
const out = {};
|
|
7296
7897
|
for (const [name, value] of relsMap.entries()) {
|
|
7297
|
-
if (!(value instanceof
|
|
7898
|
+
if (!(value instanceof Y6.Map)) continue;
|
|
7298
7899
|
const rel = {};
|
|
7299
7900
|
for (const [k, v] of value.entries()) {
|
|
7300
7901
|
rel[k] = v;
|
|
@@ -7305,6 +7906,7 @@ function readRelationships(relsMap) {
|
|
|
7305
7906
|
}
|
|
7306
7907
|
var CAMEL_TO_SNAKE = {
|
|
7307
7908
|
autoAssign: "auto_assign",
|
|
7909
|
+
autoStamp: "auto_stamp",
|
|
7308
7910
|
maxLength: "max_length",
|
|
7309
7911
|
maxCount: "max_count",
|
|
7310
7912
|
relatedIdField: "related_id_field",
|
|
@@ -7335,6 +7937,7 @@ function schemaToToml(schema) {
|
|
|
7335
7937
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
7336
7938
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
7337
7939
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7940
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
7338
7941
|
if (field.indexed) lines.push("indexed = true");
|
|
7339
7942
|
if (field.unique) lines.push("unique = true");
|
|
7340
7943
|
if (field.required) lines.push("required = true");
|
|
@@ -7381,10 +7984,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
7381
7984
|
"unique",
|
|
7382
7985
|
"required",
|
|
7383
7986
|
"auto_assign",
|
|
7987
|
+
"auto_stamp",
|
|
7384
7988
|
"max_length",
|
|
7385
7989
|
"max_count",
|
|
7386
|
-
"default"
|
|
7990
|
+
"default",
|
|
7991
|
+
"enum"
|
|
7387
7992
|
]);
|
|
7993
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
7388
7994
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7389
7995
|
"fields",
|
|
7390
7996
|
"relationships",
|
|
@@ -7426,9 +8032,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
7426
8032
|
if (raw.unique === true) opts.unique = true;
|
|
7427
8033
|
if (raw.required === true) opts.required = true;
|
|
7428
8034
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
8035
|
+
if (raw.auto_stamp !== void 0) {
|
|
8036
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
8037
|
+
throw new Error(
|
|
8038
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
8039
|
+
...VALID_AUTO_STAMP_VALUES
|
|
8040
|
+
].join(", ")}`
|
|
8041
|
+
);
|
|
8042
|
+
}
|
|
8043
|
+
opts.autoStamp = raw.auto_stamp;
|
|
8044
|
+
}
|
|
7429
8045
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
7430
8046
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
7431
8047
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
8048
|
+
if (raw.enum !== void 0) {
|
|
8049
|
+
if (raw.type !== "string") {
|
|
8050
|
+
throw new Error(
|
|
8051
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
8052
|
+
);
|
|
8053
|
+
}
|
|
8054
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
8055
|
+
throw new Error(
|
|
8056
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
8057
|
+
);
|
|
8058
|
+
}
|
|
8059
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
8060
|
+
throw new Error(
|
|
8061
|
+
`${context}: \`enum\` values must all be strings.`
|
|
8062
|
+
);
|
|
8063
|
+
}
|
|
8064
|
+
opts.enum = [...raw.enum];
|
|
8065
|
+
}
|
|
7432
8066
|
return opts;
|
|
7433
8067
|
}
|
|
7434
8068
|
function requireField(raw, field, context) {
|
|
@@ -7557,18 +8191,19 @@ async function loadSchemaFromToml(filePath, options = {}) {
|
|
|
7557
8191
|
|
|
7558
8192
|
// src/node.ts
|
|
7559
8193
|
init_metaSync();
|
|
8194
|
+
init_writeLifecycle();
|
|
7560
8195
|
|
|
7561
8196
|
// src/utils/yDocDump.ts
|
|
7562
|
-
var
|
|
8197
|
+
var Y7 = __toESM(require("yjs"), 1);
|
|
7563
8198
|
function toPlain(value) {
|
|
7564
|
-
if (value instanceof
|
|
8199
|
+
if (value instanceof Y7.Map) {
|
|
7565
8200
|
const obj = {};
|
|
7566
8201
|
value.forEach((v, k) => {
|
|
7567
8202
|
obj[k] = toPlain(v);
|
|
7568
8203
|
});
|
|
7569
8204
|
return obj;
|
|
7570
8205
|
}
|
|
7571
|
-
if (value instanceof
|
|
8206
|
+
if (value instanceof Y7.Array) {
|
|
7572
8207
|
return value.toArray().map((item) => toPlain(item));
|
|
7573
8208
|
}
|
|
7574
8209
|
return value;
|
|
@@ -7577,7 +8212,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
|
|
|
7577
8212
|
const result = {};
|
|
7578
8213
|
const includeIndexes = !!options.includeIndexes;
|
|
7579
8214
|
yDoc.share.forEach((type, name) => {
|
|
7580
|
-
if (!(type instanceof
|
|
8215
|
+
if (!(type instanceof Y7.Map)) return;
|
|
7581
8216
|
if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
|
|
7582
8217
|
const records = {};
|
|
7583
8218
|
type.forEach((recordValue, recordId) => {
|
|
@@ -7638,31 +8273,45 @@ async function getRecommendedNodeEngine() {
|
|
|
7638
8273
|
SqljsEngine,
|
|
7639
8274
|
StringSet,
|
|
7640
8275
|
UniqueConstraintViolationError,
|
|
8276
|
+
applyAutoStampsFromSchema,
|
|
8277
|
+
applyDefaultsFromSchema,
|
|
8278
|
+
applyStringSetChange,
|
|
8279
|
+
applyStringSetDelta,
|
|
8280
|
+
assertNever,
|
|
7641
8281
|
attachAndRegisterModel,
|
|
7642
8282
|
attachSchemaToClass,
|
|
7643
8283
|
autoRegisterModel,
|
|
7644
8284
|
clearMetaSyncCache,
|
|
7645
8285
|
constants,
|
|
7646
8286
|
createModelClass,
|
|
8287
|
+
decodeFunctionDefault,
|
|
7647
8288
|
defineModelSchema,
|
|
7648
8289
|
detectEnvironment,
|
|
7649
8290
|
discoverModelNames,
|
|
7650
8291
|
discoverSchema,
|
|
7651
8292
|
dumpYDocToPlain,
|
|
8293
|
+
ensureStringSetYMap,
|
|
7652
8294
|
features,
|
|
8295
|
+
fieldWriteKind,
|
|
7653
8296
|
generateULID,
|
|
7654
8297
|
getRecommendedNodeEngine,
|
|
7655
8298
|
inferFieldType,
|
|
7656
8299
|
initJsBao,
|
|
7657
8300
|
isBrowser,
|
|
8301
|
+
isKnownFieldType,
|
|
7658
8302
|
isNode,
|
|
7659
8303
|
isNodeModuleAvailable,
|
|
7660
8304
|
loadSchemaFromToml,
|
|
7661
8305
|
loadSchemaFromTomlString,
|
|
8306
|
+
parseStringSetOp,
|
|
7662
8307
|
registerFunctionDefault,
|
|
8308
|
+
registerFunctionDefaultGenerator,
|
|
8309
|
+
replaceStringSet,
|
|
7663
8310
|
resetJsBao,
|
|
8311
|
+
resolveDefaultValue,
|
|
7664
8312
|
schemaToToml,
|
|
7665
8313
|
summarizePlainYDoc,
|
|
7666
8314
|
syncInferredMeta,
|
|
7667
|
-
syncModelMeta
|
|
8315
|
+
syncModelMeta,
|
|
8316
|
+
writeField
|
|
7668
8317
|
});
|