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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/codegen.cjs
CHANGED
|
@@ -1181,7 +1181,7 @@ var SchemaExtractor = class {
|
|
|
1181
1181
|
// package.json
|
|
1182
1182
|
var package_default = {
|
|
1183
1183
|
name: "js-bao",
|
|
1184
|
-
version: "0.4.
|
|
1184
|
+
version: "0.4.2",
|
|
1185
1185
|
description: "A library providing data modeling capabilities which support live updates and queries.",
|
|
1186
1186
|
types: "dist/index.d.ts",
|
|
1187
1187
|
type: "module",
|
|
@@ -1232,10 +1232,12 @@ var package_default = {
|
|
|
1232
1232
|
},
|
|
1233
1233
|
bin: {
|
|
1234
1234
|
"js-bao-codegen": "./dist/codegen.cjs",
|
|
1235
|
-
"jsbao-codegen": "./dist/codegen.cjs"
|
|
1235
|
+
"jsbao-codegen": "./dist/codegen.cjs",
|
|
1236
|
+
"js-bao-codegen-v2": "./dist/codegen-v2.cjs",
|
|
1237
|
+
"jsbao-codegen-v2": "./dist/codegen-v2.cjs"
|
|
1236
1238
|
},
|
|
1237
1239
|
scripts: {
|
|
1238
|
-
build: "pnpm build:cli && pnpm codegen && rm -f dist/index.* dist/node.* dist/browser.* dist/cloudflare.* dist/cloudflare-do.* dist/client.* && pnpm build:main",
|
|
1240
|
+
build: "pnpm build:cli && pnpm build:cli-v2 && pnpm codegen && rm -f dist/index.* dist/node.* dist/browser.* dist/cloudflare.* dist/cloudflare-do.* dist/client.* && pnpm build:main",
|
|
1239
1241
|
"build:main": "pnpm build:browser && pnpm build:node && pnpm build:universal && pnpm build:cloudflare && pnpm build:cloudflare-do && pnpm build:client",
|
|
1240
1242
|
"build:browser": "tsup src/browser.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1241
1243
|
"build:node": "tsup src/node.ts --format esm,cjs --dts --platform node --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --no-splitting",
|
|
@@ -1244,6 +1246,7 @@ var package_default = {
|
|
|
1244
1246
|
"build:cloudflare-do": "tsup src/cloudflare-do.ts --format esm,cjs --dts --platform neutral --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1245
1247
|
"build:client": "tsup src/client.ts --format esm,cjs --dts --platform browser --external better-sqlite3 --external sql.js --external async-mutex --external ulid --external yjs --external fs --no-splitting",
|
|
1246
1248
|
"build:cli": "tsup src/cli/codegen.ts --format cjs --dts --platform node --target node18 --external commander --external typescript --external fs --external path --external util --no-splitting",
|
|
1249
|
+
"build:cli-v2": "tsup src/cli/codegen-v2.ts --format cjs --dts --platform node --target node18 --external commander --external typescript --external fs --external path --external util --no-splitting",
|
|
1247
1250
|
dev: 'pnpm build:cli && concurrently "pnpm codegen:watch" "pnpm dev:main"',
|
|
1248
1251
|
"dev:main": 'concurrently "pnpm build:browser:dev --watch" "pnpm build:node:dev --watch" "pnpm build:universal:dev --watch"',
|
|
1249
1252
|
"dev:simple": "pnpm build:cli && pnpm codegen && pnpm build:main --watch",
|
|
@@ -1297,13 +1300,7 @@ var package_default = {
|
|
|
1297
1300
|
"workers"
|
|
1298
1301
|
],
|
|
1299
1302
|
author: "Primitive LLC",
|
|
1300
|
-
license: "UNLICENSED"
|
|
1301
|
-
pnpm: {
|
|
1302
|
-
onlyBuiltDependencies: [
|
|
1303
|
-
"better-sqlite3",
|
|
1304
|
-
"esbuild"
|
|
1305
|
-
]
|
|
1306
|
-
}
|
|
1303
|
+
license: "UNLICENSED"
|
|
1307
1304
|
};
|
|
1308
1305
|
|
|
1309
1306
|
// src/cli/codegen.ts
|
package/dist/index.cjs
CHANGED
|
@@ -1858,6 +1858,9 @@ var init_BaseModel = __esm({
|
|
|
1858
1858
|
static connectedDocuments = /* @__PURE__ */ new Map();
|
|
1859
1859
|
static documentYMaps = /* @__PURE__ */ new Map();
|
|
1860
1860
|
// Maps docId to YMap for that document
|
|
1861
|
+
// Tracks nested-Y.Map stringset fields we've already attached observers to,
|
|
1862
|
+
// so attachStringSetObserversToRecord() is idempotent across re-entry.
|
|
1863
|
+
static _observedStringSetMaps = /* @__PURE__ */ new WeakSet();
|
|
1861
1864
|
// Copy-on-write state management
|
|
1862
1865
|
_localChanges = null;
|
|
1863
1866
|
_isDirty = false;
|
|
@@ -2377,16 +2380,15 @@ var init_BaseModel = __esm({
|
|
|
2377
2380
|
this._isDirty = true;
|
|
2378
2381
|
}
|
|
2379
2382
|
getStringSetCurrentValues(fieldName) {
|
|
2380
|
-
|
|
2381
|
-
if (yjsData && typeof yjsData === "object") {
|
|
2382
|
-
return Object.keys(yjsData);
|
|
2383
|
-
}
|
|
2384
|
-
return [];
|
|
2383
|
+
return this.getStringSetFromYjs(fieldName);
|
|
2385
2384
|
}
|
|
2386
2385
|
getStringSetFromYjs(fieldName) {
|
|
2387
2386
|
const yjsData = this.getFromYjs(fieldName);
|
|
2387
|
+
if (yjsData instanceof Y2.Map) {
|
|
2388
|
+
return Array.from(yjsData.keys());
|
|
2389
|
+
}
|
|
2388
2390
|
if (yjsData && typeof yjsData === "object") {
|
|
2389
|
-
return Object.keys(yjsData);
|
|
2391
|
+
return Object.keys(yjsData).filter((k) => Boolean(yjsData[k]));
|
|
2390
2392
|
}
|
|
2391
2393
|
return [];
|
|
2392
2394
|
}
|
|
@@ -2997,7 +2999,7 @@ var init_BaseModel = __esm({
|
|
|
2997
2999
|
Logger.debug(
|
|
2998
3000
|
`[_diffWithYjsData] No unsaved changes, returning empty diff`
|
|
2999
3001
|
);
|
|
3000
|
-
return { added: {}, modified: {}, removed: [] };
|
|
3002
|
+
return { added: {}, modified: {}, removed: [], stringSetChanges: {} };
|
|
3001
3003
|
}
|
|
3002
3004
|
const modelConstructor = this.constructor;
|
|
3003
3005
|
const schema = modelConstructor.getSchema();
|
|
@@ -3028,6 +3030,7 @@ var init_BaseModel = __esm({
|
|
|
3028
3030
|
const added = {};
|
|
3029
3031
|
const modified = {};
|
|
3030
3032
|
const removed = [];
|
|
3033
|
+
const stringSetChanges = {};
|
|
3031
3034
|
if (!recordYMap) {
|
|
3032
3035
|
Logger.debug(
|
|
3033
3036
|
`[_diffWithYjsData] No existing recordYMap, treating all local changes as 'added'`
|
|
@@ -3036,11 +3039,12 @@ var init_BaseModel = __esm({
|
|
|
3036
3039
|
for (const [key, value] of Object.entries(this._localChanges)) {
|
|
3037
3040
|
Logger.debug(`[_diffWithYjsData] Adding field '${key}': ${value}`);
|
|
3038
3041
|
if (value && value.type === "stringset") {
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
+
if (value.additions.size > 0 || value.removals.size > 0) {
|
|
3043
|
+
stringSetChanges[key] = {
|
|
3044
|
+
additions: value.additions,
|
|
3045
|
+
removals: value.removals
|
|
3046
|
+
};
|
|
3042
3047
|
}
|
|
3043
|
-
added[key] = stringSetData;
|
|
3044
3048
|
} else {
|
|
3045
3049
|
added[key] = value;
|
|
3046
3050
|
}
|
|
@@ -3049,9 +3053,10 @@ var init_BaseModel = __esm({
|
|
|
3049
3053
|
Logger.debug(`[_diffWithYjsData] Final diff for new record:`, {
|
|
3050
3054
|
added,
|
|
3051
3055
|
modified: {},
|
|
3052
|
-
removed: []
|
|
3056
|
+
removed: [],
|
|
3057
|
+
stringSetChanges
|
|
3053
3058
|
});
|
|
3054
|
-
return { added, modified, removed: [] };
|
|
3059
|
+
return { added, modified, removed: [], stringSetChanges };
|
|
3055
3060
|
}
|
|
3056
3061
|
Logger.debug(
|
|
3057
3062
|
`[_diffWithYjsData] Existing record found, comparing local changes with Y.js data`
|
|
@@ -3068,21 +3073,11 @@ var init_BaseModel = __esm({
|
|
|
3068
3073
|
`[_diffWithYjsData] Processing field '${key}' with local value: ${localValue}`
|
|
3069
3074
|
);
|
|
3070
3075
|
if (localValue && localValue.type === "stringset") {
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
newStringSetData[addition] = true;
|
|
3077
|
-
}
|
|
3078
|
-
for (const removal of localValue.removals) {
|
|
3079
|
-
delete newStringSetData[removal];
|
|
3080
|
-
}
|
|
3081
|
-
const yjsValue = recordYMap.get(key);
|
|
3082
|
-
if (yjsValue === void 0) {
|
|
3083
|
-
added[key] = newStringSetData;
|
|
3084
|
-
} else if (!this._deepEqual(yjsValue, newStringSetData)) {
|
|
3085
|
-
modified[key] = newStringSetData;
|
|
3076
|
+
if (localValue.additions.size > 0 || localValue.removals.size > 0) {
|
|
3077
|
+
stringSetChanges[key] = {
|
|
3078
|
+
additions: localValue.additions,
|
|
3079
|
+
removals: localValue.removals
|
|
3080
|
+
};
|
|
3086
3081
|
}
|
|
3087
3082
|
} else {
|
|
3088
3083
|
const yjsValue = recordYMap.get(key);
|
|
@@ -3108,14 +3103,43 @@ var init_BaseModel = __esm({
|
|
|
3108
3103
|
Logger.debug(`[_diffWithYjsData] Final diff result:`, {
|
|
3109
3104
|
added,
|
|
3110
3105
|
modified,
|
|
3111
|
-
removed
|
|
3106
|
+
removed,
|
|
3107
|
+
stringSetChanges
|
|
3112
3108
|
});
|
|
3113
3109
|
Logger.verbose(`[${modelConstructor.name}] Diff for ${this.id}:`, {
|
|
3114
3110
|
added: Object.keys(added),
|
|
3115
3111
|
modified: Object.keys(modified),
|
|
3116
|
-
removed
|
|
3112
|
+
removed,
|
|
3113
|
+
stringSetChanges: Object.keys(stringSetChanges)
|
|
3117
3114
|
});
|
|
3118
|
-
return { added, modified, removed };
|
|
3115
|
+
return { added, modified, removed, stringSetChanges };
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
3119
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
3120
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
3121
|
+
*/
|
|
3122
|
+
applyStringSetChangeToYMap(recordYMap, fieldName, change) {
|
|
3123
|
+
let nested = recordYMap.get(fieldName);
|
|
3124
|
+
if (!(nested instanceof Y2.Map)) {
|
|
3125
|
+
const migrated = new Y2.Map();
|
|
3126
|
+
if (nested && typeof nested === "object") {
|
|
3127
|
+
for (const [member, marker] of Object.entries(
|
|
3128
|
+
nested
|
|
3129
|
+
)) {
|
|
3130
|
+
if (Boolean(marker)) migrated.set(member, true);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
recordYMap.set(fieldName, migrated);
|
|
3134
|
+
nested = migrated;
|
|
3135
|
+
}
|
|
3136
|
+
const target = nested;
|
|
3137
|
+
for (const member of change.additions) {
|
|
3138
|
+
target.set(member, true);
|
|
3139
|
+
}
|
|
3140
|
+
for (const member of change.removals) {
|
|
3141
|
+
target.delete(member);
|
|
3142
|
+
}
|
|
3119
3143
|
}
|
|
3120
3144
|
/**
|
|
3121
3145
|
* Deep equality check for comparing field values
|
|
@@ -3354,7 +3378,7 @@ var init_BaseModel = __esm({
|
|
|
3354
3378
|
Logger.debug(`[${modelName}.save] About to calculate diff`);
|
|
3355
3379
|
const diff = this._diffWithYjsData();
|
|
3356
3380
|
Logger.debug(`[${modelName}.save] Diff result:`, diff);
|
|
3357
|
-
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0;
|
|
3381
|
+
const hasChanges = Object.keys(diff.added).length > 0 || Object.keys(diff.modified).length > 0 || diff.removed.length > 0 || Object.keys(diff.stringSetChanges).length > 0;
|
|
3358
3382
|
Logger.debug(`[${modelName}.save] hasChanges: ${hasChanges}`);
|
|
3359
3383
|
if (!hasChanges && isUpdate) {
|
|
3360
3384
|
Logger.verbose(
|
|
@@ -3472,6 +3496,16 @@ var init_BaseModel = __esm({
|
|
|
3472
3496
|
Logger.debug(`[${modelName}.save] Removing field '${key}'`);
|
|
3473
3497
|
recordYMap.delete(key);
|
|
3474
3498
|
}
|
|
3499
|
+
for (const [fieldName, change] of Object.entries(diff.stringSetChanges)) {
|
|
3500
|
+
Logger.debug(
|
|
3501
|
+
`[${modelName}.save] Applying stringset change to field '${fieldName}':`,
|
|
3502
|
+
{
|
|
3503
|
+
additions: Array.from(change.additions),
|
|
3504
|
+
removals: Array.from(change.removals)
|
|
3505
|
+
}
|
|
3506
|
+
);
|
|
3507
|
+
this.applyStringSetChangeToYMap(recordYMap, fieldName, change);
|
|
3508
|
+
}
|
|
3475
3509
|
Logger.debug(
|
|
3476
3510
|
`[${modelName}.save] After applying changes, recordYMap contents:`,
|
|
3477
3511
|
Object.fromEntries(recordYMap.entries())
|
|
@@ -4656,6 +4690,39 @@ var init_BaseModel = __esm({
|
|
|
4656
4690
|
_BaseModel.prototype.setValue = originalSetValue;
|
|
4657
4691
|
}
|
|
4658
4692
|
}
|
|
4693
|
+
/**
|
|
4694
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
4695
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
4696
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
4697
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
4698
|
+
*/
|
|
4699
|
+
static observeStringSetMapOnce(nestedMap) {
|
|
4700
|
+
if (_BaseModel._observedStringSetMaps.has(nestedMap)) return;
|
|
4701
|
+
_BaseModel._observedStringSetMaps.add(nestedMap);
|
|
4702
|
+
const modelConstructor = this;
|
|
4703
|
+
nestedMap.observe(() => {
|
|
4704
|
+
Logger.verbose(
|
|
4705
|
+
`[${modelConstructor.name}] Nested stringset Y.Map changed; notifying listeners`
|
|
4706
|
+
);
|
|
4707
|
+
modelConstructor.notifyListeners();
|
|
4708
|
+
});
|
|
4709
|
+
}
|
|
4710
|
+
/**
|
|
4711
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
4712
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
4713
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
4714
|
+
* an observer too.
|
|
4715
|
+
*/
|
|
4716
|
+
static attachStringSetObserversToRecord(recordYMap, schema) {
|
|
4717
|
+
if (!schema?.fields) return;
|
|
4718
|
+
for (const [fieldKey, fieldOptions] of schema.fields) {
|
|
4719
|
+
if (fieldOptions?.type !== "stringset") continue;
|
|
4720
|
+
const value = recordYMap.get(fieldKey);
|
|
4721
|
+
if (value instanceof Y2.Map) {
|
|
4722
|
+
this.observeStringSetMapOnce(value);
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4659
4726
|
/**
|
|
4660
4727
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
4661
4728
|
*/
|
|
@@ -4672,11 +4739,13 @@ var init_BaseModel = __esm({
|
|
|
4672
4739
|
Logger.verbose(
|
|
4673
4740
|
`[${modelName}] Setting up nested YMap observer for record ${recordId}`
|
|
4674
4741
|
);
|
|
4742
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4675
4743
|
recordYMap.observe(async (event) => {
|
|
4676
4744
|
Logger.verbose(
|
|
4677
4745
|
`[${modelName}] Nested YMap change detected for record ${recordId}:`,
|
|
4678
4746
|
event
|
|
4679
4747
|
);
|
|
4748
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4680
4749
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
4681
4750
|
if (!currentDbInstance) {
|
|
4682
4751
|
Logger.error(
|
|
@@ -4750,11 +4819,13 @@ var init_BaseModel = __esm({
|
|
|
4750
4819
|
Logger.verbose(
|
|
4751
4820
|
`[${modelName}] Setting up nested YMap observer for record ${recordId} in document ${docId}`
|
|
4752
4821
|
);
|
|
4822
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4753
4823
|
recordYMap.observe(async (event) => {
|
|
4754
4824
|
Logger.verbose(
|
|
4755
4825
|
`[${modelName}] Nested YMap change detected for record ${recordId} in document ${docId}:`,
|
|
4756
4826
|
event
|
|
4757
4827
|
);
|
|
4828
|
+
modelConstructor.attachStringSetObserversToRecord(recordYMap, schema);
|
|
4758
4829
|
const currentDbInstance = _BaseModel.dbInstance;
|
|
4759
4830
|
if (!currentDbInstance) {
|
|
4760
4831
|
Logger.error(
|
|
@@ -6901,6 +6972,7 @@ function defineModelSchema(input) {
|
|
|
6901
6972
|
const { name, fields } = input;
|
|
6902
6973
|
const options = {
|
|
6903
6974
|
name,
|
|
6975
|
+
className: input.options?.className,
|
|
6904
6976
|
uniqueConstraints: input.options?.uniqueConstraints ? [...input.options.uniqueConstraints] : void 0,
|
|
6905
6977
|
relationships: input.options?.relationships
|
|
6906
6978
|
};
|
|
@@ -7000,18 +7072,13 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
7000
7072
|
function attachSchemaToClass(modelClass, schema) {
|
|
7001
7073
|
modelClass.schema = schema;
|
|
7002
7074
|
modelClass.modelName = schema.options.name;
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
value: function() {
|
|
7006
|
-
return schema.buildRuntimeShape(modelClass);
|
|
7007
|
-
},
|
|
7008
|
-
writable: false
|
|
7009
|
-
});
|
|
7010
|
-
} else {
|
|
7011
|
-
modelClass.getSchema = function() {
|
|
7075
|
+
Object.defineProperty(modelClass, "getSchema", {
|
|
7076
|
+
value: function() {
|
|
7012
7077
|
return schema.buildRuntimeShape(modelClass);
|
|
7013
|
-
}
|
|
7014
|
-
|
|
7078
|
+
},
|
|
7079
|
+
writable: false,
|
|
7080
|
+
configurable: true
|
|
7081
|
+
});
|
|
7015
7082
|
const runtimeShape = schema.buildRuntimeShape(modelClass);
|
|
7016
7083
|
BaseModel.attachFieldAccessors(modelClass, runtimeShape.fields);
|
|
7017
7084
|
return runtimeShape;
|
|
@@ -7088,12 +7155,52 @@ var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
|
7088
7155
|
"id",
|
|
7089
7156
|
"stringset"
|
|
7090
7157
|
]);
|
|
7091
|
-
|
|
7158
|
+
var KNOWN_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
7159
|
+
"type",
|
|
7160
|
+
"indexed",
|
|
7161
|
+
"unique",
|
|
7162
|
+
"required",
|
|
7163
|
+
"auto_assign",
|
|
7164
|
+
"max_length",
|
|
7165
|
+
"max_count",
|
|
7166
|
+
"default"
|
|
7167
|
+
]);
|
|
7168
|
+
var KNOWN_MODEL_KEYS = /* @__PURE__ */ new Set([
|
|
7169
|
+
"fields",
|
|
7170
|
+
"relationships",
|
|
7171
|
+
"unique_constraints",
|
|
7172
|
+
"class_name"
|
|
7173
|
+
]);
|
|
7174
|
+
var KNOWN_RELATIONSHIP_KEYS = /* @__PURE__ */ new Set([
|
|
7175
|
+
"type",
|
|
7176
|
+
"model",
|
|
7177
|
+
"related_id_field",
|
|
7178
|
+
"join_model",
|
|
7179
|
+
"join_model_local_field",
|
|
7180
|
+
"join_model_related_field",
|
|
7181
|
+
"order_by_field",
|
|
7182
|
+
"order_direction",
|
|
7183
|
+
"join_model_order_by_field",
|
|
7184
|
+
"join_model_order_direction"
|
|
7185
|
+
]);
|
|
7186
|
+
var KNOWN_UNIQUE_CONSTRAINT_KEYS = /* @__PURE__ */ new Set(["name", "fields"]);
|
|
7187
|
+
function checkUnknownKeys(raw, known, context, strict) {
|
|
7188
|
+
if (!strict) return;
|
|
7189
|
+
for (const key of Object.keys(raw)) {
|
|
7190
|
+
if (!known.has(key)) {
|
|
7191
|
+
throw new Error(
|
|
7192
|
+
`${context}: unknown key "${key}". Allowed: ${[...known].join(", ")}`
|
|
7193
|
+
);
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
function parseFieldOptions(raw, context, strict) {
|
|
7092
7198
|
if (!raw.type || !VALID_FIELD_TYPES.has(raw.type)) {
|
|
7093
7199
|
throw new Error(
|
|
7094
7200
|
`Invalid field type "${raw.type}". Must be one of: ${[...VALID_FIELD_TYPES].join(", ")}`
|
|
7095
7201
|
);
|
|
7096
7202
|
}
|
|
7203
|
+
checkUnknownKeys(raw, KNOWN_FIELD_KEYS, context, strict);
|
|
7097
7204
|
const opts = { type: raw.type };
|
|
7098
7205
|
if (raw.indexed === true) opts.indexed = true;
|
|
7099
7206
|
if (raw.unique === true) opts.unique = true;
|
|
@@ -7109,8 +7216,9 @@ function requireField(raw, field, context) {
|
|
|
7109
7216
|
throw new Error(`Relationship ${context}: missing required field "${field}"`);
|
|
7110
7217
|
}
|
|
7111
7218
|
}
|
|
7112
|
-
function parseRelationship(raw) {
|
|
7219
|
+
function parseRelationship(raw, context, strict) {
|
|
7113
7220
|
const type = raw.type;
|
|
7221
|
+
checkUnknownKeys(raw, KNOWN_RELATIONSHIP_KEYS, context, strict);
|
|
7114
7222
|
if (type === "refersTo") {
|
|
7115
7223
|
requireField(raw, "model", "refersTo");
|
|
7116
7224
|
requireField(raw, "related_id_field", "refersTo");
|
|
@@ -7152,7 +7260,8 @@ function parseRelationship(raw) {
|
|
|
7152
7260
|
}
|
|
7153
7261
|
throw new Error(`Unknown relationship type: ${type}`);
|
|
7154
7262
|
}
|
|
7155
|
-
function loadSchemaFromTomlString(tomlString) {
|
|
7263
|
+
function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
7264
|
+
const strict = options.strict !== false;
|
|
7156
7265
|
const parsed = (0, import_smol_toml.parse)(tomlString);
|
|
7157
7266
|
const models = parsed.models;
|
|
7158
7267
|
if (!models || typeof models !== "object") {
|
|
@@ -7160,10 +7269,20 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
7160
7269
|
}
|
|
7161
7270
|
const schemas = [];
|
|
7162
7271
|
for (const [modelName, modelDef] of Object.entries(models)) {
|
|
7272
|
+
checkUnknownKeys(
|
|
7273
|
+
modelDef,
|
|
7274
|
+
KNOWN_MODEL_KEYS,
|
|
7275
|
+
`[models.${modelName}]`,
|
|
7276
|
+
strict
|
|
7277
|
+
);
|
|
7163
7278
|
const fields = {};
|
|
7164
7279
|
if (modelDef.fields) {
|
|
7165
7280
|
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
|
|
7166
|
-
fields[fieldName] = parseFieldOptions(
|
|
7281
|
+
fields[fieldName] = parseFieldOptions(
|
|
7282
|
+
fieldDef,
|
|
7283
|
+
`[models.${modelName}.fields.${fieldName}]`,
|
|
7284
|
+
strict
|
|
7285
|
+
);
|
|
7167
7286
|
}
|
|
7168
7287
|
}
|
|
7169
7288
|
let relationships;
|
|
@@ -7172,24 +7291,36 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
7172
7291
|
for (const [relName, relDef] of Object.entries(
|
|
7173
7292
|
modelDef.relationships
|
|
7174
7293
|
)) {
|
|
7175
|
-
relationships[relName] = parseRelationship(
|
|
7294
|
+
relationships[relName] = parseRelationship(
|
|
7295
|
+
relDef,
|
|
7296
|
+
`[models.${modelName}.relationships.${relName}]`,
|
|
7297
|
+
strict
|
|
7298
|
+
);
|
|
7176
7299
|
}
|
|
7177
7300
|
}
|
|
7178
7301
|
let uniqueConstraints;
|
|
7179
7302
|
if (modelDef.unique_constraints) {
|
|
7180
7303
|
uniqueConstraints = [];
|
|
7181
7304
|
for (const raw of modelDef.unique_constraints) {
|
|
7305
|
+
checkUnknownKeys(
|
|
7306
|
+
raw,
|
|
7307
|
+
KNOWN_UNIQUE_CONSTRAINT_KEYS,
|
|
7308
|
+
`[[models.${modelName}.unique_constraints]]`,
|
|
7309
|
+
strict
|
|
7310
|
+
);
|
|
7182
7311
|
uniqueConstraints.push({
|
|
7183
7312
|
name: raw.name,
|
|
7184
7313
|
fields: [...raw.fields]
|
|
7185
7314
|
});
|
|
7186
7315
|
}
|
|
7187
7316
|
}
|
|
7317
|
+
const className = typeof modelDef.class_name === "string" ? modelDef.class_name : void 0;
|
|
7188
7318
|
schemas.push(
|
|
7189
7319
|
defineModelSchema({
|
|
7190
7320
|
name: modelName,
|
|
7191
7321
|
fields,
|
|
7192
7322
|
options: {
|
|
7323
|
+
className,
|
|
7193
7324
|
uniqueConstraints,
|
|
7194
7325
|
relationships
|
|
7195
7326
|
}
|
|
@@ -7198,10 +7329,10 @@ function loadSchemaFromTomlString(tomlString) {
|
|
|
7198
7329
|
}
|
|
7199
7330
|
return schemas;
|
|
7200
7331
|
}
|
|
7201
|
-
async function loadSchemaFromToml(filePath) {
|
|
7332
|
+
async function loadSchemaFromToml(filePath, options = {}) {
|
|
7202
7333
|
const { readFile } = await import("fs/promises");
|
|
7203
7334
|
const content = await readFile(filePath, "utf-8");
|
|
7204
|
-
return loadSchemaFromTomlString(content);
|
|
7335
|
+
return loadSchemaFromTomlString(content, options);
|
|
7205
7336
|
}
|
|
7206
7337
|
|
|
7207
7338
|
// src/utils/yDocSchema.ts
|
package/dist/index.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
|
}
|
|
@@ -481,6 +488,7 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
481
488
|
permissionHint: DocumentPermissionHint;
|
|
482
489
|
}>;
|
|
483
490
|
protected static documentYMaps: Map<string, Y.Map<any>>;
|
|
491
|
+
private static _observedStringSetMaps;
|
|
484
492
|
private _localChanges;
|
|
485
493
|
private _isDirty;
|
|
486
494
|
private _isLoadingFromYjs;
|
|
@@ -555,7 +563,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
555
563
|
added: Record<string, any>;
|
|
556
564
|
modified: Record<string, any>;
|
|
557
565
|
removed: string[];
|
|
566
|
+
stringSetChanges: Record<string, {
|
|
567
|
+
additions: Set<string>;
|
|
568
|
+
removals: Set<string>;
|
|
569
|
+
}>;
|
|
558
570
|
};
|
|
571
|
+
/**
|
|
572
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
573
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
574
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
575
|
+
*/
|
|
576
|
+
private applyStringSetChangeToYMap;
|
|
559
577
|
/**
|
|
560
578
|
* Deep equality check for comparing field values
|
|
561
579
|
*/
|
|
@@ -657,6 +675,20 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
657
675
|
* Execute a callback with automatic transaction handling for all modified models
|
|
658
676
|
*/
|
|
659
677
|
static withTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
678
|
+
/**
|
|
679
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
680
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
681
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
682
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
683
|
+
*/
|
|
684
|
+
protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
|
|
685
|
+
/**
|
|
686
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
687
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
688
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
689
|
+
* an observer too.
|
|
690
|
+
*/
|
|
691
|
+
protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
|
|
660
692
|
/**
|
|
661
693
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
662
694
|
*/
|
|
@@ -847,15 +879,27 @@ declare function syncInferredMeta(yDoc: Y.Doc, modelName: string, recordData: Re
|
|
|
847
879
|
* - Compound unique constraints are [[models.*.unique_constraints]]
|
|
848
880
|
*/
|
|
849
881
|
|
|
882
|
+
interface LoadSchemaOptions {
|
|
883
|
+
/**
|
|
884
|
+
* When true (default), throw on unknown keys at the model, field,
|
|
885
|
+
* relationship, and unique-constraint level. When false, unknown
|
|
886
|
+
* keys are silently ignored (legacy behavior).
|
|
887
|
+
*/
|
|
888
|
+
strict?: boolean;
|
|
889
|
+
}
|
|
850
890
|
/**
|
|
851
891
|
* Parse a TOML string and return an array of DefinedModelSchema objects.
|
|
892
|
+
*
|
|
893
|
+
* By default operates in strict mode: unknown keys at the model, field,
|
|
894
|
+
* relationship, or unique-constraint level cause an error. Pass
|
|
895
|
+
* `{ strict: false }` to silently ignore unknown keys (legacy behavior).
|
|
852
896
|
*/
|
|
853
|
-
declare function loadSchemaFromTomlString(tomlString: string): DefinedModelSchema[];
|
|
897
|
+
declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSchemaOptions): DefinedModelSchema[];
|
|
854
898
|
/**
|
|
855
899
|
* Read a TOML file from disk and return an array of DefinedModelSchema objects.
|
|
856
900
|
* Only available in Node.js environments.
|
|
857
901
|
*/
|
|
858
|
-
declare function loadSchemaFromToml(filePath: string): Promise<DefinedModelSchema[]>;
|
|
902
|
+
declare function loadSchemaFromToml(filePath: string, options?: LoadSchemaOptions): Promise<DefinedModelSchema[]>;
|
|
859
903
|
|
|
860
904
|
/**
|
|
861
905
|
* YDoc Schema Discovery
|
package/dist/index.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
|
}
|
|
@@ -481,6 +488,7 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
481
488
|
permissionHint: DocumentPermissionHint;
|
|
482
489
|
}>;
|
|
483
490
|
protected static documentYMaps: Map<string, Y.Map<any>>;
|
|
491
|
+
private static _observedStringSetMaps;
|
|
484
492
|
private _localChanges;
|
|
485
493
|
private _isDirty;
|
|
486
494
|
private _isLoadingFromYjs;
|
|
@@ -555,7 +563,17 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
555
563
|
added: Record<string, any>;
|
|
556
564
|
modified: Record<string, any>;
|
|
557
565
|
removed: string[];
|
|
566
|
+
stringSetChanges: Record<string, {
|
|
567
|
+
additions: Set<string>;
|
|
568
|
+
removals: Set<string>;
|
|
569
|
+
}>;
|
|
558
570
|
};
|
|
571
|
+
/**
|
|
572
|
+
* Apply a stringset change set to the parent record's Y.Map by mutating a
|
|
573
|
+
* nested Y.Map keyed by member. Migrates from a legacy plain-object value
|
|
574
|
+
* if the field hasn't been touched since the wire-format change in #561.
|
|
575
|
+
*/
|
|
576
|
+
private applyStringSetChangeToYMap;
|
|
559
577
|
/**
|
|
560
578
|
* Deep equality check for comparing field values
|
|
561
579
|
*/
|
|
@@ -657,6 +675,20 @@ declare class BaseModel implements StringSetChangeTracker {
|
|
|
657
675
|
* Execute a callback with automatic transaction handling for all modified models
|
|
658
676
|
*/
|
|
659
677
|
static withTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
678
|
+
/**
|
|
679
|
+
* Attach a one-shot observer to a nested-Y.Map stringset field. Calls
|
|
680
|
+
* notifyListeners() when the nested map's keys change (i.e. when a remote
|
|
681
|
+
* member arrives via Y.applyUpdate, or a local per-member set/delete).
|
|
682
|
+
* Idempotent — re-calling on the same Y.Map is a no-op.
|
|
683
|
+
*/
|
|
684
|
+
protected static observeStringSetMapOnce(nestedMap: Y.Map<any>): void;
|
|
685
|
+
/**
|
|
686
|
+
* Walk the schema's stringset fields on `recordYMap` and observe any nested
|
|
687
|
+
* Y.Map values. Called from observer setup and from the parent observer body
|
|
688
|
+
* so newly-arrived stringset Y.Maps (local migration or remote create) get
|
|
689
|
+
* an observer too.
|
|
690
|
+
*/
|
|
691
|
+
protected static attachStringSetObserversToRecord(recordYMap: Y.Map<any>, schema: any): void;
|
|
660
692
|
/**
|
|
661
693
|
* Sets up deep observation on a nested YMap to sync field-level changes to the database
|
|
662
694
|
*/
|
|
@@ -847,15 +879,27 @@ declare function syncInferredMeta(yDoc: Y.Doc, modelName: string, recordData: Re
|
|
|
847
879
|
* - Compound unique constraints are [[models.*.unique_constraints]]
|
|
848
880
|
*/
|
|
849
881
|
|
|
882
|
+
interface LoadSchemaOptions {
|
|
883
|
+
/**
|
|
884
|
+
* When true (default), throw on unknown keys at the model, field,
|
|
885
|
+
* relationship, and unique-constraint level. When false, unknown
|
|
886
|
+
* keys are silently ignored (legacy behavior).
|
|
887
|
+
*/
|
|
888
|
+
strict?: boolean;
|
|
889
|
+
}
|
|
850
890
|
/**
|
|
851
891
|
* Parse a TOML string and return an array of DefinedModelSchema objects.
|
|
892
|
+
*
|
|
893
|
+
* By default operates in strict mode: unknown keys at the model, field,
|
|
894
|
+
* relationship, or unique-constraint level cause an error. Pass
|
|
895
|
+
* `{ strict: false }` to silently ignore unknown keys (legacy behavior).
|
|
852
896
|
*/
|
|
853
|
-
declare function loadSchemaFromTomlString(tomlString: string): DefinedModelSchema[];
|
|
897
|
+
declare function loadSchemaFromTomlString(tomlString: string, options?: LoadSchemaOptions): DefinedModelSchema[];
|
|
854
898
|
/**
|
|
855
899
|
* Read a TOML file from disk and return an array of DefinedModelSchema objects.
|
|
856
900
|
* Only available in Node.js environments.
|
|
857
901
|
*/
|
|
858
|
-
declare function loadSchemaFromToml(filePath: string): Promise<DefinedModelSchema[]>;
|
|
902
|
+
declare function loadSchemaFromToml(filePath: string, options?: LoadSchemaOptions): Promise<DefinedModelSchema[]>;
|
|
859
903
|
|
|
860
904
|
/**
|
|
861
905
|
* YDoc Schema Discovery
|