linked-rolls 0.63.0 → 0.64.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 +13 -5
- package/lib/analysis/constraints.d.ts +2 -2
- package/lib/analysis/constraints.js +17 -14
- package/lib/analysis/ownPaper.d.ts +3 -3
- package/lib/analysis/ownPaper.js +3 -3
- package/lib/analysis/reservations.d.ts +2 -2
- package/lib/analysis/reservations.js +6 -6
- package/lib/analysis/sigla.d.ts +4 -5
- package/lib/analysis/sigla.js +4 -4
- package/lib/analysis/stemma.d.ts +22 -0
- package/lib/analysis/stemma.js +38 -0
- package/lib/analysis/text.d.ts +31 -0
- package/lib/analysis/text.js +62 -0
- package/lib/analysis/witnesses.d.ts +5 -5
- package/lib/analysis/witnesses.js +22 -20
- package/lib/collation/scatter.d.ts +4 -4
- package/lib/collation/scatter.js +15 -13
- package/lib/emulation/Emulation.d.ts +7 -7
- package/lib/emulation/Emulation.js +29 -27
- package/lib/emulation/negotiation.d.ts +2 -2
- package/lib/emulation/negotiation.js +3 -2
- package/lib/index.d.ts +4 -1
- package/lib/index.js +4 -1
- package/lib/io/asJsonLd.js +11 -1
- package/lib/io/importJsonLd.js +11 -1
- package/lib/lookup/lookup.d.ts +32 -0
- package/lib/lookup/lookup.js +40 -0
- package/lib/lookup/paths.d.ts +11 -0
- package/lib/lookup/paths.js +78 -0
- package/lib/lookup/perState.d.ts +32 -0
- package/lib/lookup/perState.js +63 -0
- package/lib/model/Edit.d.ts +4 -0
- package/lib/model/Version.d.ts +7 -1
- package/lib/ops/beliefs.d.ts +1 -1
- package/lib/ops/beliefs.js +1 -1
- package/lib/ops/collation.d.ts +3 -4
- package/lib/ops/collation.js +25 -23
- package/lib/ops/commands.d.ts +4 -5
- package/lib/ops/commands.js +10 -10
- package/lib/ops/draft.d.ts +12 -12
- package/lib/ops/draft.js +11 -13
- package/lib/ops/edits.d.ts +1 -2
- package/lib/ops/edits.js +13 -10
- package/lib/ops/features.d.ts +2 -2
- package/lib/ops/features.js +3 -2
- package/lib/ops/index.d.ts +1 -1
- package/lib/ops/index.js +6 -0
- package/lib/ops/versions.d.ts +2 -3
- package/lib/ops/versions.js +6 -5
- package/lib/schema.json +2 -2
- package/package.json +1 -1
- package/lib/view/EditionView.d.ts +0 -103
- package/lib/view/EditionView.js +0 -329
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { Edition } from "../model/Edition.js";
|
|
2
|
-
import { FeatureOrPatch, HorizontalSpan, NestedFeature } from "../model/Feature.js";
|
|
3
|
-
import { AnySymbol } from "../model/Symbol.js";
|
|
4
|
-
import { Version } from "../model/Version.js";
|
|
5
|
-
import { Modification, ProductionEvent, RollCopy } from "../model/RollCopy.js";
|
|
6
|
-
import { Millimeters } from "../model/Quantity.js";
|
|
7
|
-
export type Path = (string | number)[];
|
|
8
|
-
export declare const getAt: <T>(path: Path, obj: unknown) => T | undefined;
|
|
9
|
-
export declare class EditionView {
|
|
10
|
-
readonly edition: Edition;
|
|
11
|
-
/**
|
|
12
|
-
* Map from id to its path within the edition
|
|
13
|
-
*/
|
|
14
|
-
private readonly paths;
|
|
15
|
-
/**
|
|
16
|
-
* Map from id to paths where it is referenced
|
|
17
|
-
*/
|
|
18
|
-
private readonly links;
|
|
19
|
-
/** Built when a copy is first asked for, the walk not recording it. */
|
|
20
|
-
private copiesByFeature?;
|
|
21
|
-
/**
|
|
22
|
-
* One index for each kind of entity, built from where the edition
|
|
23
|
-
* holds it. The index an entity is found in says what it is, so a
|
|
24
|
-
* lookup needs neither a type discriminator nor a cast.
|
|
25
|
-
*/
|
|
26
|
-
private readonly versionsById;
|
|
27
|
-
private readonly copiesById;
|
|
28
|
-
private readonly symbolsById;
|
|
29
|
-
private featuresById?;
|
|
30
|
-
constructor(edition: Edition);
|
|
31
|
-
/** What stands at the path, or null where nothing does. A zero or an empty string stands there as well. */
|
|
32
|
-
atPath<T>(path: Path): T | null;
|
|
33
|
-
/**
|
|
34
|
-
* Walks the edition and records where each id stands and where it is
|
|
35
|
-
* referenced. The constructor does this; a second call starts afresh
|
|
36
|
-
* rather than recording every reference twice.
|
|
37
|
-
*/
|
|
38
|
-
indexObjects(): void;
|
|
39
|
-
version(id: string): Readonly<Version> | undefined;
|
|
40
|
-
copy(id: string): Readonly<RollCopy> | undefined;
|
|
41
|
-
symbol(id: string): Readonly<AnySymbol> | undefined;
|
|
42
|
-
/** The symbols under the ids, leaving out an id that names none. */
|
|
43
|
-
symbols(ids: readonly string[]): Readonly<AnySymbol>[];
|
|
44
|
-
/** A feature or a patch on any copy, one a patch bears included. */
|
|
45
|
-
feature(id: string): Readonly<NestedFeature> | undefined;
|
|
46
|
-
/** The features and patches under the ids, leaving out an id that names none. */
|
|
47
|
-
features(ids: readonly string[]): Readonly<NestedFeature>[];
|
|
48
|
-
getPath(anyId: string): Path | undefined;
|
|
49
|
-
linksTo(anyId: string): Path[];
|
|
50
|
-
/**
|
|
51
|
-
* Calls back on the version and its ancestors along the principal
|
|
52
|
-
* line. A stemma that loops back on itself is walked once round, so
|
|
53
|
-
* that an edition in that state can still be read and repaired.
|
|
54
|
-
*/
|
|
55
|
-
travelUp(versionId: string, callback: (version: Readonly<Version>) => void): void;
|
|
56
|
-
/** The version and its ancestors along the principal line, from the version up to the root. */
|
|
57
|
-
lineageOf(versionId: string): Readonly<Version>[];
|
|
58
|
-
carriersOf(symbol: AnySymbol): Readonly<NestedFeature>[];
|
|
59
|
-
/** The carriers that state a place of their own, which a feature on a patch does not. */
|
|
60
|
-
placedCarriersOf(symbol: AnySymbol): Readonly<FeatureOrPatch>[];
|
|
61
|
-
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
62
|
-
copyOf(featureId: string): Readonly<RollCopy> | undefined;
|
|
63
|
-
/**
|
|
64
|
-
* The act a copy states a feature in: the production event that
|
|
65
|
-
* punched it, or the modification that brought it about. A feature
|
|
66
|
-
* a patch bears came onto the copy with the patch, so the act is
|
|
67
|
-
* the one that glued the patch on. Two features stand in one act
|
|
68
|
-
* where this returns the very same object, which is what
|
|
69
|
-
* `mergeFeatures` asks before it reads them as one.
|
|
70
|
-
*/
|
|
71
|
-
actOf(featureId: string): Readonly<ProductionEvent | Modification> | undefined;
|
|
72
|
-
/** The version the given one's text is read against, by its principal derivation. */
|
|
73
|
-
predecessorOf(versionId: string): Readonly<Version> | undefined;
|
|
74
|
-
/**
|
|
75
|
-
* Where along the roll the symbol lies, as its carriers put it, or
|
|
76
|
-
* nothing for a symbol no copy carries.
|
|
77
|
-
*
|
|
78
|
-
* Only the place is measured. Which track the symbol sits on is not
|
|
79
|
-
* a measurement but a question for a tracker bar, since a note of
|
|
80
|
-
* one pitch sits on exactly one position of a given bar: ask
|
|
81
|
-
* `bar.positionOf`. The carriers cannot answer it, because a copy
|
|
82
|
-
* cut for another system numbers its tracks differently, and one
|
|
83
|
-
* symbol may be carried by copies of both — averaging a red carrier
|
|
84
|
-
* on track 47 with a green one on 45 would give 46, a legal
|
|
85
|
-
* position a semitone away on either bar.
|
|
86
|
-
*/
|
|
87
|
-
placeOf(symbol: AnySymbol): Readonly<HorizontalSpan> | undefined;
|
|
88
|
-
/** Where the symbol begins, as the mean onset of its carriers, or nothing for a symbol without a place. */
|
|
89
|
-
onsetOf(symbol: AnySymbol): Millimeters | undefined;
|
|
90
|
-
/** The symbols by onset, those without a place first; symbols at one place keep their order. */
|
|
91
|
-
private inOrderOfPlace;
|
|
92
|
-
/**
|
|
93
|
-
* The symbols the version shows: what it and its ancestors insert,
|
|
94
|
-
* each version's deletions striking what it or its ancestors inserted.
|
|
95
|
-
*/
|
|
96
|
-
snapshot(versionId: string): readonly Readonly<AnySymbol>[];
|
|
97
|
-
/**
|
|
98
|
-
* Assigns a generation (depth) to every node.
|
|
99
|
-
*/
|
|
100
|
-
withGenerations(): Array<Version & {
|
|
101
|
-
generation: number;
|
|
102
|
-
}>;
|
|
103
|
-
}
|
package/lib/view/EditionView.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { isPlaced, withBorneFeatures } from "../model/Feature.js";
|
|
2
|
-
import { deletedBy, insertedBy, principalDerivationOf } from "../model/Version.js";
|
|
3
|
-
import { featuresMadeBy, featuresOf } from "../model/RollCopy.js";
|
|
4
|
-
import { idOf, idsOf } from "../model/Assumption.js";
|
|
5
|
-
import { mean } from "../model/Quantity.js";
|
|
6
|
-
export const getAt = (path, obj) => {
|
|
7
|
-
let node = obj;
|
|
8
|
-
for (const key of path) {
|
|
9
|
-
if (node == null)
|
|
10
|
-
return undefined;
|
|
11
|
-
node = node[key];
|
|
12
|
-
}
|
|
13
|
-
return node;
|
|
14
|
-
};
|
|
15
|
-
/** Keys under which an object names others by id, singly or in a list. */
|
|
16
|
-
const referenceKeys = ['delete', 'comprehends', 'motivation', 'premises', 'used'];
|
|
17
|
-
const byIdIn = (entities) => new Map(entities.map(entity => [entity.id, entity]));
|
|
18
|
-
const isObject = (v) => v !== null && typeof v === "object";
|
|
19
|
-
/** What a reference may state besides the id: a belief about it, and the tolerance a derivation was collated at. */
|
|
20
|
-
const referenceOwnKeys = new Set(['id', '@annotation', 'collationTolerance']);
|
|
21
|
-
/** An object that names another by its id and says nothing of its own beyond that. */
|
|
22
|
-
const isReferenceOnly = (keys) => keys.every(key => referenceOwnKeys.has(key));
|
|
23
|
-
const pathOf = (trail) => {
|
|
24
|
-
const path = [];
|
|
25
|
-
for (let link = trail; link !== null; link = link.up)
|
|
26
|
-
path.push(link.key);
|
|
27
|
-
return path.reverse();
|
|
28
|
-
};
|
|
29
|
-
export class EditionView {
|
|
30
|
-
constructor(edition) {
|
|
31
|
-
Object.defineProperty(this, "edition", {
|
|
32
|
-
enumerable: true,
|
|
33
|
-
configurable: true,
|
|
34
|
-
writable: true,
|
|
35
|
-
value: void 0
|
|
36
|
-
});
|
|
37
|
-
/**
|
|
38
|
-
* Map from id to its path within the edition
|
|
39
|
-
*/
|
|
40
|
-
Object.defineProperty(this, "paths", {
|
|
41
|
-
enumerable: true,
|
|
42
|
-
configurable: true,
|
|
43
|
-
writable: true,
|
|
44
|
-
value: new Map()
|
|
45
|
-
});
|
|
46
|
-
/**
|
|
47
|
-
* Map from id to paths where it is referenced
|
|
48
|
-
*/
|
|
49
|
-
Object.defineProperty(this, "links", {
|
|
50
|
-
enumerable: true,
|
|
51
|
-
configurable: true,
|
|
52
|
-
writable: true,
|
|
53
|
-
value: new Map()
|
|
54
|
-
});
|
|
55
|
-
/** Built when a copy is first asked for, the walk not recording it. */
|
|
56
|
-
Object.defineProperty(this, "copiesByFeature", {
|
|
57
|
-
enumerable: true,
|
|
58
|
-
configurable: true,
|
|
59
|
-
writable: true,
|
|
60
|
-
value: void 0
|
|
61
|
-
});
|
|
62
|
-
/**
|
|
63
|
-
* One index for each kind of entity, built from where the edition
|
|
64
|
-
* holds it. The index an entity is found in says what it is, so a
|
|
65
|
-
* lookup needs neither a type discriminator nor a cast.
|
|
66
|
-
*/
|
|
67
|
-
Object.defineProperty(this, "versionsById", {
|
|
68
|
-
enumerable: true,
|
|
69
|
-
configurable: true,
|
|
70
|
-
writable: true,
|
|
71
|
-
value: void 0
|
|
72
|
-
});
|
|
73
|
-
Object.defineProperty(this, "copiesById", {
|
|
74
|
-
enumerable: true,
|
|
75
|
-
configurable: true,
|
|
76
|
-
writable: true,
|
|
77
|
-
value: void 0
|
|
78
|
-
});
|
|
79
|
-
Object.defineProperty(this, "symbolsById", {
|
|
80
|
-
enumerable: true,
|
|
81
|
-
configurable: true,
|
|
82
|
-
writable: true,
|
|
83
|
-
value: void 0
|
|
84
|
-
});
|
|
85
|
-
Object.defineProperty(this, "featuresById", {
|
|
86
|
-
enumerable: true,
|
|
87
|
-
configurable: true,
|
|
88
|
-
writable: true,
|
|
89
|
-
value: void 0
|
|
90
|
-
});
|
|
91
|
-
this.edition = edition;
|
|
92
|
-
this.versionsById = byIdIn(edition.versions);
|
|
93
|
-
this.copiesById = byIdIn(edition.copies);
|
|
94
|
-
this.symbolsById = byIdIn(edition.versions.flatMap(insertedBy));
|
|
95
|
-
this.indexObjects();
|
|
96
|
-
}
|
|
97
|
-
/** What stands at the path, or null where nothing does. A zero or an empty string stands there as well. */
|
|
98
|
-
atPath(path) {
|
|
99
|
-
return getAt(path, this.edition) ?? null;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Walks the edition and records where each id stands and where it is
|
|
103
|
-
* referenced. The constructor does this; a second call starts afresh
|
|
104
|
-
* rather than recording every reference twice.
|
|
105
|
-
*/
|
|
106
|
-
indexObjects() {
|
|
107
|
-
this.paths.clear();
|
|
108
|
-
this.links.clear();
|
|
109
|
-
const visited = new WeakSet();
|
|
110
|
-
const link = (id, trail) => {
|
|
111
|
-
const trails = this.links.get(id);
|
|
112
|
-
if (trails)
|
|
113
|
-
trails.push(trail);
|
|
114
|
-
else
|
|
115
|
-
this.links.set(id, [trail]);
|
|
116
|
-
};
|
|
117
|
-
const index = (record, keys, trail) => {
|
|
118
|
-
if (typeof record.id !== "string")
|
|
119
|
-
return;
|
|
120
|
-
if (isReferenceOnly(keys))
|
|
121
|
-
link(record.id, { key: 'id', up: trail });
|
|
122
|
-
else if (!this.paths.has(record.id)) {
|
|
123
|
-
this.paths.set(record.id, trail);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const linkReferences = (record, trail) => referenceKeys.forEach(key => {
|
|
127
|
-
const ref = record[key];
|
|
128
|
-
if (typeof ref === "string")
|
|
129
|
-
link(ref, { key, up: trail });
|
|
130
|
-
else if (Array.isArray(ref))
|
|
131
|
-
ref.forEach((r, i) => {
|
|
132
|
-
if (typeof r === "string")
|
|
133
|
-
link(r, { key: i, up: { key, up: trail } });
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
const traverse = (node, trail) => {
|
|
137
|
-
if (!isObject(node) || visited.has(node))
|
|
138
|
-
return;
|
|
139
|
-
visited.add(node);
|
|
140
|
-
if (Array.isArray(node)) {
|
|
141
|
-
node.forEach((item, i) => traverse(item, { key: i, up: trail }));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const record = node;
|
|
145
|
-
const keys = Object.keys(record);
|
|
146
|
-
index(record, keys, trail);
|
|
147
|
-
linkReferences(record, trail);
|
|
148
|
-
keys.forEach(key => traverse(record[key], { key, up: trail }));
|
|
149
|
-
};
|
|
150
|
-
traverse(this.edition, null);
|
|
151
|
-
}
|
|
152
|
-
version(id) {
|
|
153
|
-
return this.versionsById.get(id);
|
|
154
|
-
}
|
|
155
|
-
copy(id) {
|
|
156
|
-
return this.copiesById.get(id);
|
|
157
|
-
}
|
|
158
|
-
symbol(id) {
|
|
159
|
-
return this.symbolsById.get(id);
|
|
160
|
-
}
|
|
161
|
-
/** The symbols under the ids, leaving out an id that names none. */
|
|
162
|
-
symbols(ids) {
|
|
163
|
-
return ids.flatMap(id => this.symbol(id) ?? []);
|
|
164
|
-
}
|
|
165
|
-
/** A feature or a patch on any copy, one a patch bears included. */
|
|
166
|
-
feature(id) {
|
|
167
|
-
this.featuresById ?? (this.featuresById = byIdIn(this.edition.copies.flatMap(copy => featuresOf(copy).flatMap(withBorneFeatures))));
|
|
168
|
-
return this.featuresById.get(id);
|
|
169
|
-
}
|
|
170
|
-
/** The features and patches under the ids, leaving out an id that names none. */
|
|
171
|
-
features(ids) {
|
|
172
|
-
return ids.flatMap(id => this.feature(id) ?? []);
|
|
173
|
-
}
|
|
174
|
-
getPath(anyId) {
|
|
175
|
-
const trail = this.paths.get(anyId);
|
|
176
|
-
return trail === undefined ? undefined : pathOf(trail);
|
|
177
|
-
}
|
|
178
|
-
linksTo(anyId) {
|
|
179
|
-
return (this.links.get(anyId) ?? []).map(pathOf);
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Calls back on the version and its ancestors along the principal
|
|
183
|
-
* line. A stemma that loops back on itself is walked once round, so
|
|
184
|
-
* that an edition in that state can still be read and repaired.
|
|
185
|
-
*/
|
|
186
|
-
travelUp(versionId, callback) {
|
|
187
|
-
const visited = new Set();
|
|
188
|
-
let v = this.version(versionId);
|
|
189
|
-
while (v && !visited.has(v.id)) {
|
|
190
|
-
visited.add(v.id);
|
|
191
|
-
callback(v);
|
|
192
|
-
const principal = principalDerivationOf(v);
|
|
193
|
-
v = principal && this.version(idOf(principal));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/** The version and its ancestors along the principal line, from the version up to the root. */
|
|
197
|
-
lineageOf(versionId) {
|
|
198
|
-
const lineage = [];
|
|
199
|
-
this.travelUp(versionId, version => lineage.push(version));
|
|
200
|
-
return lineage;
|
|
201
|
-
}
|
|
202
|
-
carriersOf(symbol) {
|
|
203
|
-
return this.features(idsOf(symbol.carriers));
|
|
204
|
-
}
|
|
205
|
-
/** The carriers that state a place of their own, which a feature on a patch does not. */
|
|
206
|
-
placedCarriersOf(symbol) {
|
|
207
|
-
return this.carriersOf(symbol).filter(isPlaced);
|
|
208
|
-
}
|
|
209
|
-
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
210
|
-
copyOf(featureId) {
|
|
211
|
-
if (!this.copiesByFeature) {
|
|
212
|
-
this.copiesByFeature = new Map(this.edition.copies.flatMap(copy => featuresOf(copy)
|
|
213
|
-
.flatMap(withBorneFeatures)
|
|
214
|
-
.map(feature => [feature.id, copy])));
|
|
215
|
-
}
|
|
216
|
-
return this.copiesByFeature.get(featureId);
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* The act a copy states a feature in: the production event that
|
|
220
|
-
* punched it, or the modification that brought it about. A feature
|
|
221
|
-
* a patch bears came onto the copy with the patch, so the act is
|
|
222
|
-
* the one that glued the patch on. Two features stand in one act
|
|
223
|
-
* where this returns the very same object, which is what
|
|
224
|
-
* `mergeFeatures` asks before it reads them as one.
|
|
225
|
-
*/
|
|
226
|
-
actOf(featureId) {
|
|
227
|
-
const copy = this.copyOf(featureId);
|
|
228
|
-
if (!copy)
|
|
229
|
-
return undefined;
|
|
230
|
-
const states = (features) => features.flatMap(withBorneFeatures).some(feature => feature.id === featureId);
|
|
231
|
-
if (states(copy.production?.produced ?? []))
|
|
232
|
-
return copy.production;
|
|
233
|
-
return copy.modifications.find(act => states(featuresMadeBy(act)));
|
|
234
|
-
}
|
|
235
|
-
/** The version the given one's text is read against, by its principal derivation. */
|
|
236
|
-
predecessorOf(versionId) {
|
|
237
|
-
const v = this.version(versionId);
|
|
238
|
-
const principal = v && principalDerivationOf(v);
|
|
239
|
-
return principal && this.version(idOf(principal));
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Where along the roll the symbol lies, as its carriers put it, or
|
|
243
|
-
* nothing for a symbol no copy carries.
|
|
244
|
-
*
|
|
245
|
-
* Only the place is measured. Which track the symbol sits on is not
|
|
246
|
-
* a measurement but a question for a tracker bar, since a note of
|
|
247
|
-
* one pitch sits on exactly one position of a given bar: ask
|
|
248
|
-
* `bar.positionOf`. The carriers cannot answer it, because a copy
|
|
249
|
-
* cut for another system numbers its tracks differently, and one
|
|
250
|
-
* symbol may be carried by copies of both — averaging a red carrier
|
|
251
|
-
* on track 47 with a green one on 45 would give 46, a legal
|
|
252
|
-
* position a semitone away on either bar.
|
|
253
|
-
*/
|
|
254
|
-
placeOf(symbol) {
|
|
255
|
-
const carriers = this.placedCarriersOf(symbol);
|
|
256
|
-
if (carriers.length === 0)
|
|
257
|
-
return;
|
|
258
|
-
return {
|
|
259
|
-
unit: 'mm',
|
|
260
|
-
from: mean(carriers.map(carrier => carrier.horizontal.from)),
|
|
261
|
-
to: mean(carriers.map(carrier => carrier.horizontal.to))
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
/** Where the symbol begins, as the mean onset of its carriers, or nothing for a symbol without a place. */
|
|
265
|
-
onsetOf(symbol) {
|
|
266
|
-
return this.placeOf(symbol)?.from;
|
|
267
|
-
}
|
|
268
|
-
/** The symbols by onset, those without a place first; symbols at one place keep their order. */
|
|
269
|
-
inOrderOfPlace(symbols) {
|
|
270
|
-
return symbols
|
|
271
|
-
.map(symbol => ({ symbol, at: this.onsetOf(symbol) || 0 }))
|
|
272
|
-
.sort((a, b) => a.at - b.at)
|
|
273
|
-
.map(({ symbol }) => symbol);
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* The symbols the version shows: what it and its ancestors insert,
|
|
277
|
-
* each version's deletions striking what it or its ancestors inserted.
|
|
278
|
-
*/
|
|
279
|
-
snapshot(versionId) {
|
|
280
|
-
const deleted = new Set();
|
|
281
|
-
const symbols = this.lineageOf(versionId).flatMap(version => {
|
|
282
|
-
deletedBy(version).forEach(id => deleted.add(id));
|
|
283
|
-
return insertedBy(version).filter(symbol => !deleted.has(symbol.id));
|
|
284
|
-
});
|
|
285
|
-
return this.inOrderOfPlace(symbols);
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Assigns a generation (depth) to every node.
|
|
289
|
-
*/
|
|
290
|
-
withGenerations() {
|
|
291
|
-
const memo = new Map();
|
|
292
|
-
const inStack = new Set();
|
|
293
|
-
const computeGeneration = (id) => {
|
|
294
|
-
if (memo.has(id))
|
|
295
|
-
return memo.get(id);
|
|
296
|
-
if (inStack.has(id)) {
|
|
297
|
-
throw new Error(`Cycle detected involving node '${id}'. Check parentId links.`);
|
|
298
|
-
}
|
|
299
|
-
const node = this.version(id);
|
|
300
|
-
if (!node) {
|
|
301
|
-
memo.set(id, 0);
|
|
302
|
-
return 0;
|
|
303
|
-
}
|
|
304
|
-
inStack.add(id);
|
|
305
|
-
let gen;
|
|
306
|
-
const principal = principalDerivationOf(node);
|
|
307
|
-
const basedOn = principal && idOf(principal);
|
|
308
|
-
if (basedOn === undefined) {
|
|
309
|
-
gen = 0; // root
|
|
310
|
-
}
|
|
311
|
-
else if (!this.version(basedOn)) {
|
|
312
|
-
// Orphaned parent reference — treat boundary as root
|
|
313
|
-
gen = 0;
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
gen = 1 + computeGeneration(basedOn);
|
|
317
|
-
}
|
|
318
|
-
inStack.delete(id);
|
|
319
|
-
memo.set(id, gen);
|
|
320
|
-
return gen;
|
|
321
|
-
};
|
|
322
|
-
// Compute for all nodes (order doesn’t matter)
|
|
323
|
-
const withGen = this.edition.versions.map(n => ({
|
|
324
|
-
...n,
|
|
325
|
-
generation: computeGeneration(n.id),
|
|
326
|
-
}));
|
|
327
|
-
return withGen;
|
|
328
|
-
}
|
|
329
|
-
}
|