js-bao 0.4.1 → 0.5.0
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 +104 -33
- package/dist/browser.d.cts +25 -0
- package/dist/browser.d.ts +25 -0
- package/dist/browser.js +104 -33
- package/dist/client.d.cts +25 -0
- package/dist/client.d.ts +25 -0
- package/dist/cloudflare-do.d.cts +25 -0
- package/dist/cloudflare-do.d.ts +25 -0
- package/dist/cloudflare.d.cts +25 -0
- package/dist/cloudflare.d.ts +25 -0
- package/dist/codegen-v2.cjs +14 -3
- package/dist/codegen.cjs +1 -1
- package/dist/index.cjs +104 -33
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +104 -33
- package/dist/node.cjs +104 -33
- package/dist/node.d.cts +25 -0
- package/dist/node.d.ts +25 -0
- package/dist/node.js +104 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1836,6 +1836,9 @@ var init_BaseModel = __esm({
|
|
|
1836
1836
|
static connectedDocuments = /* @__PURE__ */ new Map();
|
|
1837
1837
|
static documentYMaps = /* @__PURE__ */ new Map();
|
|
1838
1838
|
// Maps docId to YMap for that document
|
|
1839
|
+
// Tracks nested-Y.Map stringset fields we've already attached observers to,
|
|
1840
|
+
// so attachStringSetObserversToRecord() is idempotent across re-entry.
|
|
1841
|
+
static _observedStringSetMaps = /* @__PURE__ */ new WeakSet();
|
|
1839
1842
|
// Copy-on-write state management
|
|
1840
1843
|
_localChanges = null;
|
|
1841
1844
|
_isDirty = false;
|
|
@@ -2295,7 +2298,7 @@ var init_BaseModel = __esm({
|
|
|
2295
2298
|
validateBeforeSave() {
|
|
2296
2299
|
const schema = this.constructor.getSchema();
|
|
2297
2300
|
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
2298
|
-
const currentValue = this.getValue(fieldKey);
|
|
2301
|
+
const currentValue = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
2299
2302
|
if (fieldOptions.required && (currentValue === null || currentValue === void 0)) {
|
|
2300
2303
|
throw new Error(`Field ${fieldKey} is required before save`);
|
|
2301
2304
|
}
|
|
@@ -2355,16 +2358,15 @@ var init_BaseModel = __esm({
|
|
|
2355
2358
|
this._isDirty = true;
|
|
2356
2359
|
}
|
|
2357
2360
|
getStringSetCurrentValues(fieldName) {
|
|
2358
|
-
|
|
2359
|
-
if (yjsData && typeof yjsData === "object") {
|
|
2360
|
-
return Object.keys(yjsData);
|
|
2361
|
-
}
|
|
2362
|
-
return [];
|
|
2361
|
+
return this.getStringSetFromYjs(fieldName);
|
|
2363
2362
|
}
|
|
2364
2363
|
getStringSetFromYjs(fieldName) {
|
|
2365
2364
|
const yjsData = this.getFromYjs(fieldName);
|
|
2365
|
+
if (yjsData instanceof Y2.Map) {
|
|
2366
|
+
return Array.from(yjsData.keys());
|
|
2367
|
+
}
|
|
2366
2368
|
if (yjsData && typeof yjsData === "object") {
|
|
2367
|
-
return Object.keys(yjsData);
|
|
2369
|
+
return Object.keys(yjsData).filter((k) => Boolean(yjsData[k]));
|
|
2368
2370
|
}
|
|
2369
2371
|
return [];
|
|
2370
2372
|
}
|
|
@@ -2975,7 +2977,7 @@ var init_BaseModel = __esm({
|
|
|
2975
2977
|
Logger.debug(
|
|
2976
2978
|
`[_diffWithYjsData] No unsaved changes, returning empty diff`
|
|
2977
2979
|
);
|
|
2978
|
-
return { added: {}, modified: {}, removed: [] };
|
|
2980
|
+
return { added: {}, modified: {}, removed: [], stringSetChanges: {} };
|
|
2979
2981
|
}
|
|
2980
2982
|
const modelConstructor = this.constructor;
|
|
2981
2983
|
const schema = modelConstructor.getSchema();
|
|
@@ -3006,6 +3008,7 @@ var init_BaseModel = __esm({
|
|
|
3006
3008
|
const added = {};
|
|
3007
3009
|
const modified = {};
|
|
3008
3010
|
const removed = [];
|
|
3011
|
+
const stringSetChanges = {};
|
|
3009
3012
|
if (!recordYMap) {
|
|
3010
3013
|
Logger.debug(
|
|
3011
3014
|
`[_diffWithYjsData] No existing recordYMap, treating all local changes as 'added'`
|
|
@@ -3014,11 +3017,12 @@ var init_BaseModel = __esm({
|
|
|
3014
3017
|
for (const [key, value] of Object.entries(this._localChanges)) {
|
|
3015
3018
|
Logger.debug(`[_diffWithYjsData] Adding field '${key}': ${value}`);
|
|
3016
3019
|
if (value && value.type === "stringset") {
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
+
if (value.additions.size > 0 || value.removals.size > 0) {
|
|
3021
|
+
stringSetChanges[key] = {
|
|
3022
|
+
additions: value.additions,
|
|
3023
|
+
removals: value.removals
|
|
3024
|
+
};
|
|
3020
3025
|
}
|
|
3021
|
-
added[key] = stringSetData;
|
|
3022
3026
|
} else {
|
|
3023
3027
|
added[key] = value;
|
|
3024
3028
|
}
|
|
@@ -3027,9 +3031,10 @@ var init_BaseModel = __esm({
|
|
|
3027
3031
|
Logger.debug(`[_diffWithYjsData] Final diff for new record:`, {
|
|
3028
3032
|
added,
|
|
3029
3033
|
modified: {},
|
|
3030
|
-
removed: []
|
|
3034
|
+
removed: [],
|
|
3035
|
+
stringSetChanges
|
|
3031
3036
|
});
|
|
3032
|
-
return { added, modified, removed: [] };
|
|
3037
|
+
return { added, modified, removed: [], stringSetChanges };
|
|
3033
3038
|
}
|
|
3034
3039
|
Logger.debug(
|
|
3035
3040
|
`[_diffWithYjsData] Existing record found, comparing local changes with Y.js data`
|
|
@@ -3046,21 +3051,11 @@ var init_BaseModel = __esm({
|
|
|
3046
3051
|
`[_diffWithYjsData] Processing field '${key}' with local value: ${localValue}`
|
|
3047
3052
|
);
|
|
3048
3053
|
if (localValue && localValue.type === "stringset") {
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
newStringSetData[addition] = true;
|
|
3055
|
-
}
|
|
3056
|
-
for (const removal of localValue.removals) {
|
|
3057
|
-
delete newStringSetData[removal];
|
|
3058
|
-
}
|
|
3059
|
-
const yjsValue = recordYMap.get(key);
|
|
3060
|
-
if (yjsValue === void 0) {
|
|
3061
|
-
added[key] = newStringSetData;
|
|
3062
|
-
} else if (!this._deepEqual(yjsValue, newStringSetData)) {
|
|
3063
|
-
modified[key] = newStringSetData;
|
|
3054
|
+
if (localValue.additions.size > 0 || localValue.removals.size > 0) {
|
|
3055
|
+
stringSetChanges[key] = {
|
|
3056
|
+
additions: localValue.additions,
|
|
3057
|
+
removals: localValue.removals
|
|
3058
|
+
};
|
|
3064
3059
|
}
|
|
3065
3060
|
} else {
|
|
3066
3061
|
const yjsValue = recordYMap.get(key);
|
|
@@ -3086,14 +3081,43 @@ var init_BaseModel = __esm({
|
|
|
3086
3081
|
Logger.debug(`[_diffWithYjsData] Final diff result:`, {
|
|
3087
3082
|
added,
|
|
3088
3083
|
modified,
|
|
3089
|
-
removed
|
|
3084
|
+
removed,
|
|
3085
|
+
stringSetChanges
|
|
3090
3086
|
});
|
|
3091
3087
|
Logger.verbose(`[${modelConstructor.name}] Diff for ${this.id}:`, {
|
|
3092
3088
|
added: Object.keys(added),
|
|
3093
3089
|
modified: Object.keys(modified),
|
|
3094
|
-
removed
|
|
3090
|
+
removed,
|
|
3091
|
+
stringSetChanges: Object.keys(stringSetChanges)
|
|
3095
3092
|
});
|
|
3096
|
-
return { added, modified, removed };
|
|
3093
|
+
return { added, modified, removed, stringSetChanges };
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
3097
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
3098
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
3099
|
+
*/
|
|
3100
|
+
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3101
|
+
let nested = recordYMap.get(fieldName);
|
|
3102
|
+
if (!(nested instanceof Y2.Map)) {
|
|
3103
|
+
const migrated = new Y2.Map();
|
|
3104
|
+
if (nested && typeof nested === "object") {
|
|
3105
|
+
for (const [member, marker] of Object.entries(
|
|
3106
|
+
nested
|
|
3107
|
+
)) {
|
|
3108
|
+
if (Boolean(marker)) migrated.set(member, true);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
recordYMap.set(fieldName, migrated);
|
|
3112
|
+
nested = migrated;
|
|
3113
|
+
}
|
|
3114
|
+
const target = nested;
|
|
3115
|
+
for (const member of change.additions) {
|
|
3116
|
+
target.set(member, true);
|
|
3117
|
+
}
|
|
3118
|
+
for (const member of change.removals) {
|
|
3119
|
+
target.delete(member);
|
|
3120
|
+
}
|
|
3097
3121
|
}
|
|
3098
3122
|
/**
|
|
3099
3123
|
* Deep equality check for comparing field values
|
|
@@ -3332,7 +3356,7 @@ var init_BaseModel = __esm({
|
|
|
3332
3356
|
Logger.debug(`[${modelName}.save] About to calculate diff`);
|
|
3333
3357
|
const diff = this._diffWithYjsData();
|
|
3334
3358
|
Logger.debug(`[${modelName}.save] Diff result:`, diff);
|
|
3335
|
-
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0;
|
|
3359
|
+
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0 || Object.keys(diff.stringSetChanges).length > 0;
|
|
3336
3360
|
Logger.debug(`[${modelName}.save] hasChanges: ${hasChanges}`);
|
|
3337
3361
|
if (!hasChanges && isUpdate) {
|
|
3338
3362
|
Logger.verbose(
|
|
@@ -3450,6 +3474,16 @@ var init_BaseModel = __esm({
|
|
|
3450
3474
|
Logger.debug(`[${modelName}.save] Removing field '${key}'`);
|
|
3451
3475
|
recordYMap.delete(key);
|
|
3452
3476
|
}
|
|
3477
|
+
for (const [fieldName, change] of Object.entries(diff.stringSetChanges)) {
|
|
3478
|
+
Logger.debug(
|
|
3479
|
+
`[${modelName}.save] Applying stringset change to field '${fieldName}':`,
|
|
3480
|
+
{
|
|
3481
|
+
additions: Array.from(change.additions),
|
|
3482
|
+
removals: Array.from(change.removals)
|
|
3483
|
+
}
|
|
3484
|
+
);
|
|
3485
|
+
this.applyStringSetChangeToYMap(recordYMap, fieldName, change);
|
|
3486
|
+
}
|
|
3453
3487
|
Logger.debug(
|
|
3454
3488
|
`[${modelName}.save] After applying changes, recordYMap contents:`,
|
|
3455
3489
|
Object.fromEntries(recordYMap.entries())
|
|
@@ -4634,6 +4668,39 @@ var init_BaseModel = __esm({
|
|
|
4634
4668
|
_BaseModel.prototype.setValue = originalSetValue;
|
|
4635
4669
|
}
|
|
4636
4670
|
}
|
|
4671
|
+
/**
|
|
4672
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
4673
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
4674
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
4675
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
4676
|
+
*/
|
|
4677
|
+
static observeStringSetMapOnce(nestedMap) {
|
|
4678
|
+
if (_BaseModel._observedStringSetMaps.has(nestedMap)) return;
|
|
4679
|
+
_BaseModel._observedStringSetMaps.add(nestedMap);
|
|
4680
|
+
const modelConstructor = this;
|
|
4681
|
+
nestedMap.observe(() => {
|
|
4682
|
+
Logger.verbose(
|
|
4683
|
+
`[${modelConstructor.name}] Nested stringset Y.Map changed; notifying listeners`
|
|
4684
|
+
);
|
|
4685
|
+
modelConstructor.notifyListeners();
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
/**
|
|
4689
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
4690
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
4691
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
4692
|
+
* an observer too.
|
|
4693
|
+
*/
|
|
4694
|
+
static attachStringSetObserversToRecord(recordYMap, schema) {
|
|
4695
|
+
if (!schema?.fields) return;
|
|
4696
|
+
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
4697
|
+
if (fieldOptions?.type !== "stringset") continue;
|
|
4698
|
+
const value = recordYMap.get(fieldKey);
|
|
4699
|
+
if (value instanceof Y2.Map) {
|
|
4700
|
+
this.observeStringSetMapOnce(value);
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4637
4704
|
/**
|
|
4638
4705
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
4639
4706
|
*/
|
|
@@ -4650,11 +4717,13 @@ var init_BaseModel = __esm({
|
|
|
4650
4717
|
Logger.verbose(
|
|
4651
4718
|
`[${modelName}] Setting up nested YMap observer for record ${recordId}`
|
|
4652
4719
|
);
|
|
4720
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4653
4721
|
recordYMap.observe(async (event) => {
|
|
4654
4722
|
Logger.verbose(
|
|
4655
4723
|
`[${modelName}] Nested YMap change detected for record ${recordId}:`,
|
|
4656
4724
|
event
|
|
4657
4725
|
);
|
|
4726
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4658
4727
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
4659
4728
|
if (!currentDbInstance) {
|
|
4660
4729
|
Logger.error(
|
|
@@ -4728,11 +4797,13 @@ var init_BaseModel = __esm({
|
|
|
4728
4797
|
Logger.verbose(
|
|
4729
4798
|
`[${modelName}] Setting up nested YMap observer for record ${recordId} in document ${docId}`
|
|
4730
4799
|
);
|
|
4800
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4731
4801
|
recordYMap.observe(async (event) => {
|
|
4732
4802
|
Logger.verbose(
|
|
4733
4803
|
`[${modelName}] Nested YMap change detected for record ${recordId} in document ${docId}:`,
|
|
4734
4804
|
event
|
|
4735
4805
|
);
|
|
4806
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4736
4807
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
4737
4808
|
if (!currentDbInstance) {
|
|
4738
4809
|
Logger.error(
|
package/dist/node.cjs
CHANGED
|
@@ -2572,6 +2572,9 @@ var init_BaseModel = __esm({
|
|
|
2572
2572
|
static connectedDocuments = /* @__PURE__ */ new Map();
|
|
2573
2573
|
static documentYMaps = /* @__PURE__ */ new Map();
|
|
2574
2574
|
// Maps docId to YMap for that document
|
|
2575
|
+
// Tracks nested-Y.Map stringset fields we've already attached observers to,
|
|
2576
|
+
// so attachStringSetObserversToRecord() is idempotent across re-entry.
|
|
2577
|
+
static _observedStringSetMaps = /* @__PURE__ */ new WeakSet();
|
|
2575
2578
|
// Copy-on-write state management
|
|
2576
2579
|
_localChanges = null;
|
|
2577
2580
|
_isDirty = false;
|
|
@@ -3031,7 +3034,7 @@ var init_BaseModel = __esm({
|
|
|
3031
3034
|
validateBeforeSave() {
|
|
3032
3035
|
const schema = this.constructor.getSchema();
|
|
3033
3036
|
for (const [fieldKey, fieldOptions] of schema.fields.entries()) {
|
|
3034
|
-
const currentValue = this.getValue(fieldKey);
|
|
3037
|
+
const currentValue = fieldKey === "id" ? this.id : this.getValue(fieldKey);
|
|
3035
3038
|
if (fieldOptions.required && (currentValue === null || currentValue === void 0)) {
|
|
3036
3039
|
throw new Error(`Field ${fieldKey} is required before save`);
|
|
3037
3040
|
}
|
|
@@ -3091,16 +3094,15 @@ var init_BaseModel = __esm({
|
|
|
3091
3094
|
this._isDirty = true;
|
|
3092
3095
|
}
|
|
3093
3096
|
getStringSetCurrentValues(fieldName) {
|
|
3094
|
-
|
|
3095
|
-
if (yjsData && typeof yjsData === "object") {
|
|
3096
|
-
return Object.keys(yjsData);
|
|
3097
|
-
}
|
|
3098
|
-
return [];
|
|
3097
|
+
return this.getStringSetFromYjs(fieldName);
|
|
3099
3098
|
}
|
|
3100
3099
|
getStringSetFromYjs(fieldName) {
|
|
3101
3100
|
const yjsData = this.getFromYjs(fieldName);
|
|
3101
|
+
if (yjsData instanceof Y2.Map) {
|
|
3102
|
+
return Array.from(yjsData.keys());
|
|
3103
|
+
}
|
|
3102
3104
|
if (yjsData && typeof yjsData === "object") {
|
|
3103
|
-
return Object.keys(yjsData);
|
|
3105
|
+
return Object.keys(yjsData).filter((k) => Boolean(yjsData[k]));
|
|
3104
3106
|
}
|
|
3105
3107
|
return [];
|
|
3106
3108
|
}
|
|
@@ -3711,7 +3713,7 @@ var init_BaseModel = __esm({
|
|
|
3711
3713
|
Logger.debug(
|
|
3712
3714
|
`[_diffWithYjsData] No unsaved changes, returning empty diff`
|
|
3713
3715
|
);
|
|
3714
|
-
return { added: {}, modified: {}, removed: [] };
|
|
3716
|
+
return { added: {}, modified: {}, removed: [], stringSetChanges: {} };
|
|
3715
3717
|
}
|
|
3716
3718
|
const modelConstructor = this.constructor;
|
|
3717
3719
|
const schema = modelConstructor.getSchema();
|
|
@@ -3742,6 +3744,7 @@ var init_BaseModel = __esm({
|
|
|
3742
3744
|
const added = {};
|
|
3743
3745
|
const modified = {};
|
|
3744
3746
|
const removed = [];
|
|
3747
|
+
const stringSetChanges = {};
|
|
3745
3748
|
if (!recordYMap) {
|
|
3746
3749
|
Logger.debug(
|
|
3747
3750
|
`[_diffWithYjsData] No existing recordYMap, treating all local changes as 'added'`
|
|
@@ -3750,11 +3753,12 @@ var init_BaseModel = __esm({
|
|
|
3750
3753
|
for (const [key, value] of Object.entries(this._localChanges)) {
|
|
3751
3754
|
Logger.debug(`[_diffWithYjsData] Adding field '${key}': ${value}`);
|
|
3752
3755
|
if (value && value.type === "stringset") {
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
+
if (value.additions.size > 0 || value.removals.size > 0) {
|
|
3757
|
+
stringSetChanges[key] = {
|
|
3758
|
+
additions: value.additions,
|
|
3759
|
+
removals: value.removals
|
|
3760
|
+
};
|
|
3756
3761
|
}
|
|
3757
|
-
added[key] = stringSetData;
|
|
3758
3762
|
} else {
|
|
3759
3763
|
added[key] = value;
|
|
3760
3764
|
}
|
|
@@ -3763,9 +3767,10 @@ var init_BaseModel = __esm({
|
|
|
3763
3767
|
Logger.debug(`[_diffWithYjsData] Final diff for new record:`, {
|
|
3764
3768
|
added,
|
|
3765
3769
|
modified: {},
|
|
3766
|
-
removed: []
|
|
3770
|
+
removed: [],
|
|
3771
|
+
stringSetChanges
|
|
3767
3772
|
});
|
|
3768
|
-
return { added, modified, removed: [] };
|
|
3773
|
+
return { added, modified, removed: [], stringSetChanges };
|
|
3769
3774
|
}
|
|
3770
3775
|
Logger.debug(
|
|
3771
3776
|
`[_diffWithYjsData] Existing record found, comparing local changes with Y.js data`
|
|
@@ -3782,21 +3787,11 @@ var init_BaseModel = __esm({
|
|
|
3782
3787
|
`[_diffWithYjsData] Processing field '${key}' with local value: ${localValue}`
|
|
3783
3788
|
);
|
|
3784
3789
|
if (localValue && localValue.type === "stringset") {
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
newStringSetData[addition] = true;
|
|
3791
|
-
}
|
|
3792
|
-
for (const removal of localValue.removals) {
|
|
3793
|
-
delete newStringSetData[removal];
|
|
3794
|
-
}
|
|
3795
|
-
const yjsValue = recordYMap.get(key);
|
|
3796
|
-
if (yjsValue === void 0) {
|
|
3797
|
-
added[key] = newStringSetData;
|
|
3798
|
-
} else if (!this._deepEqual(yjsValue, newStringSetData)) {
|
|
3799
|
-
modified[key] = newStringSetData;
|
|
3790
|
+
if (localValue.additions.size > 0 || localValue.removals.size > 0) {
|
|
3791
|
+
stringSetChanges[key] = {
|
|
3792
|
+
additions: localValue.additions,
|
|
3793
|
+
removals: localValue.removals
|
|
3794
|
+
};
|
|
3800
3795
|
}
|
|
3801
3796
|
} else {
|
|
3802
3797
|
const yjsValue = recordYMap.get(key);
|
|
@@ -3822,14 +3817,43 @@ var init_BaseModel = __esm({
|
|
|
3822
3817
|
Logger.debug(`[_diffWithYjsData] Final diff result:`, {
|
|
3823
3818
|
added,
|
|
3824
3819
|
modified,
|
|
3825
|
-
removed
|
|
3820
|
+
removed,
|
|
3821
|
+
stringSetChanges
|
|
3826
3822
|
});
|
|
3827
3823
|
Logger.verbose(`[${modelConstructor.name}] Diff for ${this.id}:`, {
|
|
3828
3824
|
added: Object.keys(added),
|
|
3829
3825
|
modified: Object.keys(modified),
|
|
3830
|
-
removed
|
|
3826
|
+
removed,
|
|
3827
|
+
stringSetChanges: Object.keys(stringSetChanges)
|
|
3831
3828
|
});
|
|
3832
|
-
return { added, modified, removed };
|
|
3829
|
+
return { added, modified, removed, stringSetChanges };
|
|
3830
|
+
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
3833
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
3834
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
3835
|
+
*/
|
|
3836
|
+
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3837
|
+
let nested = recordYMap.get(fieldName);
|
|
3838
|
+
if (!(nested instanceof Y2.Map)) {
|
|
3839
|
+
const migrated = new Y2.Map();
|
|
3840
|
+
if (nested && typeof nested === "object") {
|
|
3841
|
+
for (const [member, marker] of Object.entries(
|
|
3842
|
+
nested
|
|
3843
|
+
)) {
|
|
3844
|
+
if (Boolean(marker)) migrated.set(member, true);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
recordYMap.set(fieldName, migrated);
|
|
3848
|
+
nested = migrated;
|
|
3849
|
+
}
|
|
3850
|
+
const target = nested;
|
|
3851
|
+
for (const member of change.additions) {
|
|
3852
|
+
target.set(member, true);
|
|
3853
|
+
}
|
|
3854
|
+
for (const member of change.removals) {
|
|
3855
|
+
target.delete(member);
|
|
3856
|
+
}
|
|
3833
3857
|
}
|
|
3834
3858
|
/**
|
|
3835
3859
|
* Deep equality check for comparing field values
|
|
@@ -4068,7 +4092,7 @@ var init_BaseModel = __esm({
|
|
|
4068
4092
|
Logger.debug(`[${modelName}.save] About to calculate diff`);
|
|
4069
4093
|
const diff = this._diffWithYjsData();
|
|
4070
4094
|
Logger.debug(`[${modelName}.save] Diff result:`, diff);
|
|
4071
|
-
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0;
|
|
4095
|
+
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0 || Object.keys(diff.stringSetChanges).length > 0;
|
|
4072
4096
|
Logger.debug(`[${modelName}.save] hasChanges: ${hasChanges}`);
|
|
4073
4097
|
if (!hasChanges && isUpdate) {
|
|
4074
4098
|
Logger.verbose(
|
|
@@ -4186,6 +4210,16 @@ var init_BaseModel = __esm({
|
|
|
4186
4210
|
Logger.debug(`[${modelName}.save] Removing field '${key}'`);
|
|
4187
4211
|
recordYMap.delete(key);
|
|
4188
4212
|
}
|
|
4213
|
+
for (const [fieldName, change] of Object.entries(diff.stringSetChanges)) {
|
|
4214
|
+
Logger.debug(
|
|
4215
|
+
`[${modelName}.save] Applying stringset change to field '${fieldName}':`,
|
|
4216
|
+
{
|
|
4217
|
+
additions: Array.from(change.additions),
|
|
4218
|
+
removals: Array.from(change.removals)
|
|
4219
|
+
}
|
|
4220
|
+
);
|
|
4221
|
+
this.applyStringSetChangeToYMap(recordYMap, fieldName, change);
|
|
4222
|
+
}
|
|
4189
4223
|
Logger.debug(
|
|
4190
4224
|
`[${modelName}.save] After applying changes, recordYMap contents:`,
|
|
4191
4225
|
Object.fromEntries(recordYMap.entries())
|
|
@@ -5370,6 +5404,39 @@ var init_BaseModel = __esm({
|
|
|
5370
5404
|
_BaseModel.prototype.setValue = originalSetValue;
|
|
5371
5405
|
}
|
|
5372
5406
|
}
|
|
5407
|
+
/**
|
|
5408
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
5409
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
5410
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
5411
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
5412
|
+
*/
|
|
5413
|
+
static observeStringSetMapOnce(nestedMap) {
|
|
5414
|
+
if (_BaseModel._observedStringSetMaps.has(nestedMap)) return;
|
|
5415
|
+
_BaseModel._observedStringSetMaps.add(nestedMap);
|
|
5416
|
+
const modelConstructor = this;
|
|
5417
|
+
nestedMap.observe(() => {
|
|
5418
|
+
Logger.verbose(
|
|
5419
|
+
`[${modelConstructor.name}] Nested stringset Y.Map changed; notifying listeners`
|
|
5420
|
+
);
|
|
5421
|
+
modelConstructor.notifyListeners();
|
|
5422
|
+
});
|
|
5423
|
+
}
|
|
5424
|
+
/**
|
|
5425
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
5426
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
5427
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
5428
|
+
* an observer too.
|
|
5429
|
+
*/
|
|
5430
|
+
static attachStringSetObserversToRecord(recordYMap, schema) {
|
|
5431
|
+
if (!schema?.fields) return;
|
|
5432
|
+
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5433
|
+
if (fieldOptions?.type !== "stringset") continue;
|
|
5434
|
+
const value = recordYMap.get(fieldKey);
|
|
5435
|
+
if (value instanceof Y2.Map) {
|
|
5436
|
+
this.observeStringSetMapOnce(value);
|
|
5437
|
+
}
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
5373
5440
|
/**
|
|
5374
5441
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
5375
5442
|
*/
|
|
@@ -5386,11 +5453,13 @@ var init_BaseModel = __esm({
|
|
|
5386
5453
|
Logger.verbose(
|
|
5387
5454
|
`[${modelName}] Setting up nested YMap observer for record ${recordId}`
|
|
5388
5455
|
);
|
|
5456
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5389
5457
|
recordYMap.observe(async (event) => {
|
|
5390
5458
|
Logger.verbose(
|
|
5391
5459
|
`[${modelName}] Nested YMap change detected for record ${recordId}:`,
|
|
5392
5460
|
event
|
|
5393
5461
|
);
|
|
5462
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5394
5463
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
5395
5464
|
if (!currentDbInstance) {
|
|
5396
5465
|
Logger.error(
|
|
@@ -5464,11 +5533,13 @@ var init_BaseModel = __esm({
|
|
|
5464
5533
|
Logger.verbose(
|
|
5465
5534
|
`[${modelName}] Setting up nested YMap observer for record ${recordId} in document ${docId}`
|
|
5466
5535
|
);
|
|
5536
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5467
5537
|
recordYMap.observe(async (event) => {
|
|
5468
5538
|
Logger.verbose(
|
|
5469
5539
|
`[${modelName}] Nested YMap change detected for record ${recordId} in document ${docId}:`,
|
|
5470
5540
|
event
|
|
5471
5541
|
);
|
|
5542
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5472
5543
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
5473
5544
|
if (!currentDbInstance) {
|
|
5474
5545
|
Logger.error(
|
package/dist/node.d.cts
CHANGED
|
@@ -454,6 +454,7 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
454
454
|
permissionHint: DocumentPermissionHint;
|
|
455
455
|
}>;
|
|
456
456
|
protected static documentYMaps: Map<string, Y.Map<any>>;
|
|
457
|
+
private static _observedStringSetMaps;
|
|
457
458
|
private _localChanges;
|
|
458
459
|
private _isDirty;
|
|
459
460
|
private _isLoadingFromYjs;
|
|
@@ -528,7 +529,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
528
529
|
added: Record<string, any>;
|
|
529
530
|
modified: Record<string, any>;
|
|
530
531
|
removed: string[];
|
|
532
|
+
stringSetChanges: Record<string, {
|
|
533
|
+
additions: Set<string>;
|
|
534
|
+
removals: Set<string>;
|
|
535
|
+
}>;
|
|
531
536
|
};
|
|
537
|
+
/**
|
|
538
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
539
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
540
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
541
|
+
*/
|
|
542
|
+
private applyStringSetChangeToYMap;
|
|
532
543
|
/**
|
|
533
544
|
* Deep equality check for comparing field values
|
|
534
545
|
*/
|
|
@@ -630,6 +641,20 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
630
641
|
* Execute a callback with automatic transaction handling for all modified models
|
|
631
642
|
*/
|
|
632
643
|
static withTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
644
|
+
/**
|
|
645
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
646
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
647
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
648
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
649
|
+
*/
|
|
650
|
+
protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
|
|
651
|
+
/**
|
|
652
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
653
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
654
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
655
|
+
* an observer too.
|
|
656
|
+
*/
|
|
657
|
+
protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
|
|
633
658
|
/**
|
|
634
659
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
635
660
|
*/
|
package/dist/node.d.ts
CHANGED
|
@@ -454,6 +454,7 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
454
454
|
permissionHint: DocumentPermissionHint;
|
|
455
455
|
}>;
|
|
456
456
|
protected static documentYMaps: Map<string, Y.Map<any>>;
|
|
457
|
+
private static _observedStringSetMaps;
|
|
457
458
|
private _localChanges;
|
|
458
459
|
private _isDirty;
|
|
459
460
|
private _isLoadingFromYjs;
|
|
@@ -528,7 +529,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
528
529
|
added: Record<string, any>;
|
|
529
530
|
modified: Record<string, any>;
|
|
530
531
|
removed: string[];
|
|
532
|
+
stringSetChanges: Record<string, {
|
|
533
|
+
additions: Set<string>;
|
|
534
|
+
removals: Set<string>;
|
|
535
|
+
}>;
|
|
531
536
|
};
|
|
537
|
+
/**
|
|
538
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
539
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
540
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
541
|
+
*/
|
|
542
|
+
private applyStringSetChangeToYMap;
|
|
532
543
|
/**
|
|
533
544
|
* Deep equality check for comparing field values
|
|
534
545
|
*/
|
|
@@ -630,6 +641,20 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
630
641
|
* Execute a callback with automatic transaction handling for all modified models
|
|
631
642
|
*/
|
|
632
643
|
static withTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
644
|
+
/**
|
|
645
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
646
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
647
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
648
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
649
|
+
*/
|
|
650
|
+
protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
|
|
651
|
+
/**
|
|
652
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
653
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
654
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
655
|
+
* an observer too.
|
|
656
|
+
*/
|
|
657
|
+
protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
|
|
633
658
|
/**
|
|
634
659
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
635
660
|
*/
|