js-bao 0.4.2 → 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 +206 -41
- package/dist/browser.d.cts +41 -0
- package/dist/browser.d.ts +41 -0
- package/dist/browser.js +203 -39
- 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 +80 -9
- package/dist/codegen.cjs +6 -1
- package/dist/index.cjs +210 -45
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +207 -43
- package/dist/node.cjs +210 -45
- package/dist/node.d.cts +41 -0
- package/dist/node.d.ts +41 -0
- package/dist/node.js +207 -43
- package/package.json +1 -1
package/dist/browser.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
|
}
|
|
@@ -2433,11 +2472,11 @@ __export(BaseModel_exports, {
|
|
|
2433
2472
|
function generateULID() {
|
|
2434
2473
|
return (0, import_ulid.ulid)();
|
|
2435
2474
|
}
|
|
2436
|
-
var
|
|
2475
|
+
var Y3, import_ulid, LogLevel, Logger, UniqueConstraintViolationError, RecordNotFoundError, SCHEMA_ACCESSORS_KEY, BaseModel2;
|
|
2437
2476
|
var init_BaseModel = __esm({
|
|
2438
2477
|
"src/models/BaseModel.ts"() {
|
|
2439
2478
|
"use strict";
|
|
2440
|
-
|
|
2479
|
+
Y3 = __toESM(require("yjs"), 1);
|
|
2441
2480
|
import_ulid = require("ulid");
|
|
2442
2481
|
init_StringSet();
|
|
2443
2482
|
init_documentTypes();
|
|
@@ -2445,6 +2484,7 @@ var init_BaseModel = __esm({
|
|
|
2445
2484
|
init_CursorManager();
|
|
2446
2485
|
init_sql();
|
|
2447
2486
|
init_metaSync();
|
|
2487
|
+
init_yjsValues();
|
|
2448
2488
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
2449
2489
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
2450
2490
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2953,7 +2993,30 @@ var init_BaseModel = __esm({
|
|
|
2953
2993
|
if (verboseEnabled) {
|
|
2954
2994
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2955
2995
|
}
|
|
2956
|
-
return fieldValue;
|
|
2996
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
3000
|
+
*
|
|
3001
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
3002
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
3003
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
3004
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
3005
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
3006
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
3007
|
+
*/
|
|
3008
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
3009
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
3010
|
+
if (fieldOptions?.type === "stringset") {
|
|
3011
|
+
return value;
|
|
3012
|
+
}
|
|
3013
|
+
if (isYjsComposite(value)) {
|
|
3014
|
+
Logger.warn(
|
|
3015
|
+
`[${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.`
|
|
3016
|
+
);
|
|
3017
|
+
return normalizeYjsValue(value);
|
|
3018
|
+
}
|
|
3019
|
+
return value;
|
|
2957
3020
|
}
|
|
2958
3021
|
getValue(fieldKey) {
|
|
2959
3022
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -3034,7 +3097,7 @@ var init_BaseModel = __esm({
|
|
|
3034
3097
|
validateBeforeSave() {
|
|
3035
3098
|
const schema = this.constructor.getSchema();
|
|
3036
3099
|
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
3037
|
-
const currentValue = this.getValue(fieldKey);
|
|
3100
|
+
const currentValue = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
3038
3101
|
if (fieldOptions.required && (currentValue === null || currentValue === void 0)) {
|
|
3039
3102
|
throw new Error(`Field ${fieldKey} is required before save`);
|
|
3040
3103
|
}
|
|
@@ -3098,7 +3161,7 @@ var init_BaseModel = __esm({
|
|
|
3098
3161
|
}
|
|
3099
3162
|
getStringSetFromYjs(fieldName) {
|
|
3100
3163
|
const yjsData = this.getFromYjs(fieldName);
|
|
3101
|
-
if (yjsData instanceof
|
|
3164
|
+
if (yjsData instanceof Y3.Map) {
|
|
3102
3165
|
return Array.from(yjsData.keys());
|
|
3103
3166
|
}
|
|
3104
3167
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -3206,7 +3269,7 @@ var init_BaseModel = __esm({
|
|
|
3206
3269
|
}
|
|
3207
3270
|
const extractStringSetValues = (value) => {
|
|
3208
3271
|
if (!value) return [];
|
|
3209
|
-
if (value instanceof
|
|
3272
|
+
if (value instanceof Y3.Map) {
|
|
3210
3273
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
3211
3274
|
}
|
|
3212
3275
|
if (Array.isArray(value)) {
|
|
@@ -3223,7 +3286,7 @@ var init_BaseModel = __esm({
|
|
|
3223
3286
|
if (!recordId) continue;
|
|
3224
3287
|
let itemData;
|
|
3225
3288
|
const stringSetValuesByField = {};
|
|
3226
|
-
if (recordData instanceof
|
|
3289
|
+
if (recordData instanceof Y3.Map) {
|
|
3227
3290
|
itemData = {};
|
|
3228
3291
|
const unknownFields = [];
|
|
3229
3292
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3342,7 +3405,7 @@ var init_BaseModel = __esm({
|
|
|
3342
3405
|
const buildUniqueKey = (recordData, fields) => {
|
|
3343
3406
|
const keyParts = [];
|
|
3344
3407
|
for (const field of fields) {
|
|
3345
|
-
const value = recordData instanceof
|
|
3408
|
+
const value = recordData instanceof Y3.Map ? recordData.get(field) : recordData[field];
|
|
3346
3409
|
if (value === null || value === void 0) {
|
|
3347
3410
|
return null;
|
|
3348
3411
|
}
|
|
@@ -3352,7 +3415,7 @@ var init_BaseModel = __esm({
|
|
|
3352
3415
|
};
|
|
3353
3416
|
const extractItemData = (key, recordData) => {
|
|
3354
3417
|
let itemData;
|
|
3355
|
-
if (recordData instanceof
|
|
3418
|
+
if (recordData instanceof Y3.Map) {
|
|
3356
3419
|
itemData = {};
|
|
3357
3420
|
const unknownFields = [];
|
|
3358
3421
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3365,7 +3428,14 @@ var init_BaseModel = __esm({
|
|
|
3365
3428
|
continue;
|
|
3366
3429
|
}
|
|
3367
3430
|
if (value !== void 0) {
|
|
3368
|
-
|
|
3431
|
+
if (isYjsComposite(value)) {
|
|
3432
|
+
Logger.warn(
|
|
3433
|
+
`[${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.`
|
|
3434
|
+
);
|
|
3435
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3436
|
+
} else {
|
|
3437
|
+
itemData[fieldKey] = value;
|
|
3438
|
+
}
|
|
3369
3439
|
}
|
|
3370
3440
|
}
|
|
3371
3441
|
if (unknownFields.length > 0) {
|
|
@@ -3388,7 +3458,14 @@ var init_BaseModel = __esm({
|
|
|
3388
3458
|
continue;
|
|
3389
3459
|
}
|
|
3390
3460
|
if (value !== void 0) {
|
|
3391
|
-
|
|
3461
|
+
if (isYjsComposite(value)) {
|
|
3462
|
+
Logger.warn(
|
|
3463
|
+
`[${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.`
|
|
3464
|
+
);
|
|
3465
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3466
|
+
} else {
|
|
3467
|
+
filteredData[fieldKey] = value;
|
|
3468
|
+
}
|
|
3392
3469
|
}
|
|
3393
3470
|
}
|
|
3394
3471
|
if (unknownFields.length > 0) {
|
|
@@ -3468,7 +3545,7 @@ var init_BaseModel = __esm({
|
|
|
3468
3545
|
if (!recordData || !key) continue;
|
|
3469
3546
|
const itemData = extractItemData(key, recordData);
|
|
3470
3547
|
if (!itemData) continue;
|
|
3471
|
-
if (change.action === "add" && recordData instanceof
|
|
3548
|
+
if (change.action === "add" && recordData instanceof Y3.Map) {
|
|
3472
3549
|
Logger.verbose(
|
|
3473
3550
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
3474
3551
|
);
|
|
@@ -3613,7 +3690,7 @@ var init_BaseModel = __esm({
|
|
|
3613
3690
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
3614
3691
|
);
|
|
3615
3692
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3616
|
-
if (recordData instanceof
|
|
3693
|
+
if (recordData instanceof Y3.Map) {
|
|
3617
3694
|
Logger.verbose(
|
|
3618
3695
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
3619
3696
|
);
|
|
@@ -3798,6 +3875,11 @@ var init_BaseModel = __esm({
|
|
|
3798
3875
|
Logger.debug(
|
|
3799
3876
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3800
3877
|
);
|
|
3878
|
+
if (isYjsComposite(yjsValue)) {
|
|
3879
|
+
Logger.warn(
|
|
3880
|
+
`[${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).`
|
|
3881
|
+
);
|
|
3882
|
+
}
|
|
3801
3883
|
if (yjsValue === void 0) {
|
|
3802
3884
|
Logger.debug(
|
|
3803
3885
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3835,8 +3917,8 @@ var init_BaseModel = __esm({
|
|
|
3835
3917
|
*/
|
|
3836
3918
|
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3837
3919
|
let nested = recordYMap.get(fieldName);
|
|
3838
|
-
if (!(nested instanceof
|
|
3839
|
-
const migrated = new
|
|
3920
|
+
if (!(nested instanceof Y3.Map)) {
|
|
3921
|
+
const migrated = new Y3.Map();
|
|
3840
3922
|
if (nested && typeof nested === "object") {
|
|
3841
3923
|
for (const [member, marker] of Object.entries(
|
|
3842
3924
|
nested
|
|
@@ -4023,6 +4105,29 @@ var init_BaseModel = __esm({
|
|
|
4023
4105
|
);
|
|
4024
4106
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
4025
4107
|
}
|
|
4108
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
4109
|
+
{
|
|
4110
|
+
const existingRecord = targetYMap.get(this.id);
|
|
4111
|
+
const isNew = !existingRecord;
|
|
4112
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
4113
|
+
const stamp = fieldOptions.autoStamp;
|
|
4114
|
+
if (!stamp) continue;
|
|
4115
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
4116
|
+
if (callerExplicit) {
|
|
4117
|
+
const localValue = this._localChanges[fieldKey];
|
|
4118
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
4119
|
+
}
|
|
4120
|
+
if (stamp === "create") {
|
|
4121
|
+
if (!isNew) continue;
|
|
4122
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
4123
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
4124
|
+
}
|
|
4125
|
+
this[fieldKey] = Date.now();
|
|
4126
|
+
if (stamp === "create") {
|
|
4127
|
+
preTransactCreateStamps.add(fieldKey);
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4026
4131
|
this.validateBeforeSave();
|
|
4027
4132
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
4028
4133
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -4053,6 +4158,14 @@ var init_BaseModel = __esm({
|
|
|
4053
4158
|
}
|
|
4054
4159
|
}
|
|
4055
4160
|
}
|
|
4161
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
4162
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
4163
|
+
delete this._localChanges[fieldKey];
|
|
4164
|
+
}
|
|
4165
|
+
if (fieldKey in dataToSave) {
|
|
4166
|
+
delete dataToSave[fieldKey];
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4056
4169
|
}
|
|
4057
4170
|
}
|
|
4058
4171
|
if (!targetYMap) {
|
|
@@ -4065,7 +4178,7 @@ var init_BaseModel = __esm({
|
|
|
4065
4178
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
4066
4179
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
4067
4180
|
if (!recordYMap) {
|
|
4068
|
-
recordYMap = new
|
|
4181
|
+
recordYMap = new Y3.Map();
|
|
4069
4182
|
Logger.verbose(
|
|
4070
4183
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
4071
4184
|
);
|
|
@@ -5092,7 +5205,7 @@ var init_BaseModel = __esm({
|
|
|
5092
5205
|
);
|
|
5093
5206
|
}
|
|
5094
5207
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
5095
|
-
if (recordYMap instanceof
|
|
5208
|
+
if (recordYMap instanceof Y3.Map) {
|
|
5096
5209
|
const instance = new this({ id: recordId });
|
|
5097
5210
|
instance._metaDocId = docId;
|
|
5098
5211
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -5163,7 +5276,7 @@ var init_BaseModel = __esm({
|
|
|
5163
5276
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
5164
5277
|
if (documentYMap) {
|
|
5165
5278
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
5166
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
5279
|
+
if (recordYMap2 && recordYMap2 instanceof Y3.Map) {
|
|
5167
5280
|
const instance = new modelConstructor({
|
|
5168
5281
|
id: recordId2
|
|
5169
5282
|
});
|
|
@@ -5200,7 +5313,7 @@ var init_BaseModel = __esm({
|
|
|
5200
5313
|
if (!recordYMap) {
|
|
5201
5314
|
return null;
|
|
5202
5315
|
}
|
|
5203
|
-
if (recordYMap instanceof
|
|
5316
|
+
if (recordYMap instanceof Y3.Map) {
|
|
5204
5317
|
const instance = new modelConstructor({
|
|
5205
5318
|
id: recordId
|
|
5206
5319
|
});
|
|
@@ -5432,7 +5545,7 @@ var init_BaseModel = __esm({
|
|
|
5432
5545
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5433
5546
|
if (fieldOptions?.type !== "stringset") continue;
|
|
5434
5547
|
const value = recordYMap.get(fieldKey);
|
|
5435
|
-
if (value instanceof
|
|
5548
|
+
if (value instanceof Y3.Map) {
|
|
5436
5549
|
this.observeStringSetMapOnce(value);
|
|
5437
5550
|
}
|
|
5438
5551
|
}
|
|
@@ -5479,7 +5592,14 @@ var init_BaseModel = __esm({
|
|
|
5479
5592
|
continue;
|
|
5480
5593
|
}
|
|
5481
5594
|
if (value !== void 0) {
|
|
5482
|
-
|
|
5595
|
+
if (isYjsComposite(value)) {
|
|
5596
|
+
Logger.warn(
|
|
5597
|
+
`[${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.`
|
|
5598
|
+
);
|
|
5599
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5600
|
+
} else {
|
|
5601
|
+
updatedData[key] = value;
|
|
5602
|
+
}
|
|
5483
5603
|
}
|
|
5484
5604
|
}
|
|
5485
5605
|
if (unknownFields.length > 0) {
|
|
@@ -5559,7 +5679,14 @@ var init_BaseModel = __esm({
|
|
|
5559
5679
|
continue;
|
|
5560
5680
|
}
|
|
5561
5681
|
if (value !== void 0) {
|
|
5562
|
-
|
|
5682
|
+
if (isYjsComposite(value)) {
|
|
5683
|
+
Logger.warn(
|
|
5684
|
+
`[${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.`
|
|
5685
|
+
);
|
|
5686
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5687
|
+
} else {
|
|
5688
|
+
updatedData[key] = value;
|
|
5689
|
+
}
|
|
5563
5690
|
}
|
|
5564
5691
|
}
|
|
5565
5692
|
if (unknownFields.length > 0) {
|
|
@@ -6546,7 +6673,8 @@ function attachAndRegisterModel(modelClass, schema) {
|
|
|
6546
6673
|
}
|
|
6547
6674
|
|
|
6548
6675
|
// src/utils/yDocSchema.ts
|
|
6549
|
-
var
|
|
6676
|
+
var Y4 = __toESM(require("yjs"), 1);
|
|
6677
|
+
init_yjsValues();
|
|
6550
6678
|
function discoverSchema(yDoc) {
|
|
6551
6679
|
const models = {};
|
|
6552
6680
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -6580,7 +6708,7 @@ function discoverModelNames(yDoc) {
|
|
|
6580
6708
|
function materializeMap(yDoc, key) {
|
|
6581
6709
|
try {
|
|
6582
6710
|
const map = yDoc.getMap(key);
|
|
6583
|
-
return map instanceof
|
|
6711
|
+
return map instanceof Y4.Map ? map : null;
|
|
6584
6712
|
} catch {
|
|
6585
6713
|
return null;
|
|
6586
6714
|
}
|
|
@@ -6590,11 +6718,11 @@ function readModelMeta(metaMap) {
|
|
|
6590
6718
|
let constraints;
|
|
6591
6719
|
let relationships;
|
|
6592
6720
|
for (const [key, value] of metaMap.entries()) {
|
|
6593
|
-
if (key === "_constraints" && value instanceof
|
|
6721
|
+
if (key === "_constraints" && value instanceof Y4.Map) {
|
|
6594
6722
|
constraints = readConstraints(value);
|
|
6595
|
-
} else if (key === "_relationships" && value instanceof
|
|
6723
|
+
} else if (key === "_relationships" && value instanceof Y4.Map) {
|
|
6596
6724
|
relationships = readRelationships(value);
|
|
6597
|
-
} else if (value instanceof
|
|
6725
|
+
} else if (value instanceof Y4.Map) {
|
|
6598
6726
|
fields[key] = readFieldMeta(value);
|
|
6599
6727
|
}
|
|
6600
6728
|
}
|
|
@@ -6613,6 +6741,10 @@ function readFieldMeta(fieldMap) {
|
|
|
6613
6741
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
6614
6742
|
if (fieldMap.get("required") === true) field.required = true;
|
|
6615
6743
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
6744
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
6745
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
6746
|
+
field.autoStamp = autoStamp;
|
|
6747
|
+
}
|
|
6616
6748
|
const def = fieldMap.get("default");
|
|
6617
6749
|
if (def !== void 0) field.default = def;
|
|
6618
6750
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -6625,7 +6757,7 @@ function inferModelFromData(dataMap) {
|
|
|
6625
6757
|
const fields = {};
|
|
6626
6758
|
let sampled = 0;
|
|
6627
6759
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
6628
|
-
if (!(recordValue instanceof
|
|
6760
|
+
if (!(recordValue instanceof Y4.Map)) continue;
|
|
6629
6761
|
if (++sampled > 5) break;
|
|
6630
6762
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
6631
6763
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -6638,7 +6770,7 @@ function inferModelFromData(dataMap) {
|
|
|
6638
6770
|
return { fields };
|
|
6639
6771
|
}
|
|
6640
6772
|
function inferTypeFromValue(value) {
|
|
6641
|
-
if (value
|
|
6773
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
6642
6774
|
switch (typeof value) {
|
|
6643
6775
|
case "string":
|
|
6644
6776
|
return "string";
|
|
@@ -6653,7 +6785,7 @@ function inferTypeFromValue(value) {
|
|
|
6653
6785
|
function readConstraints(constraintsMap) {
|
|
6654
6786
|
const out = {};
|
|
6655
6787
|
for (const [name, value] of constraintsMap.entries()) {
|
|
6656
|
-
if (!(value instanceof
|
|
6788
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
6657
6789
|
let fields = [];
|
|
6658
6790
|
const rawFields = value.get("fields");
|
|
6659
6791
|
if (typeof rawFields === "string") {
|
|
@@ -6672,7 +6804,7 @@ function readConstraints(constraintsMap) {
|
|
|
6672
6804
|
function readRelationships(relsMap) {
|
|
6673
6805
|
const out = {};
|
|
6674
6806
|
for (const [name, value] of relsMap.entries()) {
|
|
6675
|
-
if (!(value instanceof
|
|
6807
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
6676
6808
|
const rel = {};
|
|
6677
6809
|
for (const [k, v] of value.entries()) {
|
|
6678
6810
|
rel[k] = v;
|
|
@@ -6683,6 +6815,7 @@ function readRelationships(relsMap) {
|
|
|
6683
6815
|
}
|
|
6684
6816
|
var CAMEL_TO_SNAKE = {
|
|
6685
6817
|
autoAssign: "auto_assign",
|
|
6818
|
+
autoStamp: "auto_stamp",
|
|
6686
6819
|
maxLength: "max_length",
|
|
6687
6820
|
maxCount: "max_count",
|
|
6688
6821
|
relatedIdField: "related_id_field",
|
|
@@ -6713,6 +6846,7 @@ function schemaToToml(schema) {
|
|
|
6713
6846
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
6714
6847
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
6715
6848
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
6849
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
6716
6850
|
if (field.indexed) lines.push("indexed = true");
|
|
6717
6851
|
if (field.unique) lines.push("unique = true");
|
|
6718
6852
|
if (field.required) lines.push("required = true");
|
|
@@ -6759,10 +6893,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
6759
6893
|
"unique",
|
|
6760
6894
|
"required",
|
|
6761
6895
|
"auto_assign",
|
|
6896
|
+
"auto_stamp",
|
|
6762
6897
|
"max_length",
|
|
6763
6898
|
"max_count",
|
|
6764
|
-
"default"
|
|
6899
|
+
"default",
|
|
6900
|
+
"enum"
|
|
6765
6901
|
]);
|
|
6902
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
6766
6903
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
6767
6904
|
"fields",
|
|
6768
6905
|
"relationships",
|
|
@@ -6804,9 +6941,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
6804
6941
|
if (raw.unique === true) opts.unique = true;
|
|
6805
6942
|
if (raw.required === true) opts.required = true;
|
|
6806
6943
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
6944
|
+
if (raw.auto_stamp !== void 0) {
|
|
6945
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
6946
|
+
throw new Error(
|
|
6947
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
6948
|
+
...VALID_AUTO_STAMP_VALUES
|
|
6949
|
+
].join(", ")}`
|
|
6950
|
+
);
|
|
6951
|
+
}
|
|
6952
|
+
opts.autoStamp = raw.auto_stamp;
|
|
6953
|
+
}
|
|
6807
6954
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
6808
6955
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
6809
6956
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
6957
|
+
if (raw.enum !== void 0) {
|
|
6958
|
+
if (raw.type !== "string") {
|
|
6959
|
+
throw new Error(
|
|
6960
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
6961
|
+
);
|
|
6962
|
+
}
|
|
6963
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
6964
|
+
throw new Error(
|
|
6965
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
6966
|
+
);
|
|
6967
|
+
}
|
|
6968
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
6969
|
+
throw new Error(
|
|
6970
|
+
`${context}: \`enum\` values must all be strings.`
|
|
6971
|
+
);
|
|
6972
|
+
}
|
|
6973
|
+
opts.enum = [...raw.enum];
|
|
6974
|
+
}
|
|
6810
6975
|
return opts;
|
|
6811
6976
|
}
|
|
6812
6977
|
function requireField(raw, field, context) {
|
package/dist/browser.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;
|
|
@@ -495,6 +519,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
495
519
|
private ensureLocalChanges;
|
|
496
520
|
private hasLocalChange;
|
|
497
521
|
private getFromYjs;
|
|
522
|
+
/**
|
|
523
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
524
|
+
*
|
|
525
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
526
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
527
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
528
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
529
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
530
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
531
|
+
*/
|
|
532
|
+
private normalizeNonStringSetValue;
|
|
498
533
|
private getValue;
|
|
499
534
|
private setValue;
|
|
500
535
|
get isDirty(): boolean;
|
|
@@ -922,6 +957,7 @@ interface DiscoveredField {
|
|
|
922
957
|
required?: boolean;
|
|
923
958
|
default?: string | number | boolean;
|
|
924
959
|
autoAssign?: boolean;
|
|
960
|
+
autoStamp?: "create" | "update" | "both";
|
|
925
961
|
maxLength?: number;
|
|
926
962
|
maxCount?: number;
|
|
927
963
|
}
|
|
@@ -1010,6 +1046,11 @@ declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSche
|
|
|
1010
1046
|
|
|
1011
1047
|
/**
|
|
1012
1048
|
* Infer a _meta_ type string from a JS runtime value.
|
|
1049
|
+
*
|
|
1050
|
+
* Only an all-`true` Y.Map (the stringset wire shape) is tagged as
|
|
1051
|
+
* `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
|
|
1052
|
+
* Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
|
|
1053
|
+
* than being mis-tagged.
|
|
1013
1054
|
*/
|
|
1014
1055
|
declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
|
|
1015
1056
|
/**
|