svedit 0.11.1 → 0.13.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 +260 -82
- package/dist/Command.svelte.d.ts +54 -29
- package/dist/Command.svelte.d.ts.map +1 -1
- package/dist/Command.svelte.js +286 -283
- package/dist/CustomProperty.svelte +3 -5
- package/dist/CustomProperty.svelte.d.ts +3 -6
- package/dist/CustomProperty.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.d.ts +19 -6
- package/dist/KeyMapper.svelte.d.ts.map +1 -1
- package/dist/KeyMapper.svelte.js +95 -112
- package/dist/Node.svelte +40 -9
- package/dist/Node.svelte.d.ts +3 -5
- package/dist/Node.svelte.d.ts.map +1 -1
- package/dist/NodeArrayProperty.svelte +142 -18
- package/dist/NodeArrayProperty.svelte.d.ts +3 -5
- package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
- package/dist/NodeCaret.svelte +1 -1
- package/dist/NodeCaret.svelte.d.ts +6 -14
- package/dist/NodeCaret.svelte.d.ts.map +1 -1
- package/dist/NodeGap.svelte +60 -12
- package/dist/NodeGap.svelte.d.ts +7 -14
- package/dist/NodeGap.svelte.d.ts.map +1 -1
- package/dist/NodeGapMarkers.svelte +19 -10
- package/dist/NodeGapMarkers.svelte.d.ts +6 -11
- package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
- package/dist/NodeSelectionMarkers.svelte +20 -6
- package/dist/NodeSelectionMarkers.svelte.d.ts +2 -5
- package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.d.ts +85 -116
- package/dist/Session.svelte.d.ts.map +1 -1
- package/dist/Session.svelte.js +543 -571
- package/dist/Svedit.svelte +243 -216
- package/dist/Svedit.svelte.d.ts +30 -10
- package/dist/Svedit.svelte.d.ts.map +1 -1
- package/dist/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
- package/dist/TextProperty.svelte.d.ts +5 -0
- package/dist/TextProperty.svelte.d.ts.map +1 -0
- package/dist/Transaction.svelte.d.ts +101 -126
- package/dist/Transaction.svelte.d.ts.map +1 -1
- package/dist/Transaction.svelte.js +843 -827
- package/dist/UnknownNode.svelte +4 -3
- package/dist/UnknownNode.svelte.d.ts +5 -9
- package/dist/UnknownNode.svelte.d.ts.map +1 -1
- package/dist/doc_utils.d.ts +109 -122
- package/dist/doc_utils.d.ts.map +1 -1
- package/dist/doc_utils.js +726 -698
- package/dist/index.d.ts +20 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -18
- package/dist/node_visibility.svelte.d.ts +132 -5
- package/dist/node_visibility.svelte.d.ts.map +1 -1
- package/dist/node_visibility.svelte.js +535 -642
- package/dist/paste_utils.d.ts +11 -43
- package/dist/paste_utils.d.ts.map +1 -1
- package/dist/paste_utils.js +111 -149
- package/dist/transforms.svelte.d.ts +9 -7
- package/dist/transforms.svelte.d.ts.map +1 -1
- package/dist/transforms.svelte.js +126 -152
- package/dist/types.d.ts +329 -291
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +79 -95
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +392 -305
- package/package.json +69 -67
- package/src/lib/{Command.svelte.js → Command.svelte.ts} +111 -41
- package/src/lib/CustomProperty.svelte +3 -5
- package/src/lib/{KeyMapper.svelte.js → KeyMapper.svelte.ts} +28 -11
- package/src/lib/Node.svelte +40 -9
- package/src/lib/NodeArrayProperty.svelte +142 -18
- package/src/lib/NodeCaret.svelte +1 -1
- package/src/lib/NodeGap.svelte +60 -12
- package/src/lib/NodeGapMarkers.svelte +19 -10
- package/src/lib/NodeSelectionMarkers.svelte +20 -6
- package/src/lib/Session.svelte.ts +656 -0
- package/src/lib/Svedit.svelte +243 -216
- package/src/lib/{AnnotatedTextProperty.svelte → TextProperty.svelte} +71 -56
- package/src/lib/Transaction.svelte.ts +1020 -0
- package/src/lib/UnknownNode.svelte +4 -3
- package/src/lib/doc_utils.ts +1120 -0
- package/src/lib/{index.js → index.ts} +48 -1
- package/src/lib/node_visibility.svelte.ts +658 -0
- package/src/lib/{paste_utils.js → paste_utils.ts} +51 -59
- package/src/lib/{transforms.svelte.js → transforms.svelte.ts} +44 -27
- package/src/lib/{types.d.ts → types.ts} +250 -103
- package/src/lib/utils.ts +660 -0
- package/dist/AnnotatedTextProperty.svelte.d.ts +0 -7
- package/dist/AnnotatedTextProperty.svelte.d.ts.map +0 -1
- package/src/lib/Session.svelte.js +0 -575
- package/src/lib/Transaction.svelte.js +0 -846
- package/src/lib/doc_utils.js +0 -807
- package/src/lib/node_visibility.svelte.js +0 -718
- package/src/lib/utils.js +0 -465
package/src/lib/doc_utils.js
DELETED
|
@@ -1,807 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared document utilities used by both Session and Transaction.
|
|
3
|
-
*
|
|
4
|
-
* These functions operate on the core document state (schema, doc, selection, config)
|
|
5
|
-
* without any history management or transaction tracking.
|
|
6
|
-
*
|
|
7
|
-
* @import { NodeId, DocumentPath, PrimitiveType, NodeProperty, NodeArrayProperty, NodeSchema, DocumentSchema, Selection, Annotation, Document, AnnotatedTextProperty, AnnotatedText, ValidateDocumentSchema } from './types'
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
assert_path_string_segment,
|
|
12
|
-
is_path_string_segment_valid,
|
|
13
|
-
get_selection_range,
|
|
14
|
-
get_char_length,
|
|
15
|
-
serialize_path
|
|
16
|
-
} from './utils.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Identity function — keeps schema at runtime & makes IDE infer types.
|
|
20
|
-
* Similar to your define_schema pattern but for document schemas.
|
|
21
|
-
*
|
|
22
|
-
* @template {Record<string, NodeSchema>} S
|
|
23
|
-
* @param {S & ValidateDocumentSchema<S>} schema - The document schema to validate
|
|
24
|
-
* @returns {S} The same schema, but with type information preserved
|
|
25
|
-
*/
|
|
26
|
-
export function define_document_schema(schema) {
|
|
27
|
-
return schema;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Check if a string represents a valid primitive type.
|
|
32
|
-
* @param {string} type - The type string to check
|
|
33
|
-
* @returns {type is PrimitiveType} True if it's a valid primitive type
|
|
34
|
-
*/
|
|
35
|
-
export function is_primitive_type(type) {
|
|
36
|
-
return [
|
|
37
|
-
'string',
|
|
38
|
-
'number',
|
|
39
|
-
'boolean',
|
|
40
|
-
'integer',
|
|
41
|
-
'datetime',
|
|
42
|
-
'annotated_text',
|
|
43
|
-
'string_array',
|
|
44
|
-
'number_array',
|
|
45
|
-
'boolean_array',
|
|
46
|
-
'integer_array'
|
|
47
|
-
].includes(type);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Get the default node type for a property that references nodes.
|
|
52
|
-
* @param {NodeProperty | NodeArrayProperty} property_definition - The property definition
|
|
53
|
-
* @returns {string | null} The default node type, or null if none specified
|
|
54
|
-
*/
|
|
55
|
-
export function get_default_node_type(property_definition) {
|
|
56
|
-
if (!property_definition || !property_definition.node_types) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
property_definition.default_node_type ||
|
|
62
|
-
(property_definition.node_types.length === 1 ? property_definition.node_types[0] : null)
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @param {import('./types').PropertyDefinition} property_definition
|
|
68
|
-
* @returns {any}
|
|
69
|
-
*/
|
|
70
|
-
export function get_property_default(property_definition) {
|
|
71
|
-
if ('default' in property_definition) return structuredClone(property_definition.default);
|
|
72
|
-
|
|
73
|
-
if (property_definition.type === 'string') return '';
|
|
74
|
-
if (property_definition.type === 'integer') return 0;
|
|
75
|
-
if (property_definition.type === 'number') return 0;
|
|
76
|
-
if (property_definition.type === 'boolean') return false;
|
|
77
|
-
if (property_definition.type === 'annotated_text') return { text: '', annotations: [] };
|
|
78
|
-
if (property_definition.type === 'node_array') return [];
|
|
79
|
-
if (
|
|
80
|
-
property_definition.type === 'string_array' ||
|
|
81
|
-
property_definition.type === 'number_array' ||
|
|
82
|
-
property_definition.type === 'boolean_array' ||
|
|
83
|
-
property_definition.type === 'integer_array'
|
|
84
|
-
) {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Fill omitted properties with schema defaults and Svedit's built-in type defaults.
|
|
93
|
-
*
|
|
94
|
-
* This is a convenience helper for schema evolution, not a complete document
|
|
95
|
-
* migration system. Callers are still responsible for proper migrations when
|
|
96
|
-
* schema changes cannot be represented by defaults, such as property renames or
|
|
97
|
-
* data transformations.
|
|
98
|
-
*
|
|
99
|
-
* @param {any} node - The node to fill defaults for
|
|
100
|
-
* @param {DocumentSchema} schema - The document schema
|
|
101
|
-
* @returns {any} A shallow copy of the node with cloned default values filled in
|
|
102
|
-
*/
|
|
103
|
-
export function fill_node_defaults(node, schema) {
|
|
104
|
-
const node_schema = schema[node.type];
|
|
105
|
-
if (!node_schema) return { ...node };
|
|
106
|
-
|
|
107
|
-
const node_with_defaults = { ...node };
|
|
108
|
-
|
|
109
|
-
for (const [property_name, property_definition] of Object.entries(node_schema.properties)) {
|
|
110
|
-
if (node_with_defaults[property_name] === undefined) {
|
|
111
|
-
const property_default = get_property_default(property_definition);
|
|
112
|
-
if (property_default !== undefined) node_with_defaults[property_name] = property_default;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return node_with_defaults;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Fill omitted properties with schema defaults and Svedit's built-in type defaults across a document.
|
|
121
|
-
*
|
|
122
|
-
* This does not infer values, rename fields, or make an invalid migration valid
|
|
123
|
-
* by itself. Call this as one step in an explicit document migration when it is
|
|
124
|
-
* appropriate.
|
|
125
|
-
*
|
|
126
|
-
* @param {Document} doc - The document to fill defaults for
|
|
127
|
-
* @param {DocumentSchema} schema - The document schema
|
|
128
|
-
* @returns {Document} A document copy with cloned default values filled in
|
|
129
|
-
*/
|
|
130
|
-
export function fill_document_defaults(doc, schema) {
|
|
131
|
-
/** @type {Record<string, any>} */
|
|
132
|
-
const nodes = {};
|
|
133
|
-
|
|
134
|
-
for (const [node_id, node] of Object.entries(doc.nodes)) {
|
|
135
|
-
nodes[node_id] = fill_node_defaults(node, schema);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
...doc,
|
|
140
|
-
nodes
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Validate a document schema to ensure all referenced node types exist.
|
|
146
|
-
* @param {DocumentSchema} document_schema - The document schema to validate
|
|
147
|
-
* @throws {Error} Throws if the document schema is invalid
|
|
148
|
-
*/
|
|
149
|
-
export function validate_document_schema(document_schema) {
|
|
150
|
-
// Check that all referenced node types exist
|
|
151
|
-
for (const [node_type, node_schema] of Object.entries(document_schema)) {
|
|
152
|
-
for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
|
|
153
|
-
assert_path_string_segment(prop_name, `Property name "${prop_name}"`);
|
|
154
|
-
if (prop_def.type === 'node' || prop_def.type === 'node_array') {
|
|
155
|
-
const missing_types = prop_def.node_types.filter(
|
|
156
|
-
(ref_type) => !(ref_type in document_schema)
|
|
157
|
-
);
|
|
158
|
-
if (missing_types.length > 0) {
|
|
159
|
-
throw new Error(
|
|
160
|
-
`Node type "${node_type}" property "${prop_name}" references unknown node types: ${missing_types.join(', ')}. Available node types: ${Object.keys(document_schema).join(', ')}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Validate a primitive value against its schema type.
|
|
170
|
-
* @param {PrimitiveType} type - The expected type
|
|
171
|
-
* @param {any} value - The value to validate
|
|
172
|
-
* @returns {boolean} True if the value matches the type
|
|
173
|
-
*/
|
|
174
|
-
function validate_primitive_value(type, value) {
|
|
175
|
-
switch (type) {
|
|
176
|
-
case 'string':
|
|
177
|
-
return typeof value === 'string';
|
|
178
|
-
case 'number':
|
|
179
|
-
return typeof value === 'number' && !isNaN(value);
|
|
180
|
-
case 'boolean':
|
|
181
|
-
return typeof value === 'boolean';
|
|
182
|
-
case 'integer':
|
|
183
|
-
return Number.isInteger(value);
|
|
184
|
-
case 'datetime':
|
|
185
|
-
return typeof value === 'string' && !isNaN(Date.parse(value));
|
|
186
|
-
case 'annotated_text':
|
|
187
|
-
return (
|
|
188
|
-
typeof value === 'object' &&
|
|
189
|
-
value !== null &&
|
|
190
|
-
typeof value.text === 'string' &&
|
|
191
|
-
Array.isArray(value.annotations)
|
|
192
|
-
);
|
|
193
|
-
case 'string_array':
|
|
194
|
-
return Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
195
|
-
case 'number_array':
|
|
196
|
-
return Array.isArray(value) && value.every((v) => typeof v === 'number' && !isNaN(v));
|
|
197
|
-
case 'boolean_array':
|
|
198
|
-
return Array.isArray(value) && value.every((v) => typeof v === 'boolean');
|
|
199
|
-
case 'integer_array':
|
|
200
|
-
return Array.isArray(value) && value.every((v) => Number.isInteger(v));
|
|
201
|
-
default:
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Validate annotated text annotations for bounds, references, allowed types, and exclusivity.
|
|
208
|
-
*
|
|
209
|
-
* @param {string} node_id - Owner node id, used for error messages
|
|
210
|
-
* @param {string} prop_name - Owner property name, used for error messages
|
|
211
|
-
* @param {AnnotatedText} value - Annotated text value to validate
|
|
212
|
-
* @param {AnnotatedTextProperty} prop_def - Annotated text property definition
|
|
213
|
-
* @param {Record<string, any>} all_nodes - All document nodes
|
|
214
|
-
* @param {boolean} require_references - Whether referenced nodes must already exist
|
|
215
|
-
* @throws {Error} Throws if annotations are invalid
|
|
216
|
-
*/
|
|
217
|
-
function validate_annotated_text_property(node_id, prop_name, value, prop_def, all_nodes, require_references) {
|
|
218
|
-
const char_length = get_char_length(value.text);
|
|
219
|
-
const annotations = value.annotations;
|
|
220
|
-
|
|
221
|
-
for (const [index, annotation] of annotations.entries()) {
|
|
222
|
-
if (
|
|
223
|
-
typeof annotation !== 'object' ||
|
|
224
|
-
annotation === null ||
|
|
225
|
-
!Number.isInteger(annotation.start_offset) ||
|
|
226
|
-
!Number.isInteger(annotation.end_offset) ||
|
|
227
|
-
!is_id_valid(annotation.node_id)
|
|
228
|
-
) {
|
|
229
|
-
throw new Error(
|
|
230
|
-
`Node ${node_id} property ${prop_name} has an invalid annotation at index ${index}. Annotations must have integer start_offset/end_offset and a valid node_id.`
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (annotation.start_offset < 0 || annotation.end_offset > char_length) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
`Node ${node_id} property ${prop_name} annotation ${annotation.node_id} is out of bounds: ${annotation.start_offset}-${annotation.end_offset}, text length is ${char_length}.`
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (annotation.start_offset >= annotation.end_offset) {
|
|
241
|
-
throw new Error(
|
|
242
|
-
`Node ${node_id} property ${prop_name} annotation ${annotation.node_id} must not be empty or reversed: ${annotation.start_offset}-${annotation.end_offset}.`
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const referenced_node = all_nodes[annotation.node_id];
|
|
247
|
-
if (!referenced_node) {
|
|
248
|
-
if (require_references) {
|
|
249
|
-
throw new Error(
|
|
250
|
-
`Node ${node_id} property ${prop_name} annotation references missing node ${annotation.node_id}.`
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (prop_def.node_types?.length && !prop_def.node_types.includes(referenced_node.type)) {
|
|
257
|
-
throw new Error(
|
|
258
|
-
`Node ${node_id} property ${prop_name} annotation references node ${annotation.node_id} of type ${referenced_node.type}, but only types [${prop_def.node_types.join(', ')}] are allowed.`
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const sorted_annotations = annotations
|
|
264
|
-
.map((annotation, index) => ({ annotation, index }))
|
|
265
|
-
.sort((a, b) =>
|
|
266
|
-
a.annotation.start_offset - b.annotation.start_offset ||
|
|
267
|
-
a.annotation.end_offset - b.annotation.end_offset
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
for (let index = 1; index < sorted_annotations.length; index++) {
|
|
271
|
-
const previous = sorted_annotations[index - 1];
|
|
272
|
-
const current = sorted_annotations[index];
|
|
273
|
-
|
|
274
|
-
if (current.annotation.start_offset < previous.annotation.end_offset) {
|
|
275
|
-
throw new Error(
|
|
276
|
-
`Node ${node_id} property ${prop_name} has overlapping annotations: annotation index ${previous.index} (${previous.annotation.node_id}, ${previous.annotation.start_offset}-${previous.annotation.end_offset}) overlaps annotation index ${current.index} (${current.annotation.node_id}, ${current.annotation.start_offset}-${current.annotation.end_offset}). Annotations must be exclusive.`
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* @param {string} id
|
|
284
|
-
* @returns {boolean}
|
|
285
|
-
*/
|
|
286
|
-
export function is_id_valid(id) {
|
|
287
|
-
return typeof id === 'string' && id.length > 0 && is_path_string_segment_valid(id);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Validate a node against its schema.
|
|
292
|
-
* @param {any} node - The node to validate
|
|
293
|
-
* @param {DocumentSchema} schema - The document schema
|
|
294
|
-
* @param {Record<string, any>} all_nodes - All nodes in the document to check references
|
|
295
|
-
* @param {{ require_references?: boolean }} [options] - Validation options
|
|
296
|
-
* @throws {Error} Throws if the node is invalid
|
|
297
|
-
*/
|
|
298
|
-
export function validate_node(node, schema, all_nodes = {}, options = {}) {
|
|
299
|
-
const require_references = options.require_references ?? true;
|
|
300
|
-
if (!is_id_valid(node.id)) {
|
|
301
|
-
throw new Error(`Node ${node.id} has an invalid id.`);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (!node.type || !schema[node.type]) {
|
|
305
|
-
throw new Error(`Node ${node.id} has an invalid type: ${node.type}`);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const node_schema = schema[node.type];
|
|
309
|
-
|
|
310
|
-
for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
|
|
311
|
-
const value = node[prop_name];
|
|
312
|
-
|
|
313
|
-
// Check primitive types
|
|
314
|
-
if (is_primitive_type(prop_def.type)) {
|
|
315
|
-
if (!validate_primitive_value(prop_def.type, value)) {
|
|
316
|
-
throw new Error(
|
|
317
|
-
`Node ${node.id} has an invalid property: ${prop_name} must be of type ${prop_def.type}.`
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (prop_def.type === 'annotated_text') {
|
|
322
|
-
validate_annotated_text_property(
|
|
323
|
-
node.id,
|
|
324
|
-
prop_name,
|
|
325
|
-
value,
|
|
326
|
-
/** @type {AnnotatedTextProperty} */ (prop_def),
|
|
327
|
-
all_nodes,
|
|
328
|
-
require_references
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
// Check node references
|
|
332
|
-
if (prop_def.type === 'node') {
|
|
333
|
-
if (!is_id_valid(value)) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
`Node ${node.id} has an invalid property: ${prop_name} must be a valid node id.`
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
// Check if referenced node exists and is of allowed type
|
|
339
|
-
const referenced_node = all_nodes[value];
|
|
340
|
-
if (!referenced_node) {
|
|
341
|
-
if (require_references) {
|
|
342
|
-
throw new Error(
|
|
343
|
-
`Node ${node.id} property ${prop_name} references missing node ${value}.`
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
if (!prop_def.node_types.includes(referenced_node.type)) {
|
|
349
|
-
throw new Error(
|
|
350
|
-
`Node ${node.id} property ${prop_name} references node ${value} of type ${referenced_node.type}, but only types [${/** @type {NodeProperty} */ (prop_def).node_types.join(', ')}] are allowed.`
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// Check node arrays
|
|
355
|
-
else if (prop_def.type === 'node_array') {
|
|
356
|
-
if (
|
|
357
|
-
!Array.isArray(value) ||
|
|
358
|
-
!value.every((id) => typeof id === 'string' && is_id_valid(id))
|
|
359
|
-
) {
|
|
360
|
-
throw new Error(
|
|
361
|
-
`Node ${node.id} has an invalid property: ${prop_name} must be an array of node ids.`
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
// Check if all referenced nodes exist and are of allowed types
|
|
365
|
-
for (const ref_id of value) {
|
|
366
|
-
const referenced_node = all_nodes[ref_id];
|
|
367
|
-
if (!referenced_node) {
|
|
368
|
-
if (require_references) {
|
|
369
|
-
throw new Error(
|
|
370
|
-
`Node ${node.id} property ${prop_name} references missing node ${ref_id}.`
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
if (!prop_def.node_types.includes(referenced_node.type)) {
|
|
376
|
-
throw new Error(
|
|
377
|
-
`Node ${node.id} property ${prop_name} references node ${ref_id} of type ${referenced_node.type}, but only types [${prop_def.node_types.join(', ')}] are allowed.`
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Validate a document against its schema.
|
|
387
|
-
*
|
|
388
|
-
* @param {Document} doc - The document to validate
|
|
389
|
-
* @param {DocumentSchema} schema - The document schema
|
|
390
|
-
* @throws {Error} Throws if the document is invalid
|
|
391
|
-
*/
|
|
392
|
-
export function validate_document(doc, schema) {
|
|
393
|
-
if (!is_id_valid(doc.document_id)) {
|
|
394
|
-
throw new Error(`Document ${doc.document_id} has an invalid id.`);
|
|
395
|
-
}
|
|
396
|
-
for (const node of Object.values(doc.nodes)) {
|
|
397
|
-
validate_node(node, schema, doc.nodes);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Gets a value from the document at the specified path.
|
|
403
|
-
*
|
|
404
|
-
* @param {DocumentSchema} schema - The document schema
|
|
405
|
-
* @param {Document} doc - The document containing nodes
|
|
406
|
-
* @param {DocumentPath|string} path - Array path to the value, or a string node ID
|
|
407
|
-
* @returns {any} The value at the specified path
|
|
408
|
-
*/
|
|
409
|
-
export function get(schema, doc, path) {
|
|
410
|
-
if (typeof path === 'string') {
|
|
411
|
-
path = [path];
|
|
412
|
-
}
|
|
413
|
-
if (!(Array.isArray(path) && path.length >= 1)) {
|
|
414
|
-
throw new Error(`Invalid path provided ${JSON.stringify(path)}`);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
let val = doc.nodes[path[0]];
|
|
418
|
-
let val_type = 'node';
|
|
419
|
-
|
|
420
|
-
for (let i = 1; i < path.length; i++) {
|
|
421
|
-
const path_segment = path[i];
|
|
422
|
-
const path_segment_str = String(path_segment);
|
|
423
|
-
if (val_type === 'node') {
|
|
424
|
-
if (property_type(schema, val.type, path_segment_str) === 'node_array') {
|
|
425
|
-
val = val[path_segment];
|
|
426
|
-
val_type = 'node_array';
|
|
427
|
-
} else if (property_type(schema, val.type, path_segment_str) === 'annotated_text') {
|
|
428
|
-
val = val[path_segment];
|
|
429
|
-
val_type = 'annotated_text';
|
|
430
|
-
} else if (property_type(schema, val.type, path_segment_str) === 'node') {
|
|
431
|
-
val = doc.nodes[val[path_segment]];
|
|
432
|
-
val_type = 'node';
|
|
433
|
-
} else if (
|
|
434
|
-
['string_array', 'integer_array'].includes(
|
|
435
|
-
property_type(schema, val.type, path_segment_str)
|
|
436
|
-
)
|
|
437
|
-
) {
|
|
438
|
-
val = val[path_segment];
|
|
439
|
-
val_type = 'value_array';
|
|
440
|
-
} else {
|
|
441
|
-
val = val[path_segment];
|
|
442
|
-
val_type = 'value';
|
|
443
|
-
}
|
|
444
|
-
} else if (val_type === 'node_array') {
|
|
445
|
-
val = doc.nodes[val[path_segment]];
|
|
446
|
-
val_type = 'node';
|
|
447
|
-
} else if (val_type === 'value_array') {
|
|
448
|
-
val = val[path_segment];
|
|
449
|
-
val_type = 'value';
|
|
450
|
-
} else if (val_type === 'annotated_text') {
|
|
451
|
-
if (path_segment === 'text') {
|
|
452
|
-
val = val.text;
|
|
453
|
-
val_type = 'value';
|
|
454
|
-
} else if (path_segment === 'annotations') {
|
|
455
|
-
val = val.annotations;
|
|
456
|
-
val_type = 'annotation_array';
|
|
457
|
-
} else {
|
|
458
|
-
throw new Error(
|
|
459
|
-
`Invalid path segment "${path_segment}" for annotated_text. Use "text" or "annotations".`
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
} else if (val_type === 'annotation_array') {
|
|
463
|
-
val = val[path_segment];
|
|
464
|
-
val_type = 'annotation';
|
|
465
|
-
} else if (val_type === 'annotation') {
|
|
466
|
-
if (path_segment === 'node_id') {
|
|
467
|
-
val = doc.nodes[val.node_id];
|
|
468
|
-
val_type = 'node';
|
|
469
|
-
} else if (path_segment === 'start_offset') {
|
|
470
|
-
val = val.start_offset;
|
|
471
|
-
val_type = 'value';
|
|
472
|
-
} else if (path_segment === 'end_offset') {
|
|
473
|
-
val = val.end_offset;
|
|
474
|
-
val_type = 'value';
|
|
475
|
-
} else {
|
|
476
|
-
throw new Error(
|
|
477
|
-
`Invalid path segment "${path_segment}" for annotation. Use "start_offset", "end_offset", or "node_id".`
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return val;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Gets the type of a property from the schema.
|
|
487
|
-
*
|
|
488
|
-
* @param {object} schema - The document schema
|
|
489
|
-
* @param {string} type - The node type
|
|
490
|
-
* @param {string} property - The property name
|
|
491
|
-
* @returns {string} The property type
|
|
492
|
-
*/
|
|
493
|
-
export function property_type(schema, type, property) {
|
|
494
|
-
if (typeof type !== 'string') throw new Error(`Invalid type ${type} provided`);
|
|
495
|
-
if (typeof property !== 'string') throw new Error(`Invalid property ${property} provided`);
|
|
496
|
-
|
|
497
|
-
if (property === 'type') return 'string';
|
|
498
|
-
if (property === 'id') return 'string';
|
|
499
|
-
|
|
500
|
-
if (!schema[type]) throw new Error(`Type ${type} not found in schema`);
|
|
501
|
-
if (!schema[type].properties[property])
|
|
502
|
-
throw new Error(`Property ${property} not found in type ${type}`);
|
|
503
|
-
|
|
504
|
-
return schema[type].properties[property].type;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Determines the kind of a node ('block', 'text', or 'annotation').
|
|
509
|
-
*
|
|
510
|
-
* @param {object} schema - The document schema
|
|
511
|
-
* @param {any} node - The node to check
|
|
512
|
-
* @returns {'block'|'text'|'annotation'} The node kind
|
|
513
|
-
*/
|
|
514
|
-
export function kind(schema, node) {
|
|
515
|
-
return schema[node.type].kind;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Checks whether an annotation node can be switched to another annotation type
|
|
520
|
-
* without losing data.
|
|
521
|
-
*
|
|
522
|
-
* @param {DocumentSchema} schema - The document schema
|
|
523
|
-
* @param {any} annotation_node - The annotation node to switch
|
|
524
|
-
* @param {string} target_annotation_type - The target annotation type
|
|
525
|
-
* @returns {boolean} True if the annotation can be switched safely
|
|
526
|
-
*/
|
|
527
|
-
export function can_switch_annotation_type(schema, annotation_node, target_annotation_type) {
|
|
528
|
-
if (!annotation_node || annotation_node.type === target_annotation_type) return false;
|
|
529
|
-
|
|
530
|
-
const source_schema = schema[annotation_node.type];
|
|
531
|
-
const target_schema = schema[target_annotation_type];
|
|
532
|
-
if (source_schema?.kind !== 'annotation' || target_schema?.kind !== 'annotation') return false;
|
|
533
|
-
|
|
534
|
-
const source_has_properties = Object.keys(source_schema.properties ?? {}).length > 0;
|
|
535
|
-
const target_has_properties = Object.keys(target_schema.properties ?? {}).length > 0;
|
|
536
|
-
if (source_has_properties || target_has_properties) return false;
|
|
537
|
-
|
|
538
|
-
// eslint-disable-next-line no-unused-vars
|
|
539
|
-
const { id, type, ...extra_properties } = annotation_node;
|
|
540
|
-
return Object.keys(extra_properties).length === 0;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Inspects a path to get metadata about the value at that location.
|
|
545
|
-
*
|
|
546
|
-
* @param {DocumentSchema} schema - The document schema
|
|
547
|
-
* @param {Document} doc - The document containing nodes
|
|
548
|
-
* @param {DocumentPath} path - The path to inspect
|
|
549
|
-
* @returns {{kind: 'property'|'node', [key: string]: any}} Metadata about the path
|
|
550
|
-
*/
|
|
551
|
-
export function inspect(schema, doc, path) {
|
|
552
|
-
const parent = path.length > 1 ? get(schema, doc, path.slice(0, -1)) : undefined;
|
|
553
|
-
if (parent?.type) {
|
|
554
|
-
const property_name = path.at(-1);
|
|
555
|
-
return {
|
|
556
|
-
kind: 'property',
|
|
557
|
-
name: property_name,
|
|
558
|
-
...schema[parent.type].properties[property_name]
|
|
559
|
-
};
|
|
560
|
-
} else {
|
|
561
|
-
const node = get(schema, doc, path);
|
|
562
|
-
return {
|
|
563
|
-
kind: 'node',
|
|
564
|
-
id: node.id,
|
|
565
|
-
type: node.type,
|
|
566
|
-
properties: schema[node.type]
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Applies an operation to a document and returns the new document.
|
|
573
|
-
* Uses copy-on-write semantics.
|
|
574
|
-
*
|
|
575
|
-
* @param {Document} doc - The document to apply the operation to
|
|
576
|
-
* @param {Array} op - The operation to apply [type, ...args]
|
|
577
|
-
* @returns {Document} The new document with the operation applied
|
|
578
|
-
*/
|
|
579
|
-
export function apply_op(doc, op) {
|
|
580
|
-
const [type, ...args] = op;
|
|
581
|
-
if (type === 'set') {
|
|
582
|
-
const [node_id, property] = args[0];
|
|
583
|
-
const value = structuredClone(args[1]);
|
|
584
|
-
return {
|
|
585
|
-
...doc,
|
|
586
|
-
nodes: {
|
|
587
|
-
...doc.nodes,
|
|
588
|
-
[node_id]: {
|
|
589
|
-
...doc.nodes[node_id],
|
|
590
|
-
[property]: value
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
};
|
|
594
|
-
} else if (type === 'create') {
|
|
595
|
-
return {
|
|
596
|
-
...doc,
|
|
597
|
-
nodes: {
|
|
598
|
-
...doc.nodes,
|
|
599
|
-
[args[0].id]: structuredClone(args[0])
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
|
-
} else if (type === 'delete') {
|
|
603
|
-
// eslint-disable-next-line
|
|
604
|
-
const { [args[0]]: _removed, ...remaining_nodes } = doc.nodes;
|
|
605
|
-
return {
|
|
606
|
-
...doc,
|
|
607
|
-
nodes: remaining_nodes
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
return doc;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Counts how many times a node is referenced in the document.
|
|
615
|
-
*
|
|
616
|
-
* @param {DocumentSchema} schema - The document schema
|
|
617
|
-
* @param {Document} doc - The document containing nodes
|
|
618
|
-
* @param {NodeId} node_id - The node ID to count references for
|
|
619
|
-
* @returns {number} The number of references
|
|
620
|
-
*/
|
|
621
|
-
export function count_references(schema, doc, node_id) {
|
|
622
|
-
let count = 0;
|
|
623
|
-
|
|
624
|
-
for (const node of Object.values(doc.nodes)) {
|
|
625
|
-
for (const [property, value] of Object.entries(node)) {
|
|
626
|
-
if (property === 'id' || property === 'type') continue;
|
|
627
|
-
|
|
628
|
-
const prop_type = property_type(schema, node.type, property);
|
|
629
|
-
|
|
630
|
-
if (prop_type === 'node_array' && Array.isArray(value)) {
|
|
631
|
-
count += value.filter((id) => id === node_id).length;
|
|
632
|
-
} else if (prop_type === 'node' && value === node_id) {
|
|
633
|
-
count += 1;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
return count;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Gets the currently active annotation at the selection, optionally filtered by type.
|
|
643
|
-
*
|
|
644
|
-
* An annotation is considered "active" if it overlaps with the current selection in any way:
|
|
645
|
-
* - Annotation contains the selection start
|
|
646
|
-
* - Annotation contains the selection end
|
|
647
|
-
* - Selection fully contains the annotation
|
|
648
|
-
*
|
|
649
|
-
* @param {DocumentSchema} schema - The document schema
|
|
650
|
-
* @param {Document} doc - The document containing nodes
|
|
651
|
-
* @param {Selection} [selection] - The current selection
|
|
652
|
-
* @param {string} [annotation_type] - Optional annotation type to filter by
|
|
653
|
-
* @returns {Annotation | null} The active annotation, or null if none found
|
|
654
|
-
*/
|
|
655
|
-
export function get_active_annotation(schema, doc, selection, annotation_type) {
|
|
656
|
-
if (selection?.type !== 'text') return null;
|
|
657
|
-
const range = get_selection_range(selection);
|
|
658
|
-
if (!range) return null;
|
|
659
|
-
|
|
660
|
-
const annotated_text = get(schema, doc, selection.path);
|
|
661
|
-
const annotations = annotated_text.annotations;
|
|
662
|
-
|
|
663
|
-
const active_annotation =
|
|
664
|
-
annotations.find(
|
|
665
|
-
(/** @type {Annotation} */ { start_offset, end_offset }) =>
|
|
666
|
-
(start_offset <= range.start_offset && end_offset > range.start_offset) ||
|
|
667
|
-
(start_offset < range.end_offset && end_offset >= range.end_offset) ||
|
|
668
|
-
(start_offset >= range.start_offset && end_offset <= range.end_offset)
|
|
669
|
-
) || null;
|
|
670
|
-
|
|
671
|
-
if (annotation_type && active_annotation) {
|
|
672
|
-
const annotation_node = get(schema, doc, [active_annotation.node_id]);
|
|
673
|
-
return annotation_node?.type === annotation_type ? active_annotation : null;
|
|
674
|
-
} else {
|
|
675
|
-
return active_annotation;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Counts references to a node, excluding nodes marked for deletion.
|
|
681
|
-
*
|
|
682
|
-
* @param {DocumentSchema} schema - The document schema
|
|
683
|
-
* @param {Document} doc - The document containing nodes
|
|
684
|
-
* @param {NodeId} target_node_id - The node ID to count references for
|
|
685
|
-
* @param {Record<NodeId, boolean>} nodes_to_delete - Nodes marked for deletion
|
|
686
|
-
* @returns {number} The number of references excluding deleted nodes
|
|
687
|
-
*/
|
|
688
|
-
export function count_references_excluding_deleted(schema, doc, target_node_id, nodes_to_delete) {
|
|
689
|
-
let count = 0;
|
|
690
|
-
|
|
691
|
-
for (const node of Object.values(doc.nodes)) {
|
|
692
|
-
if (nodes_to_delete[node.id]) continue;
|
|
693
|
-
|
|
694
|
-
for (const [property, value] of Object.entries(node)) {
|
|
695
|
-
if (property === 'id' || property === 'type') continue;
|
|
696
|
-
|
|
697
|
-
const prop_type = property_type(schema, node.type, property);
|
|
698
|
-
|
|
699
|
-
if (prop_type === 'node_array' && Array.isArray(value)) {
|
|
700
|
-
count += value.filter((id) => id === target_node_id).length;
|
|
701
|
-
} else if (prop_type === 'node' && value === target_node_id) {
|
|
702
|
-
count += 1;
|
|
703
|
-
} else if (prop_type === 'annotated_text' && value && value.annotations) {
|
|
704
|
-
count += value.annotations.filter(
|
|
705
|
-
(annotation) => annotation.node_id === target_node_id
|
|
706
|
-
).length;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return count;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Gets ids of nodes that reference any of the target nodes.
|
|
716
|
-
*
|
|
717
|
-
* @param {DocumentSchema} schema - The document schema
|
|
718
|
-
* @param {Document} doc - The document containing nodes
|
|
719
|
-
* @param {Iterable<NodeId>} target_node_ids - Node IDs to find referrers for
|
|
720
|
-
* @returns {NodeId[]} IDs of nodes with references to any target node
|
|
721
|
-
*/
|
|
722
|
-
export function get_referencing_node_ids(schema, doc, target_node_ids) {
|
|
723
|
-
const target_ids = new Set(target_node_ids);
|
|
724
|
-
const referencing_node_ids = new Set();
|
|
725
|
-
if (target_ids.size === 0) return [];
|
|
726
|
-
|
|
727
|
-
for (const node of Object.values(doc.nodes)) {
|
|
728
|
-
for (const [property, value] of Object.entries(node)) {
|
|
729
|
-
if (property === 'id' || property === 'type') continue;
|
|
730
|
-
|
|
731
|
-
const prop_type = property_type(schema, node.type, property);
|
|
732
|
-
|
|
733
|
-
if (prop_type === 'node_array' && Array.isArray(value)) {
|
|
734
|
-
if (value.some((id) => target_ids.has(id))) {
|
|
735
|
-
referencing_node_ids.add(node.id);
|
|
736
|
-
}
|
|
737
|
-
} else if (prop_type === 'node' && target_ids.has(value)) {
|
|
738
|
-
referencing_node_ids.add(node.id);
|
|
739
|
-
} else if (prop_type === 'annotated_text' && value?.annotations) {
|
|
740
|
-
if (value.annotations.some((annotation) => target_ids.has(annotation.node_id))) {
|
|
741
|
-
referencing_node_ids.add(node.id);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
return [...referencing_node_ids];
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/**
|
|
751
|
-
* Validates a selection against the current document state.
|
|
752
|
-
* Works with any object that implements get() and inspect() (Session or Transaction).
|
|
753
|
-
*
|
|
754
|
-
* @param {Selection} selection - The selection to validate
|
|
755
|
-
* @param {{ get: Function, inspect: Function }} session_or_transaction - A Session or Transaction instance
|
|
756
|
-
* @throws {Error} Throws if the selection is invalid
|
|
757
|
-
*/
|
|
758
|
-
export function validate_selection(selection, session_or_transaction) {
|
|
759
|
-
if (!selection) return;
|
|
760
|
-
|
|
761
|
-
const selection_type = selection.type;
|
|
762
|
-
if (!['node', 'text', 'property'].includes(selection_type)) {
|
|
763
|
-
throw new Error(`Invalid selection type: ${selection_type}`);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (selection_type === 'node') {
|
|
767
|
-
const node_array = session_or_transaction.get(selection.path);
|
|
768
|
-
|
|
769
|
-
if (!Array.isArray(node_array)) {
|
|
770
|
-
throw new Error('Node selection path must point to a node_array');
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
const max_offset = node_array.length;
|
|
774
|
-
if (selection.anchor_offset < 0 || selection.anchor_offset > max_offset) {
|
|
775
|
-
throw new Error(
|
|
776
|
-
`Node selection anchor_offset (${selection.anchor_offset}) is out of bounds. Max is ${max_offset}.`
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
if (selection.focus_offset < 0 || selection.focus_offset > max_offset) {
|
|
780
|
-
throw new Error(
|
|
781
|
-
`Node selection focus_offset (${selection.focus_offset}) is out of bounds. Max is ${max_offset}.`
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
} else if (selection_type === 'text') {
|
|
785
|
-
const annotated_text = session_or_transaction.get(selection.path);
|
|
786
|
-
|
|
787
|
-
if (!annotated_text || typeof annotated_text.text !== 'string') {
|
|
788
|
-
throw new Error('Text selection path must point to annotated_text');
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
const char_length = get_char_length(annotated_text.text);
|
|
792
|
-
if (selection.anchor_offset < 0 || selection.anchor_offset > char_length) {
|
|
793
|
-
throw new Error(
|
|
794
|
-
`Text selection anchor_offset (${selection.anchor_offset}) is out of bounds. Max is ${char_length}.`
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
if (selection.focus_offset < 0 || selection.focus_offset > char_length) {
|
|
798
|
-
throw new Error(
|
|
799
|
-
`Text selection focus_offset (${selection.focus_offset}) is out of bounds. Max is ${char_length}.`
|
|
800
|
-
);
|
|
801
|
-
}
|
|
802
|
-
} else if (selection_type === 'property') {
|
|
803
|
-
if (!session_or_transaction.inspect(selection.path)) {
|
|
804
|
-
throw new Error(`Property selection path not found: ${serialize_path(selection.path)}`);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|