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/index.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;
|
|
@@ -529,6 +553,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
529
553
|
private ensureLocalChanges;
|
|
530
554
|
private hasLocalChange;
|
|
531
555
|
private getFromYjs;
|
|
556
|
+
/**
|
|
557
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
558
|
+
*
|
|
559
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
560
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
561
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
562
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
563
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
564
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
565
|
+
*/
|
|
566
|
+
private normalizeNonStringSetValue;
|
|
532
567
|
private getValue;
|
|
533
568
|
private setValue;
|
|
534
569
|
get isDirty(): boolean;
|
|
@@ -835,6 +870,11 @@ declare function attachAndRegisterModel(modelClass: typeof BaseModel, schema: De
|
|
|
835
870
|
|
|
836
871
|
/**
|
|
837
872
|
* Infer a _meta_ type string from a JS runtime value.
|
|
873
|
+
*
|
|
874
|
+
* Only an all-`true` Y.Map (the stringset wire shape) is tagged as
|
|
875
|
+
* `stringset` (issue #625) — a Y.Map carrying a composite payload, or a
|
|
876
|
+
* Y.Array / Y.Text, is not a stringset and stays untyped (`null`) rather
|
|
877
|
+
* than being mis-tagged.
|
|
838
878
|
*/
|
|
839
879
|
declare function inferFieldType(value: unknown): "string" | "number" | "boolean" | "stringset" | null;
|
|
840
880
|
/**
|
|
@@ -919,6 +959,7 @@ interface DiscoveredField {
|
|
|
919
959
|
required?: boolean;
|
|
920
960
|
default?: string | number | boolean;
|
|
921
961
|
autoAssign?: boolean;
|
|
962
|
+
autoStamp?: "create" | "update" | "both";
|
|
922
963
|
maxLength?: number;
|
|
923
964
|
maxCount?: number;
|
|
924
965
|
}
|
package/dist/index.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
|
}
|
|
@@ -1694,7 +1732,7 @@ __export(BaseModel_exports, {
|
|
|
1694
1732
|
UniqueConstraintViolationError: () => UniqueConstraintViolationError,
|
|
1695
1733
|
generateULID: () => generateULID
|
|
1696
1734
|
});
|
|
1697
|
-
import * as
|
|
1735
|
+
import * as Y3 from "yjs";
|
|
1698
1736
|
import { ulid } from "ulid";
|
|
1699
1737
|
function generateULID() {
|
|
1700
1738
|
return ulid();
|
|
@@ -1709,6 +1747,7 @@ var init_BaseModel = __esm({
|
|
|
1709
1747
|
init_CursorManager();
|
|
1710
1748
|
init_sql();
|
|
1711
1749
|
init_metaSync();
|
|
1750
|
+
init_yjsValues();
|
|
1712
1751
|
LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
1713
1752
|
LogLevel2[LogLevel2["SILENT"] = 0] = "SILENT";
|
|
1714
1753
|
LogLevel2[LogLevel2["ERROR"] = 1] = "ERROR";
|
|
@@ -2217,7 +2256,30 @@ var init_BaseModel = __esm({
|
|
|
2217
2256
|
if (verboseEnabled) {
|
|
2218
2257
|
Logger.verbose(`[getFromYjs] Returning field value: ${fieldValue}`);
|
|
2219
2258
|
}
|
|
2220
|
-
return fieldValue;
|
|
2259
|
+
return this.normalizeNonStringSetValue(fieldKey, fieldValue, schema, modelName);
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Defensive normalization for the read/sync boundary (issue #625).
|
|
2263
|
+
*
|
|
2264
|
+
* Stringset fields are owned by getStringSetFromYjs (which expects the
|
|
2265
|
+
* raw Y.Map / legacy plain-object shape — #561 / #601), so they pass
|
|
2266
|
+
* through untouched. For any other field, a composite Yjs primitive
|
|
2267
|
+
* (Y.Map / Y.Array / Y.Text) must never reach user code or the DB layer
|
|
2268
|
+
* raw: normalize it to its plain-JS projection (toJSON / toString) and
|
|
2269
|
+
* warn, rather than throwing. Scalars pass through unchanged.
|
|
2270
|
+
*/
|
|
2271
|
+
normalizeNonStringSetValue(fieldKey, value, schema, modelName) {
|
|
2272
|
+
const fieldOptions = schema?.fields?.get(fieldKey);
|
|
2273
|
+
if (fieldOptions?.type === "stringset") {
|
|
2274
|
+
return value;
|
|
2275
|
+
}
|
|
2276
|
+
if (isYjsComposite(value)) {
|
|
2277
|
+
Logger.warn(
|
|
2278
|
+
`[${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.`
|
|
2279
|
+
);
|
|
2280
|
+
return normalizeYjsValue(value);
|
|
2281
|
+
}
|
|
2282
|
+
return value;
|
|
2221
2283
|
}
|
|
2222
2284
|
getValue(fieldKey) {
|
|
2223
2285
|
const verboseEnabled = Logger.getLogLevel() >= 5 /* VERBOSE */;
|
|
@@ -2298,7 +2360,7 @@ var init_BaseModel = __esm({
|
|
|
2298
2360
|
validateBeforeSave() {
|
|
2299
2361
|
const schema = this.constructor.getSchema();
|
|
2300
2362
|
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
2301
|
-
const currentValue = this.getValue(fieldKey);
|
|
2363
|
+
const currentValue = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
2302
2364
|
if (fieldOptions.required && (currentValue === null || currentValue === void 0)) {
|
|
2303
2365
|
throw new Error(`Field ${fieldKey} is required before save`);
|
|
2304
2366
|
}
|
|
@@ -2362,7 +2424,7 @@ var init_BaseModel = __esm({
|
|
|
2362
2424
|
}
|
|
2363
2425
|
getStringSetFromYjs(fieldName) {
|
|
2364
2426
|
const yjsData = this.getFromYjs(fieldName);
|
|
2365
|
-
if (yjsData instanceof
|
|
2427
|
+
if (yjsData instanceof Y3.Map) {
|
|
2366
2428
|
return Array.from(yjsData.keys());
|
|
2367
2429
|
}
|
|
2368
2430
|
if (yjsData && typeof yjsData === "object") {
|
|
@@ -2470,7 +2532,7 @@ var init_BaseModel = __esm({
|
|
|
2470
2532
|
}
|
|
2471
2533
|
const extractStringSetValues = (value) => {
|
|
2472
2534
|
if (!value) return [];
|
|
2473
|
-
if (value instanceof
|
|
2535
|
+
if (value instanceof Y3.Map) {
|
|
2474
2536
|
return Array.from(value.entries()).filter(([, isMember]) => Boolean(isMember)).map(([key]) => key);
|
|
2475
2537
|
}
|
|
2476
2538
|
if (Array.isArray(value)) {
|
|
@@ -2487,7 +2549,7 @@ var init_BaseModel = __esm({
|
|
|
2487
2549
|
if (!recordId) continue;
|
|
2488
2550
|
let itemData;
|
|
2489
2551
|
const stringSetValuesByField = {};
|
|
2490
|
-
if (recordData instanceof
|
|
2552
|
+
if (recordData instanceof Y3.Map) {
|
|
2491
2553
|
itemData = {};
|
|
2492
2554
|
const unknownFields = [];
|
|
2493
2555
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -2606,7 +2668,7 @@ var init_BaseModel = __esm({
|
|
|
2606
2668
|
const buildUniqueKey = (recordData, fields) => {
|
|
2607
2669
|
const keyParts = [];
|
|
2608
2670
|
for (const field of fields) {
|
|
2609
|
-
const value = recordData instanceof
|
|
2671
|
+
const value = recordData instanceof Y3.Map ? recordData.get(field) : recordData[field];
|
|
2610
2672
|
if (value === null || value === void 0) {
|
|
2611
2673
|
return null;
|
|
2612
2674
|
}
|
|
@@ -2616,7 +2678,7 @@ var init_BaseModel = __esm({
|
|
|
2616
2678
|
};
|
|
2617
2679
|
const extractItemData = (key, recordData) => {
|
|
2618
2680
|
let itemData;
|
|
2619
|
-
if (recordData instanceof
|
|
2681
|
+
if (recordData instanceof Y3.Map) {
|
|
2620
2682
|
itemData = {};
|
|
2621
2683
|
const unknownFields = [];
|
|
2622
2684
|
for (const [fieldKey, value] of recordData.entries()) {
|
|
@@ -2629,7 +2691,14 @@ var init_BaseModel = __esm({
|
|
|
2629
2691
|
continue;
|
|
2630
2692
|
}
|
|
2631
2693
|
if (value !== void 0) {
|
|
2632
|
-
|
|
2694
|
+
if (isYjsComposite(value)) {
|
|
2695
|
+
Logger.warn(
|
|
2696
|
+
`[${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.`
|
|
2697
|
+
);
|
|
2698
|
+
itemData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
2699
|
+
} else {
|
|
2700
|
+
itemData[fieldKey] = value;
|
|
2701
|
+
}
|
|
2633
2702
|
}
|
|
2634
2703
|
}
|
|
2635
2704
|
if (unknownFields.length > 0) {
|
|
@@ -2652,7 +2721,14 @@ var init_BaseModel = __esm({
|
|
|
2652
2721
|
continue;
|
|
2653
2722
|
}
|
|
2654
2723
|
if (value !== void 0) {
|
|
2655
|
-
|
|
2724
|
+
if (isYjsComposite(value)) {
|
|
2725
|
+
Logger.warn(
|
|
2726
|
+
`[${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.`
|
|
2727
|
+
);
|
|
2728
|
+
filteredData[fieldKey] = normalizeYjsValueForStorage(value);
|
|
2729
|
+
} else {
|
|
2730
|
+
filteredData[fieldKey] = value;
|
|
2731
|
+
}
|
|
2656
2732
|
}
|
|
2657
2733
|
}
|
|
2658
2734
|
if (unknownFields.length > 0) {
|
|
@@ -2732,7 +2808,7 @@ var init_BaseModel = __esm({
|
|
|
2732
2808
|
if (!recordData || !key) continue;
|
|
2733
2809
|
const itemData = extractItemData(key, recordData);
|
|
2734
2810
|
if (!itemData) continue;
|
|
2735
|
-
if (change.action === "add" && recordData instanceof
|
|
2811
|
+
if (change.action === "add" && recordData instanceof Y3.Map) {
|
|
2736
2812
|
Logger.verbose(
|
|
2737
2813
|
`[${this.name}] Setting up observer on newly added nested YMap for record ${key} in document ${docId}`
|
|
2738
2814
|
);
|
|
@@ -2877,7 +2953,7 @@ var init_BaseModel = __esm({
|
|
|
2877
2953
|
`[${this.name}] Setting up observers on existing nested YMaps for ${modelName}/${docId}...`
|
|
2878
2954
|
);
|
|
2879
2955
|
for (const [recordId, recordData] of documentYMap.entries()) {
|
|
2880
|
-
if (recordData instanceof
|
|
2956
|
+
if (recordData instanceof Y3.Map) {
|
|
2881
2957
|
Logger.verbose(
|
|
2882
2958
|
`[${this.name}] Setting up observer on existing nested YMap for record ${recordId} in document ${docId}`
|
|
2883
2959
|
);
|
|
@@ -3062,6 +3138,11 @@ var init_BaseModel = __esm({
|
|
|
3062
3138
|
Logger.debug(
|
|
3063
3139
|
`[_diffWithYjsData] Field '${key}' - Y.js value: ${yjsValue}, local value: ${localValue}`
|
|
3064
3140
|
);
|
|
3141
|
+
if (isYjsComposite(yjsValue)) {
|
|
3142
|
+
Logger.warn(
|
|
3143
|
+
`[${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).`
|
|
3144
|
+
);
|
|
3145
|
+
}
|
|
3065
3146
|
if (yjsValue === void 0) {
|
|
3066
3147
|
Logger.debug(
|
|
3067
3148
|
`[_diffWithYjsData] Field '${key}' not in Y.js, adding to 'added'`
|
|
@@ -3099,8 +3180,8 @@ var init_BaseModel = __esm({
|
|
|
3099
3180
|
*/
|
|
3100
3181
|
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3101
3182
|
let nested = recordYMap.get(fieldName);
|
|
3102
|
-
if (!(nested instanceof
|
|
3103
|
-
const migrated = new
|
|
3183
|
+
if (!(nested instanceof Y3.Map)) {
|
|
3184
|
+
const migrated = new Y3.Map();
|
|
3104
3185
|
if (nested && typeof nested === "object") {
|
|
3105
3186
|
for (const [member, marker] of Object.entries(
|
|
3106
3187
|
nested
|
|
@@ -3287,6 +3368,29 @@ var init_BaseModel = __esm({
|
|
|
3287
3368
|
);
|
|
3288
3369
|
throw new Error("Cannot save item without an id. Ensure id is set.");
|
|
3289
3370
|
}
|
|
3371
|
+
const preTransactCreateStamps = /* @__PURE__ */ new Set();
|
|
3372
|
+
{
|
|
3373
|
+
const existingRecord = targetYMap.get(this.id);
|
|
3374
|
+
const isNew = !existingRecord;
|
|
3375
|
+
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
3376
|
+
const stamp = fieldOptions.autoStamp;
|
|
3377
|
+
if (!stamp) continue;
|
|
3378
|
+
const callerExplicit = this.hasLocalChange(fieldKey);
|
|
3379
|
+
if (callerExplicit) {
|
|
3380
|
+
const localValue = this._localChanges[fieldKey];
|
|
3381
|
+
if (localValue !== null && localValue !== void 0) continue;
|
|
3382
|
+
}
|
|
3383
|
+
if (stamp === "create") {
|
|
3384
|
+
if (!isNew) continue;
|
|
3385
|
+
const persisted = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
3386
|
+
if (persisted !== null && persisted !== void 0) continue;
|
|
3387
|
+
}
|
|
3388
|
+
this[fieldKey] = Date.now();
|
|
3389
|
+
if (stamp === "create") {
|
|
3390
|
+
preTransactCreateStamps.add(fieldKey);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3290
3394
|
this.validateBeforeSave();
|
|
3291
3395
|
Logger.debug(`[${modelName}.save] About to get current JS state`);
|
|
3292
3396
|
Logger.debug(`[${modelName}.save] _localChanges:`, this._localChanges);
|
|
@@ -3317,6 +3421,14 @@ var init_BaseModel = __esm({
|
|
|
3317
3421
|
}
|
|
3318
3422
|
}
|
|
3319
3423
|
}
|
|
3424
|
+
for (const fieldKey of preTransactCreateStamps) {
|
|
3425
|
+
if (this._localChanges && fieldKey in this._localChanges) {
|
|
3426
|
+
delete this._localChanges[fieldKey];
|
|
3427
|
+
}
|
|
3428
|
+
if (fieldKey in dataToSave) {
|
|
3429
|
+
delete dataToSave[fieldKey];
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3320
3432
|
}
|
|
3321
3433
|
}
|
|
3322
3434
|
if (!targetYMap) {
|
|
@@ -3329,7 +3441,7 @@ var init_BaseModel = __esm({
|
|
|
3329
3441
|
Logger.debug(`[${modelName}.save] isUpdate: ${isUpdate}`);
|
|
3330
3442
|
Logger.debug(`[${modelName}.save] recordYMap exists: ${!!recordYMap}`);
|
|
3331
3443
|
if (!recordYMap) {
|
|
3332
|
-
recordYMap = new
|
|
3444
|
+
recordYMap = new Y3.Map();
|
|
3333
3445
|
Logger.verbose(
|
|
3334
3446
|
`[${modelName}] Creating new nested YMap for record ${this.id} in document ${targetDocId || "legacy"}`
|
|
3335
3447
|
);
|
|
@@ -4356,7 +4468,7 @@ var init_BaseModel = __esm({
|
|
|
4356
4468
|
);
|
|
4357
4469
|
}
|
|
4358
4470
|
for (const [recordId, recordYMap] of documentYMap.entries()) {
|
|
4359
|
-
if (recordYMap instanceof
|
|
4471
|
+
if (recordYMap instanceof Y3.Map) {
|
|
4360
4472
|
const instance = new this({ id: recordId });
|
|
4361
4473
|
instance._metaDocId = docId;
|
|
4362
4474
|
const connectedDoc = modelConstructor.connectedDocuments.get(docId);
|
|
@@ -4427,7 +4539,7 @@ var init_BaseModel = __esm({
|
|
|
4427
4539
|
const documentYMap = modelConstructor.documentYMaps.get(documentYMapKey);
|
|
4428
4540
|
if (documentYMap) {
|
|
4429
4541
|
const recordYMap2 = documentYMap.get(recordId2);
|
|
4430
|
-
if (recordYMap2 && recordYMap2 instanceof
|
|
4542
|
+
if (recordYMap2 && recordYMap2 instanceof Y3.Map) {
|
|
4431
4543
|
const instance = new modelConstructor({
|
|
4432
4544
|
id: recordId2
|
|
4433
4545
|
});
|
|
@@ -4464,7 +4576,7 @@ var init_BaseModel = __esm({
|
|
|
4464
4576
|
if (!recordYMap) {
|
|
4465
4577
|
return null;
|
|
4466
4578
|
}
|
|
4467
|
-
if (recordYMap instanceof
|
|
4579
|
+
if (recordYMap instanceof Y3.Map) {
|
|
4468
4580
|
const instance = new modelConstructor({
|
|
4469
4581
|
id: recordId
|
|
4470
4582
|
});
|
|
@@ -4696,7 +4808,7 @@ var init_BaseModel = __esm({
|
|
|
4696
4808
|
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
4697
4809
|
if (fieldOptions?.type !== "stringset") continue;
|
|
4698
4810
|
const value = recordYMap.get(fieldKey);
|
|
4699
|
-
if (value instanceof
|
|
4811
|
+
if (value instanceof Y3.Map) {
|
|
4700
4812
|
this.observeStringSetMapOnce(value);
|
|
4701
4813
|
}
|
|
4702
4814
|
}
|
|
@@ -4743,7 +4855,14 @@ var init_BaseModel = __esm({
|
|
|
4743
4855
|
continue;
|
|
4744
4856
|
}
|
|
4745
4857
|
if (value !== void 0) {
|
|
4746
|
-
|
|
4858
|
+
if (isYjsComposite(value)) {
|
|
4859
|
+
Logger.warn(
|
|
4860
|
+
`[${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.`
|
|
4861
|
+
);
|
|
4862
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
4863
|
+
} else {
|
|
4864
|
+
updatedData[key] = value;
|
|
4865
|
+
}
|
|
4747
4866
|
}
|
|
4748
4867
|
}
|
|
4749
4868
|
if (unknownFields.length > 0) {
|
|
@@ -4823,7 +4942,14 @@ var init_BaseModel = __esm({
|
|
|
4823
4942
|
continue;
|
|
4824
4943
|
}
|
|
4825
4944
|
if (value !== void 0) {
|
|
4826
|
-
|
|
4945
|
+
if (isYjsComposite(value)) {
|
|
4946
|
+
Logger.warn(
|
|
4947
|
+
`[${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.`
|
|
4948
|
+
);
|
|
4949
|
+
updatedData[key] = normalizeYjsValueForStorage(value);
|
|
4950
|
+
} else {
|
|
4951
|
+
updatedData[key] = value;
|
|
4952
|
+
}
|
|
4827
4953
|
}
|
|
4828
4954
|
}
|
|
4829
4955
|
if (unknownFields.length > 0) {
|
|
@@ -7103,10 +7229,13 @@ var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
|
7103
7229
|
"unique",
|
|
7104
7230
|
"required",
|
|
7105
7231
|
"auto_assign",
|
|
7232
|
+
"auto_stamp",
|
|
7106
7233
|
"max_length",
|
|
7107
7234
|
"max_count",
|
|
7108
|
-
"default"
|
|
7235
|
+
"default",
|
|
7236
|
+
"enum"
|
|
7109
7237
|
]);
|
|
7238
|
+
var VALID_AUTO_STAMP_VALUES = /* @__PURE__ */ new Set(["create", "update", "both"]);
|
|
7110
7239
|
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7111
7240
|
"fields",
|
|
7112
7241
|
"relationships",
|
|
@@ -7148,9 +7277,37 @@ function parseFieldOptions(raw, context, strict) {
|
|
|
7148
7277
|
if (raw.unique === true) opts.unique = true;
|
|
7149
7278
|
if (raw.required === true) opts.required = true;
|
|
7150
7279
|
if (raw.auto_assign === true) opts.autoAssign = true;
|
|
7280
|
+
if (raw.auto_stamp !== void 0) {
|
|
7281
|
+
if (typeof raw.auto_stamp !== "string" || !VALID_AUTO_STAMP_VALUES.has(raw.auto_stamp)) {
|
|
7282
|
+
throw new Error(
|
|
7283
|
+
`${context}: invalid auto_stamp value "${raw.auto_stamp}". Must be one of: ${[
|
|
7284
|
+
...VALID_AUTO_STAMP_VALUES
|
|
7285
|
+
].join(", ")}`
|
|
7286
|
+
);
|
|
7287
|
+
}
|
|
7288
|
+
opts.autoStamp = raw.auto_stamp;
|
|
7289
|
+
}
|
|
7151
7290
|
if (raw.max_length !== void 0) opts.maxLength = raw.max_length;
|
|
7152
7291
|
if (raw.max_count !== void 0) opts.maxCount = raw.max_count;
|
|
7153
7292
|
if (raw.default !== void 0) opts.default = raw.default;
|
|
7293
|
+
if (raw.enum !== void 0) {
|
|
7294
|
+
if (raw.type !== "string") {
|
|
7295
|
+
throw new Error(
|
|
7296
|
+
`${context}: \`enum\` is only valid on a "string" field, not "${raw.type}".`
|
|
7297
|
+
);
|
|
7298
|
+
}
|
|
7299
|
+
if (!Array.isArray(raw.enum) || raw.enum.length === 0) {
|
|
7300
|
+
throw new Error(
|
|
7301
|
+
`${context}: \`enum\` must be a non-empty array of strings.`
|
|
7302
|
+
);
|
|
7303
|
+
}
|
|
7304
|
+
if (!raw.enum.every((v) => typeof v === "string")) {
|
|
7305
|
+
throw new Error(
|
|
7306
|
+
`${context}: \`enum\` values must all be strings.`
|
|
7307
|
+
);
|
|
7308
|
+
}
|
|
7309
|
+
opts.enum = [...raw.enum];
|
|
7310
|
+
}
|
|
7154
7311
|
return opts;
|
|
7155
7312
|
}
|
|
7156
7313
|
function requireField(raw, field, context) {
|
|
@@ -7278,7 +7435,8 @@ async function loadSchemaFromToml(filePath, options = {}) {
|
|
|
7278
7435
|
}
|
|
7279
7436
|
|
|
7280
7437
|
// src/utils/yDocSchema.ts
|
|
7281
|
-
|
|
7438
|
+
init_yjsValues();
|
|
7439
|
+
import * as Y4 from "yjs";
|
|
7282
7440
|
function discoverSchema(yDoc) {
|
|
7283
7441
|
const models = {};
|
|
7284
7442
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -7312,7 +7470,7 @@ function discoverModelNames(yDoc) {
|
|
|
7312
7470
|
function materializeMap(yDoc, key) {
|
|
7313
7471
|
try {
|
|
7314
7472
|
const map = yDoc.getMap(key);
|
|
7315
|
-
return map instanceof
|
|
7473
|
+
return map instanceof Y4.Map ? map : null;
|
|
7316
7474
|
} catch {
|
|
7317
7475
|
return null;
|
|
7318
7476
|
}
|
|
@@ -7322,11 +7480,11 @@ function readModelMeta(metaMap) {
|
|
|
7322
7480
|
let constraints;
|
|
7323
7481
|
let relationships;
|
|
7324
7482
|
for (const [key, value] of metaMap.entries()) {
|
|
7325
|
-
if (key === "_constraints" && value instanceof
|
|
7483
|
+
if (key === "_constraints" && value instanceof Y4.Map) {
|
|
7326
7484
|
constraints = readConstraints(value);
|
|
7327
|
-
} else if (key === "_relationships" && value instanceof
|
|
7485
|
+
} else if (key === "_relationships" && value instanceof Y4.Map) {
|
|
7328
7486
|
relationships = readRelationships(value);
|
|
7329
|
-
} else if (value instanceof
|
|
7487
|
+
} else if (value instanceof Y4.Map) {
|
|
7330
7488
|
fields[key] = readFieldMeta(value);
|
|
7331
7489
|
}
|
|
7332
7490
|
}
|
|
@@ -7345,6 +7503,10 @@ function readFieldMeta(fieldMap) {
|
|
|
7345
7503
|
if (fieldMap.get("unique") === true) field.unique = true;
|
|
7346
7504
|
if (fieldMap.get("required") === true) field.required = true;
|
|
7347
7505
|
if (fieldMap.get("autoAssign") === true) field.autoAssign = true;
|
|
7506
|
+
const autoStamp = fieldMap.get("autoStamp");
|
|
7507
|
+
if (typeof autoStamp === "string" && (autoStamp === "create" || autoStamp === "update" || autoStamp === "both")) {
|
|
7508
|
+
field.autoStamp = autoStamp;
|
|
7509
|
+
}
|
|
7348
7510
|
const def = fieldMap.get("default");
|
|
7349
7511
|
if (def !== void 0) field.default = def;
|
|
7350
7512
|
const maxLength = fieldMap.get("maxLength");
|
|
@@ -7357,7 +7519,7 @@ function inferModelFromData(dataMap) {
|
|
|
7357
7519
|
const fields = {};
|
|
7358
7520
|
let sampled = 0;
|
|
7359
7521
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
7360
|
-
if (!(recordValue instanceof
|
|
7522
|
+
if (!(recordValue instanceof Y4.Map)) continue;
|
|
7361
7523
|
if (++sampled > 5) break;
|
|
7362
7524
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
7363
7525
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -7370,7 +7532,7 @@ function inferModelFromData(dataMap) {
|
|
|
7370
7532
|
return { fields };
|
|
7371
7533
|
}
|
|
7372
7534
|
function inferTypeFromValue(value) {
|
|
7373
|
-
if (value
|
|
7535
|
+
if (isStringSetYMap(value)) return "stringset";
|
|
7374
7536
|
switch (typeof value) {
|
|
7375
7537
|
case "string":
|
|
7376
7538
|
return "string";
|
|
@@ -7385,7 +7547,7 @@ function inferTypeFromValue(value) {
|
|
|
7385
7547
|
function readConstraints(constraintsMap) {
|
|
7386
7548
|
const out = {};
|
|
7387
7549
|
for (const [name, value] of constraintsMap.entries()) {
|
|
7388
|
-
if (!(value instanceof
|
|
7550
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7389
7551
|
let fields = [];
|
|
7390
7552
|
const rawFields = value.get("fields");
|
|
7391
7553
|
if (typeof rawFields === "string") {
|
|
@@ -7404,7 +7566,7 @@ function readConstraints(constraintsMap) {
|
|
|
7404
7566
|
function readRelationships(relsMap) {
|
|
7405
7567
|
const out = {};
|
|
7406
7568
|
for (const [name, value] of relsMap.entries()) {
|
|
7407
|
-
if (!(value instanceof
|
|
7569
|
+
if (!(value instanceof Y4.Map)) continue;
|
|
7408
7570
|
const rel = {};
|
|
7409
7571
|
for (const [k, v] of value.entries()) {
|
|
7410
7572
|
rel[k] = v;
|
|
@@ -7415,6 +7577,7 @@ function readRelationships(relsMap) {
|
|
|
7415
7577
|
}
|
|
7416
7578
|
var CAMEL_TO_SNAKE = {
|
|
7417
7579
|
autoAssign: "auto_assign",
|
|
7580
|
+
autoStamp: "auto_stamp",
|
|
7418
7581
|
maxLength: "max_length",
|
|
7419
7582
|
maxCount: "max_count",
|
|
7420
7583
|
relatedIdField: "related_id_field",
|
|
@@ -7445,6 +7608,7 @@ function schemaToToml(schema) {
|
|
|
7445
7608
|
lines.push(`[models.${modelName}.fields.${fieldName}]`);
|
|
7446
7609
|
lines.push(`type = ${tomlValue(field.type)}`);
|
|
7447
7610
|
if (field.autoAssign) lines.push("auto_assign = true");
|
|
7611
|
+
if (field.autoStamp) lines.push(`auto_stamp = ${tomlValue(field.autoStamp)}`);
|
|
7448
7612
|
if (field.indexed) lines.push("indexed = true");
|
|
7449
7613
|
if (field.unique) lines.push("unique = true");
|
|
7450
7614
|
if (field.required) lines.push("required = true");
|
|
@@ -7479,16 +7643,16 @@ function schemaToToml(schema) {
|
|
|
7479
7643
|
init_BaseModel();
|
|
7480
7644
|
|
|
7481
7645
|
// src/utils/yDocDump.ts
|
|
7482
|
-
import * as
|
|
7646
|
+
import * as Y5 from "yjs";
|
|
7483
7647
|
function toPlain(value) {
|
|
7484
|
-
if (value instanceof
|
|
7648
|
+
if (value instanceof Y5.Map) {
|
|
7485
7649
|
const obj = {};
|
|
7486
7650
|
value.forEach((v, k) => {
|
|
7487
7651
|
obj[k] = toPlain(v);
|
|
7488
7652
|
});
|
|
7489
7653
|
return obj;
|
|
7490
7654
|
}
|
|
7491
|
-
if (value instanceof
|
|
7655
|
+
if (value instanceof Y5.Array) {
|
|
7492
7656
|
return value.toArray().map((item) => toPlain(item));
|
|
7493
7657
|
}
|
|
7494
7658
|
return value;
|
|
@@ -7497,7 +7661,7 @@ function dumpYDocToPlain(yDoc, options = {}) {
|
|
|
7497
7661
|
const result = {};
|
|
7498
7662
|
const includeIndexes = !!options.includeIndexes;
|
|
7499
7663
|
yDoc.share.forEach((type, name) => {
|
|
7500
|
-
if (!(type instanceof
|
|
7664
|
+
if (!(type instanceof Y5.Map)) return;
|
|
7501
7665
|
if (!includeIndexes && name.startsWith("_uniqueIdx_")) return;
|
|
7502
7666
|
const records = {};
|
|
7503
7667
|
type.forEach((recordValue, recordId) => {
|