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.d.ts
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
|
/**
|
package/dist/node.js
CHANGED
|
@@ -1161,10 +1161,46 @@ var init_DocumentQueryTranslator = __esm({
|
|
|
1161
1161
|
}
|
|
1162
1162
|
});
|
|
1163
1163
|
|
|
1164
|
-
// src/
|
|
1164
|
+
// src/utils/yjsValues.ts
|
|
1165
1165
|
import * as Y from "yjs";
|
|
1166
|
+
function isYjsComposite(value) {
|
|
1167
|
+
return value instanceof Y.Map || value instanceof Y.Array || value instanceof Y.Text;
|
|
1168
|
+
}
|
|
1169
|
+
function isStringSetYMap(value) {
|
|
1170
|
+
if (!(value instanceof Y.Map)) return false;
|
|
1171
|
+
for (const v of value.values()) {
|
|
1172
|
+
if (v !== true) return false;
|
|
1173
|
+
}
|
|
1174
|
+
return true;
|
|
1175
|
+
}
|
|
1176
|
+
function normalizeYjsValue(value) {
|
|
1177
|
+
if (value instanceof Y.Text) {
|
|
1178
|
+
return value.toString();
|
|
1179
|
+
}
|
|
1180
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1181
|
+
return value.toJSON();
|
|
1182
|
+
}
|
|
1183
|
+
return value;
|
|
1184
|
+
}
|
|
1185
|
+
function normalizeYjsValueForStorage(value) {
|
|
1186
|
+
if (value instanceof Y.Text) {
|
|
1187
|
+
return value.toString();
|
|
1188
|
+
}
|
|
1189
|
+
if (value instanceof Y.Map || value instanceof Y.Array) {
|
|
1190
|
+
return JSON.stringify(value.toJSON());
|
|
1191
|
+
}
|
|
1192
|
+
return value;
|
|
1193
|
+
}
|
|
1194
|
+
var init_yjsValues = __esm({
|
|
1195
|
+
"src/utils/yjsValues.ts"() {
|
|
1196
|
+
"use strict";
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
// src/models/metaSync.ts
|
|
1201
|
+
import * as Y2 from "yjs";
|
|
1166
1202
|
function inferFieldType(value) {
|
|
1167
|
-
if (value
|
|
1203
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
1168
1204
|
switch (typeof value) {
|
|
1169
1205
|
case "string":
|
|
1170
1206
|
return "string";
|
|
@@ -1209,7 +1245,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1209
1245
|
if (compoundConstraints.length > 0) {
|
|
1210
1246
|
let constraints = meta.get("_constraints");
|
|
1211
1247
|
if (!constraints) {
|
|
1212
|
-
constraints = new
|
|
1248
|
+
constraints = new Y2.Map();
|
|
1213
1249
|
meta.set("_constraints", constraints);
|
|
1214
1250
|
}
|
|
1215
1251
|
for (const constraint of compoundConstraints) {
|
|
@@ -1220,7 +1256,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1220
1256
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
1221
1257
|
let rels = meta.get("_relationships");
|
|
1222
1258
|
if (!rels) {
|
|
1223
|
-
rels = new
|
|
1259
|
+
rels = new Y2.Map();
|
|
1224
1260
|
meta.set("_relationships", rels);
|
|
1225
1261
|
}
|
|
1226
1262
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -1232,7 +1268,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
1232
1268
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
1233
1269
|
let fieldMeta = metaMap.get(fieldName);
|
|
1234
1270
|
if (!fieldMeta) {
|
|
1235
|
-
fieldMeta = new
|
|
1271
|
+
fieldMeta = new Y2.Map();
|
|
1236
1272
|
metaMap.set(fieldName, fieldMeta);
|
|
1237
1273
|
}
|
|
1238
1274
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -1240,6 +1276,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1240
1276
|
if (fieldOpts.unique) setIfChanged(fieldMeta, "unique", true);
|
|
1241
1277
|
if (fieldOpts.required) setIfChanged(fieldMeta, "required", true);
|
|
1242
1278
|
if (fieldOpts.autoAssign) setIfChanged(fieldMeta, "autoAssign", true);
|
|
1279
|
+
if (fieldOpts.autoStamp) setIfChanged(fieldMeta, "autoStamp", fieldOpts.autoStamp);
|
|
1243
1280
|
if (fieldOpts.maxLength !== void 0) setIfChanged(fieldMeta, "maxLength", fieldOpts.maxLength);
|
|
1244
1281
|
if (fieldOpts.maxCount !== void 0) setIfChanged(fieldMeta, "maxCount", fieldOpts.maxCount);
|
|
1245
1282
|
const encoded = encodeDefault(fieldOpts.default);
|
|
@@ -1248,7 +1285,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
1248
1285
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
1249
1286
|
let cMeta = constraintsMap.get(constraint.name);
|
|
1250
1287
|
if (!cMeta) {
|
|
1251
|
-
cMeta = new
|
|
1288
|
+
cMeta = new Y2.Map();
|
|
1252
1289
|
constraintsMap.set(constraint.name, cMeta);
|
|
1253
1290
|
}
|
|
1254
1291
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -1258,7 +1295,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
1258
1295
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
1259
1296
|
let relMeta = relsMap.get(relName);
|
|
1260
1297
|
if (!relMeta) {
|
|
1261
|
-
relMeta = new
|
|
1298
|
+
relMeta = new Y2.Map();
|
|
1262
1299
|
relsMap.set(relName, relMeta);
|
|
1263
1300
|
}
|
|
1264
1301
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -1273,7 +1310,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
1273
1310
|
if (fieldName.startsWith("_")) continue;
|
|
1274
1311
|
let fieldMeta = meta.get(fieldName);
|
|
1275
1312
|
if (!fieldMeta) {
|
|
1276
|
-
fieldMeta = new
|
|
1313
|
+
fieldMeta = new Y2.Map();
|
|
1277
1314
|
meta.set(fieldName, fieldMeta);
|
|
1278
1315
|
}
|
|
1279
1316
|
if (!fieldMeta.has("type")) {
|
|
@@ -1293,6 +1330,7 @@ var KNOWN_FUNCTION_DEFAULTS, _syncedCache;
|
|
|
1293
1330
|
var init_metaSync = __esm({
|
|
1294
1331
|
"src/models/metaSync.ts"() {
|
|
1295
1332
|
"use strict";
|
|
1333
|
+
init_yjsValues();
|
|
1296
1334
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
1297
1335
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
1298
1336
|
}
|
|
@@ -2408,7 +2446,7 @@ __export(BaseModel_exports, {
|
|
|
2408
2446
|
UniqueConstraintViolationError: () => UniqueConstraintViolationError,
|
|
2409
2447
|
generateULID: () => generateULID
|
|
2410
2448
|
});
|
|
2411
|
-
import * as
|
|
2449
|
+
import * as Y3 from "yjs";
|
|
2412
2450
|
import { ulid } from "ulid";
|
|
2413
2451
|
function generateULID() {
|
|
2414
2452
|
return ulid();
|
|
@@ -2423,6 +2461,7 @@ var init_BaseModel = __esm({
|
|
|
2423
2461
|
init_CursorManager();
|
|
2424
2462
|
init_sql();
|
|
2425
2463
|
init_metaSync();
|
|
2464
|
+
init_yjsValues();
|
|
2426
2465
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
2427
2466
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
2428
2467
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2931,7 +2970,30 @@ var init_BaseModel = __esm({
|
|
|
2931
2970
|
if (verboseEnabled) {
|
|
2932
2971
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2933
2972
|
}
|
|
2934
|
-
return fieldValue;
|
|
2973
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
2974
|
+
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
2977
|
+
*
|
|
2978
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
2979
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
2980
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
2981
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
2982
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
2983
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
2984
|
+
*/
|
|
2985
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
2986
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
2987
|
+
if (fieldOptions?.type === "stringset") {
|
|
2988
|
+
return value;
|
|
2989
|
+
}
|
|
2990
|
+
if (isYjsComposite(value)) {
|
|
2991
|
+
Logger.warn(
|
|
2992
|
+
`[${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.`
|
|
2993
|
+
);
|
|
2994
|
+
return normalizeYjsValue(value);
|
|
2995
|
+
}
|
|
2996
|
+
return value;
|
|
2935
2997
|
}
|
|
2936
2998
|
getValue(fieldKey) {
|
|
2937
2999
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -3012,7 +3074,7 @@ var init_BaseModel = __esm({
|
|
|
3012
3074
|
validateBeforeSave() {
|
|
3013
3075
|
const schema = this.constructor.getSchema();
|
|
3014
3076
|
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
3015
|
-
const currentValue = this.getValue(fieldKey);
|
|
3077
|
+
const currentValue = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
3016
3078
|
if (fieldOptions.required && (currentValue === null || currentValue === void 0)) {
|
|
3017
3079
|
throw new Error(`Field ${fieldKey} is required before save`);
|
|
3018
3080
|
}
|
|
@@ -3076,7 +3138,7 @@ var init_BaseModel = __esm({
|
|
|
3076
3138
|
}
|
|
3077
3139
|
getStringSetFromYjs(fieldName) {
|
|
3078
3140
|
const yjsData = this.getFromYjs(fieldName);
|
|
3079
|
-
if (yjsData instanceof
|
|
3141
|
+
if (yjsData instanceof Y3.Map) {
|
|
3080
3142
|
return Array.from(yjsData.keys());
|
|
3081
3143
|
}
|
|
3082
3144
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -3184,7 +3246,7 @@ var init_BaseModel = __esm({
|
|
|
3184
3246
|
}
|
|
3185
3247
|
const extractStringSetValues = (value) => {
|
|
3186
3248
|
if (!value) return [];
|
|
3187
|
-
if (value instanceof
|
|
3249
|
+
if (value instanceof Y3.Map) {
|
|
3188
3250
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
3189
3251
|
}
|
|
3190
3252
|
if (Array.isArray(value)) {
|
|
@@ -3201,7 +3263,7 @@ var init_BaseModel = __esm({
|
|
|
3201
3263
|
if (!recordId) continue;
|
|
3202
3264
|
let itemData;
|
|
3203
3265
|
const stringSetValuesByField = {};
|
|
3204
|
-
if (recordData instanceof
|
|
3266
|
+
if (recordData instanceof Y3.Map) {
|
|
3205
3267
|
itemData = {};
|
|
3206
3268
|
const unknownFields = [];
|
|
3207
3269
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3320,7 +3382,7 @@ var init_BaseModel = __esm({
|
|
|
3320
3382
|
const buildUniqueKey = (recordData, fields) => {
|
|
3321
3383
|
const keyParts = [];
|
|
3322
3384
|
for (const field of fields) {
|
|
3323
|
-
const value = recordData instanceof
|
|
3385
|
+
const value = recordData instanceof Y3.Map ? recordData.get(field) : recordData[field];
|
|
3324
3386
|
if (value === null || value === void 0) {
|
|
3325
3387
|
return null;
|
|
3326
3388
|
}
|
|
@@ -3330,7 +3392,7 @@ var init_BaseModel = __esm({
|
|
|
3330
3392
|
};
|
|
3331
3393
|
const extractItemData = (key, recordData) => {
|
|
3332
3394
|
let itemData;
|
|
3333
|
-
if (recordData instanceof
|
|
3395
|
+
if (recordData instanceof Y3.Map) {
|
|
3334
3396
|
itemData = {};
|
|
3335
3397
|
const unknownFields = [];
|
|
3336
3398
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -3343,7 +3405,14 @@ var init_BaseModel = __esm({
|
|
|
3343
3405
|
continue;
|
|
3344
3406
|
}
|
|
3345
3407
|
if (value !== void 0) {
|
|
3346
|
-
|
|
3408
|
+
if (isYjsComposite(value)) {
|
|
3409
|
+
Logger.warn(
|
|
3410
|
+
`[${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.`
|
|
3411
|
+
);
|
|
3412
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3413
|
+
} else {
|
|
3414
|
+
itemData[fieldKey] = value;
|
|
3415
|
+
}
|
|
3347
3416
|
}
|
|
3348
3417
|
}
|
|
3349
3418
|
if (unknownFields.length > 0) {
|
|
@@ -3366,7 +3435,14 @@ var init_BaseModel = __esm({
|
|
|
3366
3435
|
continue;
|
|
3367
3436
|
}
|
|
3368
3437
|
if (value !== void 0) {
|
|
3369
|
-
|
|
3438
|
+
if (isYjsComposite(value)) {
|
|
3439
|
+
Logger.warn(
|
|
3440
|
+
`[${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.`
|
|
3441
|
+
);
|
|
3442
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
3443
|
+
} else {
|
|
3444
|
+
filteredData[fieldKey] = value;
|
|
3445
|
+
}
|
|
3370
3446
|
}
|
|
3371
3447
|
}
|
|
3372
3448
|
if (unknownFields.length > 0) {
|
|
@@ -3446,7 +3522,7 @@ var init_BaseModel = __esm({
|
|
|
3446
3522
|
if (!recordData || !key) continue;
|
|
3447
3523
|
const itemData = extractItemData(key, recordData);
|
|
3448
3524
|
if (!itemData) continue;
|
|
3449
|
-
if (change.action === "add" && recordData instanceof
|
|
3525
|
+
if (change.action === "add" && recordData instanceof Y3.Map) {
|
|
3450
3526
|
Logger.verbose(
|
|
3451
3527
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
3452
3528
|
);
|
|
@@ -3591,7 +3667,7 @@ var init_BaseModel = __esm({
|
|
|
3591
3667
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
3592
3668
|
);
|
|
3593
3669
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
3594
|
-
if (recordData instanceof
|
|
3670
|
+
if (recordData instanceof Y3.Map) {
|
|
3595
3671
|
Logger.verbose(
|
|
3596
3672
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
3597
3673
|
);
|
|
@@ -3776,6 +3852,11 @@ var init_BaseModel = __esm({
|
|
|
3776
3852
|
Logger.debug(
|
|
3777
3853
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3778
3854
|
);
|
|
3855
|
+
if (isYjsComposite(yjsValue)) {
|
|
3856
|
+
Logger.warn(
|
|
3857
|
+
`[${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).`
|
|
3858
|
+
);
|
|
3859
|
+
}
|
|
3779
3860
|
if (yjsValue === void 0) {
|
|
3780
3861
|
Logger.debug(
|
|
3781
3862
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3813,8 +3894,8 @@ var init_BaseModel = __esm({
|
|
|
3813
3894
|
*/
|
|
3814
3895
|
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3815
3896
|
let nested = recordYMap.get(fieldName);
|
|
3816
|
-
if (!(nested instanceof
|
|
3817
|
-
const migrated = new
|
|
3897
|
+
if (!(nested instanceof Y3.Map)) {
|
|
3898
|
+
const migrated = new Y3.Map();
|
|
3818
3899
|
if (nested && typeof nested === "object") {
|
|
3819
3900
|
for (const [member, marker] of Object.entries(
|
|
3820
3901
|
nested
|
|
@@ -4001,6 +4082,29 @@ var init_BaseModel = __esm({
|
|
|
4001
4082
|
);
|
|
4002
4083
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
4003
4084
|
}
|
|
4085
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
4086
|
+
{
|
|
4087
|
+
const existingRecord = targetYMap.get(this.id);
|
|
4088
|
+
const isNew = !existingRecord;
|
|
4089
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
4090
|
+
const stamp = fieldOptions.autoStamp;
|
|
4091
|
+
if (!stamp) continue;
|
|
4092
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
4093
|
+
if (callerExplicit) {
|
|
4094
|
+
const localValue = this._localChanges[fieldKey];
|
|
4095
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
4096
|
+
}
|
|
4097
|
+
if (stamp === "create") {
|
|
4098
|
+
if (!isNew) continue;
|
|
4099
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
4100
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
4101
|
+
}
|
|
4102
|
+
this[fieldKey] = Date.now();
|
|
4103
|
+
if (stamp === "create") {
|
|
4104
|
+
preTransactCreateStamps.add(fieldKey);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4004
4108
|
this.validateBeforeSave();
|
|
4005
4109
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
4006
4110
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -4031,6 +4135,14 @@ var init_BaseModel = __esm({
|
|
|
4031
4135
|
}
|
|
4032
4136
|
}
|
|
4033
4137
|
}
|
|
4138
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
4139
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
4140
|
+
delete this._localChanges[fieldKey];
|
|
4141
|
+
}
|
|
4142
|
+
if (fieldKey in dataToSave) {
|
|
4143
|
+
delete dataToSave[fieldKey];
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4034
4146
|
}
|
|
4035
4147
|
}
|
|
4036
4148
|
if (!targetYMap) {
|
|
@@ -4043,7 +4155,7 @@ var init_BaseModel = __esm({
|
|
|
4043
4155
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
4044
4156
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
4045
4157
|
if (!recordYMap) {
|
|
4046
|
-
recordYMap = new
|
|
4158
|
+
recordYMap = new Y3.Map();
|
|
4047
4159
|
Logger.verbose(
|
|
4048
4160
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
4049
4161
|
);
|
|
@@ -5070,7 +5182,7 @@ var init_BaseModel = __esm({
|
|
|
5070
5182
|
);
|
|
5071
5183
|
}
|
|
5072
5184
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
5073
|
-
if (recordYMap instanceof
|
|
5185
|
+
if (recordYMap instanceof Y3.Map) {
|
|
5074
5186
|
const instance = new this({ id: recordId });
|
|
5075
5187
|
instance._metaDocId = docId;
|
|
5076
5188
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -5141,7 +5253,7 @@ var init_BaseModel = __esm({
|
|
|
5141
5253
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
5142
5254
|
if (documentYMap) {
|
|
5143
5255
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
5144
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
5256
|
+
if (recordYMap2 && recordYMap2 instanceof Y3.Map) {
|
|
5145
5257
|
const instance = new modelConstructor({
|
|
5146
5258
|
id: recordId2
|
|
5147
5259
|
});
|
|
@@ -5178,7 +5290,7 @@ var init_BaseModel = __esm({
|
|
|
5178
5290
|
if (!recordYMap) {
|
|
5179
5291
|
return null;
|
|
5180
5292
|
}
|
|
5181
|
-
if (recordYMap instanceof
|
|
5293
|
+
if (recordYMap instanceof Y3.Map) {
|
|
5182
5294
|
const instance = new modelConstructor({
|
|
5183
5295
|
id: recordId
|
|
5184
5296
|
});
|
|
@@ -5410,7 +5522,7 @@ var init_BaseModel = __esm({
|
|
|
5410
5522
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5411
5523
|
if (fieldOptions?.type !== "stringset") continue;
|
|
5412
5524
|
const value = recordYMap.get(fieldKey);
|
|
5413
|
-
if (value instanceof
|
|
5525
|
+
if (value instanceof Y3.Map) {
|
|
5414
5526
|
this.observeStringSetMapOnce(value);
|
|
5415
5527
|
}
|
|
5416
5528
|
}
|
|
@@ -5457,7 +5569,14 @@ var init_BaseModel = __esm({
|
|
|
5457
5569
|
continue;
|
|
5458
5570
|
}
|
|
5459
5571
|
if (value !== void 0) {
|
|
5460
|
-
|
|
5572
|
+
if (isYjsComposite(value)) {
|
|
5573
|
+
Logger.warn(
|
|
5574
|
+
`[${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.`
|
|
5575
|
+
);
|
|
5576
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5577
|
+
} else {
|
|
5578
|
+
updatedData[key] = value;
|
|
5579
|
+
}
|
|
5461
5580
|
}
|
|
5462
5581
|
}
|
|
5463
5582
|
if (unknownFields.length > 0) {
|
|
@@ -5537,7 +5656,14 @@ var init_BaseModel = __esm({
|
|
|
5537
5656
|
continue;
|
|
5538
5657
|
}
|
|
5539
5658
|
if (value !== void 0) {
|
|
5540
|
-
|
|
5659
|
+
if (isYjsComposite(value)) {
|
|
5660
|
+
Logger.warn(
|
|
5661
|
+
`[${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.`
|
|
5662
|
+
);
|
|
5663
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
5664
|
+
} else {
|
|
5665
|
+
updatedData[key] = value;
|
|
5666
|
+
}
|
|
5541
5667
|
}
|
|
5542
5668
|
}
|
|
5543
5669
|
if (unknownFields.length > 0) {
|
|
@@ -7105,7 +7231,8 @@ function attachAndRegisterModel(modelClass, schema) {
|
|
|
7105
7231
|
}
|
|
7106
7232
|
|
|
7107
7233
|
// src/utils/yDocSchema.ts
|
|
7108
|
-
|
|
7234
|
+
init_yjsValues();
|
|
7235
|
+
import * as Y4 from "yjs";
|
|
7109
7236
|
function discoverSchema(yDoc) {
|
|
7110
7237
|
const models = {};
|
|
7111
7238
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -7139,7 +7266,7 @@ function discoverModelNames(yDoc) {
|
|
|
7139
7266
|
function materializeMap(yDoc, key) {
|
|
7140
7267
|
try {
|
|
7141
7268
|
const map = yDoc.getMap(key);
|
|
7142
|
-
return map instanceof
|
|
7269
|
+
return map instanceof Y4.Map ? map : null;
|
|
7143
7270
|
} catch {
|
|
7144
7271
|
return null;
|
|
7145
7272
|
}
|
|
@@ -7149,11 +7276,11 @@ function readModelMeta(metaMap) {
|
|
|
7149
7276
|
let constraints;
|
|
7150
7277
|
let relationships;
|
|
7151
7278
|
for (const [key, value] of metaMap.entries()) {
|
|
7152
|
-
if (key === "_constraints" && value instanceof
|
|
7279
|
+
if (key === "_constraints" && value instanceof Y4.Map) {
|
|
7153
7280
|
constraints = readConstraints(value);
|
|
7154
|
-
} else if (key === "_relationships" && value instanceof
|
|
7281
|
+
} else if (key === "_relationships" && value instanceof Y4.Map) {
|
|
7155
7282
|
relationships = readRelationships(value);
|
|
7156
|
-
} else if (value instanceof
|
|
7283
|
+
} else if (value instanceof Y4.Map) {
|
|
7157
7284
|
fields[key] = readFieldMeta(value);
|
|
7158
7285
|
}
|
|
7159
7286
|
}
|
|
@@ -7172,6 +7299,10 @@ function readFieldMeta(fieldMap) {
|
|
|
7172
7299
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
7173
7300
|
if (fieldMap.get("required") === true) field.required = true;
|
|
7174
7301
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7302
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7303
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7304
|
+
field.autoStamp = autoStamp;
|
|
7305
|
+
}
|
|
7175
7306
|
const def = fieldMap.get("default");
|
|
7176
7307
|
if (def !== void 0) field.default = def;
|
|
7177
7308
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -7184,7 +7315,7 @@ function inferModelFromData(dataMap) {
|
|
|
7184
7315
|
const fields = {};
|
|
7185
7316
|
let sampled = 0;
|
|
7186
7317
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
7187
|
-
if (!(recordValue instanceof
|
|
7318
|
+
if (!(recordValue instanceof Y4.Map)) continue;
|
|
7188
7319
|
if (++sampled > 5) break;
|
|
7189
7320
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
7190
7321
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -7197,7 +7328,7 @@ function inferModelFromData(dataMap) {
|
|
|
7197
7328
|
return { fields };
|
|
7198
7329
|
}
|
|
7199
7330
|
function inferTypeFromValue(value) {
|
|
7200
|
-
if (value
|
|
7331
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
7201
7332
|
switch (typeof value) {
|
|
7202
7333
|
case "string":
|
|
7203
7334
|
return "string";
|
|
@@ -7212,7 +7343,7 @@ function inferTypeFromValue(value) {
|
|
|
7212
7343
|
function readConstraints(constraintsMap) {
|
|
7213
7344
|
const out = {};
|
|
7214
7345
|
for (const [name, value] of constraintsMap.entries()) {
|
|
7215
|
-
if (!(value instanceof
|
|
7346
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7216
7347
|
let fields = [];
|
|
7217
7348
|
const rawFields = value.get("fields");
|
|
7218
7349
|
if (typeof rawFields === "string") {
|
|
@@ -7231,7 +7362,7 @@ function readConstraints(constraintsMap) {
|
|
|
7231
7362
|
function readRelationships(relsMap) {
|
|
7232
7363
|
const out = {};
|
|
7233
7364
|
for (const [name, value] of relsMap.entries()) {
|
|
7234
|
-
if (!(value instanceof
|
|
7365
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7235
7366
|
const rel = {};
|
|
7236
7367
|
for (const [k, v] of value.entries()) {
|
|
7237
7368
|
rel[k] = v;
|
|
@@ -7242,6 +7373,7 @@ function readRelationships(relsMap) {
|
|
|
7242
7373
|
}
|
|
7243
7374
|
var CAMEL_TO_SNAKE = {
|
|
7244
7375
|
autoAssign: "auto_assign",
|
|
7376
|
+
autoStamp: "auto_stamp",
|
|
7245
7377
|
maxLength: "max_length",
|
|
7246
7378
|
maxCount: "max_count",
|
|
7247
7379
|
relatedIdField: "related_id_field",
|
|
@@ -7272,6 +7404,7 @@ function schemaToToml(schema) {
|
|
|
7272
7404
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
7273
7405
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
7274
7406
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7407
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
7275
7408
|
if (field.indexed) lines.push("indexed = true");
|
|
7276
7409
|
if (field.unique) lines.push("unique = true");
|
|
7277
7410
|
if (field.required) lines.push("required = true");
|
|
@@ -7318,10 +7451,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
7318
7451
|
"unique",
|
|
7319
7452
|
"required",
|
|
7320
7453
|
"auto_assign",
|
|
7454
|
+
"auto_stamp",
|
|
7321
7455
|
"max_length",
|
|
7322
7456
|
"max_count",
|
|
7323
|
-
"default"
|
|
7457
|
+
"default",
|
|
7458
|
+
"enum"
|
|
7324
7459
|
]);
|
|
7460
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
7325
7461
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7326
7462
|
"fields",
|
|
7327
7463
|
"relationships",
|
|
@@ -7363,9 +7499,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
7363
7499
|
if (raw.unique === true) opts.unique = true;
|
|
7364
7500
|
if (raw.required === true) opts.required = true;
|
|
7365
7501
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
7502
|
+
if (raw.auto_stamp !== void 0) {
|
|
7503
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
7504
|
+
throw new Error(
|
|
7505
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
7506
|
+
...VALID_AUTO_STAMP_VALUES
|
|
7507
|
+
].join(", ")}`
|
|
7508
|
+
);
|
|
7509
|
+
}
|
|
7510
|
+
opts.autoStamp = raw.auto_stamp;
|
|
7511
|
+
}
|
|
7366
7512
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
7367
7513
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
7368
7514
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
7515
|
+
if (raw.enum !== void 0) {
|
|
7516
|
+
if (raw.type !== "string") {
|
|
7517
|
+
throw new Error(
|
|
7518
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
7519
|
+
);
|
|
7520
|
+
}
|
|
7521
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
7522
|
+
throw new Error(
|
|
7523
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
7524
|
+
);
|
|
7525
|
+
}
|
|
7526
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
7527
|
+
throw new Error(
|
|
7528
|
+
`${context}: \`enum\` values must all be strings.`
|
|
7529
|
+
);
|
|
7530
|
+
}
|
|
7531
|
+
opts.enum = [...raw.enum];
|
|
7532
|
+
}
|
|
7369
7533
|
return opts;
|
|
7370
7534
|
}
|
|
7371
7535
|
function requireField(raw, field, context) {
|
|
@@ -7496,16 +7660,16 @@ async function loadSchemaFromToml(filePath, options = {}) {
|
|
|
7496
7660
|
init_metaSync();
|
|
7497
7661
|
|
|
7498
7662
|
// src/utils/yDocDump.ts
|
|
7499
|
-
import * as
|
|
7663
|
+
import * as Y5 from "yjs";
|
|
7500
7664
|
function toPlain(value) {
|
|
7501
|
-
if (value instanceof
|
|
7665
|
+
if (value instanceof Y5.Map) {
|
|
7502
7666
|
const obj = {};
|
|
7503
7667
|
value.forEach((v, k) => {
|
|
7504
7668
|
obj[k] = toPlain(v);
|
|
7505
7669
|
});
|
|
7506
7670
|
return obj;
|
|
7507
7671
|
}
|
|
7508
|
-
if (value instanceof
|
|
7672
|
+
if (value instanceof Y5.Array) {
|
|
7509
7673
|
return value.toArray().map((item) => toPlain(item));
|
|
7510
7674
|
}
|
|
7511
7675
|
return value;
|
|
@@ -7514,7 +7678,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
|
|
|
7514
7678
|
const result = {};
|
|
7515
7679
|
const includeIndexes = !!options.includeIndexes;
|
|
7516
7680
|
yDoc.share.forEach((type, name) => {
|
|
7517
|
-
if (!(type instanceof
|
|
7681
|
+
if (!(type instanceof Y5.Map)) return;
|
|
7518
7682
|
if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
|
|
7519
7683
|
const records = {};
|
|
7520
7684
|
type.forEach((recordValue, recordId) => {
|