js-bao 0.5.1 → 0.6.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/README.md +128 -30
- package/dist/browser.cjs +4759 -719
- package/dist/browser.d.cts +2290 -50
- package/dist/browser.d.ts +2290 -50
- package/dist/browser.js +4766 -722
- package/dist/client.cjs +383 -6
- package/dist/client.d.cts +810 -12
- package/dist/client.d.ts +810 -12
- package/dist/client.js +378 -4
- package/dist/cloudflare-do.cjs +1307 -219
- package/dist/cloudflare-do.d.cts +1195 -13
- package/dist/cloudflare-do.d.ts +1195 -13
- package/dist/cloudflare-do.js +1303 -217
- package/dist/cloudflare.cjs +619 -23
- package/dist/cloudflare.d.cts +1026 -13
- package/dist/cloudflare.d.ts +1026 -13
- package/dist/cloudflare.js +614 -21
- package/dist/codegen-v2.cjs +393 -19
- package/dist/codegen.cjs +2 -1
- package/dist/index.cjs +4830 -679
- package/dist/index.d.cts +2343 -52
- package/dist/index.d.ts +2343 -52
- package/dist/index.js +4746 -650
- package/dist/node.cjs +5016 -724
- package/dist/node.d.cts +2458 -58
- package/dist/node.d.ts +2458 -58
- package/dist/node.js +4906 -676
- package/package.json +2 -1
package/dist/cloudflare.js
CHANGED
|
@@ -72,8 +72,203 @@ var init_yjsValues = __esm({
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
// src/models/
|
|
75
|
+
// src/models/writeLifecycle.ts
|
|
76
76
|
import * as Y2 from "yjs";
|
|
77
|
+
import { ulid } from "ulid";
|
|
78
|
+
function assertNever(x) {
|
|
79
|
+
throw new Error(`Unhandled field type: ${String(x)}`);
|
|
80
|
+
}
|
|
81
|
+
function fieldWriteKind(type) {
|
|
82
|
+
switch (type) {
|
|
83
|
+
case "string":
|
|
84
|
+
case "number":
|
|
85
|
+
case "boolean":
|
|
86
|
+
case "date":
|
|
87
|
+
case "id":
|
|
88
|
+
return "scalar";
|
|
89
|
+
case "stringset":
|
|
90
|
+
return "stringset";
|
|
91
|
+
default:
|
|
92
|
+
return assertNever(type);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function isKnownFieldType(type) {
|
|
96
|
+
return KNOWN_FIELD_TYPES.has(type);
|
|
97
|
+
}
|
|
98
|
+
function ensureStringSetYMap(rm, fieldName) {
|
|
99
|
+
const nested = rm.get(fieldName);
|
|
100
|
+
if (nested instanceof Y2.Map) return nested;
|
|
101
|
+
const fresh = new Y2.Map();
|
|
102
|
+
if (nested && typeof nested === "object") {
|
|
103
|
+
for (const [member, marker] of Object.entries(
|
|
104
|
+
nested
|
|
105
|
+
)) {
|
|
106
|
+
if (Boolean(marker)) fresh.set(member, true);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
rm.set(fieldName, fresh);
|
|
110
|
+
return fresh;
|
|
111
|
+
}
|
|
112
|
+
function applyStringSetDelta(target, delta) {
|
|
113
|
+
if (delta.clear) {
|
|
114
|
+
for (const member of Array.from(target.keys())) target.delete(member);
|
|
115
|
+
}
|
|
116
|
+
if (delta.remove) {
|
|
117
|
+
for (const member of delta.remove) target.delete(String(member));
|
|
118
|
+
}
|
|
119
|
+
if (delta.add) {
|
|
120
|
+
for (const member of delta.add) {
|
|
121
|
+
const key = String(member);
|
|
122
|
+
if (target.get(key) !== true) target.set(key, true);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function replaceStringSet(rm, fieldName, members) {
|
|
127
|
+
const target = ensureStringSetYMap(rm, fieldName);
|
|
128
|
+
const desired = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const member of members) desired.add(String(member));
|
|
130
|
+
for (const existing of Array.from(target.keys())) {
|
|
131
|
+
if (!desired.has(existing)) target.delete(existing);
|
|
132
|
+
}
|
|
133
|
+
for (const member of desired) {
|
|
134
|
+
if (target.get(member) !== true) target.set(member, true);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function applyStringSetChange(recordYMap, fieldName, change) {
|
|
138
|
+
const target = ensureStringSetYMap(recordYMap, fieldName);
|
|
139
|
+
applyStringSetDelta(target, {
|
|
140
|
+
add: change.additions,
|
|
141
|
+
remove: change.removals
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function parseStringSetOp(value) {
|
|
145
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Y2.AbstractType) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const keys = Object.keys(value);
|
|
149
|
+
const opKeys = keys.filter((k) => k.startsWith("$"));
|
|
150
|
+
if (opKeys.length === 0) return null;
|
|
151
|
+
const known = /* @__PURE__ */ new Set(["$add", "$remove", "$clear"]);
|
|
152
|
+
const unknown = keys.filter((k) => !known.has(k));
|
|
153
|
+
if (unknown.length > 0) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Invalid stringset op: unexpected key(s) [${unknown.join(", ")}]. Only $add, $remove and $clear are supported.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const obj = value;
|
|
159
|
+
const toMembers = (raw, opName) => {
|
|
160
|
+
if (raw === void 0) return [];
|
|
161
|
+
if (!Array.isArray(raw)) {
|
|
162
|
+
throw new Error(`Invalid stringset op: ${opName} must be an array.`);
|
|
163
|
+
}
|
|
164
|
+
return raw.map((m) => String(m));
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
add: toMembers(obj.$add, "$add"),
|
|
168
|
+
remove: toMembers(obj.$remove, "$remove"),
|
|
169
|
+
clear: obj.$clear === true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function writeField(rm, declaredType, key, value, modelName) {
|
|
173
|
+
const kind = declaredType && isKnownFieldType(declaredType) ? fieldWriteKind(declaredType) : "scalar";
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
if (kind !== "stringset") {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Cannot write array value to field "${key}" on model "${modelName}": its declared type could not be resolved to a collection (stringset). Collection fields require schema metadata in the document (_meta_${modelName}); the model must be saved by a client at least once before a workflow writes one of its collection fields.`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
replaceStringSet(rm, key, value);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const op = parseStringSetOp(value);
|
|
184
|
+
if (op) {
|
|
185
|
+
if (kind !== "stringset") {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Cannot apply a stringset op ({ $add/$remove/$clear }) to field "${key}" on model "${modelName}": its declared type is ${declaredType ? `"${declaredType}"` : "unresolved"}, not a collection (stringset). Op-objects are only valid for stringset / refersToMany fields with schema metadata (_meta_${modelName}).`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
applyStringSetDelta(ensureStringSetYMap(rm, key), op);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (kind === "stringset") {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Field "${key}" on model "${modelName}" is a stringset and requires an array (replace) or { $add/$remove/$clear } (delta) value; received ${value === null ? "null" : typeof value}.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
rm.set(key, value);
|
|
199
|
+
}
|
|
200
|
+
function registerFunctionDefaultGenerator(name, fn) {
|
|
201
|
+
FUNCTION_DEFAULT_GENERATORS.set(name, fn);
|
|
202
|
+
}
|
|
203
|
+
function decodeFunctionDefault(name) {
|
|
204
|
+
return FUNCTION_DEFAULT_GENERATORS.get(name);
|
|
205
|
+
}
|
|
206
|
+
function resolveDefaultValue(rawDefault, opts = {}) {
|
|
207
|
+
if (typeof rawDefault === "function") return rawDefault();
|
|
208
|
+
if (typeof rawDefault === "string" && rawDefault.startsWith("$")) {
|
|
209
|
+
if (rawDefault.startsWith("$$")) return rawDefault.slice(1);
|
|
210
|
+
const name = rawDefault.slice(1);
|
|
211
|
+
const gen = opts.decode?.(name) ?? decodeFunctionDefault(name);
|
|
212
|
+
if (!gen) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Unknown function default "${rawDefault}" for field "${opts.fieldName ?? "?"}"${opts.modelName ? ` on model "${opts.modelName}"` : ""}: no generator is registered for "${name}". Register it via registerFunctionDefaultGenerator("${name}", fn) so the server can populate the field the same way the client does.`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return gen();
|
|
218
|
+
}
|
|
219
|
+
return rawDefault;
|
|
220
|
+
}
|
|
221
|
+
function applyDefaultsFromSchema(fields, data, opts = {}) {
|
|
222
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
223
|
+
if (fieldName === "id") continue;
|
|
224
|
+
if (!field || field.default === void 0) continue;
|
|
225
|
+
if (field.default === UNKNOWN_FUNCTION_DEFAULT) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== void 0) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
data[fieldName] = resolveDefaultValue(field.default, {
|
|
232
|
+
fieldName,
|
|
233
|
+
modelName: opts.modelName,
|
|
234
|
+
decode: opts.decodeFunctionDefault
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return data;
|
|
238
|
+
}
|
|
239
|
+
function applyAutoStampsFromSchema(fields, data, opts) {
|
|
240
|
+
const { isNew, now } = opts;
|
|
241
|
+
for (const [fieldName, field] of Object.entries(fields)) {
|
|
242
|
+
const stamp = field?.autoStamp;
|
|
243
|
+
if (!stamp) continue;
|
|
244
|
+
const explicit = Object.prototype.hasOwnProperty.call(data, fieldName) && data[fieldName] !== null && data[fieldName] !== void 0;
|
|
245
|
+
if (explicit) continue;
|
|
246
|
+
if (stamp === "create" && !isNew) continue;
|
|
247
|
+
data[fieldName] = now;
|
|
248
|
+
}
|
|
249
|
+
return data;
|
|
250
|
+
}
|
|
251
|
+
var KNOWN_FIELD_TYPE_MAP, KNOWN_FIELD_TYPES, FUNCTION_DEFAULT_GENERATORS, UNKNOWN_FUNCTION_DEFAULT;
|
|
252
|
+
var init_writeLifecycle = __esm({
|
|
253
|
+
"src/models/writeLifecycle.ts"() {
|
|
254
|
+
"use strict";
|
|
255
|
+
KNOWN_FIELD_TYPE_MAP = {
|
|
256
|
+
string: true,
|
|
257
|
+
number: true,
|
|
258
|
+
boolean: true,
|
|
259
|
+
date: true,
|
|
260
|
+
id: true,
|
|
261
|
+
stringset: true
|
|
262
|
+
};
|
|
263
|
+
KNOWN_FIELD_TYPES = new Set(Object.keys(KNOWN_FIELD_TYPE_MAP));
|
|
264
|
+
FUNCTION_DEFAULT_GENERATORS = /* @__PURE__ */ new Map();
|
|
265
|
+
registerFunctionDefaultGenerator("generate_ulid", () => ulid());
|
|
266
|
+
UNKNOWN_FUNCTION_DEFAULT = "$unknown_function";
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// src/models/metaSync.ts
|
|
271
|
+
import * as Y3 from "yjs";
|
|
77
272
|
function inferFieldType(value) {
|
|
78
273
|
if (isStringSetYMap(value)) return "stringset";
|
|
79
274
|
switch (typeof value) {
|
|
@@ -94,7 +289,10 @@ function encodeDefault(value) {
|
|
|
94
289
|
if (value === void 0 || value === null) return void 0;
|
|
95
290
|
if (typeof value === "function") {
|
|
96
291
|
const name = KNOWN_FUNCTION_DEFAULTS.get(value);
|
|
97
|
-
return name ? `$${name}` :
|
|
292
|
+
return name ? `$${name}` : UNKNOWN_FUNCTION_DEFAULT;
|
|
293
|
+
}
|
|
294
|
+
if (typeof value === "string" && value.startsWith("$")) {
|
|
295
|
+
return `$${value}`;
|
|
98
296
|
}
|
|
99
297
|
return value;
|
|
100
298
|
}
|
|
@@ -120,7 +318,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
120
318
|
if (compoundConstraints.length > 0) {
|
|
121
319
|
let constraints = meta.get("_constraints");
|
|
122
320
|
if (!constraints) {
|
|
123
|
-
constraints = new
|
|
321
|
+
constraints = new Y3.Map();
|
|
124
322
|
meta.set("_constraints", constraints);
|
|
125
323
|
}
|
|
126
324
|
for (const constraint of compoundConstraints) {
|
|
@@ -131,7 +329,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
131
329
|
if (relationships && Object.keys(relationships).length > 0) {
|
|
132
330
|
let rels = meta.get("_relationships");
|
|
133
331
|
if (!rels) {
|
|
134
|
-
rels = new
|
|
332
|
+
rels = new Y3.Map();
|
|
135
333
|
meta.set("_relationships", rels);
|
|
136
334
|
}
|
|
137
335
|
for (const [relName, relConfig] of Object.entries(relationships)) {
|
|
@@ -143,7 +341,7 @@ function syncModelMeta(yDoc, modelName, schema) {
|
|
|
143
341
|
function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
144
342
|
let fieldMeta = metaMap.get(fieldName);
|
|
145
343
|
if (!fieldMeta) {
|
|
146
|
-
fieldMeta = new
|
|
344
|
+
fieldMeta = new Y3.Map();
|
|
147
345
|
metaMap.set(fieldName, fieldMeta);
|
|
148
346
|
}
|
|
149
347
|
setIfChanged(fieldMeta, "type", fieldOpts.type);
|
|
@@ -160,7 +358,7 @@ function syncFieldMeta(metaMap, fieldName, fieldOpts) {
|
|
|
160
358
|
function syncConstraintMeta(constraintsMap, constraint) {
|
|
161
359
|
let cMeta = constraintsMap.get(constraint.name);
|
|
162
360
|
if (!cMeta) {
|
|
163
|
-
cMeta = new
|
|
361
|
+
cMeta = new Y3.Map();
|
|
164
362
|
constraintsMap.set(constraint.name, cMeta);
|
|
165
363
|
}
|
|
166
364
|
setIfChanged(cMeta, "type", "unique");
|
|
@@ -170,7 +368,7 @@ function syncConstraintMeta(constraintsMap, constraint) {
|
|
|
170
368
|
function syncRelationshipMeta(relsMap, relName, relConfig) {
|
|
171
369
|
let relMeta = relsMap.get(relName);
|
|
172
370
|
if (!relMeta) {
|
|
173
|
-
relMeta = new
|
|
371
|
+
relMeta = new Y3.Map();
|
|
174
372
|
relsMap.set(relName, relMeta);
|
|
175
373
|
}
|
|
176
374
|
for (const [key, value] of Object.entries(relConfig)) {
|
|
@@ -185,7 +383,7 @@ function syncInferredMeta(yDoc, modelName, recordData) {
|
|
|
185
383
|
if (fieldName.startsWith("_")) continue;
|
|
186
384
|
let fieldMeta = meta.get(fieldName);
|
|
187
385
|
if (!fieldMeta) {
|
|
188
|
-
fieldMeta = new
|
|
386
|
+
fieldMeta = new Y3.Map();
|
|
189
387
|
meta.set(fieldName, fieldMeta);
|
|
190
388
|
}
|
|
191
389
|
if (!fieldMeta.has("type")) {
|
|
@@ -206,16 +404,349 @@ var init_metaSync = __esm({
|
|
|
206
404
|
"src/models/metaSync.ts"() {
|
|
207
405
|
"use strict";
|
|
208
406
|
init_yjsValues();
|
|
407
|
+
init_writeLifecycle();
|
|
209
408
|
KNOWN_FUNCTION_DEFAULTS = /* @__PURE__ */ new WeakMap();
|
|
210
409
|
_syncedCache = /* @__PURE__ */ new WeakMap();
|
|
211
410
|
}
|
|
212
411
|
});
|
|
213
412
|
|
|
413
|
+
// src/utils/uniqueIndex.ts
|
|
414
|
+
import * as Y4 from "yjs";
|
|
415
|
+
function buildUniqueKey(recordData, fields) {
|
|
416
|
+
const keyParts = [];
|
|
417
|
+
for (const field of fields) {
|
|
418
|
+
const value = recordData instanceof Y4.Map ? recordData.get(field) : recordData[field];
|
|
419
|
+
if (value === null || value === void 0) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
keyParts.push(String(value));
|
|
423
|
+
}
|
|
424
|
+
return keyParts.join("|");
|
|
425
|
+
}
|
|
426
|
+
function uniqueIndexMapName(modelName, constraintName) {
|
|
427
|
+
return `_uniqueIdx_${modelName}_${constraintName}`;
|
|
428
|
+
}
|
|
429
|
+
function fieldUniqueConstraintName(modelName, fieldName) {
|
|
430
|
+
return `${modelName}_${fieldName}_unique`;
|
|
431
|
+
}
|
|
432
|
+
var init_uniqueIndex = __esm({
|
|
433
|
+
"src/utils/uniqueIndex.ts"() {
|
|
434
|
+
"use strict";
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// src/utils/largeDocuments.ts
|
|
439
|
+
import * as Y5 from "yjs";
|
|
440
|
+
var init_largeDocuments = __esm({
|
|
441
|
+
"src/utils/largeDocuments.ts"() {
|
|
442
|
+
"use strict";
|
|
443
|
+
init_yjsValues();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// src/models/OrmContext.ts
|
|
448
|
+
var OrmContext, defaultOrmContext, liveOrmContexts;
|
|
449
|
+
var init_OrmContext = __esm({
|
|
450
|
+
"src/models/OrmContext.ts"() {
|
|
451
|
+
"use strict";
|
|
452
|
+
OrmContext = class _OrmContext {
|
|
453
|
+
/** Declared before the field that reads it: static initialization is ordered. */
|
|
454
|
+
static instanceCounter = 0;
|
|
455
|
+
/**
|
|
456
|
+
* Which instance this is, for diagnostics. A process holding several clients
|
|
457
|
+
* needs a way to say WHICH one a warning is about, so every context takes the
|
|
458
|
+
* next number as it is created and keeps it for its lifetime.
|
|
459
|
+
*/
|
|
460
|
+
instanceId = ++_OrmContext.instanceCounter;
|
|
461
|
+
/** The engine this instance's models read and write through. */
|
|
462
|
+
dbEngine = null;
|
|
463
|
+
connectedDocuments = /* @__PURE__ */ new Map();
|
|
464
|
+
/** `docId_modelName` → the Y.Map that document holds for that model. */
|
|
465
|
+
documentYMaps = /* @__PURE__ */ new Map();
|
|
466
|
+
/** `subscribe()` callbacks, by model name. */
|
|
467
|
+
listenersMap = /* @__PURE__ */ new Map();
|
|
468
|
+
// Default-document resolution
|
|
469
|
+
modelNameToDefaultDocId = /* @__PURE__ */ new Map();
|
|
470
|
+
globalDefaultDocId = void 0;
|
|
471
|
+
defaultDocChangedListeners = /* @__PURE__ */ new Set();
|
|
472
|
+
modelDocMappingChangedListeners = /* @__PURE__ */ new Set();
|
|
473
|
+
/** Format-2 ("large document", #2816) record stores by document id. */
|
|
474
|
+
format2Documents = /* @__PURE__ */ new Map();
|
|
475
|
+
/** Format-2 epoch moves in flight (#2816). */
|
|
476
|
+
format2Transitions = /* @__PURE__ */ new Map();
|
|
477
|
+
/**
|
|
478
|
+
* Nested-Y.Map stringset fields this context has already observed. Per
|
|
479
|
+
* context, not global: a Y.Doc reconnected to a fresh instance has to be
|
|
480
|
+
* observed again by that instance, and a global guard would silently
|
|
481
|
+
* suppress it.
|
|
482
|
+
*/
|
|
483
|
+
observedStringSetMaps = /* @__PURE__ */ new WeakSet();
|
|
484
|
+
/**
|
|
485
|
+
* The registry this instance's schema lookups use. `null` on the standing
|
|
486
|
+
* default context, where callers fall back to the global singleton.
|
|
487
|
+
*/
|
|
488
|
+
modelRegistry = null;
|
|
489
|
+
/** Model classes routed to this context. */
|
|
490
|
+
boundModels = /* @__PURE__ */ new Set();
|
|
491
|
+
/** True once {@link destroy} has run; bound classes keep pointing here. */
|
|
492
|
+
destroyed = false;
|
|
493
|
+
/** Yjs `unobserve` thunks by owning document id. */
|
|
494
|
+
observerDisposers = /* @__PURE__ */ new Map();
|
|
495
|
+
/** Teardown the owner (the document manager) registers for `destroy()`. */
|
|
496
|
+
teardownHooks = /* @__PURE__ */ new Set();
|
|
497
|
+
static NO_DOCUMENT_KEY = "__no_document__";
|
|
498
|
+
/**
|
|
499
|
+
* Record how to remove an observer this context installed. Every `observe()`
|
|
500
|
+
* in the ORM pairs with one of these: without it a torn-down instance's
|
|
501
|
+
* callbacks keep firing on a Y.Doc that outlives it.
|
|
502
|
+
*/
|
|
503
|
+
registerObserverDisposer(docId, dispose) {
|
|
504
|
+
const key = docId ?? _OrmContext.NO_DOCUMENT_KEY;
|
|
505
|
+
const list = this.observerDisposers.get(key);
|
|
506
|
+
if (list) list.push(dispose);
|
|
507
|
+
else this.observerDisposers.set(key, [dispose]);
|
|
508
|
+
}
|
|
509
|
+
/** Remove every observer this context installed for `docId`. */
|
|
510
|
+
disposeObserversForDocument(docId) {
|
|
511
|
+
const list = this.observerDisposers.get(docId);
|
|
512
|
+
if (!list) return;
|
|
513
|
+
this.observerDisposers.delete(docId);
|
|
514
|
+
for (const dispose of list) {
|
|
515
|
+
try {
|
|
516
|
+
dispose();
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
disposeAllObservers() {
|
|
522
|
+
const buckets = Array.from(this.observerDisposers.values());
|
|
523
|
+
this.observerDisposers.clear();
|
|
524
|
+
for (const list of buckets) {
|
|
525
|
+
for (const dispose of list) {
|
|
526
|
+
try {
|
|
527
|
+
dispose();
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/** Run `hook` as part of `destroy()` (the format-2 binding registers here). */
|
|
534
|
+
onDestroy(hook) {
|
|
535
|
+
this.teardownHooks.add(hook);
|
|
536
|
+
}
|
|
537
|
+
/** The record store for `docId`, or `null` when the document is format 1. */
|
|
538
|
+
getFormat2Store(docId) {
|
|
539
|
+
if (!docId) return null;
|
|
540
|
+
return this.format2Documents.get(docId) ?? null;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* The epoch move a write to `docId` has to wait out (#2816, behavior 10), or
|
|
544
|
+
* `null` when none is in flight.
|
|
545
|
+
*/
|
|
546
|
+
format2WritesHeld(docId) {
|
|
547
|
+
if (!docId) return null;
|
|
548
|
+
return this.format2Transitions.get(docId) ?? null;
|
|
549
|
+
}
|
|
550
|
+
/** Run an epoch move with `docId`'s local writes held back (#2816). */
|
|
551
|
+
async holdFormat2Writes(docId, run) {
|
|
552
|
+
const inFlight = this.format2Transitions.get(docId);
|
|
553
|
+
if (inFlight) await inFlight.catch(() => {
|
|
554
|
+
});
|
|
555
|
+
let release;
|
|
556
|
+
const held = new Promise((resolve) => {
|
|
557
|
+
release = resolve;
|
|
558
|
+
});
|
|
559
|
+
this.format2Transitions.set(docId, held);
|
|
560
|
+
try {
|
|
561
|
+
return await run();
|
|
562
|
+
} finally {
|
|
563
|
+
if (this.format2Transitions.get(docId) === held) {
|
|
564
|
+
this.format2Transitions.delete(docId);
|
|
565
|
+
}
|
|
566
|
+
release();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/** Notify `subscribe()` callbacks registered on this context for `modelName`. */
|
|
570
|
+
notifyListeners(modelName, log, onError) {
|
|
571
|
+
const listeners = this.listenersMap.get(modelName);
|
|
572
|
+
if (!listeners) return;
|
|
573
|
+
log?.(`Notifying ${listeners.size} listeners for ${modelName}`);
|
|
574
|
+
listeners.forEach((callback) => {
|
|
575
|
+
try {
|
|
576
|
+
callback();
|
|
577
|
+
} catch (e) {
|
|
578
|
+
onError?.(e);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// ---- Default-document resolution (the `BaseModel` statics delegate here) ----
|
|
583
|
+
setModelDefaultDocumentId(modelName, docId) {
|
|
584
|
+
const previous = this.modelNameToDefaultDocId.get(modelName);
|
|
585
|
+
this.modelNameToDefaultDocId.set(modelName, docId);
|
|
586
|
+
this.announceModelDocMapping(modelName, previous, docId);
|
|
587
|
+
}
|
|
588
|
+
removeModelDefaultDocumentId(modelName) {
|
|
589
|
+
const previous = this.modelNameToDefaultDocId.get(modelName);
|
|
590
|
+
this.modelNameToDefaultDocId.delete(modelName);
|
|
591
|
+
this.announceModelDocMapping(modelName, previous, void 0);
|
|
592
|
+
}
|
|
593
|
+
clearModelDefaultDocumentIds() {
|
|
594
|
+
const entries = Array.from(this.modelNameToDefaultDocId.entries());
|
|
595
|
+
this.modelNameToDefaultDocId.clear();
|
|
596
|
+
for (const [modelName, previous] of entries) {
|
|
597
|
+
this.announceModelDocMapping(modelName, previous, void 0);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
setGlobalDefaultDocumentId(docId) {
|
|
601
|
+
const previous = this.globalDefaultDocId;
|
|
602
|
+
this.globalDefaultDocId = docId;
|
|
603
|
+
this.announceDefaultDoc(previous, docId);
|
|
604
|
+
}
|
|
605
|
+
clearGlobalDefaultDocumentId() {
|
|
606
|
+
const previous = this.globalDefaultDocId;
|
|
607
|
+
this.globalDefaultDocId = void 0;
|
|
608
|
+
this.announceDefaultDoc(previous, void 0);
|
|
609
|
+
}
|
|
610
|
+
getModelDefaultDocumentMapping() {
|
|
611
|
+
return Object.fromEntries(this.modelNameToDefaultDocId.entries());
|
|
612
|
+
}
|
|
613
|
+
getDocumentIdForModel(modelName) {
|
|
614
|
+
return this.modelNameToDefaultDocId.get(modelName);
|
|
615
|
+
}
|
|
616
|
+
getGlobalDefaultDocumentId() {
|
|
617
|
+
return this.globalDefaultDocId;
|
|
618
|
+
}
|
|
619
|
+
onDefaultDocChanged(listener) {
|
|
620
|
+
this.defaultDocChangedListeners.add(listener);
|
|
621
|
+
return () => this.defaultDocChangedListeners.delete(listener);
|
|
622
|
+
}
|
|
623
|
+
onModelDocMappingChanged(listener) {
|
|
624
|
+
this.modelDocMappingChangedListeners.add(listener);
|
|
625
|
+
return () => this.modelDocMappingChangedListeners.delete(listener);
|
|
626
|
+
}
|
|
627
|
+
/** Drop the model→document defaults that point at a disconnected document. */
|
|
628
|
+
clearMappingsForDocId(docId) {
|
|
629
|
+
const toRemove = [];
|
|
630
|
+
for (const [modelName, mappedDocId] of this.modelNameToDefaultDocId) {
|
|
631
|
+
if (mappedDocId === docId) toRemove.push(modelName);
|
|
632
|
+
}
|
|
633
|
+
for (const modelName of toRemove) {
|
|
634
|
+
this.removeModelDefaultDocumentId(modelName);
|
|
635
|
+
}
|
|
636
|
+
if (this.globalDefaultDocId === docId) {
|
|
637
|
+
this.clearGlobalDefaultDocumentId();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
announceDefaultDoc(previous, current) {
|
|
641
|
+
for (const listener of this.defaultDocChangedListeners) {
|
|
642
|
+
try {
|
|
643
|
+
listener({ previous, current });
|
|
644
|
+
} catch {
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
announceModelDocMapping(modelName, previous, current) {
|
|
649
|
+
for (const listener of this.modelDocMappingChangedListeners) {
|
|
650
|
+
try {
|
|
651
|
+
listener({ modelName, previous, current });
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Tear this instance down: observers off, engine destroyed, state cleared.
|
|
658
|
+
*
|
|
659
|
+
* Deliberately does NOT unbind its model classes. They keep resolving this
|
|
660
|
+
* context, whose engine is now null, so an operation through them throws the
|
|
661
|
+
* ordinary not-initialized error instead of silently landing on whatever
|
|
662
|
+
* other instance happens to be live (#3039, behavior 7). Only a later
|
|
663
|
+
* `initJsBao` may rebind a class whose context is destroyed.
|
|
664
|
+
*/
|
|
665
|
+
async destroy() {
|
|
666
|
+
if (this.destroyed) return;
|
|
667
|
+
this.destroyed = true;
|
|
668
|
+
this.disposeAllObservers();
|
|
669
|
+
const hooks = Array.from(this.teardownHooks);
|
|
670
|
+
this.teardownHooks.clear();
|
|
671
|
+
for (const hook of hooks) {
|
|
672
|
+
try {
|
|
673
|
+
await hook();
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const engine = this.dbEngine;
|
|
678
|
+
this.dbEngine = null;
|
|
679
|
+
if (engine) {
|
|
680
|
+
try {
|
|
681
|
+
await engine.destroy();
|
|
682
|
+
} catch {
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
this.connectedDocuments.clear();
|
|
686
|
+
this.documentYMaps.clear();
|
|
687
|
+
this.listenersMap.clear();
|
|
688
|
+
this.modelNameToDefaultDocId.clear();
|
|
689
|
+
this.globalDefaultDocId = void 0;
|
|
690
|
+
this.defaultDocChangedListeners.clear();
|
|
691
|
+
this.modelDocMappingChangedListeners.clear();
|
|
692
|
+
this.format2Documents.clear();
|
|
693
|
+
this.format2Transitions.clear();
|
|
694
|
+
this.observedStringSetMaps = /* @__PURE__ */ new WeakSet();
|
|
695
|
+
liveOrmContexts.delete(this);
|
|
696
|
+
if (defaultOrmContext === this) {
|
|
697
|
+
defaultOrmContext = new _OrmContext();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
defaultOrmContext = new OrmContext();
|
|
702
|
+
liveOrmContexts = /* @__PURE__ */ new Set();
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// src/models/format2QueryProjection.ts
|
|
707
|
+
var FORMAT2_QUERY_PROJECTION_DDL;
|
|
708
|
+
var init_format2QueryProjection = __esm({
|
|
709
|
+
"src/models/format2QueryProjection.ts"() {
|
|
710
|
+
"use strict";
|
|
711
|
+
FORMAT2_QUERY_PROJECTION_DDL = `
|
|
712
|
+
CREATE TABLE IF NOT EXISTS _query_projection (
|
|
713
|
+
doc_id TEXT NOT NULL,
|
|
714
|
+
model TEXT NOT NULL,
|
|
715
|
+
PRIMARY KEY (doc_id, model)
|
|
716
|
+
)`.trim();
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// src/models/format2Observer.ts
|
|
721
|
+
import * as Y6 from "yjs";
|
|
722
|
+
var init_format2Observer = __esm({
|
|
723
|
+
"src/models/format2Observer.ts"() {
|
|
724
|
+
"use strict";
|
|
725
|
+
init_largeDocuments();
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// src/models/format2ClientWrite.ts
|
|
730
|
+
var init_format2ClientWrite = __esm({
|
|
731
|
+
"src/models/format2ClientWrite.ts"() {
|
|
732
|
+
"use strict";
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// src/models/format2OfflineWindow.ts
|
|
737
|
+
var DAY_MS;
|
|
738
|
+
var init_format2OfflineWindow = __esm({
|
|
739
|
+
"src/models/format2OfflineWindow.ts"() {
|
|
740
|
+
"use strict";
|
|
741
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
|
|
214
745
|
// src/models/BaseModel.ts
|
|
215
|
-
import * as
|
|
216
|
-
import { ulid } from "ulid";
|
|
746
|
+
import * as Y7 from "yjs";
|
|
747
|
+
import { ulid as ulid2 } from "ulid";
|
|
217
748
|
function generateULID() {
|
|
218
|
-
return
|
|
749
|
+
return ulid2();
|
|
219
750
|
}
|
|
220
751
|
var Logger;
|
|
221
752
|
var init_BaseModel = __esm({
|
|
@@ -227,7 +758,15 @@ var init_BaseModel = __esm({
|
|
|
227
758
|
init_CursorManager();
|
|
228
759
|
init_sql();
|
|
229
760
|
init_metaSync();
|
|
761
|
+
init_writeLifecycle();
|
|
230
762
|
init_yjsValues();
|
|
763
|
+
init_uniqueIndex();
|
|
764
|
+
init_largeDocuments();
|
|
765
|
+
init_OrmContext();
|
|
766
|
+
init_format2QueryProjection();
|
|
767
|
+
init_format2Observer();
|
|
768
|
+
init_format2ClientWrite();
|
|
769
|
+
init_format2OfflineWindow();
|
|
231
770
|
Logger = class {
|
|
232
771
|
static _logLevel = 1 /* ERROR */;
|
|
233
772
|
static _logCallback = null;
|
|
@@ -314,6 +853,7 @@ var init_ModelRegistry = __esm({
|
|
|
314
853
|
"use strict";
|
|
315
854
|
init_BaseModel();
|
|
316
855
|
init_relationshipManager();
|
|
856
|
+
init_format2QueryProjection();
|
|
317
857
|
}
|
|
318
858
|
});
|
|
319
859
|
|
|
@@ -352,6 +892,32 @@ var DatabaseEngine = class {
|
|
|
352
892
|
getTableName(_modelName) {
|
|
353
893
|
throw new Error("Method not implemented.");
|
|
354
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* A SYNCHRONOUS SQL executor over this engine's database, used by the
|
|
897
|
+
* large-document (format 2) record store. That store commits a merged
|
|
898
|
+
* record row and its pending-operation entry as one transaction — the
|
|
899
|
+
* barrier a `save()` resolves behind — which the async `query()` API cannot
|
|
900
|
+
* express. Engines whose underlying SQLite is synchronous (better-sqlite3,
|
|
901
|
+
* SQL.js) provide it; anything else leaves it undefined and cannot host a
|
|
902
|
+
* large document.
|
|
903
|
+
*/
|
|
904
|
+
createFormat2Sql;
|
|
905
|
+
/**
|
|
906
|
+
* Whether what {@link createFormat2Sql} writes survives this process.
|
|
907
|
+
*
|
|
908
|
+
* On a large document the merged record view IS the document: the Y.Doc
|
|
909
|
+
* holds one epoch's overlay, and everything else — including a record a
|
|
910
|
+
* `save()` has already resolved, and the pending write that still owes it to
|
|
911
|
+
* the server — lives only in this store. An engine whose database is in
|
|
912
|
+
* memory therefore cannot host one: a reload does not cost a cache, it costs
|
|
913
|
+
* the document and the writes nobody has acknowledged yet.
|
|
914
|
+
*
|
|
915
|
+
* The platform is asked separately about its storage quota; this answers the
|
|
916
|
+
* half only the engine knows. An engine that does not implement it is taken
|
|
917
|
+
* at the platform's word, which is what keeps hosts with storage of their
|
|
918
|
+
* own — a worker handed a file store, a test — working unchanged.
|
|
919
|
+
*/
|
|
920
|
+
format2StorageIsDurable;
|
|
355
921
|
// Transaction support
|
|
356
922
|
async withTransaction(_callback) {
|
|
357
923
|
throw new Error("Method not implemented.");
|
|
@@ -1195,7 +1761,7 @@ function connectDoDb(options) {
|
|
|
1195
1761
|
|
|
1196
1762
|
// src/utils/yDocSchema.ts
|
|
1197
1763
|
init_yjsValues();
|
|
1198
|
-
import * as
|
|
1764
|
+
import * as Y8 from "yjs";
|
|
1199
1765
|
function discoverSchema(yDoc) {
|
|
1200
1766
|
const models = {};
|
|
1201
1767
|
const metaNames = /* @__PURE__ */ new Set();
|
|
@@ -1217,6 +1783,12 @@ function discoverSchema(yDoc) {
|
|
|
1217
1783
|
}
|
|
1218
1784
|
return { models };
|
|
1219
1785
|
}
|
|
1786
|
+
function discoverDeclaredModel(yDoc, modelName) {
|
|
1787
|
+
const key = `_meta_${modelName}`;
|
|
1788
|
+
if (!yDoc.share.has(key)) return void 0;
|
|
1789
|
+
const map = materializeMap(yDoc, key);
|
|
1790
|
+
return map ? readModelMeta(map) : void 0;
|
|
1791
|
+
}
|
|
1220
1792
|
function discoverModelNames(yDoc) {
|
|
1221
1793
|
const names = [];
|
|
1222
1794
|
for (const key of yDoc.share.keys()) {
|
|
@@ -1229,7 +1801,7 @@ function discoverModelNames(yDoc) {
|
|
|
1229
1801
|
function materializeMap(yDoc, key) {
|
|
1230
1802
|
try {
|
|
1231
1803
|
const map = yDoc.getMap(key);
|
|
1232
|
-
return map instanceof
|
|
1804
|
+
return map instanceof Y8.Map ? map : null;
|
|
1233
1805
|
} catch {
|
|
1234
1806
|
return null;
|
|
1235
1807
|
}
|
|
@@ -1239,11 +1811,11 @@ function readModelMeta(metaMap) {
|
|
|
1239
1811
|
let constraints;
|
|
1240
1812
|
let relationships;
|
|
1241
1813
|
for (const [key, value] of metaMap.entries()) {
|
|
1242
|
-
if (key === "_constraints" && value instanceof
|
|
1814
|
+
if (key === "_constraints" && value instanceof Y8.Map) {
|
|
1243
1815
|
constraints = readConstraints(value);
|
|
1244
|
-
} else if (key === "_relationships" && value instanceof
|
|
1816
|
+
} else if (key === "_relationships" && value instanceof Y8.Map) {
|
|
1245
1817
|
relationships = readRelationships(value);
|
|
1246
|
-
} else if (value instanceof
|
|
1818
|
+
} else if (value instanceof Y8.Map) {
|
|
1247
1819
|
fields[key] = readFieldMeta(value);
|
|
1248
1820
|
}
|
|
1249
1821
|
}
|
|
@@ -1278,7 +1850,7 @@ function inferModelFromData(dataMap) {
|
|
|
1278
1850
|
const fields = {};
|
|
1279
1851
|
let sampled = 0;
|
|
1280
1852
|
for (const [_recordId, recordValue] of dataMap.entries()) {
|
|
1281
|
-
if (!(recordValue instanceof
|
|
1853
|
+
if (!(recordValue instanceof Y8.Map)) continue;
|
|
1282
1854
|
if (++sampled > 5) break;
|
|
1283
1855
|
for (const [fieldName, value] of recordValue.entries()) {
|
|
1284
1856
|
if (fieldName.startsWith("_")) continue;
|
|
@@ -1306,7 +1878,7 @@ function inferTypeFromValue(value) {
|
|
|
1306
1878
|
function readConstraints(constraintsMap) {
|
|
1307
1879
|
const out = {};
|
|
1308
1880
|
for (const [name, value] of constraintsMap.entries()) {
|
|
1309
|
-
if (!(value instanceof
|
|
1881
|
+
if (!(value instanceof Y8.Map)) continue;
|
|
1310
1882
|
let fields = [];
|
|
1311
1883
|
const rawFields = value.get("fields");
|
|
1312
1884
|
if (typeof rawFields === "string") {
|
|
@@ -1325,7 +1897,7 @@ function readConstraints(constraintsMap) {
|
|
|
1325
1897
|
function readRelationships(relsMap) {
|
|
1326
1898
|
const out = {};
|
|
1327
1899
|
for (const [name, value] of relsMap.entries()) {
|
|
1328
|
-
if (!(value instanceof
|
|
1900
|
+
if (!(value instanceof Y8.Map)) continue;
|
|
1329
1901
|
const rel = {};
|
|
1330
1902
|
for (const [k, v] of value.entries()) {
|
|
1331
1903
|
rel[k] = v;
|
|
@@ -1404,6 +1976,7 @@ import { parse as parseToml } from "smol-toml";
|
|
|
1404
1976
|
// src/models/schema.ts
|
|
1405
1977
|
init_BaseModel();
|
|
1406
1978
|
init_ModelRegistry();
|
|
1979
|
+
init_uniqueIndex();
|
|
1407
1980
|
function defineModelSchema(input) {
|
|
1408
1981
|
const { name, fields } = input;
|
|
1409
1982
|
const options = {
|
|
@@ -1446,7 +2019,7 @@ function resolveUniqueConstraints(modelName, fields, customConstraints) {
|
|
|
1446
2019
|
for (const [fieldName, options] of fields.entries()) {
|
|
1447
2020
|
if (options.unique) {
|
|
1448
2021
|
resolved.push({
|
|
1449
|
-
name:
|
|
2022
|
+
name: fieldUniqueConstraintName(modelName, fieldName),
|
|
1450
2023
|
fields: [fieldName]
|
|
1451
2024
|
});
|
|
1452
2025
|
}
|
|
@@ -1692,20 +2265,40 @@ function loadSchemaFromTomlString(tomlString, options = {}) {
|
|
|
1692
2265
|
}
|
|
1693
2266
|
|
|
1694
2267
|
// src/cloudflare.ts
|
|
2268
|
+
init_uniqueIndex();
|
|
1695
2269
|
init_metaSync();
|
|
2270
|
+
init_writeLifecycle();
|
|
1696
2271
|
export {
|
|
1697
2272
|
DOClientEngine,
|
|
2273
|
+
applyAutoStampsFromSchema,
|
|
2274
|
+
applyDefaultsFromSchema,
|
|
2275
|
+
applyStringSetChange,
|
|
2276
|
+
applyStringSetDelta,
|
|
2277
|
+
assertNever,
|
|
2278
|
+
buildUniqueKey,
|
|
1698
2279
|
clearMetaSyncCache,
|
|
1699
2280
|
connectDoDb,
|
|
2281
|
+
decodeFunctionDefault,
|
|
2282
|
+
discoverDeclaredModel,
|
|
1700
2283
|
discoverModelNames,
|
|
1701
2284
|
discoverSchema,
|
|
2285
|
+
ensureStringSetYMap,
|
|
2286
|
+
fieldUniqueConstraintName,
|
|
2287
|
+
fieldWriteKind,
|
|
1702
2288
|
getActiveDOEngine,
|
|
1703
2289
|
inferFieldType,
|
|
1704
2290
|
initJsBaoDO,
|
|
2291
|
+
isKnownFieldType,
|
|
1705
2292
|
loadSchemaFromTomlString,
|
|
2293
|
+
parseStringSetOp,
|
|
1706
2294
|
registerFunctionDefault,
|
|
2295
|
+
registerFunctionDefaultGenerator,
|
|
2296
|
+
replaceStringSet,
|
|
1707
2297
|
resetJsBaoDO,
|
|
2298
|
+
resolveDefaultValue,
|
|
1708
2299
|
schemaToToml,
|
|
1709
2300
|
syncInferredMeta,
|
|
1710
|
-
syncModelMeta
|
|
2301
|
+
syncModelMeta,
|
|
2302
|
+
uniqueIndexMapName,
|
|
2303
|
+
writeField
|
|
1711
2304
|
};
|