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.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,244 @@ 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 assertNever(x) {
|
|
1235
|
+
throw new Error(`Unhandled field type: ${String(x)}`);
|
|
1236
|
+
}
|
|
1237
|
+
function fieldWriteKind(type) {
|
|
1238
|
+
switch (type) {
|
|
1239
|
+
case "string":
|
|
1240
|
+
case "number":
|
|
1241
|
+
case "boolean":
|
|
1242
|
+
case "date":
|
|
1243
|
+
case "id":
|
|
1244
|
+
return "scalar";
|
|
1245
|
+
case "stringset":
|
|
1246
|
+
return "stringset";
|
|
1247
|
+
default:
|
|
1248
|
+
return assertNever(type);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function isKnownFieldType(type) {
|
|
1252
|
+
return KNOWN_FIELD_TYPES.has(type);
|
|
1253
|
+
}
|
|
1254
|
+
function ensureStringSetYMap(rm, fieldName) {
|
|
1255
|
+
const nested = rm.get(fieldName);
|
|
1256
|
+
if (nested instanceof Y2.Map) return nested;
|
|
1257
|
+
const fresh = new Y2.Map();
|
|
1258
|
+
if (nested && typeof nested === "object") {
|
|
1259
|
+
for (const [member, marker] of Object.entries(
|
|
1260
|
+
nested
|
|
1261
|
+
)) {
|
|
1262
|
+
if (Boolean(marker)) fresh.set(member, true);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
rm.set(fieldName, fresh);
|
|
1266
|
+
return fresh;
|
|
1267
|
+
}
|
|
1268
|
+
function applyStringSetDelta(target, delta) {
|
|
1269
|
+
if (delta.clear) {
|
|
1270
|
+
for (const member of Array.from(target.keys())) target.delete(member);
|
|
1271
|
+
}
|
|
1272
|
+
if (delta.remove) {
|
|
1273
|
+
for (const member of delta.remove) target.delete(String(member));
|
|
1274
|
+
}
|
|
1275
|
+
if (delta.add) {
|
|
1276
|
+
for (const member of delta.add) {
|
|
1277
|
+
const key = String(member);
|
|
1278
|
+
if (target.get(key) !== true) target.set(key, true);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
function replaceStringSet(rm, fieldName, members) {
|
|
1283
|
+
const target = ensureStringSetYMap(rm, fieldName);
|
|
1284
|
+
const desired = /* @__PURE__ */ new Set();
|
|
1285
|
+
for (const member of members) desired.add(String(member));
|
|
1286
|
+
for (const existing of Array.from(target.keys())) {
|
|
1287
|
+
if (!desired.has(existing)) target.delete(existing);
|
|
1288
|
+
}
|
|
1289
|
+
for (const member of desired) {
|
|
1290
|
+
if (target.get(member) !== true) target.set(member, true);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
function applyStringSetChange(recordYMap, fieldName, change) {
|
|
1294
|
+
const target = ensureStringSetYMap(recordYMap, fieldName);
|
|
1295
|
+
applyStringSetDelta(target, {
|
|
1296
|
+
add: change.additions,
|
|
1297
|
+
remove: change.removals
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
function parseStringSetOp(value) {
|
|
1301
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Y2.AbstractType) {
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
const keys = Object.keys(value);
|
|
1305
|
+
const opKeys = keys.filter((k) => k.startsWith("$"));
|
|
1306
|
+
if (opKeys.length === 0) return null;
|
|
1307
|
+
const known = /* @__PURE__ */ new Set(["$add", "$remove", "$clear"]);
|
|
1308
|
+
const unknown = keys.filter((k) => !known.has(k));
|
|
1309
|
+
if (unknown.length > 0) {
|
|
1310
|
+
throw new Error(
|
|
1311
|
+
`Invalid stringset op: unexpected key(s) [${unknown.join(", ")}]. Only $add, $remove and $clear are supported.`
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
const obj = value;
|
|
1315
|
+
const toMembers = (raw, opName) => {
|
|
1316
|
+
if (raw === void 0) return [];
|
|
1317
|
+
if (!Array.isArray(raw)) {
|
|
1318
|
+
throw new Error(`Invalid stringset op: ${opName} must be an array.`);
|
|
1319
|
+
}
|
|
1320
|
+
return raw.map((m) => String(m));
|
|
1321
|
+
};
|
|
1322
|
+
return {
|
|
1323
|
+
add: toMembers(obj.$add, "$add"),
|
|
1324
|
+
remove: toMembers(obj.$remove, "$remove"),
|
|
1325
|
+
clear: obj.$clear === true
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function writeField(rm, declaredType, key, value, modelName) {
|
|
1329
|
+
const kind = declaredType && isKnownFieldType(declaredType) ? fieldWriteKind(declaredType) : "scalar";
|
|
1330
|
+
if (Array.isArray(value)) {
|
|
1331
|
+
if (kind !== "stringset") {
|
|
1332
|
+
throw new Error(
|
|
1333
|
+
`Cannot write array value to field "${key}" on model "${modelName}": its declared type could not be resolved to a collection (stringset). Collection fields require schema metadata in the document (_meta_${modelName}); the model must be saved by a client at least once before a workflow writes one of its collection fields.`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
replaceStringSet(rm, key, value);
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const op = parseStringSetOp(value);
|
|
1340
|
+
if (op) {
|
|
1341
|
+
if (kind !== "stringset") {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
`Cannot apply a stringset op ({ $add/$remove/$clear }) to field "${key}" on model "${modelName}": its declared type is ${declaredType ? `"${declaredType}"` : "unresolved"}, not a collection (stringset). Op-objects are only valid for stringset / refersToMany fields with schema metadata (_meta_${modelName}).`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
applyStringSetDelta(ensureStringSetYMap(rm, key), op);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
if (kind === "stringset") {
|
|
1350
|
+
throw new Error(
|
|
1351
|
+
`Field "${key}" on model "${modelName}" is a stringset and requires an array (replace) or { $add/$remove/$clear } (delta) value; received ${value === null ? "null" : typeof value}.`
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
rm.set(key, value);
|
|
1355
|
+
}
|
|
1356
|
+
function registerFunctionDefaultGenerator(name, fn) {
|
|
1357
|
+
FUNCTION_DEFAULT_GENERATORS.set(name, fn);
|
|
1358
|
+
}
|
|
1359
|
+
function decodeFunctionDefault(name) {
|
|
1360
|
+
return FUNCTION_DEFAULT_GENERATORS.get(name);
|
|
1361
|
+
}
|
|
1362
|
+
function resolveLiteralOrFunctionDefault(rawDefault) {
|
|
1363
|
+
return typeof rawDefault === "function" ? rawDefault() : rawDefault;
|
|
1364
|
+
}
|
|
1365
|
+
function resolveDefaultValue(rawDefault, opts = {}) {
|
|
1366
|
+
if (typeof rawDefault === "function") return rawDefault();
|
|
1367
|
+
if (typeof rawDefault === "string" && rawDefault.startsWith("$")) {
|
|
1368
|
+
if (rawDefault.startsWith("$$")) return rawDefault.slice(1);
|
|
1369
|
+
const name = rawDefault.slice(1);
|
|
1370
|
+
const gen = opts.decode?.(name) ?? decodeFunctionDefault(name);
|
|
1371
|
+
if (!gen) {
|
|
1372
|
+
throw new Error(
|
|
1373
|
+
`Unknown function default "${rawDefault}" for field "${opts.fieldName ?? "?"}"${opts.modelName ? ` on model "${opts.modelName}"` : ""}: no generator is registered for "${name}". Register it via registerFunctionDefaultGenerator("${name}", fn) so the server can populate the field the same way the client does.`
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
return gen();
|
|
1377
|
+
}
|
|
1378
|
+
return rawDefault;
|
|
1379
|
+
}
|
|
1380
|
+
function applyDefaultsFromSchema(fields, data, opts = {}) {
|
|
1381
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
1382
|
+
if (fieldName === "id") continue;
|
|
1383
|
+
if (!field || field.default === void 0) continue;
|
|
1384
|
+
if (field.default === UNKNOWN_FUNCTION_DEFAULT) {
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
if (Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== void 0) {
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
data[fieldName] = resolveDefaultValue(field.default, {
|
|
1391
|
+
fieldName,
|
|
1392
|
+
modelName: opts.modelName,
|
|
1393
|
+
decode: opts.decodeFunctionDefault
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
return data;
|
|
1397
|
+
}
|
|
1398
|
+
function applyAutoStampsFromSchema(fields, data, opts) {
|
|
1399
|
+
const { isNew, now } = opts;
|
|
1400
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
1401
|
+
const stamp = field?.autoStamp;
|
|
1402
|
+
if (!stamp) continue;
|
|
1403
|
+
const explicit = Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== null && data[fieldName] !== void 0;
|
|
1404
|
+
if (explicit) continue;
|
|
1405
|
+
if (stamp === "create" && !isNew) continue;
|
|
1406
|
+
data[fieldName] = now;
|
|
1407
|
+
}
|
|
1408
|
+
return data;
|
|
1409
|
+
}
|
|
1410
|
+
var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
|
|
1411
|
+
var init_writeLifecycle = __esm({
|
|
1412
|
+
"src/models/writeLifecycle.ts"() {
|
|
1413
|
+
"use strict";
|
|
1414
|
+
KNOWN_FIELD_TYPE_MAP = {
|
|
1415
|
+
string: true,
|
|
1416
|
+
number: true,
|
|
1417
|
+
boolean: true,
|
|
1418
|
+
date: true,
|
|
1419
|
+
id: true,
|
|
1420
|
+
stringset: true
|
|
1421
|
+
};
|
|
1422
|
+
KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
|
|
1423
|
+
FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
|
|
1424
|
+
registerFunctionDefaultGenerator("generate_ulid", () => ulid());
|
|
1425
|
+
UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
// src/models/metaSync.ts
|
|
1430
|
+
import * as Y3 from "yjs";
|
|
1166
1431
|
function inferFieldType(value) {
|
|
1167
|
-
if (value
|
|
1432
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
1168
1433
|
switch (typeof value) {
|
|
1169
1434
|
case "string":
|
|
1170
1435
|
return "string";
|
|
@@ -1183,7 +1448,10 @@ function encodeDefault(value) {
|
|
|
1183
1448
|
if (value === void 0 || value === null) return void 0;
|
|
1184
1449
|
if (typeof value === "function") {
|
|
1185
1450
|
const name = KNOWN_FUNCTION_DEFAULTS.get(value);
|
|
1186
|
-
return name ? `$${name}` :
|
|
1451
|
+
return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
|
|
1452
|
+
}
|
|
1453
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
1454
|
+
return `$${value}`;
|
|
1187
1455
|
}
|
|
1188
1456
|
return value;
|
|
1189
1457
|
}
|
|
@@ -1209,7 +1477,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1209
1477
|
if (compoundConstraints.length > 0) {
|
|
1210
1478
|
let constraints = meta.get("_constraints");
|
|
1211
1479
|
if (!constraints) {
|
|
1212
|
-
constraints = new
|
|
1480
|
+
constraints = new Y3.Map();
|
|
1213
1481
|
meta.set("_constraints", constraints);
|
|
1214
1482
|
}
|
|
1215
1483
|
for (const constraint of compoundConstraints) {
|
|
@@ -1220,7 +1488,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1220
1488
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
1221
1489
|
let rels = meta.get("_relationships");
|
|
1222
1490
|
if (!rels) {
|
|
1223
|
-
rels = new
|
|
1491
|
+
rels = new Y3.Map();
|
|
1224
1492
|
meta.set("_relationships", rels);
|
|
1225
1493
|
}
|
|
1226
1494
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -1232,7 +1500,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1232
1500
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
1233
1501
|
let fieldMeta = metaMap.get(fieldName);
|
|
1234
1502
|
if (!fieldMeta) {
|
|
1235
|
-
fieldMeta = new
|
|
1503
|
+
fieldMeta = new Y3.Map();
|
|
1236
1504
|
metaMap.set(fieldName, fieldMeta);
|
|
1237
1505
|
}
|
|
1238
1506
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -1240,6 +1508,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1240
1508
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
1241
1509
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
1242
1510
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
1511
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
1243
1512
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
1244
1513
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
1245
1514
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -1248,7 +1517,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1248
1517
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
1249
1518
|
let cMeta = constraintsMap.get(constraint.name);
|
|
1250
1519
|
if (!cMeta) {
|
|
1251
|
-
cMeta = new
|
|
1520
|
+
cMeta = new Y3.Map();
|
|
1252
1521
|
constraintsMap.set(constraint.name, cMeta);
|
|
1253
1522
|
}
|
|
1254
1523
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -1258,7 +1527,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
1258
1527
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
1259
1528
|
let relMeta = relsMap.get(relName);
|
|
1260
1529
|
if (!relMeta) {
|
|
1261
|
-
relMeta = new
|
|
1530
|
+
relMeta = new Y3.Map();
|
|
1262
1531
|
relsMap.set(relName, relMeta);
|
|
1263
1532
|
}
|
|
1264
1533
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -1273,7 +1542,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
1273
1542
|
if (fieldName.startsWith("_")) continue;
|
|
1274
1543
|
let fieldMeta = meta.get(fieldName);
|
|
1275
1544
|
if (!fieldMeta) {
|
|
1276
|
-
fieldMeta = new
|
|
1545
|
+
fieldMeta = new Y3.Map();
|
|
1277
1546
|
meta.set(fieldName, fieldMeta);
|
|
1278
1547
|
}
|
|
1279
1548
|
if (!fieldMeta.has("type")) {
|
|
@@ -1293,12 +1562,52 @@ var KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
|
1293
1562
|
var init_metaSync = __esm({
|
|
1294
1563
|
"src/models/metaSync.ts"() {
|
|
1295
1564
|
"use strict";
|
|
1565
|
+
init_yjsValues();
|
|
1566
|
+
init_writeLifecycle();
|
|
1296
1567
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
1297
1568
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
1298
1569
|
}
|
|
1299
1570
|
});
|
|
1300
1571
|
|
|
1572
|
+
// src/utils/uniqueIndex.ts
|
|
1573
|
+
import * as Y4 from "yjs";
|
|
1574
|
+
function buildUniqueKey(recordData, fields) {
|
|
1575
|
+
const keyParts = [];
|
|
1576
|
+
for (const field of fields) {
|
|
1577
|
+
const value = recordData instanceof Y4.Map ? recordData.get(field) : recordData[field];
|
|
1578
|
+
if (value === null || value === void 0) {
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
keyParts.push(String(value));
|
|
1582
|
+
}
|
|
1583
|
+
return keyParts.join("|");
|
|
1584
|
+
}
|
|
1585
|
+
function uniqueIndexMapName(modelName, constraintName) {
|
|
1586
|
+
return `_uniqueIdx_${modelName}_${constraintName}`;
|
|
1587
|
+
}
|
|
1588
|
+
function fieldUniqueConstraintName(modelName, fieldName) {
|
|
1589
|
+
return `${modelName}_${fieldName}_unique`;
|
|
1590
|
+
}
|
|
1591
|
+
var init_uniqueIndex = __esm({
|
|
1592
|
+
"src/utils/uniqueIndex.ts"() {
|
|
1593
|
+
"use strict";
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1301
1597
|
// src/models/relationshipManager.ts
|
|
1598
|
+
function warnIfOrderFieldNullable(modelClass, orderByField, relationshipDescription) {
|
|
1599
|
+
if (ALWAYS_PRESENT_ORDER_FIELDS.has(orderByField)) return;
|
|
1600
|
+
const schema = modelClass.getSchema?.();
|
|
1601
|
+
const fields = schema?.fields;
|
|
1602
|
+
if (!fields || typeof fields.get !== "function") return;
|
|
1603
|
+
const fieldOptions = fields.get(orderByField);
|
|
1604
|
+
if (!fieldOptions) return;
|
|
1605
|
+
if (fieldOptions.required !== true) {
|
|
1606
|
+
Logger.warn(
|
|
1607
|
+
`[RelationshipManager] ${relationshipDescription} orders by '${orderByField}', which is not marked required on ${modelClass.name}. The composite (${orderByField}, id) cursor paginates deterministically, but a row whose '${orderByField}' is actually NULL has no stable cursor position and may drop from the tail. Mark '${orderByField}' as required, or order by a required field such as 'id', to remove the risk. (Full NULL-ordering support is tracked in #1316.)`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1302
1611
|
function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
1303
1612
|
return async function() {
|
|
1304
1613
|
const relatedId = this[config.relatedIdField];
|
|
@@ -1307,6 +1616,16 @@ function generateRefersToMethod(_sourceModelClass, config, targetModelClass) {
|
|
|
1307
1616
|
};
|
|
1308
1617
|
}
|
|
1309
1618
|
function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbEngine) {
|
|
1619
|
+
const {
|
|
1620
|
+
relatedIdField,
|
|
1621
|
+
orderByField = "id",
|
|
1622
|
+
orderDirection = "ASC"
|
|
1623
|
+
} = config;
|
|
1624
|
+
warnIfOrderFieldNullable(
|
|
1625
|
+
targetModelClass,
|
|
1626
|
+
orderByField,
|
|
1627
|
+
`hasMany(${targetModelClass.name})`
|
|
1628
|
+
);
|
|
1310
1629
|
return async function(paginationArgs) {
|
|
1311
1630
|
const {
|
|
1312
1631
|
limit,
|
|
@@ -1314,71 +1633,39 @@ function generateHasManyMethod(_sourceModelClass, config, targetModelClass, _dbE
|
|
|
1314
1633
|
beforeCursor,
|
|
1315
1634
|
direction = "forward"
|
|
1316
1635
|
} = paginationArgs || {};
|
|
1317
|
-
const {
|
|
1318
|
-
relatedIdField,
|
|
1319
|
-
orderByField = "id",
|
|
1320
|
-
orderDirection = "ASC"
|
|
1321
|
-
} = config;
|
|
1322
1636
|
const targetSchema = targetModelClass.getSchema();
|
|
1323
1637
|
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
1324
1638
|
Logger.error(
|
|
1325
1639
|
`[RelationshipManager] Target model ${targetModelClass.name} for hasMany relationship has no valid schema name.`
|
|
1326
1640
|
);
|
|
1327
|
-
return { data: [],
|
|
1641
|
+
return { data: [], hasMore: false };
|
|
1328
1642
|
}
|
|
1329
|
-
const
|
|
1643
|
+
const forward = direction !== "backward";
|
|
1644
|
+
const cursor = forward ? afterCursor : beforeCursor;
|
|
1330
1645
|
const queryOptions = {
|
|
1331
|
-
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 }
|
|
1646
|
+
sort: { [orderByField]: orderDirection === "ASC" ? 1 : -1 },
|
|
1647
|
+
direction: forward ? 1 : -1
|
|
1332
1648
|
};
|
|
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 };
|
|
1649
|
+
if (limit !== void 0) queryOptions.limit = limit;
|
|
1650
|
+
if (cursor != null) queryOptions.uniqueStartKey = cursor;
|
|
1651
|
+
return targetModelClass.query(
|
|
1652
|
+
{ [relatedIdField]: this.id },
|
|
1653
|
+
queryOptions
|
|
1654
|
+
);
|
|
1379
1655
|
};
|
|
1380
1656
|
}
|
|
1381
1657
|
function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClass, joinModelClass, _dbEngine) {
|
|
1658
|
+
const {
|
|
1659
|
+
joinModelLocalField,
|
|
1660
|
+
joinModelRelatedField,
|
|
1661
|
+
joinModelOrderByField = "id",
|
|
1662
|
+
joinModelOrderDirection = "ASC"
|
|
1663
|
+
} = config;
|
|
1664
|
+
warnIfOrderFieldNullable(
|
|
1665
|
+
joinModelClass,
|
|
1666
|
+
joinModelOrderByField,
|
|
1667
|
+
`hasManyThrough(${targetModelClass.name} via ${joinModelClass.name})`
|
|
1668
|
+
);
|
|
1382
1669
|
return async function(paginationArgs) {
|
|
1383
1670
|
const {
|
|
1384
1671
|
limit,
|
|
@@ -1392,87 +1679,42 @@ function generateHasManyThroughMethod(_sourceModelClass, config, targetModelClas
|
|
|
1392
1679
|
Logger.error(
|
|
1393
1680
|
`[RelationshipManager] Join model ${joinModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
1394
1681
|
);
|
|
1395
|
-
return { data: [],
|
|
1682
|
+
return { data: [], hasMore: false };
|
|
1396
1683
|
}
|
|
1397
1684
|
if (!targetSchema || !targetSchema.options || !targetSchema.options.name) {
|
|
1398
1685
|
Logger.error(
|
|
1399
1686
|
`[RelationshipManager] Target model ${targetModelClass.name} for hasManyThrough relationship has no valid schema name.`
|
|
1400
1687
|
);
|
|
1401
|
-
return { data: [],
|
|
1688
|
+
return { data: [], hasMore: false };
|
|
1402
1689
|
}
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
joinModelRelatedField,
|
|
1406
|
-
joinModelOrderByField = "id",
|
|
1407
|
-
joinModelOrderDirection = "ASC"
|
|
1408
|
-
} = config;
|
|
1409
|
-
const joinFilter = { [joinModelLocalField]: this.id };
|
|
1690
|
+
const forward = direction !== "backward";
|
|
1691
|
+
const cursor = forward ? afterCursor : beforeCursor;
|
|
1410
1692
|
const joinQueryOptions = {
|
|
1411
1693
|
sort: {
|
|
1412
1694
|
[joinModelOrderByField]: joinModelOrderDirection === "ASC" ? 1 : -1
|
|
1413
1695
|
},
|
|
1414
|
-
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 }
|
|
1696
|
+
projection: { [joinModelRelatedField]: 1, [joinModelOrderByField]: 1 },
|
|
1697
|
+
direction: forward ? 1 : -1
|
|
1415
1698
|
};
|
|
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
|
-
}
|
|
1699
|
+
if (limit !== void 0) joinQueryOptions.limit = limit;
|
|
1700
|
+
if (cursor != null) joinQueryOptions.uniqueStartKey = cursor;
|
|
1433
1701
|
const joinResult = await joinModelClass.query(
|
|
1434
|
-
|
|
1702
|
+
{ [joinModelLocalField]: this.id },
|
|
1435
1703
|
joinQueryOptions
|
|
1436
1704
|
);
|
|
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
|
-
}
|
|
1705
|
+
const relatedIds = joinResult.data.map((r) => r[joinModelRelatedField]).filter((id) => id != null && typeof id === "string");
|
|
1706
|
+
let targetData = [];
|
|
1707
|
+
if (relatedIds.length > 0) {
|
|
1708
|
+
const targetResult = await targetModelClass.query({
|
|
1709
|
+
id: { $in: relatedIds }
|
|
1710
|
+
});
|
|
1711
|
+
targetData = targetResult.data;
|
|
1465
1712
|
}
|
|
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
1713
|
return {
|
|
1473
|
-
data:
|
|
1474
|
-
nextCursor:
|
|
1475
|
-
prevCursor:
|
|
1714
|
+
data: targetData,
|
|
1715
|
+
nextCursor: joinResult.nextCursor,
|
|
1716
|
+
prevCursor: joinResult.prevCursor,
|
|
1717
|
+
hasMore: joinResult.hasMore
|
|
1476
1718
|
};
|
|
1477
1719
|
};
|
|
1478
1720
|
}
|
|
@@ -1596,10 +1838,12 @@ function generateHasManyThroughRemoveMethod(_sourceModelClass, config, _targetMo
|
|
|
1596
1838
|
}, `remove-join-${config.joinModel}-${this.id}-${targetId}`);
|
|
1597
1839
|
};
|
|
1598
1840
|
}
|
|
1841
|
+
var ALWAYS_PRESENT_ORDER_FIELDS;
|
|
1599
1842
|
var init_relationshipManager = __esm({
|
|
1600
1843
|
"src/models/relationshipManager.ts"() {
|
|
1601
1844
|
"use strict";
|
|
1602
1845
|
init_BaseModel();
|
|
1846
|
+
ALWAYS_PRESENT_ORDER_FIELDS = /* @__PURE__ */ new Set(["id", "type"]);
|
|
1603
1847
|
}
|
|
1604
1848
|
});
|
|
1605
1849
|
|
|
@@ -2370,7 +2614,13 @@ var init_IncludeResolver = __esm({
|
|
|
2370
2614
|
}
|
|
2371
2615
|
return null;
|
|
2372
2616
|
}
|
|
2373
|
-
/**
|
|
2617
|
+
/**
|
|
2618
|
+
* Apply a projection to a single record. Assumes the projection was already
|
|
2619
|
+
* validated upstream (mixed inclusion+exclusion rejected by the translator):
|
|
2620
|
+
* `isInclusion` treats "any value === 1" as inclusion mode. `id` and `type`
|
|
2621
|
+
* are system-identity fields and are always kept in BOTH modes (mirrors the
|
|
2622
|
+
* DatabaseDO `_applyProjection`); `_meta` stays excludable.
|
|
2623
|
+
*/
|
|
2374
2624
|
_applyProjection(record, projection) {
|
|
2375
2625
|
const fields = Object.entries(projection);
|
|
2376
2626
|
if (fields.length === 0) return record;
|
|
@@ -2389,7 +2639,7 @@ var init_IncludeResolver = __esm({
|
|
|
2389
2639
|
} else {
|
|
2390
2640
|
const result = { ...record };
|
|
2391
2641
|
for (const [f, v] of fields) {
|
|
2392
|
-
if (v === 0) delete result[f];
|
|
2642
|
+
if (v === 0 && f !== "id" && f !== "type") delete result[f];
|
|
2393
2643
|
}
|
|
2394
2644
|
return result;
|
|
2395
2645
|
}
|
|
@@ -2408,10 +2658,10 @@ __export(BaseModel_exports, {
|
|
|
2408
2658
|
UniqueConstraintViolationError: () => UniqueConstraintViolationError,
|
|
2409
2659
|
generateULID: () => generateULID
|
|
2410
2660
|
});
|
|
2411
|
-
import * as
|
|
2412
|
-
import { ulid } from "ulid";
|
|
2661
|
+
import * as Y5 from "yjs";
|
|
2662
|
+
import { ulid as ulid2 } from "ulid";
|
|
2413
2663
|
function generateULID() {
|
|
2414
|
-
return
|
|
2664
|
+
return ulid2();
|
|
2415
2665
|
}
|
|
2416
2666
|
var LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
|
|
2417
2667
|
var init_BaseModel = __esm({
|
|
@@ -2423,6 +2673,9 @@ var init_BaseModel = __esm({
|
|
|
2423
2673
|
init_CursorManager();
|
|
2424
2674
|
init_sql();
|
|
2425
2675
|
init_metaSync();
|
|
2676
|
+
init_writeLifecycle();
|
|
2677
|
+
init_yjsValues();
|
|
2678
|
+
init_uniqueIndex();
|
|
2426
2679
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
2427
2680
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
2428
2681
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2807,7 +3060,9 @@ var init_BaseModel = __esm({
|
|
|
2807
3060
|
this.setValue(fieldKey, data[fieldKey]);
|
|
2808
3061
|
this._constructorProvidedFields.add(fieldKey);
|
|
2809
3062
|
} else if (fieldOptions.default !== void 0) {
|
|
2810
|
-
const defaultValue =
|
|
3063
|
+
const defaultValue = resolveLiteralOrFunctionDefault(
|
|
3064
|
+
fieldOptions.default
|
|
3065
|
+
);
|
|
2811
3066
|
this.setValue(fieldKey, defaultValue);
|
|
2812
3067
|
}
|
|
2813
3068
|
}
|
|
@@ -2931,7 +3186,30 @@ var init_BaseModel = __esm({
|
|
|
2931
3186
|
if (verboseEnabled) {
|
|
2932
3187
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2933
3188
|
}
|
|
2934
|
-
return fieldValue;
|
|
3189
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
3190
|
+
}
|
|
3191
|
+
/**
|
|
3192
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
3193
|
+
*
|
|
3194
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
3195
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
3196
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
3197
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
3198
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
3199
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
3200
|
+
*/
|
|
3201
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
3202
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
3203
|
+
if (fieldOptions?.type === "stringset") {
|
|
3204
|
+
return value;
|
|
3205
|
+
}
|
|
3206
|
+
if (isYjsComposite(value)) {
|
|
3207
|
+
Logger.warn(
|
|
3208
|
+
`[${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.`
|
|
3209
|
+
);
|
|
3210
|
+
return normalizeYjsValue(value);
|
|
3211
|
+
}
|
|
3212
|
+
return value;
|
|
2935
3213
|
}
|
|
2936
3214
|
getValue(fieldKey) {
|
|
2937
3215
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -3076,7 +3354,7 @@ var init_BaseModel = __esm({
|
|
|
3076
3354
|
}
|
|
3077
3355
|
getStringSetFromYjs(fieldName) {
|
|
3078
3356
|
const yjsData = this.getFromYjs(fieldName);
|
|
3079
|
-
if (yjsData instanceof
|
|
3357
|
+
if (yjsData instanceof Y5.Map) {
|
|
3080
3358
|
return Array.from(yjsData.keys());
|
|
3081
3359
|
}
|
|
3082
3360
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -3184,7 +3462,7 @@ var init_BaseModel = __esm({
|
|
|
3184
3462
|
}
|
|
3185
3463
|
const extractStringSetValues = (value) => {
|
|
3186
3464
|
if (!value) return [];
|
|
3187
|
-
if (value instanceof
|
|
3465
|
+
if (value instanceof Y5.Map) {
|
|
3188
3466
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
3189
3467
|
}
|
|
3190
3468
|
if (Array.isArray(value)) {
|
|
@@ -3201,7 +3479,7 @@ var init_BaseModel = __esm({
|
|
|
3201
3479
|
if (!recordId) continue;
|
|
3202
3480
|
let itemData;
|
|
3203
3481
|
const stringSetValuesByField = {};
|
|
3204
|
-
if (recordData instanceof
|
|
3482
|
+
if (recordData instanceof Y5.Map) {
|
|
3205
3483
|
itemData = {};
|
|
3206
3484
|
const unknownFields = [];
|
|
3207
3485
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3317,20 +3595,10 @@ var init_BaseModel = __esm({
|
|
|
3317
3595
|
Logger.verbose(
|
|
3318
3596
|
`[${this.name}] Setting up YMap observer for document ${docId}/${modelName}...`
|
|
3319
3597
|
);
|
|
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
|
-
};
|
|
3598
|
+
const buildUniqueKey2 = (recordData, fields) => buildUniqueKey(recordData, fields);
|
|
3331
3599
|
const extractItemData = (key, recordData) => {
|
|
3332
3600
|
let itemData;
|
|
3333
|
-
if (recordData instanceof
|
|
3601
|
+
if (recordData instanceof Y5.Map) {
|
|
3334
3602
|
itemData = {};
|
|
3335
3603
|
const unknownFields = [];
|
|
3336
3604
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3343,7 +3611,14 @@ var init_BaseModel = __esm({
|
|
|
3343
3611
|
continue;
|
|
3344
3612
|
}
|
|
3345
3613
|
if (value !== void 0) {
|
|
3346
|
-
|
|
3614
|
+
if (isYjsComposite(value)) {
|
|
3615
|
+
Logger.warn(
|
|
3616
|
+
`[${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.`
|
|
3617
|
+
);
|
|
3618
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3619
|
+
} else {
|
|
3620
|
+
itemData[fieldKey] = value;
|
|
3621
|
+
}
|
|
3347
3622
|
}
|
|
3348
3623
|
}
|
|
3349
3624
|
if (unknownFields.length > 0) {
|
|
@@ -3366,7 +3641,14 @@ var init_BaseModel = __esm({
|
|
|
3366
3641
|
continue;
|
|
3367
3642
|
}
|
|
3368
3643
|
if (value !== void 0) {
|
|
3369
|
-
|
|
3644
|
+
if (isYjsComposite(value)) {
|
|
3645
|
+
Logger.warn(
|
|
3646
|
+
`[${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.`
|
|
3647
|
+
);
|
|
3648
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3649
|
+
} else {
|
|
3650
|
+
filteredData[fieldKey] = value;
|
|
3651
|
+
}
|
|
3370
3652
|
}
|
|
3371
3653
|
}
|
|
3372
3654
|
if (unknownFields.length > 0) {
|
|
@@ -3400,7 +3682,7 @@ var init_BaseModel = __esm({
|
|
|
3400
3682
|
const recordsByUniqueKey = /* @__PURE__ */ new Map();
|
|
3401
3683
|
for (const [recordId, recordData] of allRecords.entries()) {
|
|
3402
3684
|
if (recordIdsToDiscard.has(recordId)) continue;
|
|
3403
|
-
const uniqueKey =
|
|
3685
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3404
3686
|
if (uniqueKey === null) continue;
|
|
3405
3687
|
if (!recordsByUniqueKey.has(uniqueKey)) {
|
|
3406
3688
|
recordsByUniqueKey.set(uniqueKey, []);
|
|
@@ -3446,7 +3728,7 @@ var init_BaseModel = __esm({
|
|
|
3446
3728
|
if (!recordData || !key) continue;
|
|
3447
3729
|
const itemData = extractItemData(key, recordData);
|
|
3448
3730
|
if (!itemData) continue;
|
|
3449
|
-
if (change.action === "add" && recordData instanceof
|
|
3731
|
+
if (change.action === "add" && recordData instanceof Y5.Map) {
|
|
3450
3732
|
Logger.verbose(
|
|
3451
3733
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
3452
3734
|
);
|
|
@@ -3544,9 +3826,12 @@ var init_BaseModel = __esm({
|
|
|
3544
3826
|
const recordData = documentYMap.get(idToDiscard);
|
|
3545
3827
|
if (!recordData) continue;
|
|
3546
3828
|
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
3547
|
-
const uniqueKey =
|
|
3829
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3548
3830
|
if (uniqueKey === null) continue;
|
|
3549
|
-
const constraintMapName =
|
|
3831
|
+
const constraintMapName = uniqueIndexMapName(
|
|
3832
|
+
modelName,
|
|
3833
|
+
constraint.name
|
|
3834
|
+
);
|
|
3550
3835
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
3551
3836
|
const currentIndexValue = constraintMap.get(uniqueKey);
|
|
3552
3837
|
if (currentIndexValue === idToDiscard) {
|
|
@@ -3564,11 +3849,14 @@ var init_BaseModel = __esm({
|
|
|
3564
3849
|
}, `conflict-resolution-${modelName}-${docId}`);
|
|
3565
3850
|
yDoc.transact(() => {
|
|
3566
3851
|
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
3567
|
-
const constraintMapName =
|
|
3852
|
+
const constraintMapName = uniqueIndexMapName(
|
|
3853
|
+
modelName,
|
|
3854
|
+
constraint.name
|
|
3855
|
+
);
|
|
3568
3856
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
3569
3857
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3570
3858
|
if (!recordData) continue;
|
|
3571
|
-
const uniqueKey =
|
|
3859
|
+
const uniqueKey = buildUniqueKey2(recordData, constraint.fields);
|
|
3572
3860
|
if (uniqueKey === null) continue;
|
|
3573
3861
|
const currentIndexValue = constraintMap.get(uniqueKey);
|
|
3574
3862
|
if (currentIndexValue !== recordId) {
|
|
@@ -3591,7 +3879,7 @@ var init_BaseModel = __esm({
|
|
|
3591
3879
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
3592
3880
|
);
|
|
3593
3881
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3594
|
-
if (recordData instanceof
|
|
3882
|
+
if (recordData instanceof Y5.Map) {
|
|
3595
3883
|
Logger.verbose(
|
|
3596
3884
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
3597
3885
|
);
|
|
@@ -3776,6 +4064,11 @@ var init_BaseModel = __esm({
|
|
|
3776
4064
|
Logger.debug(
|
|
3777
4065
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3778
4066
|
);
|
|
4067
|
+
if (isYjsComposite(yjsValue)) {
|
|
4068
|
+
Logger.warn(
|
|
4069
|
+
`[${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).`
|
|
4070
|
+
);
|
|
4071
|
+
}
|
|
3779
4072
|
if (yjsValue === void 0) {
|
|
3780
4073
|
Logger.debug(
|
|
3781
4074
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3812,26 +4105,7 @@ var init_BaseModel = __esm({
|
|
|
3812
4105
|
* if the field hasn't been touched since the wire-format change in #561.
|
|
3813
4106
|
*/
|
|
3814
4107
|
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
|
-
}
|
|
4108
|
+
applyStringSetChange(recordYMap, fieldName, change);
|
|
3835
4109
|
}
|
|
3836
4110
|
/**
|
|
3837
4111
|
* Deep equality check for comparing field values
|
|
@@ -4001,6 +4275,29 @@ var init_BaseModel = __esm({
|
|
|
4001
4275
|
);
|
|
4002
4276
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
4003
4277
|
}
|
|
4278
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
4279
|
+
{
|
|
4280
|
+
const existingRecord = targetYMap.get(this.id);
|
|
4281
|
+
const isNew = !existingRecord;
|
|
4282
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
4283
|
+
const stamp = fieldOptions.autoStamp;
|
|
4284
|
+
if (!stamp) continue;
|
|
4285
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
4286
|
+
if (callerExplicit) {
|
|
4287
|
+
const localValue = this._localChanges[fieldKey];
|
|
4288
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
4289
|
+
}
|
|
4290
|
+
if (stamp === "create") {
|
|
4291
|
+
if (!isNew) continue;
|
|
4292
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
4293
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
4294
|
+
}
|
|
4295
|
+
this[fieldKey] = Date.now();
|
|
4296
|
+
if (stamp === "create") {
|
|
4297
|
+
preTransactCreateStamps.add(fieldKey);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4004
4301
|
this.validateBeforeSave();
|
|
4005
4302
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
4006
4303
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -4010,6 +4307,34 @@ var init_BaseModel = __esm({
|
|
|
4010
4307
|
`[${modelName}.save] getCurrentJSState() returned:`,
|
|
4011
4308
|
dataToSave
|
|
4012
4309
|
);
|
|
4310
|
+
const uniqueMissConflicts = /* @__PURE__ */ new Map();
|
|
4311
|
+
for (const constraint of schema.resolvedUniqueConstraints) {
|
|
4312
|
+
const newUniqueKey = this._buildUniqueKey(
|
|
4313
|
+
constraint.fields,
|
|
4314
|
+
dataToSave,
|
|
4315
|
+
modelName,
|
|
4316
|
+
constraint.name
|
|
4317
|
+
);
|
|
4318
|
+
if (newUniqueKey === null) {
|
|
4319
|
+
continue;
|
|
4320
|
+
}
|
|
4321
|
+
const constraintMap = yDoc.getMap(
|
|
4322
|
+
uniqueIndexMapName(modelName, constraint.name)
|
|
4323
|
+
);
|
|
4324
|
+
if (!constraintMap.get(newUniqueKey)) {
|
|
4325
|
+
const constraintKeyValues = constraint.fields.map(
|
|
4326
|
+
(field) => dataToSave[field]
|
|
4327
|
+
);
|
|
4328
|
+
const conflictingRecord = await modelConstructor._resolveByUniqueFallback(
|
|
4329
|
+
constraint,
|
|
4330
|
+
constraintKeyValues,
|
|
4331
|
+
{ excludeId: this.id }
|
|
4332
|
+
);
|
|
4333
|
+
if (conflictingRecord) {
|
|
4334
|
+
uniqueMissConflicts.set(constraint.name, conflictingRecord.id);
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4013
4338
|
await yDoc.transact(async () => {
|
|
4014
4339
|
if (upsertOnConstraint && upsertOnKey) {
|
|
4015
4340
|
const constraintMapName = `_uniqueIdx_${modelName}_${upsertOnConstraint.name}`;
|
|
@@ -4031,6 +4356,14 @@ var init_BaseModel = __esm({
|
|
|
4031
4356
|
}
|
|
4032
4357
|
}
|
|
4033
4358
|
}
|
|
4359
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
4360
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
4361
|
+
delete this._localChanges[fieldKey];
|
|
4362
|
+
}
|
|
4363
|
+
if (fieldKey in dataToSave) {
|
|
4364
|
+
delete dataToSave[fieldKey];
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4034
4367
|
}
|
|
4035
4368
|
}
|
|
4036
4369
|
if (!targetYMap) {
|
|
@@ -4043,7 +4376,7 @@ var init_BaseModel = __esm({
|
|
|
4043
4376
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
4044
4377
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
4045
4378
|
if (!recordYMap) {
|
|
4046
|
-
recordYMap = new
|
|
4379
|
+
recordYMap = new Y5.Map();
|
|
4047
4380
|
Logger.verbose(
|
|
4048
4381
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
4049
4382
|
);
|
|
@@ -4095,7 +4428,10 @@ var init_BaseModel = __esm({
|
|
|
4095
4428
|
);
|
|
4096
4429
|
continue;
|
|
4097
4430
|
}
|
|
4098
|
-
const constraintMapName =
|
|
4431
|
+
const constraintMapName = uniqueIndexMapName(
|
|
4432
|
+
modelName,
|
|
4433
|
+
constraint.name
|
|
4434
|
+
);
|
|
4099
4435
|
const constraintMap = yDoc.getMap(constraintMapName);
|
|
4100
4436
|
Logger.verbose(
|
|
4101
4437
|
`[${modelName}] Save Check (Item '${this.id}'): GET from '${constraintMapName}' with Key '${newUniqueKey.substring(
|
|
@@ -4124,6 +4460,26 @@ var init_BaseModel = __esm({
|
|
|
4124
4460
|
this.id,
|
|
4125
4461
|
existingRecordIdWithNewKey
|
|
4126
4462
|
);
|
|
4463
|
+
} else if (!existingRecordIdWithNewKey) {
|
|
4464
|
+
const conflictingRecordId = uniqueMissConflicts.get(constraint.name);
|
|
4465
|
+
if (conflictingRecordId && conflictingRecordId !== this.id) {
|
|
4466
|
+
Logger.verbose(
|
|
4467
|
+
`[${modelName}] Save Check (Item '${this.id}'): index miss for '${constraint.name}', but engine fallback found conflicting index-less record ${conflictingRecordId}.`
|
|
4468
|
+
);
|
|
4469
|
+
throw new UniqueConstraintViolationError(
|
|
4470
|
+
`Unique constraint '${constraint.name}' violated for model '${modelName}' on fields [${constraint.fields.join(
|
|
4471
|
+
", "
|
|
4472
|
+
)}]. Attempted ID: ${this.id}. Value(s) starting with '${newUniqueKey.substring(
|
|
4473
|
+
0,
|
|
4474
|
+
50
|
|
4475
|
+
)}...' already exist for index-less record ID ${conflictingRecordId}.`,
|
|
4476
|
+
modelName,
|
|
4477
|
+
constraint.name,
|
|
4478
|
+
constraint.fields,
|
|
4479
|
+
this.id,
|
|
4480
|
+
conflictingRecordId
|
|
4481
|
+
);
|
|
4482
|
+
}
|
|
4127
4483
|
}
|
|
4128
4484
|
}
|
|
4129
4485
|
if (isUpdate && oldData) {
|
|
@@ -4467,34 +4823,46 @@ var init_BaseModel = __esm({
|
|
|
4467
4823
|
const modelName = schema.options.name;
|
|
4468
4824
|
Logger.verbose(`[${modelName}] Executing query:`, { filter, options });
|
|
4469
4825
|
const hasIncludes = options?.include && options.include.length > 0;
|
|
4470
|
-
const
|
|
4826
|
+
const requestedLimit = options?.limit;
|
|
4827
|
+
const overFetch = typeof requestedLimit === "number" && requestedLimit > 0;
|
|
4828
|
+
let translatorOptions = hasIncludes && options?.projection ? { ...options, projection: void 0 } : options;
|
|
4829
|
+
if (overFetch) {
|
|
4830
|
+
translatorOptions = {
|
|
4831
|
+
...translatorOptions || {},
|
|
4832
|
+
limit: requestedLimit + 1
|
|
4833
|
+
};
|
|
4834
|
+
}
|
|
4471
4835
|
const translator = new DocumentQueryTranslator(modelName, schema.fields);
|
|
4472
4836
|
const translatedQuery = translator.translateFind(filter, translatorOptions);
|
|
4473
4837
|
Logger.verbose(`[${modelName}] Translated SQL:`, translatedQuery.sql);
|
|
4474
4838
|
Logger.verbose(`[${modelName}] SQL params:`, translatedQuery.params);
|
|
4475
|
-
|
|
4839
|
+
let results = await _BaseModel.dbInstance.query(
|
|
4476
4840
|
translatedQuery.sql,
|
|
4477
4841
|
translatedQuery.params
|
|
4478
4842
|
);
|
|
4479
4843
|
Logger.verbose(`[${modelName}] Query returned ${results.length} results`);
|
|
4844
|
+
let hasMore = false;
|
|
4845
|
+
if (overFetch && results.length > requestedLimit) {
|
|
4846
|
+
hasMore = true;
|
|
4847
|
+
results = results.slice(0, requestedLimit);
|
|
4848
|
+
}
|
|
4480
4849
|
if (options?.include && options.include.length > 0) {
|
|
4481
4850
|
const { IncludeResolver: IncludeResolver2 } = await Promise.resolve().then(() => (init_IncludeResolver(), IncludeResolver_exports));
|
|
4482
4851
|
const resolver = new IncludeResolver2(_BaseModel.dbInstance);
|
|
4483
4852
|
await resolver.resolve(results, options.include, 0, modelName);
|
|
4484
4853
|
}
|
|
4485
|
-
const requestedLimit = options?.limit;
|
|
4486
|
-
const hasMore = CursorManager.hasMoreResults(
|
|
4487
|
-
requestedLimit,
|
|
4488
|
-
results.length
|
|
4489
|
-
);
|
|
4490
4854
|
const isFirstPage = !options?.uniqueStartKey;
|
|
4855
|
+
const direction = options?.direction || 1;
|
|
4491
4856
|
const cursors = CursorManager.generateResultCursors(
|
|
4492
4857
|
results,
|
|
4493
4858
|
translatedQuery.sortFields,
|
|
4494
|
-
|
|
4859
|
+
direction,
|
|
4495
4860
|
hasMore,
|
|
4496
4861
|
isFirstPage
|
|
4497
4862
|
);
|
|
4863
|
+
if (direction === -1) {
|
|
4864
|
+
results.reverse();
|
|
4865
|
+
}
|
|
4498
4866
|
let transformedData;
|
|
4499
4867
|
if (options?.projection && Object.keys(options.projection).length > 0) {
|
|
4500
4868
|
transformedData = results.map((row) => {
|
|
@@ -5070,7 +5438,7 @@ var init_BaseModel = __esm({
|
|
|
5070
5438
|
);
|
|
5071
5439
|
}
|
|
5072
5440
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
5073
|
-
if (recordYMap instanceof
|
|
5441
|
+
if (recordYMap instanceof Y5.Map) {
|
|
5074
5442
|
const instance = new this({ id: recordId });
|
|
5075
5443
|
instance._metaDocId = docId;
|
|
5076
5444
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -5095,6 +5463,50 @@ var init_BaseModel = __esm({
|
|
|
5095
5463
|
}
|
|
5096
5464
|
return allInstances;
|
|
5097
5465
|
}
|
|
5466
|
+
/**
|
|
5467
|
+
* Resolve a record by its unique-constraint field values via an engine query.
|
|
5468
|
+
* Used as a fallback when the `_uniqueIdx_<model>_<constraint>` map has no
|
|
5469
|
+
* entry for a key.
|
|
5470
|
+
*
|
|
5471
|
+
* The index map is a cache over the SQLite projection, not the source of
|
|
5472
|
+
* truth: every record is loaded into the projection on `initializeForDocument`
|
|
5473
|
+
* regardless of index presence, but a record written by a client predating
|
|
5474
|
+
* index maintenance (legacy data) is missing from the index. Such a record
|
|
5475
|
+
* silently escapes both `upsertByUnique` lookups and the `save()` uniqueness
|
|
5476
|
+
* check. A query over the constraint fields finds it reliably.
|
|
5477
|
+
*
|
|
5478
|
+
* Contract: RESOLVE ONLY. Returns the owning record (with `_metaDocId` set to
|
|
5479
|
+
* the document that holds it, via `queryOne`) or `null`. Callers decide
|
|
5480
|
+
* whether to heal the missing index entry (write paths) and do their own
|
|
5481
|
+
* logging — this method never mutates the index, so read paths stay
|
|
5482
|
+
* read-only. Mirrors `buildUniqueKey`: a null/undefined value in any
|
|
5483
|
+
* constraint field means the record does not participate in the constraint,
|
|
5484
|
+
* so no query runs.
|
|
5485
|
+
*/
|
|
5486
|
+
static async _resolveByUniqueFallback(constraint, keyValues, options) {
|
|
5487
|
+
if (!_BaseModel.dbInstance) {
|
|
5488
|
+
return null;
|
|
5489
|
+
}
|
|
5490
|
+
const filter = {};
|
|
5491
|
+
for (let i = 0; i < constraint.fields.length; i++) {
|
|
5492
|
+
const value = keyValues[i];
|
|
5493
|
+
if (value === null || value === void 0) {
|
|
5494
|
+
return null;
|
|
5495
|
+
}
|
|
5496
|
+
filter[constraint.fields[i]] = value;
|
|
5497
|
+
}
|
|
5498
|
+
if (options?.excludeId) {
|
|
5499
|
+
if ("id" in filter) {
|
|
5500
|
+
if (filter.id === options.excludeId) {
|
|
5501
|
+
return null;
|
|
5502
|
+
}
|
|
5503
|
+
} else {
|
|
5504
|
+
filter.id = { $ne: options.excludeId };
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
const found = await this.queryOne(filter);
|
|
5508
|
+
return found ?? null;
|
|
5509
|
+
}
|
|
5098
5510
|
static async findByUnique(constraintName, value) {
|
|
5099
5511
|
const modelConstructor = this;
|
|
5100
5512
|
const schema = modelConstructor.getSchema();
|
|
@@ -5133,7 +5545,10 @@ var init_BaseModel = __esm({
|
|
|
5133
5545
|
const documentInfo = modelConstructor.connectedDocuments.get(docId);
|
|
5134
5546
|
if (!documentInfo) continue;
|
|
5135
5547
|
const yDoc = documentInfo.yDoc;
|
|
5136
|
-
const constraintMapName2 =
|
|
5548
|
+
const constraintMapName2 = uniqueIndexMapName(
|
|
5549
|
+
currentModelName,
|
|
5550
|
+
constraint.name
|
|
5551
|
+
);
|
|
5137
5552
|
const constraintMap2 = yDoc.getMap(constraintMapName2);
|
|
5138
5553
|
const recordId2 = constraintMap2.get(uniqueKeyString);
|
|
5139
5554
|
if (recordId2) {
|
|
@@ -5141,7 +5556,7 @@ var init_BaseModel = __esm({
|
|
|
5141
5556
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
5142
5557
|
if (documentYMap) {
|
|
5143
5558
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
5144
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
5559
|
+
if (recordYMap2 && recordYMap2 instanceof Y5.Map) {
|
|
5145
5560
|
const instance = new modelConstructor({
|
|
5146
5561
|
id: recordId2
|
|
5147
5562
|
});
|
|
@@ -5155,6 +5570,16 @@ var init_BaseModel = __esm({
|
|
|
5155
5570
|
}
|
|
5156
5571
|
}
|
|
5157
5572
|
}
|
|
5573
|
+
const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
|
|
5574
|
+
constraint,
|
|
5575
|
+
keyValues
|
|
5576
|
+
);
|
|
5577
|
+
if (fallbackRecord) {
|
|
5578
|
+
Logger.verbose(
|
|
5579
|
+
`[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
|
|
5580
|
+
);
|
|
5581
|
+
return fallbackRecord;
|
|
5582
|
+
}
|
|
5158
5583
|
Logger.verbose(
|
|
5159
5584
|
`[${currentModelName}.findByUnique] Record not found in any connected document`
|
|
5160
5585
|
);
|
|
@@ -5168,17 +5593,30 @@ var init_BaseModel = __esm({
|
|
|
5168
5593
|
`[${currentModelName}] No Y.Doc is connected for this model. Connect a document via initJsBao(...).connectDocument or call initializeForDocument(yDoc, db, docId, permissionHint) before using legacy findByUnique.`
|
|
5169
5594
|
);
|
|
5170
5595
|
}
|
|
5171
|
-
const constraintMapName =
|
|
5596
|
+
const constraintMapName = uniqueIndexMapName(
|
|
5597
|
+
currentModelName,
|
|
5598
|
+
constraint.name
|
|
5599
|
+
);
|
|
5172
5600
|
const constraintMap = legacyDocInfo.yDoc.getMap(constraintMapName);
|
|
5173
5601
|
const recordId = constraintMap.get(uniqueKeyString);
|
|
5174
5602
|
if (!recordId) {
|
|
5603
|
+
const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
|
|
5604
|
+
constraint,
|
|
5605
|
+
keyValues
|
|
5606
|
+
);
|
|
5607
|
+
if (fallbackRecord) {
|
|
5608
|
+
Logger.verbose(
|
|
5609
|
+
`[${currentModelName}.findByUnique] Index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id} (legacy index-less record; index left unchanged on read path).`
|
|
5610
|
+
);
|
|
5611
|
+
return fallbackRecord;
|
|
5612
|
+
}
|
|
5175
5613
|
return null;
|
|
5176
5614
|
}
|
|
5177
5615
|
const recordYMap = legacyDocumentMap.get(recordId);
|
|
5178
5616
|
if (!recordYMap) {
|
|
5179
5617
|
return null;
|
|
5180
5618
|
}
|
|
5181
|
-
if (recordYMap instanceof
|
|
5619
|
+
if (recordYMap instanceof Y5.Map) {
|
|
5182
5620
|
const instance = new modelConstructor({
|
|
5183
5621
|
id: recordId
|
|
5184
5622
|
});
|
|
@@ -5236,7 +5674,10 @@ var init_BaseModel = __esm({
|
|
|
5236
5674
|
`[${currentModelName}] upsertByUnique for '${constraintName}': unique key cannot be built due to null/undefined values. Upsert cannot proceed reliably.`
|
|
5237
5675
|
);
|
|
5238
5676
|
}
|
|
5239
|
-
const constraintMapName =
|
|
5677
|
+
const constraintMapName = uniqueIndexMapName(
|
|
5678
|
+
currentModelName,
|
|
5679
|
+
constraint.name
|
|
5680
|
+
);
|
|
5240
5681
|
let existingRecord = null;
|
|
5241
5682
|
if (connectedDocuments && connectedDocuments.size > 0) {
|
|
5242
5683
|
for (const docId of connectedDocuments.keys()) {
|
|
@@ -5254,6 +5695,33 @@ var init_BaseModel = __esm({
|
|
|
5254
5695
|
}
|
|
5255
5696
|
} else {
|
|
5256
5697
|
}
|
|
5698
|
+
if (!existingRecord) {
|
|
5699
|
+
const fallbackRecord = await modelConstructor._resolveByUniqueFallback(
|
|
5700
|
+
constraint,
|
|
5701
|
+
keyValuesForLookup
|
|
5702
|
+
);
|
|
5703
|
+
if (fallbackRecord) {
|
|
5704
|
+
existingRecord = fallbackRecord;
|
|
5705
|
+
const healDocId = fallbackRecord._metaDocId;
|
|
5706
|
+
const healDocInfo = healDocId ? connectedDocuments.get(healDocId) : void 0;
|
|
5707
|
+
const canHeal = !!healDocInfo && healDocInfo.permissionHint !== "read";
|
|
5708
|
+
if (healDocInfo && canHeal) {
|
|
5709
|
+
healDocInfo.yDoc.transact(() => {
|
|
5710
|
+
const healMap = healDocInfo.yDoc.getMap(constraintMapName);
|
|
5711
|
+
if (healMap.get(uniqueKeyString) !== fallbackRecord.id) {
|
|
5712
|
+
healMap.set(uniqueKeyString, fallbackRecord.id);
|
|
5713
|
+
}
|
|
5714
|
+
}, `heal-unique-index-${currentModelName}-${healDocId}`);
|
|
5715
|
+
Logger.verbose(
|
|
5716
|
+
`[${currentModelName}] upsertByUnique: index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id}; healed missing index entry (legacy index-less record).`
|
|
5717
|
+
);
|
|
5718
|
+
} else {
|
|
5719
|
+
Logger.verbose(
|
|
5720
|
+
`[${currentModelName}] upsertByUnique: index miss for '${constraintName}' resolved via engine fallback to record ${fallbackRecord.id}; skipped index heal (owning document is read-only or not connected).`
|
|
5721
|
+
);
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5257
5725
|
if (options?.objectMustExist) {
|
|
5258
5726
|
if (!existingRecord) {
|
|
5259
5727
|
throw new RecordNotFoundError(
|
|
@@ -5410,7 +5878,7 @@ var init_BaseModel = __esm({
|
|
|
5410
5878
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5411
5879
|
if (fieldOptions?.type !== "stringset") continue;
|
|
5412
5880
|
const value = recordYMap.get(fieldKey);
|
|
5413
|
-
if (value instanceof
|
|
5881
|
+
if (value instanceof Y5.Map) {
|
|
5414
5882
|
this.observeStringSetMapOnce(value);
|
|
5415
5883
|
}
|
|
5416
5884
|
}
|
|
@@ -5457,7 +5925,14 @@ var init_BaseModel = __esm({
|
|
|
5457
5925
|
continue;
|
|
5458
5926
|
}
|
|
5459
5927
|
if (value !== void 0) {
|
|
5460
|
-
|
|
5928
|
+
if (isYjsComposite(value)) {
|
|
5929
|
+
Logger.warn(
|
|
5930
|
+
`[${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.`
|
|
5931
|
+
);
|
|
5932
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5933
|
+
} else {
|
|
5934
|
+
updatedData[key] = value;
|
|
5935
|
+
}
|
|
5461
5936
|
}
|
|
5462
5937
|
}
|
|
5463
5938
|
if (unknownFields.length > 0) {
|
|
@@ -5537,7 +6012,14 @@ var init_BaseModel = __esm({
|
|
|
5537
6012
|
continue;
|
|
5538
6013
|
}
|
|
5539
6014
|
if (value !== void 0) {
|
|
5540
|
-
|
|
6015
|
+
if (isYjsComposite(value)) {
|
|
6016
|
+
Logger.warn(
|
|
6017
|
+
`[${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.`
|
|
6018
|
+
);
|
|
6019
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
6020
|
+
} else {
|
|
6021
|
+
updatedData[key] = value;
|
|
6022
|
+
}
|
|
5541
6023
|
}
|
|
5542
6024
|
}
|
|
5543
6025
|
if (unknownFields.length > 0) {
|
|
@@ -5581,16 +6063,170 @@ var init_BaseModel = __esm({
|
|
|
5581
6063
|
}
|
|
5582
6064
|
});
|
|
5583
6065
|
|
|
6066
|
+
// src/utils/environment.ts
|
|
6067
|
+
var environment_exports = {};
|
|
6068
|
+
__export(environment_exports, {
|
|
6069
|
+
constants: () => constants,
|
|
6070
|
+
detectEnvironment: () => detectEnvironment,
|
|
6071
|
+
features: () => features,
|
|
6072
|
+
isBrowser: () => isBrowser,
|
|
6073
|
+
isNode: () => isNode,
|
|
6074
|
+
isNodeModuleAvailable: () => isNodeModuleAvailable
|
|
6075
|
+
});
|
|
6076
|
+
function detectEnvironment() {
|
|
6077
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
6078
|
+
return "node";
|
|
6079
|
+
}
|
|
6080
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
6081
|
+
return "browser";
|
|
6082
|
+
}
|
|
6083
|
+
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
6084
|
+
return "browser";
|
|
6085
|
+
}
|
|
6086
|
+
return "unknown";
|
|
6087
|
+
}
|
|
6088
|
+
function isNode() {
|
|
6089
|
+
return detectEnvironment() === "node";
|
|
6090
|
+
}
|
|
6091
|
+
function isBrowser() {
|
|
6092
|
+
return detectEnvironment() === "browser";
|
|
6093
|
+
}
|
|
6094
|
+
async function isNodeModuleAvailable(moduleName) {
|
|
6095
|
+
if (!isNode()) {
|
|
6096
|
+
return false;
|
|
6097
|
+
}
|
|
6098
|
+
try {
|
|
6099
|
+
await import(
|
|
6100
|
+
/* @vite-ignore */
|
|
6101
|
+
moduleName
|
|
6102
|
+
);
|
|
6103
|
+
return true;
|
|
6104
|
+
} catch {
|
|
6105
|
+
return false;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
var features, constants;
|
|
6109
|
+
var init_environment = __esm({
|
|
6110
|
+
"src/utils/environment.ts"() {
|
|
6111
|
+
"use strict";
|
|
6112
|
+
features = {
|
|
6113
|
+
get hasWebWorkers() {
|
|
6114
|
+
return isBrowser() && typeof Worker !== "undefined";
|
|
6115
|
+
},
|
|
6116
|
+
get hasFileSystem() {
|
|
6117
|
+
return isNode();
|
|
6118
|
+
},
|
|
6119
|
+
get hasWebAssembly() {
|
|
6120
|
+
return typeof WebAssembly !== "undefined";
|
|
6121
|
+
},
|
|
6122
|
+
async hasBetterSqlite3() {
|
|
6123
|
+
return await isNodeModuleAvailable("better-sqlite3");
|
|
6124
|
+
}
|
|
6125
|
+
};
|
|
6126
|
+
constants = {
|
|
6127
|
+
DEFAULT_NODE_SQLITE_PATH: ":memory:",
|
|
6128
|
+
DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6131
|
+
});
|
|
6132
|
+
|
|
5584
6133
|
// src/engines/SqljsEngine.ts
|
|
5585
6134
|
import initSqlJs from "sql.js";
|
|
5586
6135
|
import { Mutex } from "async-mutex";
|
|
5587
|
-
|
|
6136
|
+
async function resolveNodeWasmPath() {
|
|
6137
|
+
if (!isNode()) {
|
|
6138
|
+
return null;
|
|
6139
|
+
}
|
|
6140
|
+
try {
|
|
6141
|
+
const cjsRequire = typeof __require !== "undefined" ? __require : void 0;
|
|
6142
|
+
if (cjsRequire) {
|
|
6143
|
+
return cjsRequire.resolve("sql.js/dist/sql-wasm.wasm");
|
|
6144
|
+
}
|
|
6145
|
+
} catch {
|
|
6146
|
+
}
|
|
6147
|
+
try {
|
|
6148
|
+
const metaUrl = import.meta.url;
|
|
6149
|
+
if (metaUrl) {
|
|
6150
|
+
const { createRequire } = await import(
|
|
6151
|
+
/* @vite-ignore */
|
|
6152
|
+
"module"
|
|
6153
|
+
);
|
|
6154
|
+
const req = createRequire(metaUrl);
|
|
6155
|
+
return req.resolve("sql.js/dist/sql-wasm.wasm");
|
|
6156
|
+
}
|
|
6157
|
+
} catch {
|
|
6158
|
+
}
|
|
6159
|
+
return null;
|
|
6160
|
+
}
|
|
6161
|
+
async function preflightNodeWasm(wasmPath) {
|
|
6162
|
+
if (!isNode()) {
|
|
6163
|
+
return;
|
|
6164
|
+
}
|
|
6165
|
+
if (/^[a-z]+:\/\//i.test(wasmPath)) {
|
|
6166
|
+
return;
|
|
6167
|
+
}
|
|
6168
|
+
const { existsSync } = await import(
|
|
6169
|
+
/* @vite-ignore */
|
|
6170
|
+
"fs"
|
|
6171
|
+
);
|
|
6172
|
+
if (!existsSync(wasmPath)) {
|
|
6173
|
+
throw new Error(
|
|
6174
|
+
`[SqljsEngine] sql.js wasm not found at "${wasmPath}". Pass \`locateFile\`/\`wasmURL\` pointing at sql-wasm.wasm, or install \`better-sqlite3\` and use the \`node-sqlite\` engine (see getRecommendedNodeEngine()).`
|
|
6175
|
+
);
|
|
6176
|
+
}
|
|
6177
|
+
}
|
|
6178
|
+
async function loadSqlJsModule(locateFile) {
|
|
6179
|
+
let abortReject;
|
|
6180
|
+
const toError = (reason) => reason instanceof Error ? reason : new Error(
|
|
6181
|
+
`[SqljsEngine] sql.js aborted during initialization: ${String(
|
|
6182
|
+
reason
|
|
6183
|
+
)}`
|
|
6184
|
+
);
|
|
6185
|
+
const nodeProcess = isNode() && typeof process !== "undefined" ? process : void 0;
|
|
6186
|
+
const onUnhandled = (reason) => {
|
|
6187
|
+
const err = toError(reason);
|
|
6188
|
+
if (/Aborted/.test(err.message) || /Aborted/.test(String(reason))) {
|
|
6189
|
+
abortReject?.(err);
|
|
6190
|
+
return;
|
|
6191
|
+
}
|
|
6192
|
+
if (nodeProcess && nodeProcess.listenerCount("unhandledRejection") === 1) {
|
|
6193
|
+
nodeProcess.removeListener("unhandledRejection", onUnhandled);
|
|
6194
|
+
void Promise.reject(reason);
|
|
6195
|
+
}
|
|
6196
|
+
};
|
|
6197
|
+
if (nodeProcess) {
|
|
6198
|
+
nodeProcess.on("unhandledRejection", onUnhandled);
|
|
6199
|
+
}
|
|
6200
|
+
const abortGuard = new Promise((_resolve, reject) => {
|
|
6201
|
+
abortReject = reject;
|
|
6202
|
+
});
|
|
6203
|
+
abortGuard.catch(() => {
|
|
6204
|
+
});
|
|
6205
|
+
try {
|
|
6206
|
+
const initPromise = initSqlJs({
|
|
6207
|
+
locateFile,
|
|
6208
|
+
// Layer 1: emscripten onAbort.
|
|
6209
|
+
onAbort: (reason) => abortReject?.(toError(reason))
|
|
6210
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6211
|
+
});
|
|
6212
|
+
return await Promise.race([initPromise, abortGuard]);
|
|
6213
|
+
} finally {
|
|
6214
|
+
if (nodeProcess) {
|
|
6215
|
+
setTimeout(() => {
|
|
6216
|
+
nodeProcess.removeListener("unhandledRejection", onUnhandled);
|
|
6217
|
+
}, 0);
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
var SQL_WASM_URL, SQL_WASM_FILENAME, TransactionalSQLJSOperations, SqljsEngine;
|
|
5588
6222
|
var init_SqljsEngine = __esm({
|
|
5589
6223
|
"src/engines/SqljsEngine.ts"() {
|
|
5590
6224
|
"use strict";
|
|
5591
6225
|
init_BaseModel();
|
|
5592
6226
|
init_sql();
|
|
6227
|
+
init_environment();
|
|
5593
6228
|
SQL_WASM_URL = "/sql-wasm.wasm";
|
|
6229
|
+
SQL_WASM_FILENAME = "sql-wasm.wasm";
|
|
5594
6230
|
TransactionalSQLJSOperations = class {
|
|
5595
6231
|
db;
|
|
5596
6232
|
engine;
|
|
@@ -5656,22 +6292,37 @@ var init_SqljsEngine = __esm({
|
|
|
5656
6292
|
}
|
|
5657
6293
|
async initialize(engineOptions) {
|
|
5658
6294
|
Logger.info("[SqljsEngine] Initializing SQL.js engine...");
|
|
6295
|
+
const loadingModuleThisCall = !_SqljsEngine.SQL;
|
|
5659
6296
|
try {
|
|
5660
6297
|
if (!_SqljsEngine.SQL) {
|
|
5661
|
-
|
|
6298
|
+
let locateFileConfig;
|
|
6299
|
+
if (engineOptions?.locateFile) {
|
|
6300
|
+
locateFileConfig = engineOptions.locateFile;
|
|
6301
|
+
} else if (engineOptions?.wasmURL) {
|
|
6302
|
+
const url = engineOptions.wasmURL;
|
|
6303
|
+
locateFileConfig = (_file) => url;
|
|
6304
|
+
} else {
|
|
6305
|
+
const nodeWasmPath = await resolveNodeWasmPath();
|
|
6306
|
+
const resolved = nodeWasmPath ?? SQL_WASM_URL;
|
|
6307
|
+
locateFileConfig = (_file) => resolved;
|
|
6308
|
+
}
|
|
6309
|
+
const wasmPath = locateFileConfig(SQL_WASM_FILENAME);
|
|
5662
6310
|
Logger.debug(
|
|
5663
6311
|
"[SqljsEngine] Attempting to load SQL.js WASM from:",
|
|
5664
|
-
|
|
6312
|
+
wasmPath
|
|
5665
6313
|
);
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
6314
|
+
await preflightNodeWasm(wasmPath);
|
|
6315
|
+
const SQL = await loadSqlJsModule(locateFileConfig);
|
|
6316
|
+
_SqljsEngine.SQL = SQL;
|
|
5669
6317
|
Logger.info("[SqljsEngine] SQL.js WASM loaded successfully.");
|
|
5670
6318
|
}
|
|
5671
6319
|
this.db = new _SqljsEngine.SQL.Database();
|
|
5672
6320
|
Logger.info("[SqljsEngine] SQL.js Database initialized in memory.");
|
|
5673
6321
|
this.updateTableNames();
|
|
5674
6322
|
} catch (error) {
|
|
6323
|
+
if (loadingModuleThisCall) {
|
|
6324
|
+
_SqljsEngine.SQL = null;
|
|
6325
|
+
}
|
|
5675
6326
|
Logger.error("[SqljsEngine] Error during SQL.js initialization:", error);
|
|
5676
6327
|
throw error;
|
|
5677
6328
|
}
|
|
@@ -5959,73 +6610,6 @@ var init_SqljsEngine = __esm({
|
|
|
5959
6610
|
}
|
|
5960
6611
|
});
|
|
5961
6612
|
|
|
5962
|
-
// src/utils/environment.ts
|
|
5963
|
-
var environment_exports = {};
|
|
5964
|
-
__export(environment_exports, {
|
|
5965
|
-
constants: () => constants,
|
|
5966
|
-
detectEnvironment: () => detectEnvironment,
|
|
5967
|
-
features: () => features,
|
|
5968
|
-
isBrowser: () => isBrowser,
|
|
5969
|
-
isNode: () => isNode,
|
|
5970
|
-
isNodeModuleAvailable: () => isNodeModuleAvailable
|
|
5971
|
-
});
|
|
5972
|
-
function detectEnvironment() {
|
|
5973
|
-
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
5974
|
-
return "node";
|
|
5975
|
-
}
|
|
5976
|
-
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
5977
|
-
return "browser";
|
|
5978
|
-
}
|
|
5979
|
-
if (typeof self !== "undefined" && typeof self.importScripts === "function") {
|
|
5980
|
-
return "browser";
|
|
5981
|
-
}
|
|
5982
|
-
return "unknown";
|
|
5983
|
-
}
|
|
5984
|
-
function isNode() {
|
|
5985
|
-
return detectEnvironment() === "node";
|
|
5986
|
-
}
|
|
5987
|
-
function isBrowser() {
|
|
5988
|
-
return detectEnvironment() === "browser";
|
|
5989
|
-
}
|
|
5990
|
-
async function isNodeModuleAvailable(moduleName) {
|
|
5991
|
-
if (!isNode()) {
|
|
5992
|
-
return false;
|
|
5993
|
-
}
|
|
5994
|
-
try {
|
|
5995
|
-
await import(
|
|
5996
|
-
/* @vite-ignore */
|
|
5997
|
-
moduleName
|
|
5998
|
-
);
|
|
5999
|
-
return true;
|
|
6000
|
-
} catch {
|
|
6001
|
-
return false;
|
|
6002
|
-
}
|
|
6003
|
-
}
|
|
6004
|
-
var features, constants;
|
|
6005
|
-
var init_environment = __esm({
|
|
6006
|
-
"src/utils/environment.ts"() {
|
|
6007
|
-
"use strict";
|
|
6008
|
-
features = {
|
|
6009
|
-
get hasWebWorkers() {
|
|
6010
|
-
return isBrowser() && typeof Worker !== "undefined";
|
|
6011
|
-
},
|
|
6012
|
-
get hasFileSystem() {
|
|
6013
|
-
return isNode();
|
|
6014
|
-
},
|
|
6015
|
-
get hasWebAssembly() {
|
|
6016
|
-
return typeof WebAssembly !== "undefined";
|
|
6017
|
-
},
|
|
6018
|
-
async hasBetterSqlite3() {
|
|
6019
|
-
return await isNodeModuleAvailable("better-sqlite3");
|
|
6020
|
-
}
|
|
6021
|
-
};
|
|
6022
|
-
constants = {
|
|
6023
|
-
DEFAULT_NODE_SQLITE_PATH: ":memory:",
|
|
6024
|
-
DEFAULT_BROWSER_WASM_PATH: "/sql-wasm.wasm"
|
|
6025
|
-
};
|
|
6026
|
-
}
|
|
6027
|
-
});
|
|
6028
|
-
|
|
6029
6613
|
// src/engines/node/NodeSqliteEngine.ts
|
|
6030
6614
|
var NodeSqliteEngine_exports = {};
|
|
6031
6615
|
__export(NodeSqliteEngine_exports, {
|
|
@@ -6969,6 +7553,7 @@ init_StringSet();
|
|
|
6969
7553
|
// src/models/schema.ts
|
|
6970
7554
|
init_BaseModel();
|
|
6971
7555
|
init_ModelRegistry();
|
|
7556
|
+
init_uniqueIndex();
|
|
6972
7557
|
function defineModelSchema(input) {
|
|
6973
7558
|
const { name, fields } = input;
|
|
6974
7559
|
const options = {
|
|
@@ -7041,7 +7626,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
7041
7626
|
for (const [fieldName, options] of fields.entries()) {
|
|
7042
7627
|
if (options.unique) {
|
|
7043
7628
|
resolved.push({
|
|
7044
|
-
name:
|
|
7629
|
+
name: fieldUniqueConstraintName(modelName, fieldName),
|
|
7045
7630
|
fields: [fieldName]
|
|
7046
7631
|
});
|
|
7047
7632
|
}
|
|
@@ -7105,7 +7690,8 @@ function attachAndRegisterModel(modelClass, schema) {
|
|
|
7105
7690
|
}
|
|
7106
7691
|
|
|
7107
7692
|
// src/utils/yDocSchema.ts
|
|
7108
|
-
|
|
7693
|
+
init_yjsValues();
|
|
7694
|
+
import * as Y6 from "yjs";
|
|
7109
7695
|
function discoverSchema(yDoc) {
|
|
7110
7696
|
const models = {};
|
|
7111
7697
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -7139,7 +7725,7 @@ function discoverModelNames(yDoc) {
|
|
|
7139
7725
|
function materializeMap(yDoc, key) {
|
|
7140
7726
|
try {
|
|
7141
7727
|
const map = yDoc.getMap(key);
|
|
7142
|
-
return map instanceof
|
|
7728
|
+
return map instanceof Y6.Map ? map : null;
|
|
7143
7729
|
} catch {
|
|
7144
7730
|
return null;
|
|
7145
7731
|
}
|
|
@@ -7149,11 +7735,11 @@ function readModelMeta(metaMap) {
|
|
|
7149
7735
|
let constraints;
|
|
7150
7736
|
let relationships;
|
|
7151
7737
|
for (const [key, value] of metaMap.entries()) {
|
|
7152
|
-
if (key === "_constraints" && value instanceof
|
|
7738
|
+
if (key === "_constraints" && value instanceof Y6.Map) {
|
|
7153
7739
|
constraints = readConstraints(value);
|
|
7154
|
-
} else if (key === "_relationships" && value instanceof
|
|
7740
|
+
} else if (key === "_relationships" && value instanceof Y6.Map) {
|
|
7155
7741
|
relationships = readRelationships(value);
|
|
7156
|
-
} else if (value instanceof
|
|
7742
|
+
} else if (value instanceof Y6.Map) {
|
|
7157
7743
|
fields[key] = readFieldMeta(value);
|
|
7158
7744
|
}
|
|
7159
7745
|
}
|
|
@@ -7172,6 +7758,10 @@ function readFieldMeta(fieldMap) {
|
|
|
7172
7758
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
7173
7759
|
if (fieldMap.get("required") === true) field.required = true;
|
|
7174
7760
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7761
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7762
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7763
|
+
field.autoStamp = autoStamp;
|
|
7764
|
+
}
|
|
7175
7765
|
const def = fieldMap.get("default");
|
|
7176
7766
|
if (def !== void 0) field.default = def;
|
|
7177
7767
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -7184,7 +7774,7 @@ function inferModelFromData(dataMap) {
|
|
|
7184
7774
|
const fields = {};
|
|
7185
7775
|
let sampled = 0;
|
|
7186
7776
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
7187
|
-
if (!(recordValue instanceof
|
|
7777
|
+
if (!(recordValue instanceof Y6.Map)) continue;
|
|
7188
7778
|
if (++sampled > 5) break;
|
|
7189
7779
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
7190
7780
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -7197,7 +7787,7 @@ function inferModelFromData(dataMap) {
|
|
|
7197
7787
|
return { fields };
|
|
7198
7788
|
}
|
|
7199
7789
|
function inferTypeFromValue(value) {
|
|
7200
|
-
if (value
|
|
7790
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
7201
7791
|
switch (typeof value) {
|
|
7202
7792
|
case "string":
|
|
7203
7793
|
return "string";
|
|
@@ -7212,7 +7802,7 @@ function inferTypeFromValue(value) {
|
|
|
7212
7802
|
function readConstraints(constraintsMap) {
|
|
7213
7803
|
const out = {};
|
|
7214
7804
|
for (const [name, value] of constraintsMap.entries()) {
|
|
7215
|
-
if (!(value instanceof
|
|
7805
|
+
if (!(value instanceof Y6.Map)) continue;
|
|
7216
7806
|
let fields = [];
|
|
7217
7807
|
const rawFields = value.get("fields");
|
|
7218
7808
|
if (typeof rawFields === "string") {
|
|
@@ -7231,7 +7821,7 @@ function readConstraints(constraintsMap) {
|
|
|
7231
7821
|
function readRelationships(relsMap) {
|
|
7232
7822
|
const out = {};
|
|
7233
7823
|
for (const [name, value] of relsMap.entries()) {
|
|
7234
|
-
if (!(value instanceof
|
|
7824
|
+
if (!(value instanceof Y6.Map)) continue;
|
|
7235
7825
|
const rel = {};
|
|
7236
7826
|
for (const [k, v] of value.entries()) {
|
|
7237
7827
|
rel[k] = v;
|
|
@@ -7242,6 +7832,7 @@ function readRelationships(relsMap) {
|
|
|
7242
7832
|
}
|
|
7243
7833
|
var CAMEL_TO_SNAKE = {
|
|
7244
7834
|
autoAssign: "auto_assign",
|
|
7835
|
+
autoStamp: "auto_stamp",
|
|
7245
7836
|
maxLength: "max_length",
|
|
7246
7837
|
maxCount: "max_count",
|
|
7247
7838
|
relatedIdField: "related_id_field",
|
|
@@ -7272,6 +7863,7 @@ function schemaToToml(schema) {
|
|
|
7272
7863
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
7273
7864
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
7274
7865
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7866
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
7275
7867
|
if (field.indexed) lines.push("indexed = true");
|
|
7276
7868
|
if (field.unique) lines.push("unique = true");
|
|
7277
7869
|
if (field.required) lines.push("required = true");
|
|
@@ -7318,10 +7910,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
7318
7910
|
"unique",
|
|
7319
7911
|
"required",
|
|
7320
7912
|
"auto_assign",
|
|
7913
|
+
"auto_stamp",
|
|
7321
7914
|
"max_length",
|
|
7322
7915
|
"max_count",
|
|
7323
|
-
"default"
|
|
7916
|
+
"default",
|
|
7917
|
+
"enum"
|
|
7324
7918
|
]);
|
|
7919
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
7325
7920
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7326
7921
|
"fields",
|
|
7327
7922
|
"relationships",
|
|
@@ -7363,9 +7958,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
7363
7958
|
if (raw.unique === true) opts.unique = true;
|
|
7364
7959
|
if (raw.required === true) opts.required = true;
|
|
7365
7960
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
7961
|
+
if (raw.auto_stamp !== void 0) {
|
|
7962
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
7963
|
+
throw new Error(
|
|
7964
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
7965
|
+
...VALID_AUTO_STAMP_VALUES
|
|
7966
|
+
].join(", ")}`
|
|
7967
|
+
);
|
|
7968
|
+
}
|
|
7969
|
+
opts.autoStamp = raw.auto_stamp;
|
|
7970
|
+
}
|
|
7366
7971
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
7367
7972
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
7368
7973
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
7974
|
+
if (raw.enum !== void 0) {
|
|
7975
|
+
if (raw.type !== "string") {
|
|
7976
|
+
throw new Error(
|
|
7977
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
7978
|
+
);
|
|
7979
|
+
}
|
|
7980
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
7981
|
+
throw new Error(
|
|
7982
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
7983
|
+
);
|
|
7984
|
+
}
|
|
7985
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
7986
|
+
throw new Error(
|
|
7987
|
+
`${context}: \`enum\` values must all be strings.`
|
|
7988
|
+
);
|
|
7989
|
+
}
|
|
7990
|
+
opts.enum = [...raw.enum];
|
|
7991
|
+
}
|
|
7369
7992
|
return opts;
|
|
7370
7993
|
}
|
|
7371
7994
|
function requireField(raw, field, context) {
|
|
@@ -7494,18 +8117,19 @@ async function loadSchemaFromToml(filePath, options = {}) {
|
|
|
7494
8117
|
|
|
7495
8118
|
// src/node.ts
|
|
7496
8119
|
init_metaSync();
|
|
8120
|
+
init_writeLifecycle();
|
|
7497
8121
|
|
|
7498
8122
|
// src/utils/yDocDump.ts
|
|
7499
|
-
import * as
|
|
8123
|
+
import * as Y7 from "yjs";
|
|
7500
8124
|
function toPlain(value) {
|
|
7501
|
-
if (value instanceof
|
|
8125
|
+
if (value instanceof Y7.Map) {
|
|
7502
8126
|
const obj = {};
|
|
7503
8127
|
value.forEach((v, k) => {
|
|
7504
8128
|
obj[k] = toPlain(v);
|
|
7505
8129
|
});
|
|
7506
8130
|
return obj;
|
|
7507
8131
|
}
|
|
7508
|
-
if (value instanceof
|
|
8132
|
+
if (value instanceof Y7.Array) {
|
|
7509
8133
|
return value.toArray().map((item) => toPlain(item));
|
|
7510
8134
|
}
|
|
7511
8135
|
return value;
|
|
@@ -7514,7 +8138,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
|
|
|
7514
8138
|
const result = {};
|
|
7515
8139
|
const includeIndexes = !!options.includeIndexes;
|
|
7516
8140
|
yDoc.share.forEach((type, name) => {
|
|
7517
|
-
if (!(type instanceof
|
|
8141
|
+
if (!(type instanceof Y7.Map)) return;
|
|
7518
8142
|
if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
|
|
7519
8143
|
const records = {};
|
|
7520
8144
|
type.forEach((recordValue, recordId) => {
|
|
@@ -7574,31 +8198,45 @@ export {
|
|
|
7574
8198
|
SqljsEngine,
|
|
7575
8199
|
StringSet,
|
|
7576
8200
|
UniqueConstraintViolationError,
|
|
8201
|
+
applyAutoStampsFromSchema,
|
|
8202
|
+
applyDefaultsFromSchema,
|
|
8203
|
+
applyStringSetChange,
|
|
8204
|
+
applyStringSetDelta,
|
|
8205
|
+
assertNever,
|
|
7577
8206
|
attachAndRegisterModel,
|
|
7578
8207
|
attachSchemaToClass,
|
|
7579
8208
|
autoRegisterModel,
|
|
7580
8209
|
clearMetaSyncCache,
|
|
7581
8210
|
constants,
|
|
7582
8211
|
createModelClass,
|
|
8212
|
+
decodeFunctionDefault,
|
|
7583
8213
|
defineModelSchema,
|
|
7584
8214
|
detectEnvironment,
|
|
7585
8215
|
discoverModelNames,
|
|
7586
8216
|
discoverSchema,
|
|
7587
8217
|
dumpYDocToPlain,
|
|
8218
|
+
ensureStringSetYMap,
|
|
7588
8219
|
features,
|
|
8220
|
+
fieldWriteKind,
|
|
7589
8221
|
generateULID,
|
|
7590
8222
|
getRecommendedNodeEngine,
|
|
7591
8223
|
inferFieldType,
|
|
7592
8224
|
initJsBao,
|
|
7593
8225
|
isBrowser,
|
|
8226
|
+
isKnownFieldType,
|
|
7594
8227
|
isNode,
|
|
7595
8228
|
isNodeModuleAvailable,
|
|
7596
8229
|
loadSchemaFromToml,
|
|
7597
8230
|
loadSchemaFromTomlString,
|
|
8231
|
+
parseStringSetOp,
|
|
7598
8232
|
registerFunctionDefault,
|
|
8233
|
+
registerFunctionDefaultGenerator,
|
|
8234
|
+
replaceStringSet,
|
|
7599
8235
|
resetJsBao,
|
|
8236
|
+
resolveDefaultValue,
|
|
7600
8237
|
schemaToToml,
|
|
7601
8238
|
summarizePlainYDoc,
|
|
7602
8239
|
syncInferredMeta,
|
|
7603
|
-
syncModelMeta
|
|
8240
|
+
syncModelMeta,
|
|
8241
|
+
writeField
|
|
7604
8242
|
};
|