js-bao 0.4.0 → 0.4.2
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 +179 -48
- package/dist/browser.d.cts +45 -1
- package/dist/browser.d.ts +45 -1
- package/dist/browser.js +179 -48
- package/dist/client.d.cts +32 -0
- package/dist/client.d.ts +32 -0
- package/dist/cloudflare-do.cjs +70 -5
- package/dist/cloudflare-do.d.cts +47 -3
- package/dist/cloudflare-do.d.ts +47 -3
- package/dist/cloudflare-do.js +70 -5
- package/dist/cloudflare.cjs +70 -5
- package/dist/cloudflare.d.cts +45 -1
- package/dist/cloudflare.d.ts +45 -1
- package/dist/cloudflare.js +70 -5
- package/dist/codegen-v2.cjs +1766 -0
- package/dist/codegen-v2.d.cts +1 -0
- package/dist/codegen.cjs +7 -10
- package/dist/index.cjs +181 -50
- package/dist/index.d.cts +46 -2
- package/dist/index.d.ts +46 -2
- package/dist/index.js +181 -50
- package/dist/node.cjs +181 -50
- package/dist/node.d.cts +46 -2
- package/dist/node.d.ts +46 -2
- package/dist/node.js +181 -50
- package/package.json +7 -10
package/dist/browser.js
CHANGED
|
@@ -2550,6 +2550,9 @@ var init_BaseModel = __esm({
|
|
|
2550
2550
|
static connectedDocuments = /* @__PURE__ */ new Map();
|
|
2551
2551
|
static documentYMaps = /* @__PURE__ */ new Map();
|
|
2552
2552
|
// Maps docId to YMap for that document
|
|
2553
|
+
// Tracks nested-Y.Map stringset fields we've already attached observers to,
|
|
2554
|
+
// so attachStringSetObserversToRecord() is idempotent across re-entry.
|
|
2555
|
+
static _observedStringSetMaps = /* @__PURE__ */ new WeakSet();
|
|
2553
2556
|
// Copy-on-write state management
|
|
2554
2557
|
_localChanges = null;
|
|
2555
2558
|
_isDirty = false;
|
|
@@ -3069,16 +3072,15 @@ var init_BaseModel = __esm({
|
|
|
3069
3072
|
this._isDirty = true;
|
|
3070
3073
|
}
|
|
3071
3074
|
getStringSetCurrentValues(fieldName) {
|
|
3072
|
-
|
|
3073
|
-
if (yjsData && typeof yjsData === "object") {
|
|
3074
|
-
return Object.keys(yjsData);
|
|
3075
|
-
}
|
|
3076
|
-
return [];
|
|
3075
|
+
return this.getStringSetFromYjs(fieldName);
|
|
3077
3076
|
}
|
|
3078
3077
|
getStringSetFromYjs(fieldName) {
|
|
3079
3078
|
const yjsData = this.getFromYjs(fieldName);
|
|
3079
|
+
if (yjsData instanceof Y2.Map) {
|
|
3080
|
+
return Array.from(yjsData.keys());
|
|
3081
|
+
}
|
|
3080
3082
|
if (yjsData && typeof yjsData === "object") {
|
|
3081
|
-
return Object.keys(yjsData);
|
|
3083
|
+
return Object.keys(yjsData).filter((k) => Boolean(yjsData[k]));
|
|
3082
3084
|
}
|
|
3083
3085
|
return [];
|
|
3084
3086
|
}
|
|
@@ -3689,7 +3691,7 @@ var init_BaseModel = __esm({
|
|
|
3689
3691
|
Logger.debug(
|
|
3690
3692
|
`[_diffWithYjsData] No unsaved changes, returning empty diff`
|
|
3691
3693
|
);
|
|
3692
|
-
return { added: {}, modified: {}, removed: [] };
|
|
3694
|
+
return { added: {}, modified: {}, removed: [], stringSetChanges: {} };
|
|
3693
3695
|
}
|
|
3694
3696
|
const modelConstructor = this.constructor;
|
|
3695
3697
|
const schema = modelConstructor.getSchema();
|
|
@@ -3720,6 +3722,7 @@ var init_BaseModel = __esm({
|
|
|
3720
3722
|
const added = {};
|
|
3721
3723
|
const modified = {};
|
|
3722
3724
|
const removed = [];
|
|
3725
|
+
const stringSetChanges = {};
|
|
3723
3726
|
if (!recordYMap) {
|
|
3724
3727
|
Logger.debug(
|
|
3725
3728
|
`[_diffWithYjsData] No existing recordYMap, treating all local changes as 'added'`
|
|
@@ -3728,11 +3731,12 @@ var init_BaseModel = __esm({
|
|
|
3728
3731
|
for (const [key, value] of Object.entries(this._localChanges)) {
|
|
3729
3732
|
Logger.debug(`[_diffWithYjsData] Adding field '${key}': ${value}`);
|
|
3730
3733
|
if (value && value.type === "stringset") {
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
+
if (value.additions.size > 0 || value.removals.size > 0) {
|
|
3735
|
+
stringSetChanges[key] = {
|
|
3736
|
+
additions: value.additions,
|
|
3737
|
+
removals: value.removals
|
|
3738
|
+
};
|
|
3734
3739
|
}
|
|
3735
|
-
added[key] = stringSetData;
|
|
3736
3740
|
} else {
|
|
3737
3741
|
added[key] = value;
|
|
3738
3742
|
}
|
|
@@ -3741,9 +3745,10 @@ var init_BaseModel = __esm({
|
|
|
3741
3745
|
Logger.debug(`[_diffWithYjsData] Final diff for new record:`, {
|
|
3742
3746
|
added,
|
|
3743
3747
|
modified: {},
|
|
3744
|
-
removed: []
|
|
3748
|
+
removed: [],
|
|
3749
|
+
stringSetChanges
|
|
3745
3750
|
});
|
|
3746
|
-
return { added, modified, removed: [] };
|
|
3751
|
+
return { added, modified, removed: [], stringSetChanges };
|
|
3747
3752
|
}
|
|
3748
3753
|
Logger.debug(
|
|
3749
3754
|
`[_diffWithYjsData] Existing record found, comparing local changes with Y.js data`
|
|
@@ -3760,21 +3765,11 @@ var init_BaseModel = __esm({
|
|
|
3760
3765
|
`[_diffWithYjsData] Processing field '${key}' with local value: ${localValue}`
|
|
3761
3766
|
);
|
|
3762
3767
|
if (localValue && localValue.type === "stringset") {
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
newStringSetData[addition] = true;
|
|
3769
|
-
}
|
|
3770
|
-
for (const removal of localValue.removals) {
|
|
3771
|
-
delete newStringSetData[removal];
|
|
3772
|
-
}
|
|
3773
|
-
const yjsValue = recordYMap.get(key);
|
|
3774
|
-
if (yjsValue === void 0) {
|
|
3775
|
-
added[key] = newStringSetData;
|
|
3776
|
-
} else if (!this._deepEqual(yjsValue, newStringSetData)) {
|
|
3777
|
-
modified[key] = newStringSetData;
|
|
3768
|
+
if (localValue.additions.size > 0 || localValue.removals.size > 0) {
|
|
3769
|
+
stringSetChanges[key] = {
|
|
3770
|
+
additions: localValue.additions,
|
|
3771
|
+
removals: localValue.removals
|
|
3772
|
+
};
|
|
3778
3773
|
}
|
|
3779
3774
|
} else {
|
|
3780
3775
|
const yjsValue = recordYMap.get(key);
|
|
@@ -3800,14 +3795,43 @@ var init_BaseModel = __esm({
|
|
|
3800
3795
|
Logger.debug(`[_diffWithYjsData] Final diff result:`, {
|
|
3801
3796
|
added,
|
|
3802
3797
|
modified,
|
|
3803
|
-
removed
|
|
3798
|
+
removed,
|
|
3799
|
+
stringSetChanges
|
|
3804
3800
|
});
|
|
3805
3801
|
Logger.verbose(`[${modelConstructor.name}] Diff for ${this.id}:`, {
|
|
3806
3802
|
added: Object.keys(added),
|
|
3807
3803
|
modified: Object.keys(modified),
|
|
3808
|
-
removed
|
|
3804
|
+
removed,
|
|
3805
|
+
stringSetChanges: Object.keys(stringSetChanges)
|
|
3809
3806
|
});
|
|
3810
|
-
return { added, modified, removed };
|
|
3807
|
+
return { added, modified, removed, stringSetChanges };
|
|
3808
|
+
}
|
|
3809
|
+
/**
|
|
3810
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
3811
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
3812
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
3813
|
+
*/
|
|
3814
|
+
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3815
|
+
let nested = recordYMap.get(fieldName);
|
|
3816
|
+
if (!(nested instanceof Y2.Map)) {
|
|
3817
|
+
const migrated = new Y2.Map();
|
|
3818
|
+
if (nested && typeof nested === "object") {
|
|
3819
|
+
for (const [member, marker] of Object.entries(
|
|
3820
|
+
nested
|
|
3821
|
+
)) {
|
|
3822
|
+
if (Boolean(marker)) migrated.set(member, true);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
recordYMap.set(fieldName, migrated);
|
|
3826
|
+
nested = migrated;
|
|
3827
|
+
}
|
|
3828
|
+
const target = nested;
|
|
3829
|
+
for (const member of change.additions) {
|
|
3830
|
+
target.set(member, true);
|
|
3831
|
+
}
|
|
3832
|
+
for (const member of change.removals) {
|
|
3833
|
+
target.delete(member);
|
|
3834
|
+
}
|
|
3811
3835
|
}
|
|
3812
3836
|
/**
|
|
3813
3837
|
* Deep equality check for comparing field values
|
|
@@ -4046,7 +4070,7 @@ var init_BaseModel = __esm({
|
|
|
4046
4070
|
Logger.debug(`[${modelName}.save] About to calculate diff`);
|
|
4047
4071
|
const diff = this._diffWithYjsData();
|
|
4048
4072
|
Logger.debug(`[${modelName}.save] Diff result:`, diff);
|
|
4049
|
-
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0;
|
|
4073
|
+
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0 || Object.keys(diff.stringSetChanges).length > 0;
|
|
4050
4074
|
Logger.debug(`[${modelName}.save] hasChanges: ${hasChanges}`);
|
|
4051
4075
|
if (!hasChanges && isUpdate) {
|
|
4052
4076
|
Logger.verbose(
|
|
@@ -4164,6 +4188,16 @@ var init_BaseModel = __esm({
|
|
|
4164
4188
|
Logger.debug(`[${modelName}.save] Removing field '${key}'`);
|
|
4165
4189
|
recordYMap.delete(key);
|
|
4166
4190
|
}
|
|
4191
|
+
for (const [fieldName, change] of Object.entries(diff.stringSetChanges)) {
|
|
4192
|
+
Logger.debug(
|
|
4193
|
+
`[${modelName}.save] Applying stringset change to field '${fieldName}':`,
|
|
4194
|
+
{
|
|
4195
|
+
additions: Array.from(change.additions),
|
|
4196
|
+
removals: Array.from(change.removals)
|
|
4197
|
+
}
|
|
4198
|
+
);
|
|
4199
|
+
this.applyStringSetChangeToYMap(recordYMap, fieldName, change);
|
|
4200
|
+
}
|
|
4167
4201
|
Logger.debug(
|
|
4168
4202
|
`[${modelName}.save] After applying changes, recordYMap contents:`,
|
|
4169
4203
|
Object.fromEntries(recordYMap.entries())
|
|
@@ -5348,6 +5382,39 @@ var init_BaseModel = __esm({
|
|
|
5348
5382
|
_BaseModel.prototype.setValue = originalSetValue;
|
|
5349
5383
|
}
|
|
5350
5384
|
}
|
|
5385
|
+
/**
|
|
5386
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
5387
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
5388
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
5389
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
5390
|
+
*/
|
|
5391
|
+
static observeStringSetMapOnce(nestedMap) {
|
|
5392
|
+
if (_BaseModel._observedStringSetMaps.has(nestedMap)) return;
|
|
5393
|
+
_BaseModel._observedStringSetMaps.add(nestedMap);
|
|
5394
|
+
const modelConstructor = this;
|
|
5395
|
+
nestedMap.observe(() => {
|
|
5396
|
+
Logger.verbose(
|
|
5397
|
+
`[${modelConstructor.name}] Nested stringset Y.Map changed; notifying listeners`
|
|
5398
|
+
);
|
|
5399
|
+
modelConstructor.notifyListeners();
|
|
5400
|
+
});
|
|
5401
|
+
}
|
|
5402
|
+
/**
|
|
5403
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
5404
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
5405
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
5406
|
+
* an observer too.
|
|
5407
|
+
*/
|
|
5408
|
+
static attachStringSetObserversToRecord(recordYMap, schema) {
|
|
5409
|
+
if (!schema?.fields) return;
|
|
5410
|
+
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
5411
|
+
if (fieldOptions?.type !== "stringset") continue;
|
|
5412
|
+
const value = recordYMap.get(fieldKey);
|
|
5413
|
+
if (value instanceof Y2.Map) {
|
|
5414
|
+
this.observeStringSetMapOnce(value);
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
}
|
|
5351
5418
|
/**
|
|
5352
5419
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
5353
5420
|
*/
|
|
@@ -5364,11 +5431,13 @@ var init_BaseModel = __esm({
|
|
|
5364
5431
|
Logger.verbose(
|
|
5365
5432
|
`[${modelName}] Setting up nested YMap observer for record ${recordId}`
|
|
5366
5433
|
);
|
|
5434
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5367
5435
|
recordYMap.observe(async (event) => {
|
|
5368
5436
|
Logger.verbose(
|
|
5369
5437
|
`[${modelName}] Nested YMap change detected for record ${recordId}:`,
|
|
5370
5438
|
event
|
|
5371
5439
|
);
|
|
5440
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5372
5441
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
5373
5442
|
if (!currentDbInstance) {
|
|
5374
5443
|
Logger.error(
|
|
@@ -5442,11 +5511,13 @@ var init_BaseModel = __esm({
|
|
|
5442
5511
|
Logger.verbose(
|
|
5443
5512
|
`[${modelName}] Setting up nested YMap observer for record ${recordId} in document ${docId}`
|
|
5444
5513
|
);
|
|
5514
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5445
5515
|
recordYMap.observe(async (event) => {
|
|
5446
5516
|
Logger.verbose(
|
|
5447
5517
|
`[${modelName}] Nested YMap change detected for record ${recordId} in document ${docId}:`,
|
|
5448
5518
|
event
|
|
5449
5519
|
);
|
|
5520
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
5450
5521
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
5451
5522
|
if (!currentDbInstance) {
|
|
5452
5523
|
Logger.error(
|
|
@@ -6286,6 +6357,7 @@ function defineModelSchema(input) {
|
|
|
6286
6357
|
const { name, fields } = input;
|
|
6287
6358
|
const options = {
|
|
6288
6359
|
name,
|
|
6360
|
+
className: input.options?.className,
|
|
6289
6361
|
uniqueConstraints: input.options?.uniqueConstraints ? [...input.options.uniqueConstraints] : void 0,
|
|
6290
6362
|
relationships: input.options?.relationships
|
|
6291
6363
|
};
|
|
@@ -6385,18 +6457,13 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
6385
6457
|
function attachSchemaToClass(modelClass, schema) {
|
|
6386
6458
|
modelClass.schema = schema;
|
|
6387
6459
|
modelClass.modelName = schema.options.name;
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
value: function() {
|
|
6391
|
-
return schema.buildRuntimeShape(modelClass);
|
|
6392
|
-
},
|
|
6393
|
-
writable: false
|
|
6394
|
-
});
|
|
6395
|
-
} else {
|
|
6396
|
-
modelClass.getSchema = function() {
|
|
6460
|
+
Object.defineProperty(modelClass, "getSchema", {
|
|
6461
|
+
value: function() {
|
|
6397
6462
|
return schema.buildRuntimeShape(modelClass);
|
|
6398
|
-
}
|
|
6399
|
-
|
|
6463
|
+
},
|
|
6464
|
+
writable: false,
|
|
6465
|
+
configurable: true
|
|
6466
|
+
});
|
|
6400
6467
|
const runtimeShape = schema.buildRuntimeShape(modelClass);
|
|
6401
6468
|
BaseModel2.attachFieldAccessors(modelClass, runtimeShape.fields);
|
|
6402
6469
|
return runtimeShape;
|
|
@@ -6629,12 +6696,52 @@ var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
|
6629
6696
|
"id",
|
|
6630
6697
|
"stringset"
|
|
6631
6698
|
]);
|
|
6632
|
-
|
|
6699
|
+
var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
6700
|
+
"type",
|
|
6701
|
+
"indexed",
|
|
6702
|
+
"unique",
|
|
6703
|
+
"required",
|
|
6704
|
+
"auto_assign",
|
|
6705
|
+
"max_length",
|
|
6706
|
+
"max_count",
|
|
6707
|
+
"default"
|
|
6708
|
+
]);
|
|
6709
|
+
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
6710
|
+
"fields",
|
|
6711
|
+
"relationships",
|
|
6712
|
+
"unique_constraints",
|
|
6713
|
+
"class_name"
|
|
6714
|
+
]);
|
|
6715
|
+
var KNOWN_RELATIONSHIP_KEYS = /* @__PURE__ */ new Set([
|
|
6716
|
+
"type",
|
|
6717
|
+
"model",
|
|
6718
|
+
"related_id_field",
|
|
6719
|
+
"join_model",
|
|
6720
|
+
"join_model_local_field",
|
|
6721
|
+
"join_model_related_field",
|
|
6722
|
+
"order_by_field",
|
|
6723
|
+
"order_direction",
|
|
6724
|
+
"join_model_order_by_field",
|
|
6725
|
+
"join_model_order_direction"
|
|
6726
|
+
]);
|
|
6727
|
+
var KNOWN_UNIQUE_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["name", "fields"]);
|
|
6728
|
+
function checkUnknownKeys(raw, known, context, strict) {
|
|
6729
|
+
if (!strict) return;
|
|
6730
|
+
for (const key of Object.keys(raw)) {
|
|
6731
|
+
if (!known.has(key)) {
|
|
6732
|
+
throw new Error(
|
|
6733
|
+
`${context}: unknown key "${key}". Allowed: ${[...known].join(", ")}`
|
|
6734
|
+
);
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
function parseFieldOptions(raw, context, strict) {
|
|
6633
6739
|
if (!raw.type || !VALID_FIELD_TYPES.has(raw.type)) {
|
|
6634
6740
|
throw new Error(
|
|
6635
6741
|
`Invalid field type "${raw.type}". Must be one of: ${[...VALID_FIELD_TYPES].join(", ")}`
|
|
6636
6742
|
);
|
|
6637
6743
|
}
|
|
6744
|
+
checkUnknownKeys(raw, KNOWN_FIELD_KEYS, context, strict);
|
|
6638
6745
|
const opts = { type: raw.type };
|
|
6639
6746
|
if (raw.indexed === true) opts.indexed = true;
|
|
6640
6747
|
if (raw.unique === true) opts.unique = true;
|
|
@@ -6650,8 +6757,9 @@ function requireField(raw, field, context) {
|
|
|
6650
6757
|
throw new Error(`Relationship ${context}: missing required field "${field}"`);
|
|
6651
6758
|
}
|
|
6652
6759
|
}
|
|
6653
|
-
function parseRelationship(raw) {
|
|
6760
|
+
function parseRelationship(raw, context, strict) {
|
|
6654
6761
|
const type = raw.type;
|
|
6762
|
+
checkUnknownKeys(raw, KNOWN_RELATIONSHIP_KEYS, context, strict);
|
|
6655
6763
|
if (type === "refersTo") {
|
|
6656
6764
|
requireField(raw, "model", "refersTo");
|
|
6657
6765
|
requireField(raw, "related_id_field", "refersTo");
|
|
@@ -6693,7 +6801,8 @@ function parseRelationship(raw) {
|
|
|
6693
6801
|
}
|
|
6694
6802
|
throw new Error(`Unknown relationship type: ${type}`);
|
|
6695
6803
|
}
|
|
6696
|
-
function loadSchemaFromTomlString(tomlString) {
|
|
6804
|
+
function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
6805
|
+
const strict = options.strict !== false;
|
|
6697
6806
|
const parsed = parseToml(tomlString);
|
|
6698
6807
|
const models = parsed.models;
|
|
6699
6808
|
if (!models || typeof models !== "object") {
|
|
@@ -6701,10 +6810,20 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
6701
6810
|
}
|
|
6702
6811
|
const schemas = [];
|
|
6703
6812
|
for (const [modelName, modelDef] of Object.entries(models)) {
|
|
6813
|
+
checkUnknownKeys(
|
|
6814
|
+
modelDef,
|
|
6815
|
+
KNOWN_MODEL_KEYS,
|
|
6816
|
+
`[models.${modelName}]`,
|
|
6817
|
+
strict
|
|
6818
|
+
);
|
|
6704
6819
|
const fields = {};
|
|
6705
6820
|
if (modelDef.fields) {
|
|
6706
6821
|
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
|
|
6707
|
-
fields[fieldName] = parseFieldOptions(
|
|
6822
|
+
fields[fieldName] = parseFieldOptions(
|
|
6823
|
+
fieldDef,
|
|
6824
|
+
`[models.${modelName}.fields.${fieldName}]`,
|
|
6825
|
+
strict
|
|
6826
|
+
);
|
|
6708
6827
|
}
|
|
6709
6828
|
}
|
|
6710
6829
|
let relationships;
|
|
@@ -6713,24 +6832,36 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
6713
6832
|
for (const [relName, relDef] of Object.entries(
|
|
6714
6833
|
modelDef.relationships
|
|
6715
6834
|
)) {
|
|
6716
|
-
relationships[relName] = parseRelationship(
|
|
6835
|
+
relationships[relName] = parseRelationship(
|
|
6836
|
+
relDef,
|
|
6837
|
+
`[models.${modelName}.relationships.${relName}]`,
|
|
6838
|
+
strict
|
|
6839
|
+
);
|
|
6717
6840
|
}
|
|
6718
6841
|
}
|
|
6719
6842
|
let uniqueConstraints;
|
|
6720
6843
|
if (modelDef.unique_constraints) {
|
|
6721
6844
|
uniqueConstraints = [];
|
|
6722
6845
|
for (const raw of modelDef.unique_constraints) {
|
|
6846
|
+
checkUnknownKeys(
|
|
6847
|
+
raw,
|
|
6848
|
+
KNOWN_UNIQUE_CONSTRAINT_KEYS,
|
|
6849
|
+
`[[models.${modelName}.unique_constraints]]`,
|
|
6850
|
+
strict
|
|
6851
|
+
);
|
|
6723
6852
|
uniqueConstraints.push({
|
|
6724
6853
|
name: raw.name,
|
|
6725
6854
|
fields: [...raw.fields]
|
|
6726
6855
|
});
|
|
6727
6856
|
}
|
|
6728
6857
|
}
|
|
6858
|
+
const className = typeof modelDef.class_name === "string" ? modelDef.class_name : void 0;
|
|
6729
6859
|
schemas.push(
|
|
6730
6860
|
defineModelSchema({
|
|
6731
6861
|
name: modelName,
|
|
6732
6862
|
fields,
|
|
6733
6863
|
options: {
|
|
6864
|
+
className,
|
|
6734
6865
|
uniqueConstraints,
|
|
6735
6866
|
relationships
|
|
6736
6867
|
}
|
package/dist/client.d.cts
CHANGED
|
@@ -40,6 +40,13 @@ interface UniqueConstraintConfig {
|
|
|
40
40
|
}
|
|
41
41
|
interface ModelOptions {
|
|
42
42
|
name: string;
|
|
43
|
+
/**
|
|
44
|
+
* Optional PascalCase class name. Used by the v2 codegen to drive
|
|
45
|
+
* generated TypeScript class names (and relationship method names that
|
|
46
|
+
* derive from a target's class name). When absent, the v2 codegen
|
|
47
|
+
* falls back to suffix-based singularization of `name`.
|
|
48
|
+
*/
|
|
49
|
+
className?: string;
|
|
43
50
|
uniqueConstraints?: UniqueConstraintConfig[];
|
|
44
51
|
relationships?: Record<string, RelationshipConfig>;
|
|
45
52
|
}
|
|
@@ -346,6 +353,7 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
346
353
|
permissionHint: DocumentPermissionHint;
|
|
347
354
|
}>;
|
|
348
355
|
protected static documentYMaps: Map<string, Y.Map<any>>;
|
|
356
|
+
private static _observedStringSetMaps;
|
|
349
357
|
private _localChanges;
|
|
350
358
|
private _isDirty;
|
|
351
359
|
private _isLoadingFromYjs;
|
|
@@ -420,7 +428,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
420
428
|
added: Record<string, any>;
|
|
421
429
|
modified: Record<string, any>;
|
|
422
430
|
removed: string[];
|
|
431
|
+
stringSetChanges: Record<string, {
|
|
432
|
+
additions: Set<string>;
|
|
433
|
+
removals: Set<string>;
|
|
434
|
+
}>;
|
|
423
435
|
};
|
|
436
|
+
/**
|
|
437
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
438
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
439
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
440
|
+
*/
|
|
441
|
+
private applyStringSetChangeToYMap;
|
|
424
442
|
/**
|
|
425
443
|
* Deep equality check for comparing field values
|
|
426
444
|
*/
|
|
@@ -522,6 +540,20 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
522
540
|
* Execute a callback with automatic transaction handling for all modified models
|
|
523
541
|
*/
|
|
524
542
|
static withTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
543
|
+
/**
|
|
544
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
545
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
546
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
547
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
548
|
+
*/
|
|
549
|
+
protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
|
|
550
|
+
/**
|
|
551
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
552
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
553
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
554
|
+
* an observer too.
|
|
555
|
+
*/
|
|
556
|
+
protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
|
|
525
557
|
/**
|
|
526
558
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
527
559
|
*/
|
package/dist/client.d.ts
CHANGED
|
@@ -40,6 +40,13 @@ interface UniqueConstraintConfig {
|
|
|
40
40
|
}
|
|
41
41
|
interface ModelOptions {
|
|
42
42
|
name: string;
|
|
43
|
+
/**
|
|
44
|
+
* Optional PascalCase class name. Used by the v2 codegen to drive
|
|
45
|
+
* generated TypeScript class names (and relationship method names that
|
|
46
|
+
* derive from a target's class name). When absent, the v2 codegen
|
|
47
|
+
* falls back to suffix-based singularization of `name`.
|
|
48
|
+
*/
|
|
49
|
+
className?: string;
|
|
43
50
|
uniqueConstraints?: UniqueConstraintConfig[];
|
|
44
51
|
relationships?: Record<string, RelationshipConfig>;
|
|
45
52
|
}
|
|
@@ -346,6 +353,7 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
346
353
|
permissionHint: DocumentPermissionHint;
|
|
347
354
|
}>;
|
|
348
355
|
protected static documentYMaps: Map<string, Y.Map<any>>;
|
|
356
|
+
private static _observedStringSetMaps;
|
|
349
357
|
private _localChanges;
|
|
350
358
|
private _isDirty;
|
|
351
359
|
private _isLoadingFromYjs;
|
|
@@ -420,7 +428,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
420
428
|
added: Record<string, any>;
|
|
421
429
|
modified: Record<string, any>;
|
|
422
430
|
removed: string[];
|
|
431
|
+
stringSetChanges: Record<string, {
|
|
432
|
+
additions: Set<string>;
|
|
433
|
+
removals: Set<string>;
|
|
434
|
+
}>;
|
|
423
435
|
};
|
|
436
|
+
/**
|
|
437
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
438
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
439
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
440
|
+
*/
|
|
441
|
+
private applyStringSetChangeToYMap;
|
|
424
442
|
/**
|
|
425
443
|
* Deep equality check for comparing field values
|
|
426
444
|
*/
|
|
@@ -522,6 +540,20 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
522
540
|
* Execute a callback with automatic transaction handling for all modified models
|
|
523
541
|
*/
|
|
524
542
|
static withTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
543
|
+
/**
|
|
544
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
545
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
546
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
547
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
548
|
+
*/
|
|
549
|
+
protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
|
|
550
|
+
/**
|
|
551
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
552
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
553
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
554
|
+
* an observer too.
|
|
555
|
+
*/
|
|
556
|
+
protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
|
|
525
557
|
/**
|
|
526
558
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
527
559
|
*/
|
package/dist/cloudflare-do.cjs
CHANGED
|
@@ -4266,6 +4266,7 @@ function defineModelSchema(input) {
|
|
|
4266
4266
|
const { name, fields } = input;
|
|
4267
4267
|
const options = {
|
|
4268
4268
|
name,
|
|
4269
|
+
className: input.options?.className,
|
|
4269
4270
|
uniqueConstraints: input.options?.uniqueConstraints ? [...input.options.uniqueConstraints] : void 0,
|
|
4270
4271
|
relationships: input.options?.relationships
|
|
4271
4272
|
};
|
|
@@ -4342,12 +4343,52 @@ var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
|
4342
4343
|
"id",
|
|
4343
4344
|
"stringset"
|
|
4344
4345
|
]);
|
|
4345
|
-
|
|
4346
|
+
var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
4347
|
+
"type",
|
|
4348
|
+
"indexed",
|
|
4349
|
+
"unique",
|
|
4350
|
+
"required",
|
|
4351
|
+
"auto_assign",
|
|
4352
|
+
"max_length",
|
|
4353
|
+
"max_count",
|
|
4354
|
+
"default"
|
|
4355
|
+
]);
|
|
4356
|
+
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
4357
|
+
"fields",
|
|
4358
|
+
"relationships",
|
|
4359
|
+
"unique_constraints",
|
|
4360
|
+
"class_name"
|
|
4361
|
+
]);
|
|
4362
|
+
var KNOWN_RELATIONSHIP_KEYS = /* @__PURE__ */ new Set([
|
|
4363
|
+
"type",
|
|
4364
|
+
"model",
|
|
4365
|
+
"related_id_field",
|
|
4366
|
+
"join_model",
|
|
4367
|
+
"join_model_local_field",
|
|
4368
|
+
"join_model_related_field",
|
|
4369
|
+
"order_by_field",
|
|
4370
|
+
"order_direction",
|
|
4371
|
+
"join_model_order_by_field",
|
|
4372
|
+
"join_model_order_direction"
|
|
4373
|
+
]);
|
|
4374
|
+
var KNOWN_UNIQUE_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["name", "fields"]);
|
|
4375
|
+
function checkUnknownKeys(raw, known, context, strict) {
|
|
4376
|
+
if (!strict) return;
|
|
4377
|
+
for (const key of Object.keys(raw)) {
|
|
4378
|
+
if (!known.has(key)) {
|
|
4379
|
+
throw new Error(
|
|
4380
|
+
`${context}: unknown key "${key}". Allowed: ${[...known].join(", ")}`
|
|
4381
|
+
);
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
function parseFieldOptions(raw, context, strict) {
|
|
4346
4386
|
if (!raw.type || !VALID_FIELD_TYPES.has(raw.type)) {
|
|
4347
4387
|
throw new Error(
|
|
4348
4388
|
`Invalid field type "${raw.type}". Must be one of: ${[...VALID_FIELD_TYPES].join(", ")}`
|
|
4349
4389
|
);
|
|
4350
4390
|
}
|
|
4391
|
+
checkUnknownKeys(raw, KNOWN_FIELD_KEYS, context, strict);
|
|
4351
4392
|
const opts = { type: raw.type };
|
|
4352
4393
|
if (raw.indexed === true) opts.indexed = true;
|
|
4353
4394
|
if (raw.unique === true) opts.unique = true;
|
|
@@ -4363,8 +4404,9 @@ function requireField(raw, field, context) {
|
|
|
4363
4404
|
throw new Error(`Relationship ${context}: missing required field "${field}"`);
|
|
4364
4405
|
}
|
|
4365
4406
|
}
|
|
4366
|
-
function parseRelationship(raw) {
|
|
4407
|
+
function parseRelationship(raw, context, strict) {
|
|
4367
4408
|
const type = raw.type;
|
|
4409
|
+
checkUnknownKeys(raw, KNOWN_RELATIONSHIP_KEYS, context, strict);
|
|
4368
4410
|
if (type === "refersTo") {
|
|
4369
4411
|
requireField(raw, "model", "refersTo");
|
|
4370
4412
|
requireField(raw, "related_id_field", "refersTo");
|
|
@@ -4406,7 +4448,8 @@ function parseRelationship(raw) {
|
|
|
4406
4448
|
}
|
|
4407
4449
|
throw new Error(`Unknown relationship type: ${type}`);
|
|
4408
4450
|
}
|
|
4409
|
-
function loadSchemaFromTomlString(tomlString) {
|
|
4451
|
+
function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
4452
|
+
const strict = options.strict !== false;
|
|
4410
4453
|
const parsed = (0, import_smol_toml.parse)(tomlString);
|
|
4411
4454
|
const models = parsed.models;
|
|
4412
4455
|
if (!models || typeof models !== "object") {
|
|
@@ -4414,10 +4457,20 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
4414
4457
|
}
|
|
4415
4458
|
const schemas = [];
|
|
4416
4459
|
for (const [modelName, modelDef] of Object.entries(models)) {
|
|
4460
|
+
checkUnknownKeys(
|
|
4461
|
+
modelDef,
|
|
4462
|
+
KNOWN_MODEL_KEYS,
|
|
4463
|
+
`[models.${modelName}]`,
|
|
4464
|
+
strict
|
|
4465
|
+
);
|
|
4417
4466
|
const fields = {};
|
|
4418
4467
|
if (modelDef.fields) {
|
|
4419
4468
|
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
|
|
4420
|
-
fields[fieldName] = parseFieldOptions(
|
|
4469
|
+
fields[fieldName] = parseFieldOptions(
|
|
4470
|
+
fieldDef,
|
|
4471
|
+
`[models.${modelName}.fields.${fieldName}]`,
|
|
4472
|
+
strict
|
|
4473
|
+
);
|
|
4421
4474
|
}
|
|
4422
4475
|
}
|
|
4423
4476
|
let relationships;
|
|
@@ -4426,24 +4479,36 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
4426
4479
|
for (const [relName, relDef] of Object.entries(
|
|
4427
4480
|
modelDef.relationships
|
|
4428
4481
|
)) {
|
|
4429
|
-
relationships[relName] = parseRelationship(
|
|
4482
|
+
relationships[relName] = parseRelationship(
|
|
4483
|
+
relDef,
|
|
4484
|
+
`[models.${modelName}.relationships.${relName}]`,
|
|
4485
|
+
strict
|
|
4486
|
+
);
|
|
4430
4487
|
}
|
|
4431
4488
|
}
|
|
4432
4489
|
let uniqueConstraints;
|
|
4433
4490
|
if (modelDef.unique_constraints) {
|
|
4434
4491
|
uniqueConstraints = [];
|
|
4435
4492
|
for (const raw of modelDef.unique_constraints) {
|
|
4493
|
+
checkUnknownKeys(
|
|
4494
|
+
raw,
|
|
4495
|
+
KNOWN_UNIQUE_CONSTRAINT_KEYS,
|
|
4496
|
+
`[[models.${modelName}.unique_constraints]]`,
|
|
4497
|
+
strict
|
|
4498
|
+
);
|
|
4436
4499
|
uniqueConstraints.push({
|
|
4437
4500
|
name: raw.name,
|
|
4438
4501
|
fields: [...raw.fields]
|
|
4439
4502
|
});
|
|
4440
4503
|
}
|
|
4441
4504
|
}
|
|
4505
|
+
const className = typeof modelDef.class_name === "string" ? modelDef.class_name : void 0;
|
|
4442
4506
|
schemas.push(
|
|
4443
4507
|
defineModelSchema({
|
|
4444
4508
|
name: modelName,
|
|
4445
4509
|
fields,
|
|
4446
4510
|
options: {
|
|
4511
|
+
className,
|
|
4447
4512
|
uniqueConstraints,
|
|
4448
4513
|
relationships
|
|
4449
4514
|
}
|