@mengine/medeo-client 2.1.0 → 2.1.1-dsl.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -25
- package/dist/base64-D-jd-dr5.js +16 -0
- package/dist/dsl-EV6kCdr6.js +5852 -0
- package/dist/dsl.d.ts +3 -0
- package/dist/dsl.js +3 -0
- package/dist/index-B7fC7q7P.d.ts +728 -0
- package/dist/index-Cu7SdKN5.d.ts +940 -0
- package/dist/index-yifEtZSP.d.ts +197 -0
- package/dist/index.d.ts +5 -1141
- package/dist/index.js +109 -2606
- package/dist/legacy.d.ts +1044 -0
- package/dist/legacy.js +4349 -0
- package/dist/{loro-relay-doc-cJSY-uau.js → loro-relay-doc-Bi6JKqtQ.js} +11 -7
- package/dist/relay.js +1 -1
- package/dist/schemas.d.ts +606 -0
- package/dist/schemas.js +442 -0
- package/dist/shared-iWnU2osE.js +39 -0
- package/dist/storage-BW3tI_ER.js +801 -0
- package/dist/testing.d.ts +22 -9
- package/dist/testing.js +168 -50
- package/dist/video-draft-types-TXRv3wS6.js +1419 -0
- package/dist/video-draft-types-c_shaRyq.d.ts +809 -0
- package/package.json +16 -6
- package/dist/chunk-D7D4PA-g.js +0 -13
- package/dist/document-B_JQwrC5.js +0 -1630
- package/dist/index-CnZ9l3rb.d.ts +0 -2077
|
@@ -0,0 +1,1419 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
2
|
+
import { Cursor } from "loro-crdt";
|
|
3
|
+
//#region ../medeo-dsl/src/ids.ts
|
|
4
|
+
function bytesToBase64url(bytes) {
|
|
5
|
+
let binary = "";
|
|
6
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
7
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
8
|
+
}
|
|
9
|
+
function base64urlToBytes(value) {
|
|
10
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) throw new Error("Invalid base64url");
|
|
11
|
+
const bytes = Uint8Array.from(atob(value.replaceAll("-", "+").replaceAll("_", "/")), (c) => c.charCodeAt(0));
|
|
12
|
+
if (bytesToBase64url(bytes) !== value) throw new Error("Non-canonical base64url");
|
|
13
|
+
return bytes;
|
|
14
|
+
}
|
|
15
|
+
function tupleHash(tuple) {
|
|
16
|
+
return bytesToBase64url(sha256(new TextEncoder().encode(JSON.stringify(tuple))));
|
|
17
|
+
}
|
|
18
|
+
function resourceId(kind, system, key) {
|
|
19
|
+
return `${kind}:${tupleHash([system, key])}`;
|
|
20
|
+
}
|
|
21
|
+
function relationId(table, value) {
|
|
22
|
+
const v = value;
|
|
23
|
+
switch (table) {
|
|
24
|
+
case "clip-source-relations": return `cs:${v.clipId}`;
|
|
25
|
+
case "time-anchor-relations": return `ta:${v.from}`;
|
|
26
|
+
case "caption-content-relations": return `cc:${v.captionId}`;
|
|
27
|
+
case "caption-alignment-relations": return `ca:${v.captionId}`;
|
|
28
|
+
case "voice-timbre-relations": return `voice-timbre:${tupleHash([v.recordingId, v.voiceId])}`;
|
|
29
|
+
case "derived-from-relations": return `derived-from:${tupleHash([v.entityId, v.sourceId])}`;
|
|
30
|
+
case "from-asset-relations": return `from-asset:${v.audioScriptId}`;
|
|
31
|
+
case "to-asset-relations": return `to-asset:${v.audioScriptId}`;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Unknown relation table: ${table}`);
|
|
34
|
+
}
|
|
35
|
+
const defaultIdFactory = (kind) => `${kind}_${globalThis.crypto.randomUUID()}`;
|
|
36
|
+
const resourceKinds = {
|
|
37
|
+
"media-entities": "media",
|
|
38
|
+
"voice-entities": "voice",
|
|
39
|
+
"asset-entities": "asset"
|
|
40
|
+
};
|
|
41
|
+
/** A resource row's ID is derived from its locator, so one external resource is one entity. */
|
|
42
|
+
function assertResourceIdentity(table, id, value) {
|
|
43
|
+
const kind = resourceKinds[table];
|
|
44
|
+
if (kind && id !== resourceId(kind, value.system, value.key)) throw new Error("Resource identity does not match locator");
|
|
45
|
+
}
|
|
46
|
+
/** A relation row's ID is derived from its uniqueness key. */
|
|
47
|
+
function assertRelationIdentity(table, id, value) {
|
|
48
|
+
if (id !== relationId(table, value)) throw new Error("Relation ID does not match endpoints");
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region ../medeo-dsl/src/fields.ts
|
|
52
|
+
const string = (v) => typeof v === "string";
|
|
53
|
+
const nonempty = (v) => string(v) && v.length > 0;
|
|
54
|
+
const finite = (v) => typeof v === "number" && Number.isFinite(v);
|
|
55
|
+
const integer = (v) => typeof v === "number" && Number.isSafeInteger(v);
|
|
56
|
+
const nonnegative = (v) => integer(v) && v >= 0;
|
|
57
|
+
const positive = (v) => integer(v) && v > 0;
|
|
58
|
+
const oneOf = (...values) => (v) => values.includes(v);
|
|
59
|
+
const required = (check) => ({ check });
|
|
60
|
+
const optional$1 = (check) => ({
|
|
61
|
+
check,
|
|
62
|
+
optional: true
|
|
63
|
+
});
|
|
64
|
+
function isRecord(value) {
|
|
65
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Cursor);
|
|
66
|
+
}
|
|
67
|
+
function assertId(value) {
|
|
68
|
+
if (!nonempty(value) || [
|
|
69
|
+
"$cid",
|
|
70
|
+
"__proto__",
|
|
71
|
+
"constructor",
|
|
72
|
+
"prototype"
|
|
73
|
+
].includes(value)) throw new Error("Expected a nonempty, non-reserved ID");
|
|
74
|
+
}
|
|
75
|
+
function validLanguage(value) {
|
|
76
|
+
if (typeof value !== "string") return false;
|
|
77
|
+
if (/^x(?:-[a-z0-9]{1,8})+$/i.test(value)) return true;
|
|
78
|
+
try {
|
|
79
|
+
return value.length > 0 && Intl.getCanonicalLocales(value).length === 1;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function object(fields) {
|
|
85
|
+
return (v) => {
|
|
86
|
+
try {
|
|
87
|
+
checkFields(v, fields);
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function checkFields(value, fields, partial = false) {
|
|
95
|
+
if (!isRecord(value)) throw new Error("Expected an object");
|
|
96
|
+
for (const key of Object.keys(value)) if (!Object.hasOwn(fields, key)) throw new Error(`Unknown field: ${key}`);
|
|
97
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
98
|
+
if (partial && !Object.hasOwn(value, key)) continue;
|
|
99
|
+
const v = value[key];
|
|
100
|
+
if (v === void 0 && field.optional) continue;
|
|
101
|
+
if (!field.check(v)) throw new Error(`Invalid or missing field: ${key}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const styleFields = {
|
|
105
|
+
font: optional$1(object({
|
|
106
|
+
system: required(oneOf("font-library")),
|
|
107
|
+
key: required(nonempty)
|
|
108
|
+
})),
|
|
109
|
+
fontSize: optional$1(finite),
|
|
110
|
+
fontColor: optional$1(string),
|
|
111
|
+
fontWeight: optional$1(finite),
|
|
112
|
+
entranceAnimation: optional$1(string),
|
|
113
|
+
entranceAnimationDurationMs: optional$1(nonnegative),
|
|
114
|
+
strokeColor: optional$1(string),
|
|
115
|
+
strokeWidth: optional$1(finite),
|
|
116
|
+
positionX: optional$1(finite),
|
|
117
|
+
positionY: optional$1(finite)
|
|
118
|
+
};
|
|
119
|
+
const seqMarkerFields = {
|
|
120
|
+
inMs: required(nonnegative),
|
|
121
|
+
outMs: required(nonnegative),
|
|
122
|
+
rate: optional$1((v) => finite(v) && v > 0),
|
|
123
|
+
fillTimeline: optional$1(oneOf(true))
|
|
124
|
+
};
|
|
125
|
+
/** A sequence marker's own fields; the window being nonempty is checked where it is written. */
|
|
126
|
+
function assertSeqMarker(value) {
|
|
127
|
+
checkFields(value, seqMarkerFields);
|
|
128
|
+
}
|
|
129
|
+
const locator = {
|
|
130
|
+
system: required(oneOf("memota", "memota-speech")),
|
|
131
|
+
key: required(nonempty)
|
|
132
|
+
};
|
|
133
|
+
const entityFields = {
|
|
134
|
+
"timeline-entities": {},
|
|
135
|
+
"track-entities": {
|
|
136
|
+
role: required(oneOf("video_clip", "speech", "caption", "bgm")),
|
|
137
|
+
hidden: optional$1((v) => typeof v === "boolean")
|
|
138
|
+
},
|
|
139
|
+
"clip-entities": {
|
|
140
|
+
seqMarker: required(object(seqMarkerFields)),
|
|
141
|
+
volume: optional$1(finite)
|
|
142
|
+
},
|
|
143
|
+
"media-entities": {
|
|
144
|
+
...locator,
|
|
145
|
+
kind: required(oneOf("video", "audio", "voiceover-recording", "image")),
|
|
146
|
+
storageKey: optional$1(nonempty),
|
|
147
|
+
durationMs: optional$1(positive),
|
|
148
|
+
digest: optional$1(nonempty)
|
|
149
|
+
},
|
|
150
|
+
"audio-script-entities": {
|
|
151
|
+
body: required(string),
|
|
152
|
+
defaultLanguage: optional$1(validLanguage)
|
|
153
|
+
},
|
|
154
|
+
"caption-entities": {
|
|
155
|
+
text: optional$1(string),
|
|
156
|
+
style: optional$1(object(styleFields))
|
|
157
|
+
},
|
|
158
|
+
"voice-entities": {
|
|
159
|
+
system: required(oneOf("voice-library")),
|
|
160
|
+
key: required(nonempty),
|
|
161
|
+
name: optional$1(string)
|
|
162
|
+
},
|
|
163
|
+
"asset-entities": {
|
|
164
|
+
...locator,
|
|
165
|
+
storageKey: optional$1(nonempty),
|
|
166
|
+
digest: optional$1(nonempty)
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const entityTables = Object.keys(entityFields);
|
|
170
|
+
const relationFields = {
|
|
171
|
+
"clip-source-relations": {
|
|
172
|
+
clipId: required(nonempty),
|
|
173
|
+
sourceId: required(nonempty)
|
|
174
|
+
},
|
|
175
|
+
"time-anchor-relations": {
|
|
176
|
+
from: required(nonempty),
|
|
177
|
+
to: required(nonempty),
|
|
178
|
+
offsetMs: required(integer),
|
|
179
|
+
fallback_abs_ms: required(nonnegative)
|
|
180
|
+
},
|
|
181
|
+
"caption-content-relations": {
|
|
182
|
+
captionId: required(nonempty),
|
|
183
|
+
audioScriptId: required(nonempty),
|
|
184
|
+
start: required((v) => v instanceof Cursor),
|
|
185
|
+
end: required((v) => v instanceof Cursor)
|
|
186
|
+
},
|
|
187
|
+
"caption-alignment-relations": {
|
|
188
|
+
captionId: required(nonempty),
|
|
189
|
+
mediaId: required(nonempty),
|
|
190
|
+
startMs: required(nonnegative),
|
|
191
|
+
endMs: required(nonnegative)
|
|
192
|
+
},
|
|
193
|
+
"voice-timbre-relations": {
|
|
194
|
+
recordingId: required(nonempty),
|
|
195
|
+
voiceId: required(nonempty)
|
|
196
|
+
},
|
|
197
|
+
"derived-from-relations": {
|
|
198
|
+
entityId: required(nonempty),
|
|
199
|
+
sourceId: required(nonempty)
|
|
200
|
+
},
|
|
201
|
+
"from-asset-relations": {
|
|
202
|
+
audioScriptId: required(nonempty),
|
|
203
|
+
assetId: required(nonempty)
|
|
204
|
+
},
|
|
205
|
+
"to-asset-relations": {
|
|
206
|
+
audioScriptId: required(nonempty),
|
|
207
|
+
assetId: required(nonempty)
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const relationTables = Object.keys(relationFields);
|
|
211
|
+
function assertEntity(table, value) {
|
|
212
|
+
checkFields(value, entityFields[table]);
|
|
213
|
+
if (table === "media-entities") {
|
|
214
|
+
if (value.kind === "image" && value.durationMs !== void 0) throw new Error("Image has no intrinsic duration");
|
|
215
|
+
if (value.kind !== "image" && !positive(value.durationMs)) throw new Error("Finite media requires durationMs");
|
|
216
|
+
if (["audio", "voiceover-recording"].includes(value.kind) && !nonempty(value.storageKey)) throw new Error("Audio requires storageKey");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Validate only touched fields, so an unrelated edit can proceed in a merged, invalid document. */
|
|
220
|
+
function assertEntityPatch(table, patch, current) {
|
|
221
|
+
if (!isRecord(patch)) throw new Error("Expected an entity patch");
|
|
222
|
+
if (table === "audio-script-entities" && Object.hasOwn(patch, "body")) throw new Error("Edit body through the Text adapter");
|
|
223
|
+
if (table === "caption-entities" && current.style !== void 0 && Object.hasOwn(patch, "style") && patch.style === void 0) throw new Error("Reset individual style fields; retain the style Map");
|
|
224
|
+
if (table === "caption-entities" && current.text !== void 0 && patch.text !== void 0) throw new Error("Edit Caption text through the Text adapter");
|
|
225
|
+
const next = mergeFields(current, patch);
|
|
226
|
+
for (const key of Object.keys(patch)) {
|
|
227
|
+
const field = entityFields[table][key];
|
|
228
|
+
if (!field || !field.optional && next[key] === void 0 || next[key] !== void 0 && !field.check(next[key])) throw new Error(`Invalid patch field: ${key}`);
|
|
229
|
+
}
|
|
230
|
+
if (table === "media-entities" || table === "voice-entities" || table === "asset-entities") {
|
|
231
|
+
for (const key of [
|
|
232
|
+
"system",
|
|
233
|
+
"key",
|
|
234
|
+
"kind"
|
|
235
|
+
]) if (Object.hasOwn(patch, key) && patch[key] !== current[key]) throw new Error(`Resource identity field is immutable: ${key}`);
|
|
236
|
+
}
|
|
237
|
+
if (table === "media-entities") {
|
|
238
|
+
if (Object.hasOwn(patch, "durationMs")) {
|
|
239
|
+
if (next.kind === "image" ? next.durationMs !== void 0 : !positive(next.durationMs)) throw new Error("durationMs must match the media kind");
|
|
240
|
+
}
|
|
241
|
+
if (Object.hasOwn(patch, "storageKey") && ["audio", "voiceover-recording"].includes(next.kind) && !nonempty(next.storageKey)) throw new Error("Audio requires storageKey");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function assertRelation(table, value) {
|
|
245
|
+
checkFields(value, relationFields[table]);
|
|
246
|
+
for (const [key, endpoint] of Object.entries(value)) if (key.endsWith("Id") || key === "from" || key === "to") assertId(endpoint);
|
|
247
|
+
}
|
|
248
|
+
/** Undefined deletes; nested patches retain sibling fields and an existing empty style Map. */
|
|
249
|
+
function mergeFields(target, patch) {
|
|
250
|
+
const result = { ...target };
|
|
251
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
252
|
+
if ([
|
|
253
|
+
"$cid",
|
|
254
|
+
"__proto__",
|
|
255
|
+
"constructor",
|
|
256
|
+
"prototype"
|
|
257
|
+
].includes(key)) throw new Error(`Reserved field: ${key}`);
|
|
258
|
+
if (value === void 0) delete result[key];
|
|
259
|
+
else result[key] = isRecord(value) ? mergeFields(isRecord(result[key]) ? result[key] : {}, value) : value;
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/schema/resolver.js
|
|
265
|
+
function getMapFieldSchema(schema, key) {
|
|
266
|
+
if (!schema) return void 0;
|
|
267
|
+
if (Object.prototype.hasOwnProperty.call(schema.definition, key)) return schema.definition[key];
|
|
268
|
+
if (schema.type === "loro-map" && "catchallType" in schema) return schema.catchallType;
|
|
269
|
+
}
|
|
270
|
+
function getChildSchema(schema, childKey) {
|
|
271
|
+
if (!schema) return void 0;
|
|
272
|
+
switch (schema.type) {
|
|
273
|
+
case "schema":
|
|
274
|
+
case "loro-map": return childKey === void 0 ? void 0 : getMapFieldSchema(schema, String(childKey));
|
|
275
|
+
case "loro-list":
|
|
276
|
+
case "loro-movable-list": return schema.itemSchema;
|
|
277
|
+
case "loro-tree": return schema.nodeSchema;
|
|
278
|
+
default: return;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function getChildContainerSchema(schema, childKey) {
|
|
282
|
+
const childSchema = getChildSchema(schema, childKey);
|
|
283
|
+
if (!childSchema) return void 0;
|
|
284
|
+
switch (childSchema.type) {
|
|
285
|
+
case "loro-map":
|
|
286
|
+
case "loro-list":
|
|
287
|
+
case "loro-movable-list":
|
|
288
|
+
case "loro-text":
|
|
289
|
+
case "loro-tree": return childSchema;
|
|
290
|
+
default: return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/constants.js
|
|
295
|
+
const CID_KEY = "$cid";
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/container-inference.js
|
|
298
|
+
function isPlainObjectValue(value) {
|
|
299
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp) && typeof value !== "function";
|
|
300
|
+
}
|
|
301
|
+
function inferContainerTypeFromValue$1(value, defaults) {
|
|
302
|
+
switch (getContainerValueKind(value)) {
|
|
303
|
+
case "map": return "Map";
|
|
304
|
+
case "array": return defaults?.defaultMovableList ? "MovableList" : "List";
|
|
305
|
+
case "string": return defaults?.defaultLoroText ? "Text" : void 0;
|
|
306
|
+
default: return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function inferSchemaContainerTypeFromValue(value, defaults) {
|
|
310
|
+
switch (inferContainerTypeFromValue$1(value, defaults)) {
|
|
311
|
+
case "Map": return "loro-map";
|
|
312
|
+
case "List": return "loro-list";
|
|
313
|
+
case "MovableList": return "loro-movable-list";
|
|
314
|
+
case "Text": return "loro-text";
|
|
315
|
+
default: return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function matchesContainerType(containerType, value) {
|
|
319
|
+
const kind = getContainerValueKind(value);
|
|
320
|
+
switch (containerType) {
|
|
321
|
+
case "Map": return kind === "map";
|
|
322
|
+
case "List":
|
|
323
|
+
case "MovableList":
|
|
324
|
+
case "Tree": return kind === "array";
|
|
325
|
+
case "Text": return kind === "string";
|
|
326
|
+
default: return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function getContainerValueKind(value) {
|
|
330
|
+
if (isPlainObjectValue(value)) return "map";
|
|
331
|
+
if (Array.isArray(value)) return "array";
|
|
332
|
+
if (typeof value === "string") return "string";
|
|
333
|
+
return "other";
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/core/utils.js
|
|
337
|
+
/**
|
|
338
|
+
* Utility functions for Loro Mirror core
|
|
339
|
+
*/
|
|
340
|
+
/**
|
|
341
|
+
* Check if a schema has a transform.
|
|
342
|
+
* Distinguishes between the .transform() builder method (function) and an actual
|
|
343
|
+
* TransformDefinition object (with decode/encode functions).
|
|
344
|
+
*/
|
|
345
|
+
function hasTransform(schema) {
|
|
346
|
+
if (schema === void 0) return false;
|
|
347
|
+
const t = schema.transform;
|
|
348
|
+
return t != null && typeof t === "object";
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get the transform from a schema, or undefined if none exists.
|
|
352
|
+
*/
|
|
353
|
+
function getTransform(schema) {
|
|
354
|
+
if (hasTransform(schema)) return schema.transform;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Get the effective equality strategy for a transform.
|
|
358
|
+
* Default: "reference-equality"
|
|
359
|
+
*/
|
|
360
|
+
function getEqualityStrategy(schema, strategyIfNotTransformable) {
|
|
361
|
+
const transform = getTransform(schema);
|
|
362
|
+
if (!transform) return strategyIfNotTransformable;
|
|
363
|
+
return transform.isEqual ?? "reference-equality";
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Apply decode transform to a CRDT value.
|
|
367
|
+
* Null/undefined pass through as-is - transform is only called for real values.
|
|
368
|
+
*/
|
|
369
|
+
function applyDecode(schema, crdtValue) {
|
|
370
|
+
if (crdtValue === null || crdtValue === void 0) return crdtValue;
|
|
371
|
+
const transform = getTransform(schema);
|
|
372
|
+
return transform ? transform.decode(crdtValue) : crdtValue;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Recursively decode JSON values using schema transforms.
|
|
376
|
+
* Mutates in-place. Used for snapshot initialization where toJSON() skips decode.
|
|
377
|
+
*/
|
|
378
|
+
function decodeNestedJsonValues(json, schema) {
|
|
379
|
+
if (json === null || json === void 0 || !schema) return json;
|
|
380
|
+
switch (schema.type) {
|
|
381
|
+
case "loro-map":
|
|
382
|
+
if (!isObject(json)) return json;
|
|
383
|
+
for (const key of Object.keys(json)) {
|
|
384
|
+
const fieldSchema = getChildSchema(schema, key);
|
|
385
|
+
json[key] = decodeNestedJsonValues(json[key], fieldSchema);
|
|
386
|
+
}
|
|
387
|
+
return json;
|
|
388
|
+
case "loro-list":
|
|
389
|
+
case "loro-movable-list": {
|
|
390
|
+
if (!Array.isArray(json)) return json;
|
|
391
|
+
const itemSchema = getChildSchema(schema);
|
|
392
|
+
for (let i = 0; i < json.length; i++) json[i] = decodeNestedJsonValues(json[i], itemSchema);
|
|
393
|
+
return json;
|
|
394
|
+
}
|
|
395
|
+
case "loro-tree": {
|
|
396
|
+
if (!Array.isArray(json)) return json;
|
|
397
|
+
const nodeSchema = getChildSchema(schema);
|
|
398
|
+
const walk = (nodes) => {
|
|
399
|
+
for (const node of nodes) if (node != null && typeof node == "object") {
|
|
400
|
+
if ("data" in node && node.data !== void 0) node.data = decodeNestedJsonValues(node.data, nodeSchema);
|
|
401
|
+
if ("children" in node && Array.isArray(node.children)) walk(node.children);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
walk(json);
|
|
405
|
+
return json;
|
|
406
|
+
}
|
|
407
|
+
case "loro-text":
|
|
408
|
+
case "ignore": return json;
|
|
409
|
+
default: return applyDecode(schema, json);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Apply encode transform to a domain value.
|
|
414
|
+
* Null/undefined pass through as-is - transform is only called for real values.
|
|
415
|
+
*/
|
|
416
|
+
function applyEncode(schema, domainValue) {
|
|
417
|
+
if (domainValue === null || domainValue === void 0) return domainValue;
|
|
418
|
+
const transform = getTransform(schema);
|
|
419
|
+
return transform ? transform.encode(domainValue) : domainValue;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Check if two domain values are equal according to the schema's equality strategy.
|
|
423
|
+
*
|
|
424
|
+
* Reference equality is always checked first (performance optimization).
|
|
425
|
+
* If refs match, returns true (no change).
|
|
426
|
+
* If refs differ, behavior depends on isEqual setting.
|
|
427
|
+
*/
|
|
428
|
+
function valuesEqual(schema, oldValue, newValue, strategyIfNotTransformable) {
|
|
429
|
+
if (oldValue === newValue) return true;
|
|
430
|
+
const strategy = getEqualityStrategy(schema, strategyIfNotTransformable);
|
|
431
|
+
if (strategy === "reference-equality") return false;
|
|
432
|
+
else if (strategy === "encoded-value-equality") return applyEncode(schema, oldValue) === applyEncode(schema, newValue);
|
|
433
|
+
else if (strategy === "deep-equality") return deepEqual(oldValue, newValue);
|
|
434
|
+
else return strategy(oldValue, newValue);
|
|
435
|
+
}
|
|
436
|
+
function defineCidProperty(target, cid) {
|
|
437
|
+
if (!isObject(target) || Object.prototype.hasOwnProperty.call(target, "$cid")) return;
|
|
438
|
+
Object.defineProperty(target, CID_KEY, { value: cid });
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Restore the read-only `$cid` contract on a freshly produced state.
|
|
442
|
+
*
|
|
443
|
+
* `defineCidProperty` stamps `$cid` as `{ writable: false, enumerable: false,
|
|
444
|
+
* configurable: false }`. Immer's `useStrictShallowCopy` preserves the descriptor, but it
|
|
445
|
+
* deliberately rewrites every non-writable data property to `{ writable: true,
|
|
446
|
+
* configurable: true }` so the draft stays mutable — which would leave `$cid` writable on
|
|
447
|
+
* every object Immer copied, letting callers reassign a published `$cid`.
|
|
448
|
+
*
|
|
449
|
+
* Two things keep this proportional to what Immer actually copied rather than to the size
|
|
450
|
+
* of the state:
|
|
451
|
+
*
|
|
452
|
+
* - `prev` is the state `produce` was called with, and Immer shares everything it did not
|
|
453
|
+
* copy, so a subtree reference-equal to `prev` cannot have been touched.
|
|
454
|
+
* - An already-locked `$cid` means this object was not copied this round, so its children
|
|
455
|
+
* are the same references they were when it was last hardened and are locked too. This
|
|
456
|
+
* is what makes a reorder cheap: moved items are shared references that land at a new
|
|
457
|
+
* index, so `prev` no longer lines up with them, but they are still locked.
|
|
458
|
+
*/
|
|
459
|
+
function hardenCidDescriptors(next, prev, visited) {
|
|
460
|
+
if (next === prev) return;
|
|
461
|
+
if (Array.isArray(next)) {
|
|
462
|
+
if ((visited ?? (visited = /* @__PURE__ */ new Set())).has(next)) return;
|
|
463
|
+
visited.add(next);
|
|
464
|
+
const prevArr = Array.isArray(prev) ? prev : void 0;
|
|
465
|
+
for (let i = 0; i < next.length; i++) hardenCidDescriptors(next[i], prevArr?.[i], visited);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (!isObject(next)) return;
|
|
469
|
+
const descriptor = Object.getOwnPropertyDescriptor(next, CID_KEY);
|
|
470
|
+
if (descriptor) {
|
|
471
|
+
if (!descriptor.configurable) return;
|
|
472
|
+
if ("value" in descriptor) Object.defineProperty(next, CID_KEY, {
|
|
473
|
+
value: descriptor.value,
|
|
474
|
+
writable: false,
|
|
475
|
+
enumerable: false,
|
|
476
|
+
configurable: false
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if ((visited ?? (visited = /* @__PURE__ */ new Set())).has(next)) return;
|
|
480
|
+
visited.add(next);
|
|
481
|
+
const prevObj = isObject(prev) ? prev : void 0;
|
|
482
|
+
for (const key of Object.keys(next)) hardenCidDescriptors(next[key], prevObj?.[key], visited);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Check if a value is an object
|
|
486
|
+
*/
|
|
487
|
+
function isObject(value) {
|
|
488
|
+
return isPlainObjectValue(value);
|
|
489
|
+
}
|
|
490
|
+
const UNSAFE_KEYS = new Set([
|
|
491
|
+
"__proto__",
|
|
492
|
+
"constructor",
|
|
493
|
+
"prototype"
|
|
494
|
+
]);
|
|
495
|
+
/**
|
|
496
|
+
* Recursively removes undefined values from an object.
|
|
497
|
+
* This treats undefined values as non-existent fields.
|
|
498
|
+
* Preserves non-enumerable properties like $cid.
|
|
499
|
+
* Returns the original object if no undefined values are found.
|
|
500
|
+
* Protects against prototype pollution by skipping unsafe keys.
|
|
501
|
+
*/
|
|
502
|
+
function stripUndefined(value) {
|
|
503
|
+
return stripUndefinedInner(value, 0, void 0);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Depth at which we start recording the recursion path to detect cycles.
|
|
507
|
+
*
|
|
508
|
+
* This runs over the whole state on every `setState`, and tracking every node costs more
|
|
509
|
+
* than the stripping itself. Mirror state is a tree whose depth follows the schema, so a
|
|
510
|
+
* real state never gets near this; only a cycle recurses without bound. Once the path is
|
|
511
|
+
* being recorded, any cycle revisits one of its own nodes within a single lap, so starting
|
|
512
|
+
* late still terminates.
|
|
513
|
+
*/
|
|
514
|
+
const CYCLE_GUARD_DEPTH = 64;
|
|
515
|
+
/**
|
|
516
|
+
* Placeholder for a key that must not reach the rebuilt object: an unsafe key, or one whose
|
|
517
|
+
* value is `undefined` and is therefore treated as a non-existent field.
|
|
518
|
+
*/
|
|
519
|
+
const DROPPED = Symbol("dropped");
|
|
520
|
+
function stripUndefinedInner(value, depth, path) {
|
|
521
|
+
if (value === void 0) return value;
|
|
522
|
+
if (Array.isArray(value)) {
|
|
523
|
+
if (depth >= CYCLE_GUARD_DEPTH) {
|
|
524
|
+
if ((path ?? (path = /* @__PURE__ */ new Set())).has(value)) return value;
|
|
525
|
+
path.add(value);
|
|
526
|
+
}
|
|
527
|
+
let hasChanges = false;
|
|
528
|
+
const result = value.map((item) => {
|
|
529
|
+
const stripped = stripUndefinedInner(item, depth + 1, path);
|
|
530
|
+
if (stripped !== item) hasChanges = true;
|
|
531
|
+
return stripped;
|
|
532
|
+
});
|
|
533
|
+
path?.delete(value);
|
|
534
|
+
return hasChanges ? result : value;
|
|
535
|
+
}
|
|
536
|
+
if (isObject(value)) {
|
|
537
|
+
if (depth >= CYCLE_GUARD_DEPTH) {
|
|
538
|
+
if ((path ?? (path = /* @__PURE__ */ new Set())).has(value)) return value;
|
|
539
|
+
path.add(value);
|
|
540
|
+
}
|
|
541
|
+
const keys = Object.keys(value);
|
|
542
|
+
const strippedValues = Array.from({ length: keys.length });
|
|
543
|
+
let changed = false;
|
|
544
|
+
for (let i = 0; i < keys.length; i++) {
|
|
545
|
+
const key = keys[i];
|
|
546
|
+
if (UNSAFE_KEYS.has(key)) {
|
|
547
|
+
strippedValues[i] = DROPPED;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const val = value[key];
|
|
551
|
+
if (val === void 0) {
|
|
552
|
+
strippedValues[i] = DROPPED;
|
|
553
|
+
changed = true;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const stripped = stripUndefinedInner(val, depth + 1, path);
|
|
557
|
+
strippedValues[i] = stripped;
|
|
558
|
+
if (stripped !== val) changed = true;
|
|
559
|
+
}
|
|
560
|
+
path?.delete(value);
|
|
561
|
+
if (!changed) return value;
|
|
562
|
+
const result = Object.create(null);
|
|
563
|
+
const allProps = Object.getOwnPropertyNames(value);
|
|
564
|
+
for (const key of allProps) {
|
|
565
|
+
if (UNSAFE_KEYS.has(key)) continue;
|
|
566
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
567
|
+
if (descriptor && !descriptor.enumerable) Object.defineProperty(result, key, descriptor);
|
|
568
|
+
}
|
|
569
|
+
for (let i = 0; i < keys.length; i++) {
|
|
570
|
+
const val = strippedValues[i];
|
|
571
|
+
if (val === DROPPED) continue;
|
|
572
|
+
Object.defineProperty(result, keys[i], {
|
|
573
|
+
value: val,
|
|
574
|
+
writable: true,
|
|
575
|
+
enumerable: true,
|
|
576
|
+
configurable: true
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Performs a deep equality check between two values
|
|
585
|
+
*/
|
|
586
|
+
function deepEqual(a, b) {
|
|
587
|
+
if (a === b) return true;
|
|
588
|
+
if (a === null || b === null || typeof a !== "object" && typeof a !== "function" || typeof b !== "object" && typeof b !== "function") return false;
|
|
589
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
590
|
+
if (a.length !== b.length) return false;
|
|
591
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
595
|
+
if (a instanceof RegExp && b instanceof RegExp) return a.toString() === b.toString();
|
|
596
|
+
if (!Array.isArray(a) && !Array.isArray(b)) {
|
|
597
|
+
const keysA = Object.keys(a);
|
|
598
|
+
const keysB = Object.keys(b);
|
|
599
|
+
if (keysA.length !== keysB.length) return false;
|
|
600
|
+
for (const key of keysA) {
|
|
601
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
602
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
603
|
+
}
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
return false;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Recursively compare the `$cid` markers of two state trees.
|
|
610
|
+
*
|
|
611
|
+
* `deepEqual` only walks enumerable keys, so it cannot see `$cid` (which is stamped as a
|
|
612
|
+
* non-enumerable descriptor). Used by `Mirror.checkStateConsistency` so that a tampered
|
|
613
|
+
* or stale `$cid` is reported as a divergence instead of silently sitting in the state.
|
|
614
|
+
* Only the markers are compared here; value equality is `deepEqual`'s job.
|
|
615
|
+
*/
|
|
616
|
+
function cidsEqual(a, b, seen) {
|
|
617
|
+
if (a === b) return true;
|
|
618
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
619
|
+
if (a.length !== b.length) return false;
|
|
620
|
+
if (markSeen(a, b, seen ?? (seen = /* @__PURE__ */ new Map())) === "seen") return true;
|
|
621
|
+
for (let i = 0; i < a.length; i++) if (!cidsEqual(a[i], b[i], seen)) return false;
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
if (!isObject(a) || !isObject(b)) return true;
|
|
625
|
+
if (a["$cid"] !== b["$cid"]) return false;
|
|
626
|
+
if (markSeen(a, b, seen ?? (seen = /* @__PURE__ */ new Map())) === "seen") return true;
|
|
627
|
+
for (const key of Object.keys(a)) {
|
|
628
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) continue;
|
|
629
|
+
if (!cidsEqual(a[key], b[key], seen)) return false;
|
|
630
|
+
}
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
function markSeen(a, b, seen) {
|
|
634
|
+
let partners = seen.get(a);
|
|
635
|
+
if (!partners) {
|
|
636
|
+
partners = /* @__PURE__ */ new Set();
|
|
637
|
+
seen.set(a, partners);
|
|
638
|
+
}
|
|
639
|
+
if (partners.has(b)) return "seen";
|
|
640
|
+
partners.add(b);
|
|
641
|
+
return "new";
|
|
642
|
+
}
|
|
643
|
+
function containerIdToContainerType(containerId) {
|
|
644
|
+
const parts = containerId.split(":");
|
|
645
|
+
return parts[parts.length - 1];
|
|
646
|
+
}
|
|
647
|
+
function getRootContainerByType(doc, key, type) {
|
|
648
|
+
if (type === "Text") return doc.getText(key);
|
|
649
|
+
else if (type === "List") return doc.getList(key);
|
|
650
|
+
else if (type === "MovableList") return doc.getMovableList(key);
|
|
651
|
+
else if (type === "Map") return doc.getMap(key);
|
|
652
|
+
else if (type === "Tree") return doc.getTree(key);
|
|
653
|
+
else throw new Error();
|
|
654
|
+
}
|
|
655
|
+
function insertChildToMap(containerId, key, value, inferOptions) {
|
|
656
|
+
const ct = tryInferContainerType(value, inferOptions);
|
|
657
|
+
if (ct) return {
|
|
658
|
+
container: containerId,
|
|
659
|
+
key,
|
|
660
|
+
value,
|
|
661
|
+
kind: "insert-container",
|
|
662
|
+
childContainerType: ct
|
|
663
|
+
};
|
|
664
|
+
return {
|
|
665
|
+
container: containerId,
|
|
666
|
+
key,
|
|
667
|
+
value,
|
|
668
|
+
kind: "insert"
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
function tryUpdateToContainer(change, toUpdate, schema, inferOptions) {
|
|
672
|
+
if (!toUpdate) return change;
|
|
673
|
+
if (change.kind !== "insert" && change.kind !== "set") return change;
|
|
674
|
+
const effectiveInferOptions = applySchemaToInferOptions(schema, inferOptions);
|
|
675
|
+
const containerType = schema ? schemaToContainerType(schema) ?? tryInferContainerType(change.value, effectiveInferOptions) : tryInferContainerType(change.value, effectiveInferOptions);
|
|
676
|
+
if (containerType == null || schema && hasTransform(schema)) {
|
|
677
|
+
const encodedValue = applyEncode(schema, change.value);
|
|
678
|
+
return encodedValue !== change.value ? {
|
|
679
|
+
...change,
|
|
680
|
+
value: encodedValue
|
|
681
|
+
} : change;
|
|
682
|
+
}
|
|
683
|
+
if (change.kind === "insert") return {
|
|
684
|
+
container: change.container,
|
|
685
|
+
key: change.key,
|
|
686
|
+
value: change.value,
|
|
687
|
+
kind: "insert-container",
|
|
688
|
+
childContainerType: containerType
|
|
689
|
+
};
|
|
690
|
+
if (change.kind === "set") return {
|
|
691
|
+
container: change.container,
|
|
692
|
+
key: change.key,
|
|
693
|
+
value: change.value,
|
|
694
|
+
kind: "set-container",
|
|
695
|
+
childContainerType: containerType
|
|
696
|
+
};
|
|
697
|
+
return change;
|
|
698
|
+
}
|
|
699
|
+
function schemaToContainerType(schema) {
|
|
700
|
+
const containerType = schema.getContainerType();
|
|
701
|
+
return containerType === null ? void 0 : containerType;
|
|
702
|
+
}
|
|
703
|
+
function tryInferContainerType(value, defaults) {
|
|
704
|
+
return inferContainerTypeFromValue$1(value, defaults);
|
|
705
|
+
}
|
|
706
|
+
function applySchemaToInferOptions(schema, base) {
|
|
707
|
+
if (!schema || schema.type !== "any") return base;
|
|
708
|
+
const next = { ...base };
|
|
709
|
+
next.defaultLoroText = schema.options.defaultLoroText ?? false;
|
|
710
|
+
if (schema.options.defaultMovableList !== void 0) next.defaultMovableList = schema.options.defaultMovableList;
|
|
711
|
+
return next;
|
|
712
|
+
}
|
|
713
|
+
function isValueOfContainerType(containerType, value) {
|
|
714
|
+
return matchesContainerType(containerType, value);
|
|
715
|
+
}
|
|
716
|
+
function inferContainerTypeFromValue(value, defaults) {
|
|
717
|
+
return inferSchemaContainerTypeFromValue(value, defaults);
|
|
718
|
+
}
|
|
719
|
+
function isObjectLike(value) {
|
|
720
|
+
return typeof value === "object";
|
|
721
|
+
}
|
|
722
|
+
function isArrayLike(value) {
|
|
723
|
+
return Array.isArray(value);
|
|
724
|
+
}
|
|
725
|
+
function isStateAndSchemaOfType(values, stateGuard, schemaGuard) {
|
|
726
|
+
return stateGuard(values.oldState) && stateGuard(values.newState) && (!values.schema || schemaGuard(values.schema));
|
|
727
|
+
}
|
|
728
|
+
function isTreeID(id) {
|
|
729
|
+
if (!(typeof id === "string")) return false;
|
|
730
|
+
return !!id.match(/[0-9]+@[0-9]+/);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Stringify a value safely, handling non-JSON-serializable types.
|
|
734
|
+
* Handles BigInt, Date, RegExp, functions, and custom objects.
|
|
735
|
+
*/
|
|
736
|
+
function safeStringify(value, indent = 2) {
|
|
737
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
738
|
+
function replacer(val) {
|
|
739
|
+
if (val === null || val === void 0) return val;
|
|
740
|
+
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") return val;
|
|
741
|
+
if (typeof val === "bigint") return `[BigInt: ${val.toString()}]`;
|
|
742
|
+
if (typeof val === "function") return `[Function: ${val.name || "anonymous"}]`;
|
|
743
|
+
if (typeof val === "symbol") return `[Symbol: ${val.description || ""}]`;
|
|
744
|
+
if (typeof val === "object") {
|
|
745
|
+
if (seen.has(val)) return "[Circular]";
|
|
746
|
+
seen.add(val);
|
|
747
|
+
if (val instanceof Date) return `[Date: ${val.toISOString()}]`;
|
|
748
|
+
if (val instanceof RegExp) return `[RegExp: ${val.toString()}]`;
|
|
749
|
+
if (val instanceof Error) return `[Error: ${val.message}]`;
|
|
750
|
+
if (Array.isArray(val)) return val.map(replacer);
|
|
751
|
+
const result = {};
|
|
752
|
+
for (const key of Object.keys(val)) result[key] = replacer(val[key]);
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
return Object.prototype.toString.call(val);
|
|
756
|
+
}
|
|
757
|
+
return JSON.stringify(replacer(value), null, indent);
|
|
758
|
+
}
|
|
759
|
+
//#endregion
|
|
760
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/schema/validators.js
|
|
761
|
+
const schemaValidationCache = /* @__PURE__ */ new WeakMap();
|
|
762
|
+
function isCacheableValue(value) {
|
|
763
|
+
return typeof value === "object" && value !== null;
|
|
764
|
+
}
|
|
765
|
+
function isSchemaValidated(schema, value) {
|
|
766
|
+
if (!isCacheableValue(value)) return false;
|
|
767
|
+
return schemaValidationCache.get(value)?.has(schema) ?? false;
|
|
768
|
+
}
|
|
769
|
+
function markSchemaValidated(schema, value) {
|
|
770
|
+
if (!isCacheableValue(value)) return;
|
|
771
|
+
let cache = schemaValidationCache.get(value);
|
|
772
|
+
if (!cache) {
|
|
773
|
+
cache = /* @__PURE__ */ new WeakSet();
|
|
774
|
+
schemaValidationCache.set(value, cache);
|
|
775
|
+
}
|
|
776
|
+
cache.add(schema);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Type guard for LoroMapSchema
|
|
780
|
+
*/
|
|
781
|
+
function isLoroMapSchema(schema) {
|
|
782
|
+
return !!schema && schema.type === "loro-map";
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Type guard for LoroListSchema
|
|
786
|
+
*/
|
|
787
|
+
function isLoroListSchema(schema) {
|
|
788
|
+
return !!schema && schema.type === "loro-list";
|
|
789
|
+
}
|
|
790
|
+
function isListLikeSchema(schema) {
|
|
791
|
+
return isLoroListSchema(schema) || isLoroMovableListSchema(schema);
|
|
792
|
+
}
|
|
793
|
+
function isLoroMovableListSchema(schema) {
|
|
794
|
+
return !!schema && schema.type === "loro-movable-list";
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Type guard for RootSchemaType
|
|
798
|
+
*/
|
|
799
|
+
function isRootSchemaType(schema) {
|
|
800
|
+
return !!schema && schema.type === "schema";
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Whether a schema subtree contains any `Ignore` field. Recursive schemas are
|
|
804
|
+
* tolerated (each schema object is visited once).
|
|
805
|
+
*/
|
|
806
|
+
function schemaContainsIgnoreField(schema) {
|
|
807
|
+
const visited = /* @__PURE__ */ new Set();
|
|
808
|
+
const walk = (s) => {
|
|
809
|
+
if (!s || visited.has(s)) return false;
|
|
810
|
+
visited.add(s);
|
|
811
|
+
if (s.type === "ignore") return true;
|
|
812
|
+
switch (s.type) {
|
|
813
|
+
case "schema":
|
|
814
|
+
case "loro-map": {
|
|
815
|
+
const record = s;
|
|
816
|
+
return Object.values(record.definition).some(walk) || walk(record.catchallType);
|
|
817
|
+
}
|
|
818
|
+
case "loro-list":
|
|
819
|
+
case "loro-movable-list": return walk(s.itemSchema);
|
|
820
|
+
case "loro-tree": return walk(s.nodeSchema);
|
|
821
|
+
default: return false;
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
return walk(schema);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Type guard for LoroTreeSchema
|
|
828
|
+
*/
|
|
829
|
+
function isLoroTreeSchema(schema) {
|
|
830
|
+
return !!schema && schema.type === "loro-tree";
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Check if a schema is for a Loro container
|
|
834
|
+
*/
|
|
835
|
+
function isContainerSchema(schema) {
|
|
836
|
+
return !!schema && (schema.type === "loro-map" || schema.type === "loro-list" || schema.type === "loro-text" || schema.type === "loro-movable-list" || schema.type === "loro-tree");
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Validate a value against a schema
|
|
840
|
+
*
|
|
841
|
+
* When `options.ignoreUnknownProperties` is true, the root schema check does
|
|
842
|
+
* not reject properties missing from the schema definition — typically
|
|
843
|
+
* written by peers on a newer schema version. (Nested map schemas never
|
|
844
|
+
* reject undeclared properties.)
|
|
845
|
+
*/
|
|
846
|
+
function validateSchema(schema, value, options) {
|
|
847
|
+
const errors = [];
|
|
848
|
+
const actualType = schema.type;
|
|
849
|
+
if (schema.options.required && (value === void 0 || value === null)) {
|
|
850
|
+
errors.push("Value is required");
|
|
851
|
+
return {
|
|
852
|
+
valid: false,
|
|
853
|
+
errors
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
if (value === void 0 || value === null) return { valid: true };
|
|
857
|
+
const useCache = !options?.ignoreUnknownProperties;
|
|
858
|
+
if (useCache && isSchemaValidated(schema, value)) return { valid: true };
|
|
859
|
+
switch (actualType) {
|
|
860
|
+
case "any":
|
|
861
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" && value !== null && value !== void 0 && !Array.isArray(value) && !isObject(value)) errors.push("Value must be JSON-like");
|
|
862
|
+
break;
|
|
863
|
+
case "string":
|
|
864
|
+
case "number":
|
|
865
|
+
case "boolean":
|
|
866
|
+
validateTransformablePrimitive(schema, value, actualType, errors);
|
|
867
|
+
break;
|
|
868
|
+
case "ignore": break;
|
|
869
|
+
case "loro-text":
|
|
870
|
+
if (typeof value !== "string") errors.push("Content must be a string");
|
|
871
|
+
break;
|
|
872
|
+
case "loro-map":
|
|
873
|
+
if (!isObject(value)) errors.push("Value must be an object");
|
|
874
|
+
else if (isLoroMapSchema(schema)) {
|
|
875
|
+
for (const key in schema.definition) if (Object.prototype.hasOwnProperty.call(schema.definition, key)) {
|
|
876
|
+
const propSchema = schema.definition[key];
|
|
877
|
+
const propValue = value[key];
|
|
878
|
+
const result = validateSchema(propSchema, propValue);
|
|
879
|
+
if (!result.valid && result.errors) {
|
|
880
|
+
const prefixedErrors = result.errors.map((err) => `${key}: ${err}`);
|
|
881
|
+
errors.push(...prefixedErrors);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
case "loro-movable-list":
|
|
887
|
+
case "loro-list":
|
|
888
|
+
if (!Array.isArray(value)) errors.push("Value must be an array");
|
|
889
|
+
else if (isLoroListSchema(schema) || isLoroMovableListSchema(schema)) {
|
|
890
|
+
const itemSchema = schema.itemSchema;
|
|
891
|
+
value.forEach((item, index) => {
|
|
892
|
+
const result = validateSchema(itemSchema, item);
|
|
893
|
+
if (!result.valid && result.errors) {
|
|
894
|
+
const prefixedErrors = result.errors.map((err) => `Item ${index}: ${err}`);
|
|
895
|
+
errors.push(...prefixedErrors);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
break;
|
|
900
|
+
case "loro-tree": {
|
|
901
|
+
if (!Array.isArray(value)) {
|
|
902
|
+
errors.push("Value must be an array of tree nodes");
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
if (!isLoroTreeSchema(schema)) {
|
|
906
|
+
errors.push("Invalid tree schema");
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
const validateNode = (node, path) => {
|
|
910
|
+
if (!isObject(node)) {
|
|
911
|
+
errors.push(`${path}: Node must be an object`);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const n = node;
|
|
915
|
+
if (n.id != null && typeof n.id !== "string") errors.push(`${path}: id must be a string if provided`);
|
|
916
|
+
const dataResult = validateSchema(schema.nodeSchema, n.data);
|
|
917
|
+
if (!dataResult.valid && dataResult.errors) errors.push(...dataResult.errors.map((e) => `${path}.data: ${e}`));
|
|
918
|
+
if (!Array.isArray(n.children)) errors.push(`${path}: children must be an array`);
|
|
919
|
+
else n.children.forEach((child, idx) => {
|
|
920
|
+
validateNode(child, `${path}.children[${idx}]`);
|
|
921
|
+
});
|
|
922
|
+
};
|
|
923
|
+
value.forEach((node, i) => {
|
|
924
|
+
validateNode(node, `node[${i}]`);
|
|
925
|
+
});
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
case "schema":
|
|
929
|
+
if (!isObject(value)) errors.push("Value must be an object");
|
|
930
|
+
else if (isRootSchemaType(schema)) if (!isObject(value)) errors.push("Value must be an object");
|
|
931
|
+
else {
|
|
932
|
+
for (const key in schema.definition) if (Object.prototype.hasOwnProperty.call(schema.definition, key)) {
|
|
933
|
+
const propSchema = schema.definition[key];
|
|
934
|
+
const propValue = value[key];
|
|
935
|
+
const result = validateSchema(propSchema, propValue);
|
|
936
|
+
if (!result.valid && result.errors) {
|
|
937
|
+
const prefixedErrors = result.errors.map((err) => `${key}: ${err}`);
|
|
938
|
+
errors.push(...prefixedErrors);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
for (const key in value) if (!Object.prototype.hasOwnProperty.call(schema.definition, key)) {
|
|
942
|
+
if (options?.ignoreUnknownProperties) continue;
|
|
943
|
+
errors.push(`Unknown property: ${key}`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
else errors.push(`Should be a schema, but got ${actualType}`);
|
|
947
|
+
break;
|
|
948
|
+
default: errors.push(`Unknown schema type: ${actualType}`);
|
|
949
|
+
}
|
|
950
|
+
if (schema.options.validate && typeof schema.options.validate === "function") try {
|
|
951
|
+
const customValidation = schema.options.validate(value);
|
|
952
|
+
if (customValidation !== true) {
|
|
953
|
+
const errorMessage = typeof customValidation === "string" ? customValidation : "Value failed custom validation";
|
|
954
|
+
errors.push(errorMessage);
|
|
955
|
+
}
|
|
956
|
+
} catch (error) {
|
|
957
|
+
errors.push(`Validation error: ${String(error)}`);
|
|
958
|
+
}
|
|
959
|
+
if (errors.length === 0) {
|
|
960
|
+
if (useCache) markSchemaValidated(schema, value);
|
|
961
|
+
return { valid: true };
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
valid: false,
|
|
965
|
+
errors
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function validateTransformablePrimitive(schema, value, expectedType, errors) {
|
|
969
|
+
const transform = getTransform(schema);
|
|
970
|
+
if (transform) {
|
|
971
|
+
if (transform.validate) try {
|
|
972
|
+
const result = transform.validate(value);
|
|
973
|
+
if (result !== true) errors.push(typeof result === "string" ? result : "Transform validation failed");
|
|
974
|
+
} catch (error) {
|
|
975
|
+
errors.push(`Transform validation error: ${String(error)}`);
|
|
976
|
+
}
|
|
977
|
+
if (transform.validateEncodedType) try {
|
|
978
|
+
const encoded = applyEncode(schema, value);
|
|
979
|
+
if (typeof encoded !== expectedType) errors.push(`Transform encode must return a ${expectedType}, got ${typeof encoded}`);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
errors.push(`Transform encode validation error: ${String(error)}`);
|
|
982
|
+
}
|
|
983
|
+
} else if (typeof value !== expectedType) errors.push(`Value must be a ${expectedType}`);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Get default value for a schema
|
|
987
|
+
* Based on the schema type, it might return a plain value or a wrapped value
|
|
988
|
+
*/
|
|
989
|
+
function getDefaultValue(schema) {
|
|
990
|
+
if ("defaultValue" in schema.options) return schema.options.defaultValue;
|
|
991
|
+
switch (schema.type) {
|
|
992
|
+
case "any": return;
|
|
993
|
+
case "string":
|
|
994
|
+
if (schema.options.required === false) return;
|
|
995
|
+
if (getTransform(schema)) return;
|
|
996
|
+
return "";
|
|
997
|
+
case "number":
|
|
998
|
+
if (schema.options.required === false) return;
|
|
999
|
+
if (getTransform(schema)) return;
|
|
1000
|
+
return 0;
|
|
1001
|
+
case "boolean":
|
|
1002
|
+
if (schema.options.required === false) return;
|
|
1003
|
+
if (getTransform(schema)) return;
|
|
1004
|
+
return false;
|
|
1005
|
+
case "loro-text": {
|
|
1006
|
+
const value = schema.options.required ? "" : void 0;
|
|
1007
|
+
if (value === void 0) return void 0;
|
|
1008
|
+
return value;
|
|
1009
|
+
}
|
|
1010
|
+
case "loro-map":
|
|
1011
|
+
if (isLoroMapSchema(schema)) {
|
|
1012
|
+
const result = {};
|
|
1013
|
+
for (const key in schema.definition) if (Object.prototype.hasOwnProperty.call(schema.definition, key)) {
|
|
1014
|
+
const value = getDefaultValue(schema.definition[key]);
|
|
1015
|
+
if (value !== void 0) result[key] = value;
|
|
1016
|
+
}
|
|
1017
|
+
return result;
|
|
1018
|
+
}
|
|
1019
|
+
return {};
|
|
1020
|
+
case "loro-list": return [];
|
|
1021
|
+
case "loro-tree": {
|
|
1022
|
+
const value = schema.options.required ? [] : void 0;
|
|
1023
|
+
if (value === void 0) return void 0;
|
|
1024
|
+
return value;
|
|
1025
|
+
}
|
|
1026
|
+
case "schema":
|
|
1027
|
+
if (isRootSchemaType(schema)) {
|
|
1028
|
+
const result = {};
|
|
1029
|
+
for (const key in schema.definition) if (Object.prototype.hasOwnProperty.call(schema.definition, key)) {
|
|
1030
|
+
const value = getDefaultValue(schema.definition[key]);
|
|
1031
|
+
if (value !== void 0) result[key] = value;
|
|
1032
|
+
}
|
|
1033
|
+
return result;
|
|
1034
|
+
}
|
|
1035
|
+
return {};
|
|
1036
|
+
default: return;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region ../../node_modules/.pnpm/loro-mirror@2.3.3_patch_hash=a551a0e4aa4f61ec88b0d9e970a183b5ee2955107a2e0bb03b378bc510c2f395_loro-crdt@1.16.1/node_modules/loro-mirror/dist/schema/index.js
|
|
1041
|
+
/**
|
|
1042
|
+
* Create a schema definition
|
|
1043
|
+
*/
|
|
1044
|
+
function schema(definition, options) {
|
|
1045
|
+
return {
|
|
1046
|
+
type: "schema",
|
|
1047
|
+
definition,
|
|
1048
|
+
options: options || {},
|
|
1049
|
+
getContainerType() {
|
|
1050
|
+
return "Map";
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Define a string field
|
|
1056
|
+
*/
|
|
1057
|
+
schema.String = (function(options) {
|
|
1058
|
+
const baseSchema = {
|
|
1059
|
+
type: "string",
|
|
1060
|
+
options: options || {},
|
|
1061
|
+
getContainerType: () => {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
return {
|
|
1066
|
+
...baseSchema,
|
|
1067
|
+
transform: (def) => ({
|
|
1068
|
+
...baseSchema,
|
|
1069
|
+
transform: def
|
|
1070
|
+
})
|
|
1071
|
+
};
|
|
1072
|
+
});
|
|
1073
|
+
/**
|
|
1074
|
+
* Define an any field (runtime-inferred by Mirror)
|
|
1075
|
+
*/
|
|
1076
|
+
schema.Any = function(options) {
|
|
1077
|
+
return {
|
|
1078
|
+
type: "any",
|
|
1079
|
+
options: options || {},
|
|
1080
|
+
getContainerType: () => {
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
};
|
|
1085
|
+
/**
|
|
1086
|
+
* Define a number field
|
|
1087
|
+
*/
|
|
1088
|
+
schema.Number = function(options) {
|
|
1089
|
+
const baseSchema = {
|
|
1090
|
+
type: "number",
|
|
1091
|
+
options: options || {},
|
|
1092
|
+
getContainerType: () => {
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
return {
|
|
1097
|
+
...baseSchema,
|
|
1098
|
+
transform: (def) => ({
|
|
1099
|
+
...baseSchema,
|
|
1100
|
+
transform: def
|
|
1101
|
+
})
|
|
1102
|
+
};
|
|
1103
|
+
};
|
|
1104
|
+
/**
|
|
1105
|
+
* Define a boolean field
|
|
1106
|
+
*/
|
|
1107
|
+
schema.Boolean = function(options) {
|
|
1108
|
+
const baseSchema = {
|
|
1109
|
+
type: "boolean",
|
|
1110
|
+
options: options || {},
|
|
1111
|
+
getContainerType: () => {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
return {
|
|
1116
|
+
...baseSchema,
|
|
1117
|
+
transform: (def) => ({
|
|
1118
|
+
...baseSchema,
|
|
1119
|
+
transform: def
|
|
1120
|
+
})
|
|
1121
|
+
};
|
|
1122
|
+
};
|
|
1123
|
+
/**
|
|
1124
|
+
* Define a field to be ignored (not synced with Loro)
|
|
1125
|
+
*/
|
|
1126
|
+
schema.Ignore = function(options) {
|
|
1127
|
+
return {
|
|
1128
|
+
type: "ignore",
|
|
1129
|
+
options: options || {},
|
|
1130
|
+
getContainerType: () => {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
};
|
|
1135
|
+
/**
|
|
1136
|
+
* Define a Loro map
|
|
1137
|
+
*/
|
|
1138
|
+
schema.LoroMap = function(definition, options) {
|
|
1139
|
+
const baseSchema = {
|
|
1140
|
+
type: "loro-map",
|
|
1141
|
+
definition,
|
|
1142
|
+
options: options || {},
|
|
1143
|
+
getContainerType: () => {
|
|
1144
|
+
return "Map";
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
const schemaWithCatchall = {
|
|
1148
|
+
...baseSchema,
|
|
1149
|
+
catchall: (catchallSchema) => {
|
|
1150
|
+
return {
|
|
1151
|
+
...baseSchema,
|
|
1152
|
+
catchallType: catchallSchema,
|
|
1153
|
+
catchall: (newCatchallSchema) => {
|
|
1154
|
+
return {
|
|
1155
|
+
...baseSchema,
|
|
1156
|
+
catchallType: newCatchallSchema,
|
|
1157
|
+
catchall: schemaWithCatchall.catchall
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
return schemaWithCatchall;
|
|
1164
|
+
};
|
|
1165
|
+
/**
|
|
1166
|
+
* Create a dynamic record schema (like zod's z.record)
|
|
1167
|
+
*/
|
|
1168
|
+
schema.LoroMapRecord = function(valueSchema, options) {
|
|
1169
|
+
return {
|
|
1170
|
+
type: "loro-map",
|
|
1171
|
+
definition: {},
|
|
1172
|
+
catchallType: valueSchema,
|
|
1173
|
+
options: options || {},
|
|
1174
|
+
getContainerType: () => {
|
|
1175
|
+
return "Map";
|
|
1176
|
+
},
|
|
1177
|
+
catchall: (newCatchallSchema) => {
|
|
1178
|
+
return schema.LoroMapRecord(newCatchallSchema, options);
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
};
|
|
1182
|
+
/**
|
|
1183
|
+
* Define a Loro list
|
|
1184
|
+
*/
|
|
1185
|
+
schema.LoroList = function(itemSchema, idSelector, options) {
|
|
1186
|
+
return {
|
|
1187
|
+
type: "loro-list",
|
|
1188
|
+
itemSchema,
|
|
1189
|
+
idSelector,
|
|
1190
|
+
options: options || {},
|
|
1191
|
+
getContainerType: () => {
|
|
1192
|
+
return "List";
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
};
|
|
1196
|
+
schema.LoroMovableList = function(itemSchema, idSelector, options) {
|
|
1197
|
+
return {
|
|
1198
|
+
type: "loro-movable-list",
|
|
1199
|
+
itemSchema,
|
|
1200
|
+
idSelector,
|
|
1201
|
+
options: options || {},
|
|
1202
|
+
getContainerType: () => {
|
|
1203
|
+
return "MovableList";
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
};
|
|
1207
|
+
/**
|
|
1208
|
+
* Define a Loro text field
|
|
1209
|
+
*/
|
|
1210
|
+
schema.LoroText = function(options) {
|
|
1211
|
+
return {
|
|
1212
|
+
type: "loro-text",
|
|
1213
|
+
options: options || {},
|
|
1214
|
+
getContainerType: () => {
|
|
1215
|
+
return "Text";
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
};
|
|
1219
|
+
/**
|
|
1220
|
+
* Define a Loro tree
|
|
1221
|
+
*
|
|
1222
|
+
* Each tree node has a `data` map described by `nodeSchema`.
|
|
1223
|
+
*/
|
|
1224
|
+
schema.LoroTree = function(nodeSchema, options) {
|
|
1225
|
+
return {
|
|
1226
|
+
type: "loro-tree",
|
|
1227
|
+
nodeSchema,
|
|
1228
|
+
options: options || {},
|
|
1229
|
+
getContainerType() {
|
|
1230
|
+
return "Tree";
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
};
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region ../medeo-dsl/src/schema/codec.ts
|
|
1236
|
+
function isInvalidAtomic(value) {
|
|
1237
|
+
return typeof value === "object" && value !== null && "invalid" in value && value.invalid === true;
|
|
1238
|
+
}
|
|
1239
|
+
function codec(encode, decode) {
|
|
1240
|
+
return {
|
|
1241
|
+
encode: (v) => isInvalidAtomic(v) ? v.raw : encode(v),
|
|
1242
|
+
decode: (raw) => {
|
|
1243
|
+
try {
|
|
1244
|
+
return decode(raw);
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
return {
|
|
1247
|
+
invalid: true,
|
|
1248
|
+
raw,
|
|
1249
|
+
reason: String(error)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
const timeAnchorCodec = codec((v) => {
|
|
1256
|
+
assertRelation("time-anchor-relations", v);
|
|
1257
|
+
return JSON.stringify({
|
|
1258
|
+
from: v.from,
|
|
1259
|
+
to: v.to,
|
|
1260
|
+
offsetMs: v.offsetMs,
|
|
1261
|
+
fallback_abs_ms: v.fallback_abs_ms
|
|
1262
|
+
});
|
|
1263
|
+
}, (raw) => {
|
|
1264
|
+
const v = JSON.parse(raw);
|
|
1265
|
+
assertRelation("time-anchor-relations", v);
|
|
1266
|
+
return v;
|
|
1267
|
+
});
|
|
1268
|
+
const captionContentCodec = codec((v) => {
|
|
1269
|
+
assertRelation("caption-content-relations", v);
|
|
1270
|
+
return JSON.stringify({
|
|
1271
|
+
captionId: v.captionId,
|
|
1272
|
+
audioScriptId: v.audioScriptId,
|
|
1273
|
+
start: bytesToBase64url(v.start.encode()),
|
|
1274
|
+
end: bytesToBase64url(v.end.encode())
|
|
1275
|
+
});
|
|
1276
|
+
}, (raw) => {
|
|
1277
|
+
const v = JSON.parse(raw);
|
|
1278
|
+
if (typeof v?.start !== "string" || typeof v?.end !== "string") throw new Error("Expected encoded Cursor pair");
|
|
1279
|
+
const value = {
|
|
1280
|
+
...v,
|
|
1281
|
+
start: Cursor.decode(base64urlToBytes(v.start)),
|
|
1282
|
+
end: Cursor.decode(base64urlToBytes(v.end))
|
|
1283
|
+
};
|
|
1284
|
+
assertRelation("caption-content-relations", value);
|
|
1285
|
+
return value;
|
|
1286
|
+
});
|
|
1287
|
+
/** A Clip's sequence marker is stored as one string, so it is taken or replaced whole. */
|
|
1288
|
+
const seqMarkerCodec = codec((v) => {
|
|
1289
|
+
assertSeqMarker(v);
|
|
1290
|
+
return JSON.stringify({
|
|
1291
|
+
inMs: v.inMs,
|
|
1292
|
+
outMs: v.outMs,
|
|
1293
|
+
...v.rate === void 0 ? {} : { rate: v.rate },
|
|
1294
|
+
...v.fillTimeline === void 0 ? {} : { fillTimeline: v.fillTimeline }
|
|
1295
|
+
});
|
|
1296
|
+
}, (raw) => {
|
|
1297
|
+
const v = JSON.parse(raw);
|
|
1298
|
+
assertSeqMarker(v);
|
|
1299
|
+
return v;
|
|
1300
|
+
});
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region ../medeo-dsl/src/schema/loro-shape.ts
|
|
1303
|
+
const optional = { required: false };
|
|
1304
|
+
const mergeable = { mergeableMapChildContainers: true };
|
|
1305
|
+
const strings = (keys) => Object.fromEntries(keys.map((key) => [key, schema.String()]));
|
|
1306
|
+
const relation = (keys) => schema.LoroMapRecord(schema.LoroMap(strings(keys)), mergeable);
|
|
1307
|
+
const atomicValue = (codec) => schema.String().transform({
|
|
1308
|
+
...codec,
|
|
1309
|
+
isEqual: "encoded-value-equality",
|
|
1310
|
+
validateEncodedType: true
|
|
1311
|
+
});
|
|
1312
|
+
const atomic = (codec) => schema.LoroMapRecord(atomicValue(codec));
|
|
1313
|
+
/** Only direct children inherit mergeable identity. Body and ordinary entity rows keep normal identities. */
|
|
1314
|
+
const medeoDslLoroShape = schema({
|
|
1315
|
+
"timeline-entities": schema.LoroMapRecord(schema.LoroMap({})),
|
|
1316
|
+
"track-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1317
|
+
role: schema.String(),
|
|
1318
|
+
hidden: schema.Boolean(optional)
|
|
1319
|
+
})),
|
|
1320
|
+
"clip-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1321
|
+
seqMarker: atomicValue(seqMarkerCodec),
|
|
1322
|
+
volume: schema.Number(optional)
|
|
1323
|
+
})),
|
|
1324
|
+
"media-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1325
|
+
kind: schema.String(),
|
|
1326
|
+
system: schema.String(),
|
|
1327
|
+
key: schema.String(),
|
|
1328
|
+
storageKey: schema.String(optional),
|
|
1329
|
+
durationMs: schema.Number(optional),
|
|
1330
|
+
digest: schema.String(optional)
|
|
1331
|
+
}), mergeable),
|
|
1332
|
+
"audio-script-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1333
|
+
body: schema.LoroText(),
|
|
1334
|
+
defaultLanguage: schema.String(optional)
|
|
1335
|
+
})),
|
|
1336
|
+
"caption-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1337
|
+
text: schema.LoroText(optional),
|
|
1338
|
+
style: schema.LoroMap({
|
|
1339
|
+
font: schema.LoroMap({
|
|
1340
|
+
system: schema.String(),
|
|
1341
|
+
key: schema.String()
|
|
1342
|
+
}, optional),
|
|
1343
|
+
fontSize: schema.Number(optional),
|
|
1344
|
+
fontColor: schema.String(optional),
|
|
1345
|
+
fontWeight: schema.Number(optional),
|
|
1346
|
+
entranceAnimation: schema.String(optional),
|
|
1347
|
+
entranceAnimationDurationMs: schema.Number(optional),
|
|
1348
|
+
strokeColor: schema.String(optional),
|
|
1349
|
+
strokeWidth: schema.Number(optional),
|
|
1350
|
+
positionX: schema.Number(optional),
|
|
1351
|
+
positionY: schema.Number(optional)
|
|
1352
|
+
}, {
|
|
1353
|
+
...optional,
|
|
1354
|
+
...mergeable
|
|
1355
|
+
})
|
|
1356
|
+
}, mergeable)),
|
|
1357
|
+
"voice-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1358
|
+
system: schema.String(),
|
|
1359
|
+
key: schema.String(),
|
|
1360
|
+
name: schema.String(optional)
|
|
1361
|
+
}), mergeable),
|
|
1362
|
+
"asset-entities": schema.LoroMapRecord(schema.LoroMap({
|
|
1363
|
+
system: schema.String(),
|
|
1364
|
+
key: schema.String(),
|
|
1365
|
+
storageKey: schema.String(optional),
|
|
1366
|
+
digest: schema.String(optional)
|
|
1367
|
+
}), mergeable),
|
|
1368
|
+
"contain-relations": schema.LoroTree(schema.LoroMap({ entityId: schema.String() })),
|
|
1369
|
+
"clip-source-relations": relation(["clipId", "sourceId"]),
|
|
1370
|
+
"time-anchor-relations": atomic(timeAnchorCodec),
|
|
1371
|
+
"caption-content-relations": atomic(captionContentCodec),
|
|
1372
|
+
"caption-alignment-relations": schema.LoroMapRecord(schema.LoroMap({
|
|
1373
|
+
captionId: schema.String(),
|
|
1374
|
+
mediaId: schema.String(),
|
|
1375
|
+
startMs: schema.Number(),
|
|
1376
|
+
endMs: schema.Number()
|
|
1377
|
+
}), mergeable),
|
|
1378
|
+
"voice-timbre-relations": relation(["recordingId", "voiceId"]),
|
|
1379
|
+
"derived-from-relations": relation(["entityId", "sourceId"]),
|
|
1380
|
+
"from-asset-relations": relation(["audioScriptId", "assetId"]),
|
|
1381
|
+
"to-asset-relations": relation(["audioScriptId", "assetId"])
|
|
1382
|
+
});
|
|
1383
|
+
//#endregion
|
|
1384
|
+
//#region ../medeo-dsl/src/schema/document-format.ts
|
|
1385
|
+
/** Format discrimination does not register containers or alter the document. */
|
|
1386
|
+
function documentFormat(doc) {
|
|
1387
|
+
const roots = doc.getShallowValue();
|
|
1388
|
+
const legacy = [
|
|
1389
|
+
"medeo",
|
|
1390
|
+
"timeline",
|
|
1391
|
+
"tracks",
|
|
1392
|
+
"part_library"
|
|
1393
|
+
].some((key) => Object.hasOwn(roots, key));
|
|
1394
|
+
const dsl = Object.keys(roots).some((key) => key.endsWith("-entities") || key.endsWith("-relations"));
|
|
1395
|
+
return legacy && dsl ? "mixed" : legacy ? "legacy" : dsl ? "dsl" : "unknown";
|
|
1396
|
+
}
|
|
1397
|
+
var UnsupportedDocumentFormatError = class extends Error {
|
|
1398
|
+
code = "unsupported_document_format";
|
|
1399
|
+
constructor() {
|
|
1400
|
+
super("Expected a new DSL document; legacy, mixed or uninitialized content is not supported");
|
|
1401
|
+
this.name = "UnsupportedDocumentFormatError";
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
/** Run after initial import, before opening an editable handle. Empty new documents still have one Timeline. */
|
|
1405
|
+
function assertDslDocument(doc) {
|
|
1406
|
+
if (documentFormat(doc) !== "dsl" || doc.getMap("timeline-entities").keys().length !== 1) throw new UnsupportedDocumentFormatError();
|
|
1407
|
+
}
|
|
1408
|
+
//#endregion
|
|
1409
|
+
//#region ../medeo-dsl/src/schema/text-styles.ts
|
|
1410
|
+
/** How rich-text marks extend at their edges: a language runs on into new text, a pause stays on its space. */
|
|
1411
|
+
const textStyles = {
|
|
1412
|
+
language: { expand: "after" },
|
|
1413
|
+
pauseMs: { expand: "none" }
|
|
1414
|
+
};
|
|
1415
|
+
//#endregion
|
|
1416
|
+
//#region src/dsl/projection/video-draft-types.ts
|
|
1417
|
+
const DEFAULT_UNIT_TIME_MS = 33.333;
|
|
1418
|
+
//#endregion
|
|
1419
|
+
export { assertRelation as $, hardenCidDescriptors as A, safeStringify as B, applySchemaToInferOptions as C, deepEqual as D, decodeNestedJsonValues as E, isObject as F, valuesEqual as G, stripUndefined as H, isObjectLike as I, getChildSchema as J, CID_KEY as K, isStateAndSchemaOfType as L, inferContainerTypeFromValue as M, insertChildToMap as N, defineCidProperty as O, isArrayLike as P, assertId as Q, isTreeID as R, applyEncode as S, containerIdToContainerType as T, tryInferContainerType as U, schemaToContainerType as V, tryUpdateToContainer as W, assertEntity as X, getMapFieldSchema as Y, assertEntityPatch as Z, isLoroTreeSchema as _, documentFormat as a, assertRelationIdentity as at, validateSchema as b, isInvalidAtomic as c, relationId as ct, getDefaultValue as d, entityTables as et, isContainerSchema as f, isLoroMovableListSchema as g, isLoroMapSchema as h, assertDslDocument as i, validLanguage as it, hasTransform as j, getRootContainerByType as k, timeAnchorCodec as l, resourceId as lt, isLoroListSchema as m, textStyles as n, mergeFields as nt, medeoDslLoroShape as o, assertResourceIdentity as ot, isListLikeSchema as p, getChildContainerSchema as q, UnsupportedDocumentFormatError as r, relationTables as rt, captionContentCodec as s, defaultIdFactory as st, DEFAULT_UNIT_TIME_MS as t, isRecord as tt, schema as u, tupleHash as ut, isRootSchemaType as v, cidsEqual as w, applyDecode as x, schemaContainsIgnoreField as y, isValueOfContainerType as z };
|