js-bao 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.cjs +205 -40
- package/dist/browser.d.cts +41 -0
- package/dist/browser.d.ts +41 -0
- package/dist/browser.js +202 -38
- package/dist/client.cjs +15 -4
- package/dist/client.d.cts +78 -0
- package/dist/client.d.ts +78 -0
- package/dist/client.js +12 -2
- package/dist/cloudflare-do.cjs +191 -26
- package/dist/cloudflare-do.d.cts +127 -1
- package/dist/cloudflare-do.d.ts +127 -1
- package/dist/cloudflare-do.js +188 -24
- package/dist/cloudflare.cjs +78 -21
- package/dist/cloudflare.d.cts +84 -0
- package/dist/cloudflare.d.ts +84 -0
- package/dist/cloudflare.js +75 -19
- package/dist/codegen-v2.cjs +67 -7
- package/dist/codegen.cjs +6 -1
- package/dist/index.cjs +209 -44
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +206 -42
- package/dist/node.cjs +209 -44
- package/dist/node.d.cts +41 -0
- package/dist/node.d.ts +41 -0
- package/dist/node.js +206 -42
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1183,9 +1183,46 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
1183
1183
|
}
|
|
1184
1184
|
});
|
|
1185
1185
|
|
|
1186
|
+
// src/utils/yjsValues.ts
|
|
1187
|
+
function isYjsComposite(value) {
|
|
1188
|
+
return value instanceof Y.Map || value instanceof Y.Array || value instanceof Y.Text;
|
|
1189
|
+
}
|
|
1190
|
+
function isStringSetYMap(value) {
|
|
1191
|
+
if (!(value instanceof Y.Map)) return false;
|
|
1192
|
+
for (const v of value.values()) {
|
|
1193
|
+
if (v !== true) return false;
|
|
1194
|
+
}
|
|
1195
|
+
return true;
|
|
1196
|
+
}
|
|
1197
|
+
function normalizeYjsValue(value) {
|
|
1198
|
+
if (value instanceof Y.Text) {
|
|
1199
|
+
return value.toString();
|
|
1200
|
+
}
|
|
1201
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1202
|
+
return value.toJSON();
|
|
1203
|
+
}
|
|
1204
|
+
return value;
|
|
1205
|
+
}
|
|
1206
|
+
function normalizeYjsValueForStorage(value) {
|
|
1207
|
+
if (value instanceof Y.Text) {
|
|
1208
|
+
return value.toString();
|
|
1209
|
+
}
|
|
1210
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1211
|
+
return JSON.stringify(value.toJSON());
|
|
1212
|
+
}
|
|
1213
|
+
return value;
|
|
1214
|
+
}
|
|
1215
|
+
var Y;
|
|
1216
|
+
var init_yjsValues = __esm({
|
|
1217
|
+
"src/utils/yjsValues.ts"() {
|
|
1218
|
+
"use strict";
|
|
1219
|
+
Y = __toESM(require("yjs"), 1);
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1186
1223
|
// src/models/metaSync.ts
|
|
1187
1224
|
function inferFieldType(value) {
|
|
1188
|
-
if (value
|
|
1225
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
1189
1226
|
switch (typeof value) {
|
|
1190
1227
|
case "string":
|
|
1191
1228
|
return "string";
|
|
@@ -1230,7 +1267,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1230
1267
|
if (compoundConstraints.length > 0) {
|
|
1231
1268
|
let constraints = meta.get("_constraints");
|
|
1232
1269
|
if (!constraints) {
|
|
1233
|
-
constraints = new
|
|
1270
|
+
constraints = new Y2.Map();
|
|
1234
1271
|
meta.set("_constraints", constraints);
|
|
1235
1272
|
}
|
|
1236
1273
|
for (const constraint of compoundConstraints) {
|
|
@@ -1241,7 +1278,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1241
1278
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
1242
1279
|
let rels = meta.get("_relationships");
|
|
1243
1280
|
if (!rels) {
|
|
1244
|
-
rels = new
|
|
1281
|
+
rels = new Y2.Map();
|
|
1245
1282
|
meta.set("_relationships", rels);
|
|
1246
1283
|
}
|
|
1247
1284
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -1253,7 +1290,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1253
1290
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
1254
1291
|
let fieldMeta = metaMap.get(fieldName);
|
|
1255
1292
|
if (!fieldMeta) {
|
|
1256
|
-
fieldMeta = new
|
|
1293
|
+
fieldMeta = new Y2.Map();
|
|
1257
1294
|
metaMap.set(fieldName, fieldMeta);
|
|
1258
1295
|
}
|
|
1259
1296
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -1261,6 +1298,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1261
1298
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
1262
1299
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
1263
1300
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
1301
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
1264
1302
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
1265
1303
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
1266
1304
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -1269,7 +1307,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1269
1307
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
1270
1308
|
let cMeta = constraintsMap.get(constraint.name);
|
|
1271
1309
|
if (!cMeta) {
|
|
1272
|
-
cMeta = new
|
|
1310
|
+
cMeta = new Y2.Map();
|
|
1273
1311
|
constraintsMap.set(constraint.name, cMeta);
|
|
1274
1312
|
}
|
|
1275
1313
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -1279,7 +1317,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
1279
1317
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
1280
1318
|
let relMeta = relsMap.get(relName);
|
|
1281
1319
|
if (!relMeta) {
|
|
1282
|
-
relMeta = new
|
|
1320
|
+
relMeta = new Y2.Map();
|
|
1283
1321
|
relsMap.set(relName, relMeta);
|
|
1284
1322
|
}
|
|
1285
1323
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -1294,7 +1332,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
1294
1332
|
if (fieldName.startsWith("_")) continue;
|
|
1295
1333
|
let fieldMeta = meta.get(fieldName);
|
|
1296
1334
|
if (!fieldMeta) {
|
|
1297
|
-
fieldMeta = new
|
|
1335
|
+
fieldMeta = new Y2.Map();
|
|
1298
1336
|
meta.set(fieldName, fieldMeta);
|
|
1299
1337
|
}
|
|
1300
1338
|
if (!fieldMeta.has("type")) {
|
|
@@ -1310,11 +1348,12 @@ function setIfChanged(map, key, value) {
|
|
|
1310
1348
|
map.set(key, value);
|
|
1311
1349
|
}
|
|
1312
1350
|
}
|
|
1313
|
-
var
|
|
1351
|
+
var Y2, KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
1314
1352
|
var init_metaSync = __esm({
|
|
1315
1353
|
"src/models/metaSync.ts"() {
|
|
1316
1354
|
"use strict";
|
|
1317
|
-
|
|
1355
|
+
Y2 = __toESM(require("yjs"), 1);
|
|
1356
|
+
init_yjsValues();
|
|
1318
1357
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
1319
1358
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
1320
1359
|
}
|
|
@@ -1719,11 +1758,11 @@ __export(BaseModel_exports, {
|
|
|
1719
1758
|
function generateULID() {
|
|
1720
1759
|
return (0, import_ulid.ulid)();
|
|
1721
1760
|
}
|
|
1722
|
-
var
|
|
1761
|
+
var Y3, import_ulid, LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel;
|
|
1723
1762
|
var init_BaseModel = __esm({
|
|
1724
1763
|
"src/models/BaseModel.ts"() {
|
|
1725
1764
|
"use strict";
|
|
1726
|
-
|
|
1765
|
+
Y3 = __toESM(require("yjs"), 1);
|
|
1727
1766
|
import_ulid = require("ulid");
|
|
1728
1767
|
init_StringSet();
|
|
1729
1768
|
init_documentTypes();
|
|
@@ -1731,6 +1770,7 @@ var init_BaseModel = __esm({
|
|
|
1731
1770
|
init_CursorManager();
|
|
1732
1771
|
init_sql();
|
|
1733
1772
|
init_metaSync();
|
|
1773
|
+
init_yjsValues();
|
|
1734
1774
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
1735
1775
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
1736
1776
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2239,7 +2279,30 @@ var init_BaseModel = __esm({
|
|
|
2239
2279
|
if (verboseEnabled) {
|
|
2240
2280
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2241
2281
|
}
|
|
2242
|
-
return fieldValue;
|
|
2282
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
2286
|
+
*
|
|
2287
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
2288
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
2289
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
2290
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
2291
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
2292
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
2293
|
+
*/
|
|
2294
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
2295
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
2296
|
+
if (fieldOptions?.type === "stringset") {
|
|
2297
|
+
return value;
|
|
2298
|
+
}
|
|
2299
|
+
if (isYjsComposite(value)) {
|
|
2300
|
+
Logger.warn(
|
|
2301
|
+
`[${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.`
|
|
2302
|
+
);
|
|
2303
|
+
return normalizeYjsValue(value);
|
|
2304
|
+
}
|
|
2305
|
+
return value;
|
|
2243
2306
|
}
|
|
2244
2307
|
getValue(fieldKey) {
|
|
2245
2308
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -2384,7 +2447,7 @@ var init_BaseModel = __esm({
|
|
|
2384
2447
|
}
|
|
2385
2448
|
getStringSetFromYjs(fieldName) {
|
|
2386
2449
|
const yjsData = this.getFromYjs(fieldName);
|
|
2387
|
-
if (yjsData instanceof
|
|
2450
|
+
if (yjsData instanceof Y3.Map) {
|
|
2388
2451
|
return Array.from(yjsData.keys());
|
|
2389
2452
|
}
|
|
2390
2453
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -2492,7 +2555,7 @@ var init_BaseModel = __esm({
|
|
|
2492
2555
|
}
|
|
2493
2556
|
const extractStringSetValues = (value) => {
|
|
2494
2557
|
if (!value) return [];
|
|
2495
|
-
if (value instanceof
|
|
2558
|
+
if (value instanceof Y3.Map) {
|
|
2496
2559
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
2497
2560
|
}
|
|
2498
2561
|
if (Array.isArray(value)) {
|
|
@@ -2509,7 +2572,7 @@ var init_BaseModel = __esm({
|
|
|
2509
2572
|
if (!recordId) continue;
|
|
2510
2573
|
let itemData;
|
|
2511
2574
|
const stringSetValuesByField = {};
|
|
2512
|
-
if (recordData instanceof
|
|
2575
|
+
if (recordData instanceof Y3.Map) {
|
|
2513
2576
|
itemData = {};
|
|
2514
2577
|
const unknownFields = [];
|
|
2515
2578
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -2628,7 +2691,7 @@ var init_BaseModel = __esm({
|
|
|
2628
2691
|
const buildUniqueKey = (recordData, fields) => {
|
|
2629
2692
|
const keyParts = [];
|
|
2630
2693
|
for (const field of fields) {
|
|
2631
|
-
const value = recordData instanceof
|
|
2694
|
+
const value = recordData instanceof Y3.Map ? recordData.get(field) : recordData[field];
|
|
2632
2695
|
if (value === null || value === void 0) {
|
|
2633
2696
|
return null;
|
|
2634
2697
|
}
|
|
@@ -2638,7 +2701,7 @@ var init_BaseModel = __esm({
|
|
|
2638
2701
|
};
|
|
2639
2702
|
const extractItemData = (key, recordData) => {
|
|
2640
2703
|
let itemData;
|
|
2641
|
-
if (recordData instanceof
|
|
2704
|
+
if (recordData instanceof Y3.Map) {
|
|
2642
2705
|
itemData = {};
|
|
2643
2706
|
const unknownFields = [];
|
|
2644
2707
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -2651,7 +2714,14 @@ var init_BaseModel = __esm({
|
|
|
2651
2714
|
continue;
|
|
2652
2715
|
}
|
|
2653
2716
|
if (value !== void 0) {
|
|
2654
|
-
|
|
2717
|
+
if (isYjsComposite(value)) {
|
|
2718
|
+
Logger.warn(
|
|
2719
|
+
`[${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.`
|
|
2720
|
+
);
|
|
2721
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
2722
|
+
} else {
|
|
2723
|
+
itemData[fieldKey] = value;
|
|
2724
|
+
}
|
|
2655
2725
|
}
|
|
2656
2726
|
}
|
|
2657
2727
|
if (unknownFields.length > 0) {
|
|
@@ -2674,7 +2744,14 @@ var init_BaseModel = __esm({
|
|
|
2674
2744
|
continue;
|
|
2675
2745
|
}
|
|
2676
2746
|
if (value !== void 0) {
|
|
2677
|
-
|
|
2747
|
+
if (isYjsComposite(value)) {
|
|
2748
|
+
Logger.warn(
|
|
2749
|
+
`[${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.`
|
|
2750
|
+
);
|
|
2751
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
2752
|
+
} else {
|
|
2753
|
+
filteredData[fieldKey] = value;
|
|
2754
|
+
}
|
|
2678
2755
|
}
|
|
2679
2756
|
}
|
|
2680
2757
|
if (unknownFields.length > 0) {
|
|
@@ -2754,7 +2831,7 @@ var init_BaseModel = __esm({
|
|
|
2754
2831
|
if (!recordData || !key) continue;
|
|
2755
2832
|
const itemData = extractItemData(key, recordData);
|
|
2756
2833
|
if (!itemData) continue;
|
|
2757
|
-
if (change.action === "add" && recordData instanceof
|
|
2834
|
+
if (change.action === "add" && recordData instanceof Y3.Map) {
|
|
2758
2835
|
Logger.verbose(
|
|
2759
2836
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
2760
2837
|
);
|
|
@@ -2899,7 +2976,7 @@ var init_BaseModel = __esm({
|
|
|
2899
2976
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
2900
2977
|
);
|
|
2901
2978
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
2902
|
-
if (recordData instanceof
|
|
2979
|
+
if (recordData instanceof Y3.Map) {
|
|
2903
2980
|
Logger.verbose(
|
|
2904
2981
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
2905
2982
|
);
|
|
@@ -3084,6 +3161,11 @@ var init_BaseModel = __esm({
|
|
|
3084
3161
|
Logger.debug(
|
|
3085
3162
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3086
3163
|
);
|
|
3164
|
+
if (isYjsComposite(yjsValue)) {
|
|
3165
|
+
Logger.warn(
|
|
3166
|
+
`[${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).`
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3087
3169
|
if (yjsValue === void 0) {
|
|
3088
3170
|
Logger.debug(
|
|
3089
3171
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3121,8 +3203,8 @@ var init_BaseModel = __esm({
|
|
|
3121
3203
|
*/
|
|
3122
3204
|
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3123
3205
|
let nested = recordYMap.get(fieldName);
|
|
3124
|
-
if (!(nested instanceof
|
|
3125
|
-
const migrated = new
|
|
3206
|
+
if (!(nested instanceof Y3.Map)) {
|
|
3207
|
+
const migrated = new Y3.Map();
|
|
3126
3208
|
if (nested && typeof nested === "object") {
|
|
3127
3209
|
for (const [member, marker] of Object.entries(
|
|
3128
3210
|
nested
|
|
@@ -3309,6 +3391,29 @@ var init_BaseModel = __esm({
|
|
|
3309
3391
|
);
|
|
3310
3392
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
3311
3393
|
}
|
|
3394
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
3395
|
+
{
|
|
3396
|
+
const existingRecord = targetYMap.get(this.id);
|
|
3397
|
+
const isNew = !existingRecord;
|
|
3398
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
3399
|
+
const stamp = fieldOptions.autoStamp;
|
|
3400
|
+
if (!stamp) continue;
|
|
3401
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
3402
|
+
if (callerExplicit) {
|
|
3403
|
+
const localValue = this._localChanges[fieldKey];
|
|
3404
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
3405
|
+
}
|
|
3406
|
+
if (stamp === "create") {
|
|
3407
|
+
if (!isNew) continue;
|
|
3408
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
3409
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
3410
|
+
}
|
|
3411
|
+
this[fieldKey] = Date.now();
|
|
3412
|
+
if (stamp === "create") {
|
|
3413
|
+
preTransactCreateStamps.add(fieldKey);
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3312
3417
|
this.validateBeforeSave();
|
|
3313
3418
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
3314
3419
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -3339,6 +3444,14 @@ var init_BaseModel = __esm({
|
|
|
3339
3444
|
}
|
|
3340
3445
|
}
|
|
3341
3446
|
}
|
|
3447
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
3448
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
3449
|
+
delete this._localChanges[fieldKey];
|
|
3450
|
+
}
|
|
3451
|
+
if (fieldKey in dataToSave) {
|
|
3452
|
+
delete dataToSave[fieldKey];
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3342
3455
|
}
|
|
3343
3456
|
}
|
|
3344
3457
|
if (!targetYMap) {
|
|
@@ -3351,7 +3464,7 @@ var init_BaseModel = __esm({
|
|
|
3351
3464
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
3352
3465
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
3353
3466
|
if (!recordYMap) {
|
|
3354
|
-
recordYMap = new
|
|
3467
|
+
recordYMap = new Y3.Map();
|
|
3355
3468
|
Logger.verbose(
|
|
3356
3469
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
3357
3470
|
);
|
|
@@ -4378,7 +4491,7 @@ var init_BaseModel = __esm({
|
|
|
4378
4491
|
);
|
|
4379
4492
|
}
|
|
4380
4493
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
4381
|
-
if (recordYMap instanceof
|
|
4494
|
+
if (recordYMap instanceof Y3.Map) {
|
|
4382
4495
|
const instance = new this({ id: recordId });
|
|
4383
4496
|
instance._metaDocId = docId;
|
|
4384
4497
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -4449,7 +4562,7 @@ var init_BaseModel = __esm({
|
|
|
4449
4562
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
4450
4563
|
if (documentYMap) {
|
|
4451
4564
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
4452
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
4565
|
+
if (recordYMap2 && recordYMap2 instanceof Y3.Map) {
|
|
4453
4566
|
const instance = new modelConstructor({
|
|
4454
4567
|
id: recordId2
|
|
4455
4568
|
});
|
|
@@ -4486,7 +4599,7 @@ var init_BaseModel = __esm({
|
|
|
4486
4599
|
if (!recordYMap) {
|
|
4487
4600
|
return null;
|
|
4488
4601
|
}
|
|
4489
|
-
if (recordYMap instanceof
|
|
4602
|
+
if (recordYMap instanceof Y3.Map) {
|
|
4490
4603
|
const instance = new modelConstructor({
|
|
4491
4604
|
id: recordId
|
|
4492
4605
|
});
|
|
@@ -4718,7 +4831,7 @@ var init_BaseModel = __esm({
|
|
|
4718
4831
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
4719
4832
|
if (fieldOptions?.type !== "stringset") continue;
|
|
4720
4833
|
const value = recordYMap.get(fieldKey);
|
|
4721
|
-
if (value instanceof
|
|
4834
|
+
if (value instanceof Y3.Map) {
|
|
4722
4835
|
this.observeStringSetMapOnce(value);
|
|
4723
4836
|
}
|
|
4724
4837
|
}
|
|
@@ -4765,7 +4878,14 @@ var init_BaseModel = __esm({
|
|
|
4765
4878
|
continue;
|
|
4766
4879
|
}
|
|
4767
4880
|
if (value !== void 0) {
|
|
4768
|
-
|
|
4881
|
+
if (isYjsComposite(value)) {
|
|
4882
|
+
Logger.warn(
|
|
4883
|
+
`[${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.`
|
|
4884
|
+
);
|
|
4885
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
4886
|
+
} else {
|
|
4887
|
+
updatedData[key] = value;
|
|
4888
|
+
}
|
|
4769
4889
|
}
|
|
4770
4890
|
}
|
|
4771
4891
|
if (unknownFields.length > 0) {
|
|
@@ -4845,7 +4965,14 @@ var init_BaseModel = __esm({
|
|
|
4845
4965
|
continue;
|
|
4846
4966
|
}
|
|
4847
4967
|
if (value !== void 0) {
|
|
4848
|
-
|
|
4968
|
+
if (isYjsComposite(value)) {
|
|
4969
|
+
Logger.warn(
|
|
4970
|
+
`[${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.`
|
|
4971
|
+
);
|
|
4972
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
4973
|
+
} else {
|
|
4974
|
+
updatedData[key] = value;
|
|
4975
|
+
}
|
|
4849
4976
|
}
|
|
4850
4977
|
}
|
|
4851
4978
|
if (unknownFields.length > 0) {
|
|
@@ -7161,10 +7288,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
7161
7288
|
"unique",
|
|
7162
7289
|
"required",
|
|
7163
7290
|
"auto_assign",
|
|
7291
|
+
"auto_stamp",
|
|
7164
7292
|
"max_length",
|
|
7165
7293
|
"max_count",
|
|
7166
|
-
"default"
|
|
7294
|
+
"default",
|
|
7295
|
+
"enum"
|
|
7167
7296
|
]);
|
|
7297
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
7168
7298
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7169
7299
|
"fields",
|
|
7170
7300
|
"relationships",
|
|
@@ -7206,9 +7336,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
7206
7336
|
if (raw.unique === true) opts.unique = true;
|
|
7207
7337
|
if (raw.required === true) opts.required = true;
|
|
7208
7338
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
7339
|
+
if (raw.auto_stamp !== void 0) {
|
|
7340
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
7341
|
+
throw new Error(
|
|
7342
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
7343
|
+
...VALID_AUTO_STAMP_VALUES
|
|
7344
|
+
].join(", ")}`
|
|
7345
|
+
);
|
|
7346
|
+
}
|
|
7347
|
+
opts.autoStamp = raw.auto_stamp;
|
|
7348
|
+
}
|
|
7209
7349
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
7210
7350
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
7211
7351
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
7352
|
+
if (raw.enum !== void 0) {
|
|
7353
|
+
if (raw.type !== "string") {
|
|
7354
|
+
throw new Error(
|
|
7355
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
7356
|
+
);
|
|
7357
|
+
}
|
|
7358
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
7359
|
+
throw new Error(
|
|
7360
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
7361
|
+
);
|
|
7362
|
+
}
|
|
7363
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
7364
|
+
throw new Error(
|
|
7365
|
+
`${context}: \`enum\` values must all be strings.`
|
|
7366
|
+
);
|
|
7367
|
+
}
|
|
7368
|
+
opts.enum = [...raw.enum];
|
|
7369
|
+
}
|
|
7212
7370
|
return opts;
|
|
7213
7371
|
}
|
|
7214
7372
|
function requireField(raw, field, context) {
|
|
@@ -7336,7 +7494,8 @@ async function loadSchemaFromToml(filePath, options = {}) {
|
|
|
7336
7494
|
}
|
|
7337
7495
|
|
|
7338
7496
|
// src/utils/yDocSchema.ts
|
|
7339
|
-
var
|
|
7497
|
+
var Y4 = __toESM(require("yjs"), 1);
|
|
7498
|
+
init_yjsValues();
|
|
7340
7499
|
function discoverSchema(yDoc) {
|
|
7341
7500
|
const models = {};
|
|
7342
7501
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -7370,7 +7529,7 @@ function discoverModelNames(yDoc) {
|
|
|
7370
7529
|
function materializeMap(yDoc, key) {
|
|
7371
7530
|
try {
|
|
7372
7531
|
const map = yDoc.getMap(key);
|
|
7373
|
-
return map instanceof
|
|
7532
|
+
return map instanceof Y4.Map ? map : null;
|
|
7374
7533
|
} catch {
|
|
7375
7534
|
return null;
|
|
7376
7535
|
}
|
|
@@ -7380,11 +7539,11 @@ function readModelMeta(metaMap) {
|
|
|
7380
7539
|
let constraints;
|
|
7381
7540
|
let relationships;
|
|
7382
7541
|
for (const [key, value] of metaMap.entries()) {
|
|
7383
|
-
if (key === "_constraints" && value instanceof
|
|
7542
|
+
if (key === "_constraints" && value instanceof Y4.Map) {
|
|
7384
7543
|
constraints = readConstraints(value);
|
|
7385
|
-
} else if (key === "_relationships" && value instanceof
|
|
7544
|
+
} else if (key === "_relationships" && value instanceof Y4.Map) {
|
|
7386
7545
|
relationships = readRelationships(value);
|
|
7387
|
-
} else if (value instanceof
|
|
7546
|
+
} else if (value instanceof Y4.Map) {
|
|
7388
7547
|
fields[key] = readFieldMeta(value);
|
|
7389
7548
|
}
|
|
7390
7549
|
}
|
|
@@ -7403,6 +7562,10 @@ function readFieldMeta(fieldMap) {
|
|
|
7403
7562
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
7404
7563
|
if (fieldMap.get("required") === true) field.required = true;
|
|
7405
7564
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7565
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7566
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7567
|
+
field.autoStamp = autoStamp;
|
|
7568
|
+
}
|
|
7406
7569
|
const def = fieldMap.get("default");
|
|
7407
7570
|
if (def !== void 0) field.default = def;
|
|
7408
7571
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -7415,7 +7578,7 @@ function inferModelFromData(dataMap) {
|
|
|
7415
7578
|
const fields = {};
|
|
7416
7579
|
let sampled = 0;
|
|
7417
7580
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
7418
|
-
if (!(recordValue instanceof
|
|
7581
|
+
if (!(recordValue instanceof Y4.Map)) continue;
|
|
7419
7582
|
if (++sampled > 5) break;
|
|
7420
7583
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
7421
7584
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -7428,7 +7591,7 @@ function inferModelFromData(dataMap) {
|
|
|
7428
7591
|
return { fields };
|
|
7429
7592
|
}
|
|
7430
7593
|
function inferTypeFromValue(value) {
|
|
7431
|
-
if (value
|
|
7594
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
7432
7595
|
switch (typeof value) {
|
|
7433
7596
|
case "string":
|
|
7434
7597
|
return "string";
|
|
@@ -7443,7 +7606,7 @@ function inferTypeFromValue(value) {
|
|
|
7443
7606
|
function readConstraints(constraintsMap) {
|
|
7444
7607
|
const out = {};
|
|
7445
7608
|
for (const [name, value] of constraintsMap.entries()) {
|
|
7446
|
-
if (!(value instanceof
|
|
7609
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7447
7610
|
let fields = [];
|
|
7448
7611
|
const rawFields = value.get("fields");
|
|
7449
7612
|
if (typeof rawFields === "string") {
|
|
@@ -7462,7 +7625,7 @@ function readConstraints(constraintsMap) {
|
|
|
7462
7625
|
function readRelationships(relsMap) {
|
|
7463
7626
|
const out = {};
|
|
7464
7627
|
for (const [name, value] of relsMap.entries()) {
|
|
7465
|
-
if (!(value instanceof
|
|
7628
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7466
7629
|
const rel = {};
|
|
7467
7630
|
for (const [k, v] of value.entries()) {
|
|
7468
7631
|
rel[k] = v;
|
|
@@ -7473,6 +7636,7 @@ function readRelationships(relsMap) {
|
|
|
7473
7636
|
}
|
|
7474
7637
|
var CAMEL_TO_SNAKE = {
|
|
7475
7638
|
autoAssign: "auto_assign",
|
|
7639
|
+
autoStamp: "auto_stamp",
|
|
7476
7640
|
maxLength: "max_length",
|
|
7477
7641
|
maxCount: "max_count",
|
|
7478
7642
|
relatedIdField: "related_id_field",
|
|
@@ -7503,6 +7667,7 @@ function schemaToToml(schema) {
|
|
|
7503
7667
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
7504
7668
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
7505
7669
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7670
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
7506
7671
|
if (field.indexed) lines.push("indexed = true");
|
|
7507
7672
|
if (field.unique) lines.push("unique = true");
|
|
7508
7673
|
if (field.required) lines.push("required = true");
|
|
@@ -7537,16 +7702,16 @@ function schemaToToml(schema) {
|
|
|
7537
7702
|
init_BaseModel();
|
|
7538
7703
|
|
|
7539
7704
|
// src/utils/yDocDump.ts
|
|
7540
|
-
var
|
|
7705
|
+
var Y5 = __toESM(require("yjs"), 1);
|
|
7541
7706
|
function toPlain(value) {
|
|
7542
|
-
if (value instanceof
|
|
7707
|
+
if (value instanceof Y5.Map) {
|
|
7543
7708
|
const obj = {};
|
|
7544
7709
|
value.forEach((v, k) => {
|
|
7545
7710
|
obj[k] = toPlain(v);
|
|
7546
7711
|
});
|
|
7547
7712
|
return obj;
|
|
7548
7713
|
}
|
|
7549
|
-
if (value instanceof
|
|
7714
|
+
if (value instanceof Y5.Array) {
|
|
7550
7715
|
return value.toArray().map((item) => toPlain(item));
|
|
7551
7716
|
}
|
|
7552
7717
|
return value;
|
|
@@ -7555,7 +7720,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
|
|
|
7555
7720
|
const result = {};
|
|
7556
7721
|
const includeIndexes = !!options.includeIndexes;
|
|
7557
7722
|
yDoc.share.forEach((type, name) => {
|
|
7558
|
-
if (!(type instanceof
|
|
7723
|
+
if (!(type instanceof Y5.Map)) return;
|
|
7559
7724
|
if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
|
|
7560
7725
|
const records = {};
|
|
7561
7726
|
type.forEach((recordValue, recordId) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -31,8 +31,32 @@ interface FieldOptions {
|
|
|
31
31
|
unique?: boolean;
|
|
32
32
|
default?: any | (() => any);
|
|
33
33
|
autoAssign?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Auto-timestamp this field with `Date.now()` (milliseconds) on save.
|
|
36
|
+
*
|
|
37
|
+
* - `'create'` — stamp only on the first save (when `isNew === true`).
|
|
38
|
+
* - `'update'` — stamp on every save (create AND update).
|
|
39
|
+
* - `'both'` — stamp on every save (create AND update).
|
|
40
|
+
*
|
|
41
|
+
* If the caller passes an explicit value for the field in `data`, the
|
|
42
|
+
* explicit value wins and the stamp is skipped. The stamp is applied
|
|
43
|
+
* in `beforeSave` BEFORE any user-defined hooks, so user hooks may
|
|
44
|
+
* still overwrite the value if they wish (last writer wins).
|
|
45
|
+
*/
|
|
46
|
+
autoStamp?: "create" | "update" | "both";
|
|
34
47
|
maxLength?: number;
|
|
35
48
|
maxCount?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Allowed-value set for a `string` field. When present, the codegen
|
|
51
|
+
* generators (database-type codegen + doc-model v2 codegen) emit a
|
|
52
|
+
* TypeScript string-literal union (`"a" | "b" | "c"`) instead of a bare
|
|
53
|
+
* `string` for this field.
|
|
54
|
+
*
|
|
55
|
+
* Advisory / codegen-only: this is a TS-emission hint. The runtime and the
|
|
56
|
+
* server do NOT enforce enum membership on write (see #843). Only valid on
|
|
57
|
+
* `string` fields, and must be a non-empty array of strings.
|
|
58
|
+
*/
|
|
59
|
+
enum?: string[];
|
|
36
60
|
}
|
|
37
61
|
interface UniqueConstraintConfig {
|
|
38
62
|
name: string;
|
|
@@ -529,6 +553,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
529
553
|
private ensureLocalChanges;
|
|
530
554
|
private hasLocalChange;
|
|
531
555
|
private getFromYjs;
|
|
556
|
+
/**
|
|
557
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
558
|
+
*
|
|
559
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
560
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
561
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
562
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
563
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
564
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
565
|
+
*/
|
|
566
|
+
private normalizeNonStringSetValue;
|
|
532
567
|
private getValue;
|
|
533
568
|
private setValue;
|
|
534
569
|
get isDirty(): boolean;
|
|
@@ -835,6 +870,11 @@ declare function attachAndRegisterModel(modelClass: typeof BaseModel, schema: De
|
|
|
835
870
|
|
|
836
871
|
/**
|
|
837
872
|
* Infer a _meta_ type string from a JS runtime value.
|
|
873
|
+
*
|
|
874
|
+
* Only an all-`true` Y.Map (the stringset wire shape) is tagged as
|
|
875
|
+
* `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
|
|
876
|
+
* Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
|
|
877
|
+
* than being mis-tagged.
|
|
838
878
|
*/
|
|
839
879
|
declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
|
|
840
880
|
/**
|
|
@@ -919,6 +959,7 @@ interface DiscoveredField {
|
|
|
919
959
|
required?: boolean;
|
|
920
960
|
default?: string | number | boolean;
|
|
921
961
|
autoAssign?: boolean;
|
|
962
|
+
autoStamp?: "create" | "update" | "both";
|
|
922
963
|
maxLength?: number;
|
|
923
964
|
maxCount?: number;
|
|
924
965
|
}
|