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
package/lib/collation/scatter.js
CHANGED
|
@@ -4,12 +4,14 @@ import { mean, mm } from "../model/Quantity.js";
|
|
|
4
4
|
import { excessKurtosisOf, histogramOf, normalQuantile, spreadOf, standardise, tailOf } from "./statistics.js";
|
|
5
5
|
import { groupBy } from "../shared/utils.js";
|
|
6
6
|
import { deletedBy, insertedBy } from "../model/Version.js";
|
|
7
|
+
import { carriersOf, placedCarriersOf, snapshotOf } from "../analysis/text.js";
|
|
8
|
+
import { copyOfFeature, symbolsIn, versionIn } from "../lookup/lookup.js";
|
|
7
9
|
const placeOf = (carriers) => ({
|
|
8
10
|
from: mean(carriers.map(carrier => carrier.horizontal.from)),
|
|
9
11
|
to: mean(carriers.map(carrier => carrier.horizontal.to))
|
|
10
12
|
});
|
|
11
|
-
const sitsOn = (
|
|
12
|
-
const copy =
|
|
13
|
+
const sitsOn = (edition, copies) => (feature) => {
|
|
14
|
+
const copy = copyOfFeature(edition, feature.id);
|
|
13
15
|
return copy !== undefined && copies.has(copy.id);
|
|
14
16
|
};
|
|
15
17
|
/**
|
|
@@ -34,10 +36,10 @@ const sitsOn = (view, copies) => (feature) => {
|
|
|
34
36
|
* A symbol only one side carries measures nothing and is passed over,
|
|
35
37
|
* which leaves out exactly the insertions and the deletions.
|
|
36
38
|
*/
|
|
37
|
-
export const readingsOf = (
|
|
38
|
-
const tested = sitsOn(
|
|
39
|
+
export const readingsOf = (edition, symbols, copies) => {
|
|
40
|
+
const tested = sitsOn(edition, copies);
|
|
39
41
|
return symbols.flatMap((symbol) => {
|
|
40
|
-
const carriers =
|
|
42
|
+
const carriers = placedCarriersOf(edition, symbol);
|
|
41
43
|
const here = carriers.filter(tested);
|
|
42
44
|
const there = carriers.filter(carrier => !tested(carrier));
|
|
43
45
|
if (here.length === 0 || there.length === 0)
|
|
@@ -120,7 +122,7 @@ const scatterIn = (group, sample, { binWidth = DEFAULT_BIN_WIDTH, stated }) => {
|
|
|
120
122
|
export const scatterOf = (readings, options = {}) => [...groupBy(readings, reading => (options.groupOf ?? byWhatItIs)(reading.symbol))]
|
|
121
123
|
.flatMap(([group, sample]) => scatterIn(group, sample, options));
|
|
122
124
|
/** How one copy's readings of a version's text scatter against the readings of the copies it is collated with. */
|
|
123
|
-
export const scatterOfCopy = (
|
|
125
|
+
export const scatterOfCopy = (edition, versionId, copyId, options = {}) => scatterOf(readingsOf(edition, snapshotOf(edition, versionId), new Set([copyId])), options);
|
|
124
126
|
const covering = (windows) => {
|
|
125
127
|
const lowest = Math.min(...windows.map(window => window.offset - window.tolerance));
|
|
126
128
|
const highest = Math.max(...windows.map(window => window.offset + window.tolerance));
|
|
@@ -189,10 +191,10 @@ export const changesBetween = (readings, inForce, proposed) => ({
|
|
|
189
191
|
separated: readings.filter(({ displacement }) => admits(inForce, displacement) && !admits(proposed, displacement)),
|
|
190
192
|
merged: readings.filter(({ displacement }) => !admits(inForce, displacement) && admits(proposed, displacement))
|
|
191
193
|
});
|
|
192
|
-
const copiesBearing = (
|
|
194
|
+
const copiesBearing = (edition, symbols) => {
|
|
193
195
|
const tally = symbols.reduce((counts, symbol) => {
|
|
194
|
-
const bearers = new Set(
|
|
195
|
-
const copy =
|
|
196
|
+
const bearers = new Set(carriersOf(edition, symbol).flatMap(carrier => {
|
|
197
|
+
const copy = copyOfFeature(edition, carrier.id);
|
|
196
198
|
return copy ? [copy.id] : [];
|
|
197
199
|
}));
|
|
198
200
|
bearers.forEach(copy => counts.set(copy, (counts.get(copy) ?? 0) + 1));
|
|
@@ -219,13 +221,13 @@ const copiesBearing = (view, symbols) => {
|
|
|
219
221
|
* neither side, which is the case where the edition has genuinely
|
|
220
222
|
* stopped saying and an editor has to.
|
|
221
223
|
*/
|
|
222
|
-
export const sidesOf = (
|
|
223
|
-
const version =
|
|
224
|
+
export const sidesOf = (edition, versionId) => {
|
|
225
|
+
const version = versionIn(edition, versionId);
|
|
224
226
|
if (!version)
|
|
225
227
|
return undefined;
|
|
226
228
|
return {
|
|
227
|
-
child: copiesBearing(
|
|
228
|
-
parent: copiesBearing(
|
|
229
|
+
child: copiesBearing(edition, insertedBy(version)),
|
|
230
|
+
parent: copiesBearing(edition, symbolsIn(edition, deletedBy(version)))
|
|
229
231
|
};
|
|
230
232
|
};
|
|
231
233
|
const inMillimetres = (value) => `${value.toFixed(2)} mm`;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { MidiFile } from "midifile-ts";
|
|
2
|
-
import { EditionView } from "../view/EditionView.js";
|
|
3
2
|
import type { TrackerBar } from "../systems/TrackerBar.js";
|
|
4
3
|
import { Version } from "../model/Version.js";
|
|
5
4
|
import { AnyPerformedRollFeature, EmulatedCurve, NegotiatedEvent, ReproducingSystem } from "../systems/ReproducingSystem.js";
|
|
6
5
|
import { Millimeters } from "../model/Quantity.js";
|
|
6
|
+
import { Edition } from "../model/Edition.js";
|
|
7
7
|
export type EmulationScope = {
|
|
8
8
|
/** Only notes whose onset lies within this span of the roll are played. */
|
|
9
9
|
range?: [Millimeters, Millimeters];
|
|
@@ -12,17 +12,17 @@ export type EmulationScope = {
|
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* The events moved to where their statements put them, the events given
|
|
15
|
-
* left as they were. The
|
|
15
|
+
* left as they were. The edition supplies the copies, whose measurements
|
|
16
16
|
* decide how far before or after its reference a command goes, and a
|
|
17
17
|
* punch diameter, or a millimetre, where no copy agrees with a statement.
|
|
18
18
|
*/
|
|
19
|
-
export declare const withPlacementsApplied: (
|
|
19
|
+
export declare const withPlacementsApplied: (edition: Edition, events: readonly NegotiatedEvent[]) => NegotiatedEvent[];
|
|
20
20
|
/**
|
|
21
21
|
* The commands of a version as the bar performs them, in order of place,
|
|
22
22
|
* within the scope and with the placements applied. A note plays only
|
|
23
23
|
* where its onset falls in the range; expressions play throughout.
|
|
24
24
|
*/
|
|
25
|
-
export declare const negotiatedEventsOf: (
|
|
25
|
+
export declare const negotiatedEventsOf: (edition: Edition, version: Readonly<Version>, bar: TrackerBar, { range }?: Pick<EmulationScope, 'range'>) => NegotiatedEvent[];
|
|
26
26
|
/** A version performed: what was negotiated, and what the system made of it. */
|
|
27
27
|
export interface Emulated {
|
|
28
28
|
/** The id of the version performed. */
|
|
@@ -36,7 +36,7 @@ export interface Emulated {
|
|
|
36
36
|
* Performs a version on a reproducing system: the symbols are negotiated
|
|
37
37
|
* into placed events and the system plays them. Nothing given is changed.
|
|
38
38
|
*/
|
|
39
|
-
export declare const emulate: <Options extends object>(system: ReproducingSystem<Options>, version: Readonly<Version>,
|
|
39
|
+
export declare const emulate: <Options extends object>(system: ReproducingSystem<Options>, version: Readonly<Version>, edition: Edition, options?: Options, { range, skipToFirstNote }?: EmulationScope) => Emulated;
|
|
40
40
|
/**
|
|
41
41
|
* The performed events as a MIDI file, in which every note and pedal step
|
|
42
42
|
* is labelled with the symbol it performs. The file names the system, the
|
|
@@ -57,8 +57,8 @@ export declare class Emulation<Options extends object> {
|
|
|
57
57
|
source?: string;
|
|
58
58
|
constructor(system: ReproducingSystem<Options>, options?: Options);
|
|
59
59
|
/** Moves the negotiated events to where their statements put them; see `withPlacementsApplied`. */
|
|
60
|
-
applyConstraints(
|
|
61
|
-
emulateVersion(version: Version,
|
|
60
|
+
applyConstraints(edition: Edition): void;
|
|
61
|
+
emulateVersion(version: Version, edition: Edition, scope?: EmulationScope): AnyPerformedRollFeature[];
|
|
62
62
|
findEventsPerforming(id: string): AnyPerformedRollFeature[];
|
|
63
63
|
asMIDI(): MidiFile;
|
|
64
64
|
}
|
|
@@ -5,25 +5,27 @@ import { add, mean, mm, seconds, subtract } from "../model/Quantity.js";
|
|
|
5
5
|
import { punchDiameterOf } from "../model/RollCopy.js";
|
|
6
6
|
import { toOwnPaperOf } from "../analysis/ownPaper.js";
|
|
7
7
|
import { negotiatedEventOf } from "./negotiation.js";
|
|
8
|
+
import { placedCarriersOf, snapshotOf } from "../analysis/text.js";
|
|
9
|
+
import { copyOfFeature, symbolIn } from "../lookup/lookup.js";
|
|
8
10
|
/** The punch diameter the edition's copies report, where any of them does. */
|
|
9
|
-
const meanPunchDiameterOf = (
|
|
10
|
-
const measured =
|
|
11
|
+
const meanPunchDiameterOf = (edition) => {
|
|
12
|
+
const measured = edition.copies
|
|
11
13
|
.map(punchDiameterOf)
|
|
12
14
|
.filter(value => value !== undefined);
|
|
13
15
|
return measured.length > 0 ? mean(measured) : undefined;
|
|
14
16
|
};
|
|
15
|
-
const propertiesOf = (
|
|
16
|
-
punchDiameter: meanPunchDiameterOf(
|
|
17
|
-
tempo:
|
|
18
|
-
toOwnPaper: toOwnPaperOf(
|
|
17
|
+
const propertiesOf = (edition, version) => ({
|
|
18
|
+
punchDiameter: meanPunchDiameterOf(edition),
|
|
19
|
+
tempo: edition.tempoAdjustment,
|
|
20
|
+
toOwnPaper: toOwnPaperOf(edition, version)
|
|
19
21
|
});
|
|
20
22
|
/** The onset a symbol has on each copy carrying it, by the copy's id, as the mean of its chains there. */
|
|
21
|
-
const onsetsByCopy = (
|
|
22
|
-
const symbol =
|
|
23
|
+
const onsetsByCopy = (edition, symbolId) => {
|
|
24
|
+
const symbol = symbolIn(edition, symbolId);
|
|
23
25
|
if (!symbol)
|
|
24
26
|
return new Map();
|
|
25
|
-
const onsets =
|
|
26
|
-
const copy =
|
|
27
|
+
const onsets = placedCarriersOf(edition, symbol).flatMap((carrier) => {
|
|
28
|
+
const copy = copyOfFeature(edition, carrier.id);
|
|
27
29
|
return copy ? [[copy.id, carrier.horizontal.from]] : [];
|
|
28
30
|
});
|
|
29
31
|
const copies = new Set(onsets.map(([copy]) => copy));
|
|
@@ -34,9 +36,9 @@ const onsetsByCopy = (view, symbolId) => {
|
|
|
34
36
|
* the follower on each copy carrying both, negative where the follower
|
|
35
37
|
* comes first there.
|
|
36
38
|
*/
|
|
37
|
-
const offsetsBetween = (
|
|
38
|
-
const references = onsetsByCopy(
|
|
39
|
-
return [...onsetsByCopy(
|
|
39
|
+
const offsetsBetween = (edition, followerId, referenceId) => {
|
|
40
|
+
const references = onsetsByCopy(edition, referenceId);
|
|
41
|
+
return [...onsetsByCopy(edition, followerId)].flatMap(([copy, onset]) => {
|
|
40
42
|
const reference = references.get(copy);
|
|
41
43
|
return reference === undefined ? [] : [subtract(onset, reference)];
|
|
42
44
|
});
|
|
@@ -91,13 +93,13 @@ const displacementsOf = (events, offsetsBetween, gap) => {
|
|
|
91
93
|
const earliestOf = (times) => times.length === 0 ? seconds(0) : times.reduce((soonest, at) => at < soonest ? at : soonest);
|
|
92
94
|
/**
|
|
93
95
|
* The events moved to where their statements put them, the events given
|
|
94
|
-
* left as they were. The
|
|
96
|
+
* left as they were. The edition supplies the copies, whose measurements
|
|
95
97
|
* decide how far before or after its reference a command goes, and a
|
|
96
98
|
* punch diameter, or a millimetre, where no copy agrees with a statement.
|
|
97
99
|
*/
|
|
98
|
-
export const withPlacementsApplied = (
|
|
99
|
-
const gap = meanPunchDiameterOf(
|
|
100
|
-
const offsets = (follower, reference) => offsetsBetween(
|
|
100
|
+
export const withPlacementsApplied = (edition, events) => {
|
|
101
|
+
const gap = meanPunchDiameterOf(edition) ?? mm(1);
|
|
102
|
+
const offsets = (follower, reference) => offsetsBetween(edition, follower, reference);
|
|
101
103
|
const displacements = displacementsOf(events, offsets, gap);
|
|
102
104
|
return events.map(event => {
|
|
103
105
|
const distance = displacements.get(event);
|
|
@@ -112,11 +114,11 @@ export const withPlacementsApplied = (view, events) => {
|
|
|
112
114
|
* within the scope and with the placements applied. A note plays only
|
|
113
115
|
* where its onset falls in the range; expressions play throughout.
|
|
114
116
|
*/
|
|
115
|
-
export const negotiatedEventsOf = (
|
|
117
|
+
export const negotiatedEventsOf = (edition, version, bar, { range } = {}) => {
|
|
116
118
|
const inScope = (event) => !range || event.type !== 'note' || (event.horizontal.from > range[0] && event.horizontal.from < range[1]);
|
|
117
|
-
return withPlacementsApplied(
|
|
119
|
+
return withPlacementsApplied(edition, snapshotOf(edition, version.id)
|
|
118
120
|
.filter(isCommand)
|
|
119
|
-
.map(symbol => negotiatedEventOf(
|
|
121
|
+
.map(symbol => negotiatedEventOf(edition, symbol, bar))
|
|
120
122
|
.filter(event => event !== null)
|
|
121
123
|
.filter(inScope));
|
|
122
124
|
};
|
|
@@ -124,11 +126,11 @@ export const negotiatedEventsOf = (view, version, bar, { range } = {}) => {
|
|
|
124
126
|
* Performs a version on a reproducing system: the symbols are negotiated
|
|
125
127
|
* into placed events and the system plays them. Nothing given is changed.
|
|
126
128
|
*/
|
|
127
|
-
export const emulate = (system, version,
|
|
128
|
-
const negotiated = negotiatedEventsOf(
|
|
129
|
+
export const emulate = (system, version, edition, options = system.defaultOptions, { range, skipToFirstNote = false } = {}) => {
|
|
130
|
+
const negotiated = negotiatedEventsOf(edition, version, system.trackerBar, { range });
|
|
129
131
|
if (negotiated.length === 0)
|
|
130
132
|
return { source: version.id, negotiated, events: [], curves: [] };
|
|
131
|
-
const performance = system.perform(negotiated, options, propertiesOf(
|
|
133
|
+
const performance = system.perform(negotiated, options, propertiesOf(edition, version));
|
|
132
134
|
const onsets = performance.events.filter(event => event.type === 'noteOn').map(event => event.at);
|
|
133
135
|
const origin = skipToFirstNote ? earliestOf(onsets) : seconds(0);
|
|
134
136
|
return {
|
|
@@ -262,11 +264,11 @@ export class Emulation {
|
|
|
262
264
|
this.options = options;
|
|
263
265
|
}
|
|
264
266
|
/** Moves the negotiated events to where their statements put them; see `withPlacementsApplied`. */
|
|
265
|
-
applyConstraints(
|
|
266
|
-
this.negotiatedEvents = withPlacementsApplied(
|
|
267
|
+
applyConstraints(edition) {
|
|
268
|
+
this.negotiatedEvents = withPlacementsApplied(edition, this.negotiatedEvents);
|
|
267
269
|
}
|
|
268
|
-
emulateVersion(version,
|
|
269
|
-
const emulated = emulate(this.system, version,
|
|
270
|
+
emulateVersion(version, edition, scope = {}) {
|
|
271
|
+
const emulated = emulate(this.system, version, edition, this.options, scope);
|
|
270
272
|
this.source = emulated.source;
|
|
271
273
|
this.negotiatedEvents = [...emulated.negotiated];
|
|
272
274
|
this.curves = emulated.curves;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { EditionView } from "../view/EditionView.js";
|
|
2
1
|
import type { Expression, Note } from "../model/Symbol.js";
|
|
3
2
|
import type { TrackerBar } from "../systems/TrackerBar.js";
|
|
4
3
|
import type { NegotiatedEvent } from "../systems/ReproducingSystem.js";
|
|
4
|
+
import { Edition } from "../model/Edition.js";
|
|
5
5
|
/**
|
|
6
6
|
* The symbol as a performance needs it: where it lies, and the
|
|
7
7
|
* position the performing bar reads it on.
|
|
@@ -11,4 +11,4 @@ import type { NegotiatedEvent } from "../systems/ReproducingSystem.js";
|
|
|
11
11
|
* green version still inherits cannot be performed on a green
|
|
12
12
|
* machine, and an edit has yet to say what took its place.
|
|
13
13
|
*/
|
|
14
|
-
export declare const negotiatedEventOf: (
|
|
14
|
+
export declare const negotiatedEventOf: (edition: Edition, symbol: Note | Expression, bar: TrackerBar) => NegotiatedEvent | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { placeOf } from "../analysis/text.js";
|
|
1
2
|
/**
|
|
2
3
|
* The symbol as a performance needs it: where it lies, and the
|
|
3
4
|
* position the performing bar reads it on.
|
|
@@ -7,8 +8,8 @@
|
|
|
7
8
|
* green version still inherits cannot be performed on a green
|
|
8
9
|
* machine, and an edit has yet to say what took its place.
|
|
9
10
|
*/
|
|
10
|
-
export const negotiatedEventOf = (
|
|
11
|
-
const horizontal =
|
|
11
|
+
export const negotiatedEventOf = (edition, symbol, bar) => {
|
|
12
|
+
const horizontal = placeOf(edition, symbol);
|
|
12
13
|
const position = bar.positionOf(symbol);
|
|
13
14
|
if (!horizontal || position === undefined)
|
|
14
15
|
return null;
|
package/lib/index.d.ts
CHANGED
|
@@ -31,7 +31,10 @@ export * from './analysis/witnesses.js';
|
|
|
31
31
|
export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten, type AlignmentResult } from './collation/alignment.js';
|
|
32
32
|
export * from './collation/statistics.js';
|
|
33
33
|
export * from './model/Edition.js';
|
|
34
|
-
export * from './
|
|
34
|
+
export * from './lookup/lookup.js';
|
|
35
|
+
export * from './lookup/paths.js';
|
|
36
|
+
export * from './analysis/stemma.js';
|
|
37
|
+
export * from './analysis/text.js';
|
|
35
38
|
export * from './analysis/ownPaper.js';
|
|
36
39
|
export * from './analysis/paper.js';
|
|
37
40
|
export * from './emulation/negotiation.js';
|
package/lib/index.js
CHANGED
|
@@ -24,7 +24,10 @@ export * from './analysis/witnesses.js';
|
|
|
24
24
|
export { alignFeatures, ALIGNMENT_METHOD, fromAxis, ownFeaturesOf, toAxis, tooShortToShorten } from './collation/alignment.js';
|
|
25
25
|
export * from './collation/statistics.js';
|
|
26
26
|
export * from './model/Edition.js';
|
|
27
|
-
export * from './
|
|
27
|
+
export * from './lookup/lookup.js';
|
|
28
|
+
export * from './lookup/paths.js';
|
|
29
|
+
export * from './analysis/stemma.js';
|
|
30
|
+
export * from './analysis/text.js';
|
|
28
31
|
export * from './analysis/ownPaper.js';
|
|
29
32
|
export * from './analysis/paper.js';
|
|
30
33
|
export * from './emulation/negotiation.js';
|
package/lib/io/asJsonLd.js
CHANGED
|
@@ -59,6 +59,16 @@ const withSystemContext = (version) => {
|
|
|
59
59
|
return system ? { '@context': `https://w3id.org/reo/${system}/context.jsonld`, ...version } : version;
|
|
60
60
|
};
|
|
61
61
|
const withSystemContexts = (edition) => Array.isArray(edition.versions) ? { ...edition, versions: edition.versions.map(withSystemContext) } : edition;
|
|
62
|
+
/**
|
|
63
|
+
* Gives every version the act that made it, stating nothing of it where
|
|
64
|
+
* nothing is known. The axioms of reo.ttl order that act in time against
|
|
65
|
+
* the recording, the copies and the act that made the version it derives
|
|
66
|
+
* from, and a reasoner cannot place a node the graph lacks. An import
|
|
67
|
+
* drops it again where it states nothing.
|
|
68
|
+
*/
|
|
69
|
+
const withCreations = (edition) => Array.isArray(edition.versions)
|
|
70
|
+
? { ...edition, versions: edition.versions.map((version) => version.creation ? version : { ...version, creation: {} }) }
|
|
71
|
+
: edition;
|
|
62
72
|
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
63
73
|
/** The act with what it changed named: an attachment augments the copy, a removal diminishes it. */
|
|
64
74
|
const changing = (copyId) => (act) => {
|
|
@@ -157,7 +167,7 @@ const atOwnPlacesAll = (edition) => edition.copies.some(copy => copy.measurement
|
|
|
157
167
|
? { ...edition, copies: edition.copies.map(atOwnPlaces) }
|
|
158
168
|
: edition;
|
|
159
169
|
export const asJsonLd = (edition) => {
|
|
160
|
-
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withChanges(asJsonLdEntity(atOwnPlacesAll(edition)))));
|
|
170
|
+
const { node, quoted } = withDoubtedReferencesQuoted(withSystemContexts(withCreations(withChanges(asJsonLdEntity(atOwnPlacesAll(edition))))));
|
|
161
171
|
// The context is the export's own; one carried in from an import must not override it.
|
|
162
172
|
const { base, '@context': carried, ...rest } = node;
|
|
163
173
|
return {
|
package/lib/io/importJsonLd.js
CHANGED
|
@@ -71,6 +71,16 @@ const withPlainCopyIds = (json) => ({
|
|
|
71
71
|
'@id': typeof copy['@id'] === 'string' ? plainCopyId(copy['@id']) : copy['@id']
|
|
72
72
|
}))
|
|
73
73
|
});
|
|
74
|
+
/** The document without the creations an export gave versions that state none. */
|
|
75
|
+
const withoutEmptyCreations = (json) => Array.isArray(json.versions)
|
|
76
|
+
? {
|
|
77
|
+
...json,
|
|
78
|
+
versions: json.versions.map((version) => {
|
|
79
|
+
const { creation, ...rest } = version;
|
|
80
|
+
return creation !== null && typeof creation === 'object' && Object.keys(creation).length === 0 ? rest : version;
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
: json;
|
|
74
84
|
/** A statement an export quoted rather than stated: an included node whose id is a triple. */
|
|
75
85
|
const isQuotedStatement = (node) => node !== null && typeof node === 'object' && node['@id'] !== null && typeof node['@id'] === 'object';
|
|
76
86
|
/** The reference a quoted statement made, annotated again with the belief the export set beside it. */
|
|
@@ -125,7 +135,7 @@ const withQuotedStatementsInPlace = (edition) => {
|
|
|
125
135
|
* its axis, which is where the copy's alignment carries them.
|
|
126
136
|
*/
|
|
127
137
|
export const importJsonLd = (json) => {
|
|
128
|
-
const { '@context': context, ...document } = withPlainCopyIds(migrate(withQuotedStatementsInPlace(json)));
|
|
138
|
+
const { '@context': context, ...document } = withoutEmptyCreations(withPlainCopyIds(migrate(withQuotedStatementsInPlace(json))));
|
|
129
139
|
const edition = fromJsonLdEntity(document);
|
|
130
140
|
edition.base = Array.isArray(context)
|
|
131
141
|
? context.find((c) => c['@base'])?.['@base'] || ''
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding what an edition holds by its id. Each kind of entity has an
|
|
3
|
+
* index of its own, built from where the edition holds it when first
|
|
4
|
+
* asked for and kept for as long as the state is (`perState`). The index
|
|
5
|
+
* an entity is found in says what it is, so a lookup needs neither a
|
|
6
|
+
* type discriminator nor a cast.
|
|
7
|
+
*/
|
|
8
|
+
import { Edition } from "../model/Edition.js";
|
|
9
|
+
import { NestedFeature } from "../model/Feature.js";
|
|
10
|
+
import { AnySymbol } from "../model/Symbol.js";
|
|
11
|
+
import { Version } from "../model/Version.js";
|
|
12
|
+
import { Modification, ProductionEvent, RollCopy } from "../model/RollCopy.js";
|
|
13
|
+
export declare const versionIn: (edition: Pick<Edition, 'versions'>, id: string) => Readonly<Version> | undefined;
|
|
14
|
+
export declare const copyIn: (edition: Pick<Edition, 'copies'>, id: string) => Readonly<RollCopy> | undefined;
|
|
15
|
+
/** A symbol any version inserts. */
|
|
16
|
+
export declare const symbolIn: (edition: Pick<Edition, 'versions'>, id: string) => Readonly<AnySymbol> | undefined;
|
|
17
|
+
/** The symbols under the ids, leaving out an id that names none. */
|
|
18
|
+
export declare const symbolsIn: (edition: Pick<Edition, 'versions'>, ids: readonly string[]) => Readonly<AnySymbol>[];
|
|
19
|
+
/** A feature or a patch on any copy, one a patch bears included. */
|
|
20
|
+
export declare const featureIn: (edition: Pick<Edition, 'copies'>, id: string) => Readonly<NestedFeature> | undefined;
|
|
21
|
+
/** The features and patches under the ids, leaving out an id that names none. */
|
|
22
|
+
export declare const featuresIn: (edition: Pick<Edition, 'copies'>, ids: readonly string[]) => Readonly<NestedFeature>[];
|
|
23
|
+
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
24
|
+
export declare const copyOfFeature: (edition: Pick<Edition, 'copies'>, featureId: string) => Readonly<RollCopy> | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The act a copy states a feature in: the production event that
|
|
27
|
+
* punched it, or the modification that brought it about. A feature
|
|
28
|
+
* a patch bears came onto the copy with the patch, so the act is
|
|
29
|
+
* the one that glued the patch on. Two features stand in one act
|
|
30
|
+
* where this returns the very same object.
|
|
31
|
+
*/
|
|
32
|
+
export declare const actOf: (edition: Pick<Edition, 'copies'>, featureId: string) => Readonly<ProductionEvent | Modification> | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { withBorneFeatures } from "../model/Feature.js";
|
|
2
|
+
import { insertedBy } from "../model/Version.js";
|
|
3
|
+
import { featuresMadeBy, featuresOf } from "../model/RollCopy.js";
|
|
4
|
+
import { perState } from "./perState.js";
|
|
5
|
+
const byIdIn = (entities) => new Map(entities.map(entity => [entity.id, entity]));
|
|
6
|
+
const versionsById = perState((edition) => byIdIn(edition.versions));
|
|
7
|
+
const copiesById = perState((edition) => byIdIn(edition.copies));
|
|
8
|
+
const symbolsById = perState((edition) => byIdIn(edition.versions.flatMap(insertedBy)));
|
|
9
|
+
/** Every feature and patch on the copies, those a patch bears included. */
|
|
10
|
+
const featuresOnCopies = (edition) => edition.copies.flatMap(copy => featuresOf(copy).flatMap(withBorneFeatures).map(feature => ({ feature, copy })));
|
|
11
|
+
const featuresById = perState((edition) => new Map(featuresOnCopies(edition).map(({ feature }) => [feature.id, feature])));
|
|
12
|
+
const copiesByFeature = perState((edition) => new Map(featuresOnCopies(edition).map(({ feature, copy }) => [feature.id, copy])));
|
|
13
|
+
export const versionIn = (edition, id) => versionsById(edition).get(id);
|
|
14
|
+
export const copyIn = (edition, id) => copiesById(edition).get(id);
|
|
15
|
+
/** A symbol any version inserts. */
|
|
16
|
+
export const symbolIn = (edition, id) => symbolsById(edition).get(id);
|
|
17
|
+
/** The symbols under the ids, leaving out an id that names none. */
|
|
18
|
+
export const symbolsIn = (edition, ids) => ids.flatMap(id => symbolIn(edition, id) ?? []);
|
|
19
|
+
/** A feature or a patch on any copy, one a patch bears included. */
|
|
20
|
+
export const featureIn = (edition, id) => featuresById(edition).get(id);
|
|
21
|
+
/** The features and patches under the ids, leaving out an id that names none. */
|
|
22
|
+
export const featuresIn = (edition, ids) => ids.flatMap(id => featureIn(edition, id) ?? []);
|
|
23
|
+
/** The copy a feature sits on, a patch and everything it bears included. */
|
|
24
|
+
export const copyOfFeature = (edition, featureId) => copiesByFeature(edition).get(featureId);
|
|
25
|
+
/**
|
|
26
|
+
* The act a copy states a feature in: the production event that
|
|
27
|
+
* punched it, or the modification that brought it about. A feature
|
|
28
|
+
* a patch bears came onto the copy with the patch, so the act is
|
|
29
|
+
* the one that glued the patch on. Two features stand in one act
|
|
30
|
+
* where this returns the very same object.
|
|
31
|
+
*/
|
|
32
|
+
export const actOf = (edition, featureId) => {
|
|
33
|
+
const copy = copyOfFeature(edition, featureId);
|
|
34
|
+
if (!copy)
|
|
35
|
+
return undefined;
|
|
36
|
+
const states = (features) => features.flatMap(withBorneFeatures).some(feature => feature.id === featureId);
|
|
37
|
+
if (states(copy.production?.produced ?? []))
|
|
38
|
+
return copy.production;
|
|
39
|
+
return copy.modifications.find(act => states(featuresMadeBy(act)));
|
|
40
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where in an edition each id stands and where it is referenced, as
|
|
3
|
+
* paths an operation can follow into a draft of it.
|
|
4
|
+
*/
|
|
5
|
+
import { Edition } from "../model/Edition.js";
|
|
6
|
+
export type Path = (string | number)[];
|
|
7
|
+
export declare const getAt: <T>(path: Path, obj: unknown) => T | undefined;
|
|
8
|
+
/** Where the entity with the id stands in the edition, or nothing where it holds none. */
|
|
9
|
+
export declare const pathIn: (edition: Edition, id: string) => Path | undefined;
|
|
10
|
+
/** Where the edition references the id. */
|
|
11
|
+
export declare const linksTo: (edition: Edition, id: string) => Path[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { perState } from "./perState.js";
|
|
2
|
+
export const getAt = (path, obj) => {
|
|
3
|
+
let node = obj;
|
|
4
|
+
for (const key of path) {
|
|
5
|
+
if (node == null)
|
|
6
|
+
return undefined;
|
|
7
|
+
node = node[key];
|
|
8
|
+
}
|
|
9
|
+
return node;
|
|
10
|
+
};
|
|
11
|
+
/** Keys under which an object names others by id, singly or in a list. */
|
|
12
|
+
const referenceKeys = ['delete', 'comprehends', 'motivation', 'premises', 'used'];
|
|
13
|
+
const isObject = (v) => v !== null && typeof v === "object";
|
|
14
|
+
/** What a reference may state besides the id: a belief about it, and the tolerance a derivation was collated at. */
|
|
15
|
+
const referenceOwnKeys = new Set(['id', '@annotation', 'collationTolerance']);
|
|
16
|
+
/** An object that names another by its id and says nothing of its own beyond that. */
|
|
17
|
+
const isReferenceOnly = (keys) => keys.every(key => referenceOwnKeys.has(key));
|
|
18
|
+
const laidOut = (trail) => {
|
|
19
|
+
const path = [];
|
|
20
|
+
for (let link = trail; link !== null; link = link.up)
|
|
21
|
+
path.push(link.key);
|
|
22
|
+
return path.reverse();
|
|
23
|
+
};
|
|
24
|
+
/** Walks the edition and records where each id stands and where it is referenced. */
|
|
25
|
+
const placesIn = perState((edition) => {
|
|
26
|
+
const paths = new Map();
|
|
27
|
+
const links = new Map();
|
|
28
|
+
const visited = new WeakSet();
|
|
29
|
+
const link = (id, trail) => {
|
|
30
|
+
const trails = links.get(id);
|
|
31
|
+
if (trails)
|
|
32
|
+
trails.push(trail);
|
|
33
|
+
else
|
|
34
|
+
links.set(id, [trail]);
|
|
35
|
+
};
|
|
36
|
+
const index = (record, keys, trail) => {
|
|
37
|
+
if (typeof record.id !== "string")
|
|
38
|
+
return;
|
|
39
|
+
if (isReferenceOnly(keys))
|
|
40
|
+
link(record.id, { key: 'id', up: trail });
|
|
41
|
+
else if (!paths.has(record.id)) {
|
|
42
|
+
paths.set(record.id, trail);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const linkReferences = (record, trail) => referenceKeys.forEach(key => {
|
|
46
|
+
const ref = record[key];
|
|
47
|
+
if (typeof ref === "string")
|
|
48
|
+
link(ref, { key, up: trail });
|
|
49
|
+
else if (Array.isArray(ref))
|
|
50
|
+
ref.forEach((r, i) => {
|
|
51
|
+
if (typeof r === "string")
|
|
52
|
+
link(r, { key: i, up: { key, up: trail } });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
const traverse = (node, trail) => {
|
|
56
|
+
if (!isObject(node) || visited.has(node))
|
|
57
|
+
return;
|
|
58
|
+
visited.add(node);
|
|
59
|
+
if (Array.isArray(node)) {
|
|
60
|
+
node.forEach((item, i) => traverse(item, { key: i, up: trail }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const record = node;
|
|
64
|
+
const keys = Object.keys(record);
|
|
65
|
+
index(record, keys, trail);
|
|
66
|
+
linkReferences(record, trail);
|
|
67
|
+
keys.forEach(key => traverse(record[key], { key, up: trail }));
|
|
68
|
+
};
|
|
69
|
+
traverse(edition, null);
|
|
70
|
+
return { paths, links };
|
|
71
|
+
});
|
|
72
|
+
/** Where the entity with the id stands in the edition, or nothing where it holds none. */
|
|
73
|
+
export const pathIn = (edition, id) => {
|
|
74
|
+
const trail = placesIn(edition).paths.get(id);
|
|
75
|
+
return trail === undefined ? undefined : laidOut(trail);
|
|
76
|
+
};
|
|
77
|
+
/** Where the edition references the id. */
|
|
78
|
+
export const linksTo = (edition, id) => (placesIn(edition).links.get(id) ?? []).map(laidOut);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Draft } from "immer";
|
|
2
|
+
/**
|
|
3
|
+
* The draft as it stands, as plain data: the state it was drafted from
|
|
4
|
+
* while nothing has changed it, and a copy of it as it now stands once
|
|
5
|
+
* something has.
|
|
6
|
+
*/
|
|
7
|
+
export declare const takenOf: <T extends object>(draft: Draft<T>) => T;
|
|
8
|
+
/**
|
|
9
|
+
* The edition as a state that no longer changes. It is frozen, deeply
|
|
10
|
+
* and once, as immer freezes what it produces, since what is worked out
|
|
11
|
+
* from a state is kept for as long as the state is: changed in place
|
|
12
|
+
* afterwards, it would leave that behind unnoticed, and frozen, the
|
|
13
|
+
* change throws instead. An edition immer produced is frozen already,
|
|
14
|
+
* and freezing stops at whatever is. A copy taken of a draft is left as
|
|
15
|
+
* it is (`takenOf`).
|
|
16
|
+
*
|
|
17
|
+
* A draft is no such state, since the next write changes it. It is read
|
|
18
|
+
* as it stands by taking `stateOf(draft)` first, once for all that is
|
|
19
|
+
* asked of it; asked of directly, it would have to be copied for every
|
|
20
|
+
* question.
|
|
21
|
+
*/
|
|
22
|
+
export declare const settled: <T extends object>(edition: T) => T;
|
|
23
|
+
/**
|
|
24
|
+
* What is worked out from an edition, once for each state of it.
|
|
25
|
+
*
|
|
26
|
+
* Every change to an edition yields a new object, immer's produce
|
|
27
|
+
* included, so the state an object stands for never changes and what
|
|
28
|
+
* was worked out from it holds for as long as the object is around.
|
|
29
|
+
* Nothing is worked out before it is first asked for, and nothing is
|
|
30
|
+
* kept once the state is gone.
|
|
31
|
+
*/
|
|
32
|
+
export declare const perState: <E extends object, T>(work: (edition: E) => T) => ((edition: E) => T);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { current, freeze, isDraft, original } from "immer";
|
|
2
|
+
/**
|
|
3
|
+
* Copies taken of a draft as it stood, for an operation to read. Such a
|
|
4
|
+
* copy is the operation's alone and is not frozen: what the operation
|
|
5
|
+
* reads from it may go into the draft, and a later operation on the same
|
|
6
|
+
* draft may go on to change it there, as it may anything written there.
|
|
7
|
+
*/
|
|
8
|
+
const takenOfDrafts = new WeakSet();
|
|
9
|
+
/**
|
|
10
|
+
* The draft as it stands, as plain data: the state it was drafted from
|
|
11
|
+
* while nothing has changed it, and a copy of it as it now stands once
|
|
12
|
+
* something has.
|
|
13
|
+
*/
|
|
14
|
+
export const takenOf = (draft) => {
|
|
15
|
+
const state = current(draft);
|
|
16
|
+
if (state !== original(draft))
|
|
17
|
+
takenOfDrafts.add(state);
|
|
18
|
+
return state;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The edition as a state that no longer changes. It is frozen, deeply
|
|
22
|
+
* and once, as immer freezes what it produces, since what is worked out
|
|
23
|
+
* from a state is kept for as long as the state is: changed in place
|
|
24
|
+
* afterwards, it would leave that behind unnoticed, and frozen, the
|
|
25
|
+
* change throws instead. An edition immer produced is frozen already,
|
|
26
|
+
* and freezing stops at whatever is. A copy taken of a draft is left as
|
|
27
|
+
* it is (`takenOf`).
|
|
28
|
+
*
|
|
29
|
+
* A draft is no such state, since the next write changes it. It is read
|
|
30
|
+
* as it stands by taking `stateOf(draft)` first, once for all that is
|
|
31
|
+
* asked of it; asked of directly, it would have to be copied for every
|
|
32
|
+
* question.
|
|
33
|
+
*/
|
|
34
|
+
export const settled = (edition) => {
|
|
35
|
+
if (isDraft(edition)) {
|
|
36
|
+
throw new Error('An edition is asked about as a state, not as a draft: take stateOf(draft) first.');
|
|
37
|
+
}
|
|
38
|
+
return takenOfDrafts.has(edition) ? edition : freeze(edition, true);
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* What is worked out from an edition, once for each state of it.
|
|
42
|
+
*
|
|
43
|
+
* Every change to an edition yields a new object, immer's produce
|
|
44
|
+
* included, so the state an object stands for never changes and what
|
|
45
|
+
* was worked out from it holds for as long as the object is around.
|
|
46
|
+
* Nothing is worked out before it is first asked for, and nothing is
|
|
47
|
+
* kept once the state is gone.
|
|
48
|
+
*/
|
|
49
|
+
export const perState = (work) => {
|
|
50
|
+
const known = new WeakMap();
|
|
51
|
+
return edition => {
|
|
52
|
+
// A state known already was settled when it became known, and a
|
|
53
|
+
// draft never is, so only what is asked of for the first time
|
|
54
|
+
// needs settling.
|
|
55
|
+
const kept = known.get(edition);
|
|
56
|
+
if (kept)
|
|
57
|
+
return kept.value;
|
|
58
|
+
const state = settled(edition);
|
|
59
|
+
const entry = { value: work(state) };
|
|
60
|
+
known.set(state, entry);
|
|
61
|
+
return entry.value;
|
|
62
|
+
};
|
|
63
|
+
};
|
package/lib/model/Edit.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export type EditType = typeof editTypes[number];
|
|
|
23
23
|
* If an edit is the interpretation of a metamark,
|
|
24
24
|
* such as a pencil mark, this should be made explicit
|
|
25
25
|
* using a meaning comprehension on the `@annotation` field.
|
|
26
|
+
* An edit states no date. That it ended no later than the act that
|
|
27
|
+
* made a feature carrying a symbol it inserts, a reasoner derives with
|
|
28
|
+
* the axioms of reo.ttl; which of those dates bounds it closest is for
|
|
29
|
+
* a query to pick.
|
|
26
30
|
* @see reo:Edit
|
|
27
31
|
*/
|
|
28
32
|
export interface Edit extends WithId, Assumption {
|
package/lib/model/Version.d.ts
CHANGED
|
@@ -93,7 +93,13 @@ export interface Version extends WithId {
|
|
|
93
93
|
basedOn?: Derivation[];
|
|
94
94
|
/**
|
|
95
95
|
* The act that made this version, where it is known: who carried it
|
|
96
|
-
* out, when, and by what rule.
|
|
96
|
+
* out, when, and by what rule. The export states the act for every
|
|
97
|
+
* version, stating nothing of it where nothing is known, so that the
|
|
98
|
+
* axioms of reo.ttl can place it in time: it started after the
|
|
99
|
+
* recording and the creation of every version it derives from had
|
|
100
|
+
* started, and ended no later than the punching of any copy carrying
|
|
101
|
+
* a symbol it added. A date stated here stands beside those; which of
|
|
102
|
+
* them bounds the act closest is for a query to pick.
|
|
97
103
|
* @see lrmoo:R17i was created by
|
|
98
104
|
*/
|
|
99
105
|
creation?: VersionCreation;
|