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/node.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) {
|
|
@@ -7168,7 +7295,8 @@ function attachAndRegisterModel(modelClass, schema) {
|
|
|
7168
7295
|
}
|
|
7169
7296
|
|
|
7170
7297
|
// src/utils/yDocSchema.ts
|
|
7171
|
-
var
|
|
7298
|
+
var Y4 = __toESM(require("yjs"), 1);
|
|
7299
|
+
init_yjsValues();
|
|
7172
7300
|
function discoverSchema(yDoc) {
|
|
7173
7301
|
const models = {};
|
|
7174
7302
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -7202,7 +7330,7 @@ function discoverModelNames(yDoc) {
|
|
|
7202
7330
|
function materializeMap(yDoc, key) {
|
|
7203
7331
|
try {
|
|
7204
7332
|
const map = yDoc.getMap(key);
|
|
7205
|
-
return map instanceof
|
|
7333
|
+
return map instanceof Y4.Map ? map : null;
|
|
7206
7334
|
} catch {
|
|
7207
7335
|
return null;
|
|
7208
7336
|
}
|
|
@@ -7212,11 +7340,11 @@ function readModelMeta(metaMap) {
|
|
|
7212
7340
|
let constraints;
|
|
7213
7341
|
let relationships;
|
|
7214
7342
|
for (const [key, value] of metaMap.entries()) {
|
|
7215
|
-
if (key === "_constraints" && value instanceof
|
|
7343
|
+
if (key === "_constraints" && value instanceof Y4.Map) {
|
|
7216
7344
|
constraints = readConstraints(value);
|
|
7217
|
-
} else if (key === "_relationships" && value instanceof
|
|
7345
|
+
} else if (key === "_relationships" && value instanceof Y4.Map) {
|
|
7218
7346
|
relationships = readRelationships(value);
|
|
7219
|
-
} else if (value instanceof
|
|
7347
|
+
} else if (value instanceof Y4.Map) {
|
|
7220
7348
|
fields[key] = readFieldMeta(value);
|
|
7221
7349
|
}
|
|
7222
7350
|
}
|
|
@@ -7235,6 +7363,10 @@ function readFieldMeta(fieldMap) {
|
|
|
7235
7363
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
7236
7364
|
if (fieldMap.get("required") === true) field.required = true;
|
|
7237
7365
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7366
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7367
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7368
|
+
field.autoStamp = autoStamp;
|
|
7369
|
+
}
|
|
7238
7370
|
const def = fieldMap.get("default");
|
|
7239
7371
|
if (def !== void 0) field.default = def;
|
|
7240
7372
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -7247,7 +7379,7 @@ function inferModelFromData(dataMap) {
|
|
|
7247
7379
|
const fields = {};
|
|
7248
7380
|
let sampled = 0;
|
|
7249
7381
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
7250
|
-
if (!(recordValue instanceof
|
|
7382
|
+
if (!(recordValue instanceof Y4.Map)) continue;
|
|
7251
7383
|
if (++sampled > 5) break;
|
|
7252
7384
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
7253
7385
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -7260,7 +7392,7 @@ function inferModelFromData(dataMap) {
|
|
|
7260
7392
|
return { fields };
|
|
7261
7393
|
}
|
|
7262
7394
|
function inferTypeFromValue(value) {
|
|
7263
|
-
if (value
|
|
7395
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
7264
7396
|
switch (typeof value) {
|
|
7265
7397
|
case "string":
|
|
7266
7398
|
return "string";
|
|
@@ -7275,7 +7407,7 @@ function inferTypeFromValue(value) {
|
|
|
7275
7407
|
function readConstraints(constraintsMap) {
|
|
7276
7408
|
const out = {};
|
|
7277
7409
|
for (const [name, value] of constraintsMap.entries()) {
|
|
7278
|
-
if (!(value instanceof
|
|
7410
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7279
7411
|
let fields = [];
|
|
7280
7412
|
const rawFields = value.get("fields");
|
|
7281
7413
|
if (typeof rawFields === "string") {
|
|
@@ -7294,7 +7426,7 @@ function readConstraints(constraintsMap) {
|
|
|
7294
7426
|
function readRelationships(relsMap) {
|
|
7295
7427
|
const out = {};
|
|
7296
7428
|
for (const [name, value] of relsMap.entries()) {
|
|
7297
|
-
if (!(value instanceof
|
|
7429
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7298
7430
|
const rel = {};
|
|
7299
7431
|
for (const [k, v] of value.entries()) {
|
|
7300
7432
|
rel[k] = v;
|
|
@@ -7305,6 +7437,7 @@ function readRelationships(relsMap) {
|
|
|
7305
7437
|
}
|
|
7306
7438
|
var CAMEL_TO_SNAKE = {
|
|
7307
7439
|
autoAssign: "auto_assign",
|
|
7440
|
+
autoStamp: "auto_stamp",
|
|
7308
7441
|
maxLength: "max_length",
|
|
7309
7442
|
maxCount: "max_count",
|
|
7310
7443
|
relatedIdField: "related_id_field",
|
|
@@ -7335,6 +7468,7 @@ function schemaToToml(schema) {
|
|
|
7335
7468
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
7336
7469
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
7337
7470
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7471
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
7338
7472
|
if (field.indexed) lines.push("indexed = true");
|
|
7339
7473
|
if (field.unique) lines.push("unique = true");
|
|
7340
7474
|
if (field.required) lines.push("required = true");
|
|
@@ -7381,10 +7515,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
7381
7515
|
"unique",
|
|
7382
7516
|
"required",
|
|
7383
7517
|
"auto_assign",
|
|
7518
|
+
"auto_stamp",
|
|
7384
7519
|
"max_length",
|
|
7385
7520
|
"max_count",
|
|
7386
|
-
"default"
|
|
7521
|
+
"default",
|
|
7522
|
+
"enum"
|
|
7387
7523
|
]);
|
|
7524
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
7388
7525
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7389
7526
|
"fields",
|
|
7390
7527
|
"relationships",
|
|
@@ -7426,9 +7563,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
7426
7563
|
if (raw.unique === true) opts.unique = true;
|
|
7427
7564
|
if (raw.required === true) opts.required = true;
|
|
7428
7565
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
7566
|
+
if (raw.auto_stamp !== void 0) {
|
|
7567
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
7568
|
+
throw new Error(
|
|
7569
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
7570
|
+
...VALID_AUTO_STAMP_VALUES
|
|
7571
|
+
].join(", ")}`
|
|
7572
|
+
);
|
|
7573
|
+
}
|
|
7574
|
+
opts.autoStamp = raw.auto_stamp;
|
|
7575
|
+
}
|
|
7429
7576
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
7430
7577
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
7431
7578
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
7579
|
+
if (raw.enum !== void 0) {
|
|
7580
|
+
if (raw.type !== "string") {
|
|
7581
|
+
throw new Error(
|
|
7582
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
7583
|
+
);
|
|
7584
|
+
}
|
|
7585
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
7586
|
+
throw new Error(
|
|
7587
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
7588
|
+
);
|
|
7589
|
+
}
|
|
7590
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
7591
|
+
throw new Error(
|
|
7592
|
+
`${context}: \`enum\` values must all be strings.`
|
|
7593
|
+
);
|
|
7594
|
+
}
|
|
7595
|
+
opts.enum = [...raw.enum];
|
|
7596
|
+
}
|
|
7432
7597
|
return opts;
|
|
7433
7598
|
}
|
|
7434
7599
|
function requireField(raw, field, context) {
|
|
@@ -7559,16 +7724,16 @@ async function loadSchemaFromToml(filePath, options = {}) {
|
|
|
7559
7724
|
init_metaSync();
|
|
7560
7725
|
|
|
7561
7726
|
// src/utils/yDocDump.ts
|
|
7562
|
-
var
|
|
7727
|
+
var Y5 = __toESM(require("yjs"), 1);
|
|
7563
7728
|
function toPlain(value) {
|
|
7564
|
-
if (value instanceof
|
|
7729
|
+
if (value instanceof Y5.Map) {
|
|
7565
7730
|
const obj = {};
|
|
7566
7731
|
value.forEach((v, k) => {
|
|
7567
7732
|
obj[k] = toPlain(v);
|
|
7568
7733
|
});
|
|
7569
7734
|
return obj;
|
|
7570
7735
|
}
|
|
7571
|
-
if (value instanceof
|
|
7736
|
+
if (value instanceof Y5.Array) {
|
|
7572
7737
|
return value.toArray().map((item) => toPlain(item));
|
|
7573
7738
|
}
|
|
7574
7739
|
return value;
|
|
@@ -7577,7 +7742,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
|
|
|
7577
7742
|
const result = {};
|
|
7578
7743
|
const includeIndexes = !!options.includeIndexes;
|
|
7579
7744
|
yDoc.share.forEach((type, name) => {
|
|
7580
|
-
if (!(type instanceof
|
|
7745
|
+
if (!(type instanceof Y5.Map)) return;
|
|
7581
7746
|
if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
|
|
7582
7747
|
const records = {};
|
|
7583
7748
|
type.forEach((recordValue, recordId) => {
|
package/dist/node.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;
|
|
@@ -951,6 +986,7 @@ interface DiscoveredField {
|
|
|
951
986
|
required?: boolean;
|
|
952
987
|
default?: string | number | boolean;
|
|
953
988
|
autoAssign?: boolean;
|
|
989
|
+
autoStamp?: "create" | "update" | "both";
|
|
954
990
|
maxLength?: number;
|
|
955
991
|
maxCount?: number;
|
|
956
992
|
}
|
|
@@ -1044,6 +1080,11 @@ declare function loadSchemaFromToml(filePath: string, options?: LoadSchemaOption
|
|
|
1044
1080
|
|
|
1045
1081
|
/**
|
|
1046
1082
|
* Infer a _meta_ type string from a JS runtime value.
|
|
1083
|
+
*
|
|
1084
|
+
* Only an all-`true` Y.Map (the stringset wire shape) is tagged as
|
|
1085
|
+
* `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
|
|
1086
|
+
* Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
|
|
1087
|
+
* than being mis-tagged.
|
|
1047
1088
|
*/
|
|
1048
1089
|
declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
|
|
1049
1090
|
/**
|