linked-rolls 0.63.0 → 0.65.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.
Files changed (67) hide show
  1. package/README.md +13 -5
  2. package/lib/analysis/constraints.d.ts +2 -2
  3. package/lib/analysis/constraints.js +38 -32
  4. package/lib/analysis/ownPaper.d.ts +3 -3
  5. package/lib/analysis/ownPaper.js +3 -3
  6. package/lib/analysis/reservations.d.ts +2 -2
  7. package/lib/analysis/reservations.js +6 -6
  8. package/lib/analysis/sigla.d.ts +4 -5
  9. package/lib/analysis/sigla.js +4 -4
  10. package/lib/analysis/stemma.d.ts +22 -0
  11. package/lib/analysis/stemma.js +38 -0
  12. package/lib/analysis/text.d.ts +31 -0
  13. package/lib/analysis/text.js +62 -0
  14. package/lib/analysis/witnesses.d.ts +5 -5
  15. package/lib/analysis/witnesses.js +27 -22
  16. package/lib/collation/alignment.js +55 -20
  17. package/lib/collation/scatter.d.ts +6 -6
  18. package/lib/collation/scatter.js +15 -13
  19. package/lib/emulation/Emulation.d.ts +7 -7
  20. package/lib/emulation/Emulation.js +29 -27
  21. package/lib/emulation/negotiation.d.ts +2 -2
  22. package/lib/emulation/negotiation.js +3 -2
  23. package/lib/index.d.ts +4 -1
  24. package/lib/index.js +4 -1
  25. package/lib/io/asJsonLd.js +43 -38
  26. package/lib/io/importJsonLd.js +11 -1
  27. package/lib/io/migrate.js +20 -26
  28. package/lib/lookup/lookup.d.ts +32 -0
  29. package/lib/lookup/lookup.js +40 -0
  30. package/lib/lookup/paths.d.ts +11 -0
  31. package/lib/lookup/paths.js +78 -0
  32. package/lib/lookup/perState.d.ts +32 -0
  33. package/lib/lookup/perState.js +63 -0
  34. package/lib/model/Edit.d.ts +4 -0
  35. package/lib/model/Edition.d.ts +0 -13
  36. package/lib/model/Version.d.ts +7 -1
  37. package/lib/ops/beliefs.d.ts +1 -1
  38. package/lib/ops/beliefs.js +1 -1
  39. package/lib/ops/collation.d.ts +3 -4
  40. package/lib/ops/collation.js +38 -32
  41. package/lib/ops/commands.d.ts +4 -5
  42. package/lib/ops/commands.js +10 -10
  43. package/lib/ops/copies.js +11 -3
  44. package/lib/ops/draft.d.ts +12 -12
  45. package/lib/ops/draft.js +11 -13
  46. package/lib/ops/edits.d.ts +1 -2
  47. package/lib/ops/edits.js +13 -10
  48. package/lib/ops/features.d.ts +2 -2
  49. package/lib/ops/features.js +3 -2
  50. package/lib/ops/immutable.d.ts +7 -1
  51. package/lib/ops/immutable.js +22 -8
  52. package/lib/ops/index.d.ts +1 -1
  53. package/lib/ops/index.js +6 -0
  54. package/lib/ops/versions.d.ts +2 -3
  55. package/lib/ops/versions.js +6 -5
  56. package/lib/readers/phillipsEroll.js +1 -8
  57. package/lib/readers/stanfordAton.js +2 -6
  58. package/lib/schema.json +2 -33
  59. package/lib/systems/welte.d.ts +17 -0
  60. package/lib/systems/welte.js +17 -1
  61. package/lib/systems/welteT100/system.d.ts +1 -16
  62. package/lib/systems/welteT100/system.js +3 -17
  63. package/lib/systems/welteT98/system.d.ts +1 -12
  64. package/lib/systems/welteT98/system.js +3 -13
  65. package/package.json +1 -1
  66. package/lib/view/EditionView.d.ts +0 -103
  67. package/lib/view/EditionView.js +0 -329
package/README.md CHANGED
@@ -22,7 +22,7 @@ single undo step.
22
22
 
23
23
  ```ts
24
24
  import { produce } from 'immer'
25
- import { alignCopy, asJsonLd, connectVersions, createVersion, EditionView, importJsonLd, readFromStanfordAton } from 'linked-rolls'
25
+ import { alignCopy, asJsonLd, connectVersions, createVersion, importJsonLd, readFromStanfordAton, snapshotOf } from 'linked-rolls'
26
26
 
27
27
  let edition = importJsonLd(json)
28
28
 
@@ -32,13 +32,21 @@ const copy = readFromStanfordAton(aton)
32
32
  edition = produce(edition, createVersion(copy))
33
33
  edition = produce(edition, alignCopy(copy.id))
34
34
 
35
- // Collate that version against another one
36
- const view = new EditionView(edition)
37
- edition = produce(edition, connectVersions(view, childId, parentId))
35
+ // Collate that version against another one, and read what it shows
36
+ edition = produce(edition, connectVersions(childId, parentId))
37
+ const symbols = snapshotOf(edition, childId)
38
38
 
39
39
  const document = asJsonLd(edition)
40
40
  ```
41
41
 
42
+ What is read off an edition, such as `snapshotOf`, `placeOf`,
43
+ `versionIn` or `pathIn`, is worked out once for each state of it and
44
+ kept for as long as the state is. Every change through `produce` yields
45
+ a new state, so nothing needs to be told that the edition changed. For
46
+ the same reason an edition is frozen once anything has been asked of it,
47
+ as immer freezes what it produces: change it through an operation, not
48
+ in place. A draft is asked about as `stateOf(draft)`.
49
+
42
50
  Within the library every place along the roll is a place on the edition's
43
51
  axis, the millimetres of its reference copy. A document holds each copy's
44
52
  features at the copy's own places, as they were read, together with the
@@ -65,7 +73,7 @@ optional peer dependency that you install only if you need playback.
65
73
  import { emulate, midiOf } from 'linked-rolls'
66
74
  import { welteT100System } from 'linked-rolls/welte-t100'
67
75
 
68
- const { events, source } = emulate(welteT100System, version, view)
76
+ const { events, source } = emulate(welteT100System, version, edition)
69
77
  const midi = midiOf(events, welteT100System.name, welteT100System.defaultOptions, source)
70
78
  ```
71
79
 
@@ -1,4 +1,4 @@
1
- import { EditionView } from "../view/EditionView.js";
1
+ import { Edition } from "../model/Edition.js";
2
2
  export type ConstraintProblem = {
3
3
  version: string;
4
4
  symbol: string;
@@ -14,4 +14,4 @@ export type ConstraintProblem = {
14
14
  * track that does not say what its symbol says, and a strike that takes
15
15
  * nothing out.
16
16
  */
17
- export declare const constraintProblems: (view: EditionView) => ConstraintProblem[];
17
+ export declare const constraintProblems: (edition: Edition) => ConstraintProblem[];
@@ -6,6 +6,9 @@ import { keyOf } from "../systems/TrackerBar.js";
6
6
  import { trackerBarOf } from "../systems/index.js";
7
7
  import { barOf } from "../model/RollCopy.js";
8
8
  import { deletedBy, insertedBy } from "../model/Version.js";
9
+ import { predecessorOf } from "./stemma.js";
10
+ import { placedCarriersOf, snapshotOf } from "./text.js";
11
+ import { copyOfFeature } from "../lookup/lookup.js";
9
12
  const missingReference = {
10
13
  alignedWith: 'alignment-reference-missing',
11
14
  before: 'before-reference-missing',
@@ -77,20 +80,19 @@ const typesNotOnTheBar = (version, snapshot) => {
77
80
  * out says 59. Collation across systems consults only the place, so
78
81
  * without this nothing checks the tracks at all.
79
82
  */
80
- const carriersOffTheirMeaning = (view, version, commands) => {
81
- const misread = (carrier, symbol) => {
82
- const copy = view.copyOf(carrier.id);
83
- if (!copy)
84
- return false;
83
+ const carriedOffItsMeaning = (edition) => (symbol) => {
84
+ const key = keyOf(symbol);
85
+ return placedCarriersOf(edition, symbol).some(carrier => {
86
+ const copy = copyOfFeature(edition, carrier.id);
85
87
  // A carrier lying across several positions reads as several commands,
86
88
  // and carries the symbol as long as one of them is the symbol's.
87
- return !barOf(copy).meaningsOf(carrier.vertical)
88
- .some(meaning => keyOf(meaning) === keyOf(symbol));
89
- };
90
- return commands
91
- .filter(symbol => view.placedCarriersOf(symbol).some(carrier => misread(carrier, symbol)))
92
- .map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
89
+ return copy !== undefined && !barOf(copy).meaningsOf(carrier.vertical).some(meaning => keyOf(meaning) === key);
90
+ });
93
91
  };
92
+ /** The commands the version shows among those carried off their meaning. */
93
+ const carriersOffTheirMeaning = (misread, version, commands) => commands
94
+ .filter(symbol => misread.has(symbol))
95
+ .map(symbol => ({ version, symbol: symbol.id, problem: 'carrier-on-another-track' }));
94
96
  /**
95
97
  * Where a copy of the version's own system strays from the others in
96
98
  * its paper by more than paper does.
@@ -101,13 +103,9 @@ const carriersOffTheirMeaning = (view, version, commands) => {
101
103
  * speed, and averaging it in would hide both that and the fact that the
102
104
  * playback rests on a guess.
103
105
  */
104
- const paperDisagreed = (view, version) => {
105
- const own = new Set(paperOfVersion(view, version)?.copies ?? []);
106
- return alignmentProblems(view.edition)
107
- .some(({ copy, problem }) => problem === 'paper-beyond-its-spread' && own.has(copy))
108
- ? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
109
- : [];
110
- };
106
+ const paperDisagreed = (edition, strays, version) => paperOfVersion(edition, version)?.copies.some(copy => strays.has(copy))
107
+ ? [{ version: version.id, symbol: version.id, problem: 'copies-disagree-on-the-paper' }]
108
+ : [];
111
109
  /**
112
110
  * A version's strikes that take nothing out of its text: each deleted
113
111
  * symbol has to be one the parent hands down, or one the version put
@@ -120,10 +118,10 @@ const paperDisagreed = (view, version) => {
120
118
  * into the text unstruck. Nothing else in the edition shows it: the
121
119
  * counts move, and only by a handful.
122
120
  */
123
- const strikesBitingNothing = (view, version) => {
124
- const parent = view.predecessorOf(version.id);
121
+ const strikesBitingNothing = (edition, version) => {
122
+ const parent = predecessorOf(edition, version.id);
125
123
  const reachable = new Set([
126
- ...(parent ? view.snapshot(parent.id).map(symbol => symbol.id) : []),
124
+ ...(parent ? snapshotOf(edition, parent.id).map(symbol => symbol.id) : []),
127
125
  ...insertedBy(version).map(symbol => symbol.id)
128
126
  ]);
129
127
  return deletedBy(version)
@@ -140,14 +138,22 @@ const strikesBitingNothing = (view, version) => {
140
138
  * track that does not say what its symbol says, and a strike that takes
141
139
  * nothing out.
142
140
  */
143
- export const constraintProblems = (view) => view.edition.versions.flatMap(version => {
144
- const snapshot = view.snapshot(version.id);
145
- const commands = snapshot.filter(isCommand);
146
- return [
147
- ...problemsIn(version.id, commands),
148
- ...typesNotOnTheBar(version, snapshot),
149
- ...carriersOffTheirMeaning(view, version.id, commands),
150
- ...paperDisagreed(view, version),
151
- ...strikesBitingNothing(view, version)
152
- ];
153
- });
141
+ export const constraintProblems = (edition) => {
142
+ // What a symbol's carriers and the copies' paper say holds whichever
143
+ // version shows them, so it is asked once for the edition.
144
+ const misread = new Set(edition.versions.flatMap(insertedBy).filter(isCommand).filter(carriedOffItsMeaning(edition)));
145
+ const strays = new Set(alignmentProblems(edition)
146
+ .filter(({ problem }) => problem === 'paper-beyond-its-spread')
147
+ .map(({ copy }) => copy));
148
+ return edition.versions.flatMap(version => {
149
+ const snapshot = snapshotOf(edition, version.id);
150
+ const commands = snapshot.filter(isCommand);
151
+ return [
152
+ ...problemsIn(version.id, commands),
153
+ ...typesNotOnTheBar(version, snapshot),
154
+ ...carriersOffTheirMeaning(misread, version.id, commands),
155
+ ...paperDisagreed(edition, strays, version),
156
+ ...strikesBitingNothing(edition, version)
157
+ ];
158
+ });
159
+ };
@@ -1,8 +1,8 @@
1
- import type { EditionView } from "../view/EditionView.js";
2
1
  import type { Version } from "../model/Version.js";
3
2
  import { PaperOfSystem } from "./paper.js";
3
+ import { Edition } from "../model/Edition.js";
4
4
  /** What the alignments say about the paper of the version's own system, where they say anything. */
5
- export declare const paperOfVersion: (view: EditionView, version: Readonly<Version>) => PaperOfSystem | undefined;
5
+ export declare const paperOfVersion: (edition: Edition, version: Readonly<Version>) => PaperOfSystem | undefined;
6
6
  /**
7
7
  * How a place on the edition's shared axis relates to the paper of
8
8
  * this version: place × factor = millimetres of its own paper.
@@ -18,4 +18,4 @@ export declare const paperOfVersion: (view: EditionView, version: Readonly<Versi
18
18
  * Nothing where no copy of the version's system measures its paper,
19
19
  * which leaves the performance on the axis.
20
20
  */
21
- export declare const toOwnPaperOf: (view: EditionView, version: Readonly<Version>) => number | undefined;
21
+ export declare const toOwnPaperOf: (edition: Edition, version: Readonly<Version>) => number | undefined;
@@ -1,9 +1,9 @@
1
1
  import { defaultTrackerBar, trackerBarOf } from "../systems/index.js";
2
2
  import { paperOf } from "./paper.js";
3
3
  /** What the alignments say about the paper of the version's own system, where they say anything. */
4
- export const paperOfVersion = (view, version) => {
4
+ export const paperOfVersion = (edition, version) => {
5
5
  const system = (trackerBarOf(version.system) ?? defaultTrackerBar).id;
6
- return paperOf(view.edition)?.systems.find(paper => paper.system === system);
6
+ return paperOf(edition)?.systems.find(paper => paper.system === system);
7
7
  };
8
8
  /**
9
9
  * How a place on the edition's shared axis relates to the paper of
@@ -20,4 +20,4 @@ export const paperOfVersion = (view, version) => {
20
20
  * Nothing where no copy of the version's system measures its paper,
21
21
  * which leaves the performance on the axis.
22
22
  */
23
- export const toOwnPaperOf = (view, version) => paperOfVersion(view, version)?.toOwnPaper;
23
+ export const toOwnPaperOf = (edition, version) => paperOfVersion(edition, version)?.toOwnPaper;
@@ -1,6 +1,6 @@
1
1
  import { RollCopy } from "../model/RollCopy.js";
2
2
  import { Version } from "../model/Version.js";
3
- import { EditionView } from "../view/EditionView.js";
3
+ import { Edition } from "../model/Edition.js";
4
4
  export declare const reservationTypes: readonly ['source-not-stated', 'source-undocumented', 'software-not-named', 'instrument-not-named', 'features-interpreted', 'no-physical-evidence', 'measurement-undocumented', 'not-calibrated', 'system-unknown', 'keeper-unknown'];
5
5
  export type ReservationType = typeof reservationTypes[number];
6
6
  /**
@@ -27,4 +27,4 @@ export declare const reservationsAbout: (copy: RollCopy) => Reservation[];
27
27
  export declare const versionReservationTypes: readonly ['text-not-stated', 'witnessed-by-statement-only', 'no-direct-witness'];
28
28
  export type VersionReservationType = typeof versionReservationTypes[number];
29
29
  /** What the edition cannot vouch for in a version, in the order the checks are listed. */
30
- export declare const reservationsAboutVersion: (view: EditionView, version: Readonly<Version>) => Reservation<VersionReservationType>[];
30
+ export declare const reservationsAboutVersion: (edition: Edition, version: Readonly<Version>) => Reservation<VersionReservationType>[];
@@ -105,12 +105,12 @@ export const versionReservationTypes = [
105
105
  'witnessed-by-statement-only',
106
106
  'no-direct-witness'
107
107
  ];
108
- const textStated = (_view, version) => version.edits ? undefined : {
108
+ const textStated = (_edition, version) => version.edits ? undefined : {
109
109
  type: 'text-not-stated',
110
110
  note: 'The version does not state its edits, so it reads as the version it derives from.'
111
111
  };
112
- const witnessedByFeatures = (view, version) => {
113
- const witnesses = witnessesOf(view, version.id);
112
+ const witnessedByFeatures = (edition, version) => {
113
+ const witnesses = witnessesOf(edition, version.id);
114
114
  return witnesses.length > 0 && witnesses.every(({ by }) => by === 'statement') ? {
115
115
  type: 'witnessed-by-statement-only',
116
116
  note: 'No copy\'s features carry what the version inserts; only copies that state they carry it bear witness to it.'
@@ -121,8 +121,8 @@ const witnessedByFeatures = (view, version) => {
121
121
  * Its text is a reconstruction from below, as a lost state's is, and
122
122
  * nothing surviving shows it as it stood.
123
123
  */
124
- const witnessedAtFirstHand = (view, version) => {
125
- const carriers = witnessesOf(view, version.id).filter(({ by }) => by === 'carriers');
124
+ const witnessedAtFirstHand = (edition, version) => {
125
+ const carriers = witnessesOf(edition, version.id).filter(({ by }) => by === 'carriers');
126
126
  return carriers.length > 0 && carriers.every(({ through }) => through !== undefined) ? {
127
127
  type: 'no-direct-witness',
128
128
  note: 'No copy carries it at first hand; its text is a reconstruction from the versions derived from it.'
@@ -130,4 +130,4 @@ const witnessedAtFirstHand = (view, version) => {
130
130
  };
131
131
  const versionChecks = [textStated, witnessedByFeatures, witnessedAtFirstHand];
132
132
  /** What the edition cannot vouch for in a version, in the order the checks are listed. */
133
- export const reservationsAboutVersion = (view, version) => versionChecks.flatMap(check => check(view, version) ?? []);
133
+ export const reservationsAboutVersion = (edition, version) => versionChecks.flatMap(check => check(edition, version) ?? []);
@@ -1,13 +1,12 @@
1
1
  import { Edition } from '../model/Edition.js';
2
- import { EditionView } from '../view/EditionView.js';
3
2
  /**
4
- * What the sigla are read off: the versions alone, or a view, which knows
5
- * the copies as well and so can tell which versions a witness shows.
3
+ * What the sigla are read off: the versions alone, or the edition, whose
4
+ * copies tell which versions a witness shows.
6
5
  */
7
- export type Stemma = Pick<Edition, 'versions'> | EditionView;
6
+ export type Stemma = Pick<Edition, 'versions'> | Edition;
8
7
  /**
9
8
  * The siglum of every version, as the stemma stands: where it sits
10
- * (`alongTheStemma`) and, given a view, whether any copy shows it.
9
+ * (`alongTheStemma`) and, given the edition, whether any copy shows it.
11
10
  *
12
11
  * A version no copy's features carry at first hand is inferred, whether
13
12
  * it is reached only through the versions derived from it or a copy does
@@ -91,7 +91,7 @@ const alongTheStemma = (versions) => {
91
91
  }
92
92
  return sigla;
93
93
  };
94
- const isView = (stemma) => !('versions' in stemma);
94
+ const holdsCopies = (stemma) => 'copies' in stemma;
95
95
  /**
96
96
  * The sigla with the inferred versions in lowercase, as editions mark a
97
97
  * state nothing surviving shows. The letter still names the system and
@@ -100,7 +100,7 @@ const isView = (stemma) => !('versions' in stemma);
100
100
  const marking = (sigla, attested) => new Map([...sigla].map(([id, siglum]) => [id, attested.has(id) ? siglum : siglum.toLowerCase()]));
101
101
  /**
102
102
  * The siglum of every version, as the stemma stands: where it sits
103
- * (`alongTheStemma`) and, given a view, whether any copy shows it.
103
+ * (`alongTheStemma`) and, given the edition, whether any copy shows it.
104
104
  *
105
105
  * A version no copy's features carry at first hand is inferred, whether
106
106
  * it is reached only through the versions derived from it or a copy does
@@ -112,8 +112,8 @@ const marking = (sigla, attested) => new Map([...sigla].map(([id, siglum]) => [i
112
112
  * A siglum is computed anew when the stemma or the copies change, so
113
113
  * nothing should cite one without saying which state it belongs to.
114
114
  */
115
- export const siglaOf = (stemma) => isView(stemma)
116
- ? marking(alongTheStemma(stemma.edition.versions), attestedVersions(stemma))
115
+ export const siglaOf = (stemma) => holdsCopies(stemma)
116
+ ? marking(alongTheStemma(stemma.versions), attestedVersions(stemma))
117
117
  : alongTheStemma(stemma.versions);
118
118
  /** The siglum of one version as the stemma stands, or nothing where the edition holds no such version. */
119
119
  export const siglumOf = (stemma, versionId) => siglaOf(stemma).get(versionId);
@@ -0,0 +1,22 @@
1
+ /** Walking the stemma of an edition along the principal derivations. */
2
+ import { Edition } from "../model/Edition.js";
3
+ import { Version } from "../model/Version.js";
4
+ /**
5
+ * The version and its ancestors along the principal line, from the
6
+ * version up to the root. A stemma that loops back on itself is walked
7
+ * once round, so that an edition in that state can still be read and
8
+ * repaired.
9
+ */
10
+ export declare const lineageOf: (edition: Pick<Edition, 'versions'>, versionId: string) => Readonly<Version>[];
11
+ /** The version the given one's text is read against, by its principal derivation. */
12
+ export declare const predecessorOf: (edition: Pick<Edition, 'versions'>, versionId: string) => Readonly<Version> | undefined;
13
+ /**
14
+ * Every version with its generation: how many principal derivations
15
+ * lie between it and the root. A version whose parent the edition does
16
+ * not hold counts as a root. In a stemma that loops, the loop is walked
17
+ * once round, as `lineageOf` walks it, so that the stemma of an edition
18
+ * in that state can still be drawn and repaired.
19
+ */
20
+ export declare const withGenerations: (edition: Pick<Edition, 'versions'>) => Array<Version & {
21
+ generation: number;
22
+ }>;
@@ -0,0 +1,38 @@
1
+ import { principalDerivationOf } from "../model/Version.js";
2
+ import { idOf } from "../model/Assumption.js";
3
+ import { versionIn } from "../lookup/lookup.js";
4
+ /**
5
+ * The version and its ancestors along the principal line, from the
6
+ * version up to the root. A stemma that loops back on itself is walked
7
+ * once round, so that an edition in that state can still be read and
8
+ * repaired.
9
+ */
10
+ export const lineageOf = (edition, versionId) => {
11
+ const lineage = [];
12
+ const visited = new Set();
13
+ let v = versionIn(edition, versionId);
14
+ while (v && !visited.has(v.id)) {
15
+ visited.add(v.id);
16
+ lineage.push(v);
17
+ const principal = principalDerivationOf(v);
18
+ v = principal && versionIn(edition, idOf(principal));
19
+ }
20
+ return lineage;
21
+ };
22
+ /** The version the given one's text is read against, by its principal derivation. */
23
+ export const predecessorOf = (edition, versionId) => {
24
+ const v = versionIn(edition, versionId);
25
+ const principal = v && principalDerivationOf(v);
26
+ return principal && versionIn(edition, idOf(principal));
27
+ };
28
+ /**
29
+ * Every version with its generation: how many principal derivations
30
+ * lie between it and the root. A version whose parent the edition does
31
+ * not hold counts as a root. In a stemma that loops, the loop is walked
32
+ * once round, as `lineageOf` walks it, so that the stemma of an edition
33
+ * in that state can still be drawn and repaired.
34
+ */
35
+ export const withGenerations = (edition) => edition.versions.map(version => ({
36
+ ...version,
37
+ generation: lineageOf(edition, version.id).length - 1
38
+ }));
@@ -0,0 +1,31 @@
1
+ /** What a version shows, and where along the roll its symbols lie. */
2
+ import { Edition } from "../model/Edition.js";
3
+ import { FeatureOrPatch, HorizontalSpan, NestedFeature } from "../model/Feature.js";
4
+ import { AnySymbol } from "../model/Symbol.js";
5
+ import { Millimeters } from "../model/Quantity.js";
6
+ export declare const carriersOf: (edition: Pick<Edition, 'copies'>, symbol: AnySymbol) => Readonly<NestedFeature>[];
7
+ /** The carriers that state a place of their own, which a feature on a patch does not. */
8
+ export declare const placedCarriersOf: (edition: Pick<Edition, 'copies'>, symbol: AnySymbol) => Readonly<FeatureOrPatch>[];
9
+ /**
10
+ * Where along the roll the symbol lies, as its carriers put it, or
11
+ * nothing for a symbol no copy carries.
12
+ *
13
+ * Only the place is measured. Which track the symbol sits on is not
14
+ * a measurement but a question for a tracker bar, since a note of
15
+ * one pitch sits on exactly one position of a given bar: ask
16
+ * `bar.positionOf`. The carriers cannot answer it, because a copy
17
+ * cut for another system numbers its tracks differently, and one
18
+ * symbol may be carried by copies of both — averaging a red carrier
19
+ * on track 47 with a green one on 45 would give 46, a legal
20
+ * position a semitone away on either bar.
21
+ */
22
+ export declare const placeOf: (edition: Pick<Edition, 'copies'>, symbol: AnySymbol) => Readonly<HorizontalSpan> | undefined;
23
+ /** Where the symbol begins, as the mean onset of its carriers, or nothing for a symbol without a place. */
24
+ export declare const onsetOf: (edition: Pick<Edition, 'copies'>, symbol: AnySymbol) => Millimeters | undefined;
25
+ /**
26
+ * The symbols the version shows: what it and its ancestors insert,
27
+ * each version's deletions striking what it or its ancestors inserted.
28
+ * They are worked out once for each state of the edition, and the list
29
+ * is frozen, since everyone who asks is handed the same one.
30
+ */
31
+ export declare const snapshotOf: (edition: Edition, versionId: string) => readonly Readonly<AnySymbol>[];
@@ -0,0 +1,62 @@
1
+ import { isPlaced } from "../model/Feature.js";
2
+ import { deletedBy, insertedBy } from "../model/Version.js";
3
+ import { idsOf } from "../model/Assumption.js";
4
+ import { mean } from "../model/Quantity.js";
5
+ import { featuresIn } from "../lookup/lookup.js";
6
+ import { perState } from "../lookup/perState.js";
7
+ import { lineageOf } from "./stemma.js";
8
+ export const carriersOf = (edition, symbol) => featuresIn(edition, idsOf(symbol.carriers));
9
+ /** The carriers that state a place of their own, which a feature on a patch does not. */
10
+ export const placedCarriersOf = (edition, symbol) => carriersOf(edition, symbol).filter(isPlaced);
11
+ /**
12
+ * Where along the roll the symbol lies, as its carriers put it, or
13
+ * nothing for a symbol no copy carries.
14
+ *
15
+ * Only the place is measured. Which track the symbol sits on is not
16
+ * a measurement but a question for a tracker bar, since a note of
17
+ * one pitch sits on exactly one position of a given bar: ask
18
+ * `bar.positionOf`. The carriers cannot answer it, because a copy
19
+ * cut for another system numbers its tracks differently, and one
20
+ * symbol may be carried by copies of both — averaging a red carrier
21
+ * on track 47 with a green one on 45 would give 46, a legal
22
+ * position a semitone away on either bar.
23
+ */
24
+ export const placeOf = (edition, symbol) => {
25
+ const carriers = placedCarriersOf(edition, symbol);
26
+ if (carriers.length === 0)
27
+ return;
28
+ return {
29
+ unit: 'mm',
30
+ from: mean(carriers.map(carrier => carrier.horizontal.from)),
31
+ to: mean(carriers.map(carrier => carrier.horizontal.to))
32
+ };
33
+ };
34
+ /** Where the symbol begins, as the mean onset of its carriers, or nothing for a symbol without a place. */
35
+ export const onsetOf = (edition, symbol) => placeOf(edition, symbol)?.from;
36
+ /** The symbols by onset, those without a place first; symbols at one place keep their order. */
37
+ const inOrderOfPlace = (edition, symbols) => symbols
38
+ .map(symbol => ({ symbol, at: onsetOf(edition, symbol) || 0 }))
39
+ .sort((a, b) => a.at - b.at)
40
+ .map(({ symbol }) => symbol);
41
+ /** The snapshots of a state asked for so far, by version. */
42
+ const snapshotsIn = perState((_) => new Map());
43
+ /**
44
+ * The symbols the version shows: what it and its ancestors insert,
45
+ * each version's deletions striking what it or its ancestors inserted.
46
+ * They are worked out once for each state of the edition, and the list
47
+ * is frozen, since everyone who asks is handed the same one.
48
+ */
49
+ export const snapshotOf = (edition, versionId) => {
50
+ const known = snapshotsIn(edition);
51
+ const kept = known.get(versionId);
52
+ if (kept)
53
+ return kept;
54
+ const deleted = new Set();
55
+ const symbols = lineageOf(edition, versionId).flatMap(version => {
56
+ deletedBy(version).forEach(id => deleted.add(id));
57
+ return insertedBy(version).filter(symbol => !deleted.has(symbol.id));
58
+ });
59
+ const snapshot = Object.freeze(inOrderOfPlace(edition, symbols));
60
+ known.set(versionId, snapshot);
61
+ return snapshot;
62
+ };
@@ -1,5 +1,5 @@
1
1
  import { Belief, Certainty } from "../model/Assumption.js";
2
- import { EditionView } from "../view/EditionView.js";
2
+ import { Edition } from "../model/Edition.js";
3
3
  export type WitnessBy = 'carriers' | 'statement';
4
4
  /** A copy that bears witness to a version, and how. */
5
5
  export interface Witness {
@@ -25,7 +25,7 @@ export interface Witness {
25
25
  * the versions derived from it, or is attested by statement alone, and
26
26
  * its text is a reconstruction rather than a reading.
27
27
  */
28
- export declare const attestedVersions: (view: EditionView) => ReadonlySet<string>;
28
+ export declare const attestedVersions: (edition: Edition) => ReadonlySet<string>;
29
29
  /**
30
30
  * The copies that bear witness to the version: those whose features carry
31
31
  * a symbol the version's own edits insert, and those that state they carry
@@ -44,12 +44,12 @@ export declare const attestedVersions: (view: EditionView) => ReadonlySet<string
44
44
  * reported as it stands. A version whose edits are not stated inserts
45
45
  * nothing and is witnessed by statement alone.
46
46
  */
47
- export declare const witnessesOf: (view: EditionView, versionId: string) => Witness[];
47
+ export declare const witnessesOf: (edition: Edition, versionId: string) => Witness[];
48
48
  /**
49
49
  * The versions the copy bears witness to, each with how, in the order the
50
50
  * edition lists its versions.
51
51
  */
52
- export declare const versionsWitnessedBy: (view: EditionView, copyId: string) => (Witness & {
52
+ export declare const versionsWitnessedBy: (edition: Edition, copyId: string) => (Witness & {
53
53
  version: string;
54
54
  })[];
55
55
  export type CarriageProblem = {
@@ -62,4 +62,4 @@ export type CarriageProblem = {
62
62
  * made although the copy's features carry symbols, which say by
63
63
  * themselves what it carries, and one naming a version the edition lacks.
64
64
  */
65
- export declare const carriageProblems: (view: EditionView) => CarriageProblem[];
65
+ export declare const carriageProblems: (edition: Edition) => CarriageProblem[];
@@ -1,48 +1,53 @@
1
1
  import { certaintyOf, idOf, idsOf } from "../model/Assumption.js";
2
2
  import { insertedBy } from "../model/Version.js";
3
+ import { lineageOf } from "./stemma.js";
4
+ import { copyOfFeature, versionIn } from "../lookup/lookup.js";
5
+ import { perState } from "../lookup/perState.js";
3
6
  /** The copies whose features carry any of the symbols. */
4
- const copiesCarrying = (view, symbols) => new Set(symbols
7
+ const copiesCarrying = (edition, symbols) => new Set(symbols
5
8
  .flatMap(symbol => idsOf(symbol.carriers))
6
9
  .flatMap(id => {
7
- const copy = view.copyOf(id);
10
+ const copy = copyOfFeature(edition, id);
8
11
  return copy ? [copy.id] : [];
9
12
  }));
10
13
  /** Every copy carrying what a version inserts, version by version, the copies in the order the edition lists them. */
11
- const carriagesIn = (view) => view.edition.versions.flatMap(version => {
12
- const carrying = copiesCarrying(view, insertedBy(version));
13
- const depth = view.lineageOf(version.id).length;
14
- return view.edition.copies
14
+ const carriagesIn = (edition) => edition.versions.flatMap(version => {
15
+ const carrying = copiesCarrying(edition, insertedBy(version));
16
+ const depth = lineageOf(edition, version.id).length;
17
+ return edition.copies
15
18
  .filter(copy => carrying.has(copy.id))
16
19
  .map((copy) => ({ copy: copy.id, version: version.id, depth }));
17
20
  });
18
- const carriersIn = (view) => {
19
- const carriages = carriagesIn(view);
21
+ /** What the copies' features attest, worked out once for each state of the edition. */
22
+ const carriersIn = perState((edition) => {
23
+ const carriages = carriagesIn(edition);
24
+ const byVersion = Map.groupBy(carriages, carriage => carriage.version);
20
25
  return {
21
- copiesOf: new Map(view.edition.versions.map(version => [
26
+ copiesOf: new Map(edition.versions.map(version => [
22
27
  version.id,
23
- carriages.filter(carriage => carriage.version === version.id).map(carriage => carriage.copy)
28
+ (byVersion.get(version.id) ?? []).map(carriage => carriage.copy)
24
29
  ])),
25
30
  // Written in order of depth, so a copy's latest carriage is set last and stands.
26
31
  latestOf: new Map([...carriages]
27
32
  .sort((one, other) => one.depth - other.depth)
28
33
  .map(carriage => [carriage.copy, carriage.version]))
29
34
  };
30
- };
35
+ });
31
36
  /**
32
37
  * The versions a copy's features carry at first hand: for every copy,
33
38
  * the latest state it bears. A version left out is reached only through
34
39
  * the versions derived from it, or is attested by statement alone, and
35
40
  * its text is a reconstruction rather than a reading.
36
41
  */
37
- export const attestedVersions = (view) => new Set(carriersIn(view).latestOf.values());
38
- const witnessesIn = (carriers, view, versionId) => {
42
+ export const attestedVersions = (edition) => new Set(carriersIn(edition).latestOf.values());
43
+ const witnessesIn = (carriers, edition, versionId) => {
39
44
  const carrying = carriers.copiesOf.get(versionId) ?? [];
40
45
  const byCarriers = carrying.map((copy) => {
41
46
  const latest = carriers.latestOf.get(copy);
42
47
  return { copy, by: 'carriers', ...(latest !== undefined && latest !== versionId && { through: latest }) };
43
48
  });
44
49
  const carryingIt = new Set(carrying);
45
- const byStatement = view.edition.copies
50
+ const byStatement = edition.copies
46
51
  .filter(copy => !carryingIt.has(copy.id))
47
52
  .flatMap(copy => (copy.carries ?? [])
48
53
  .filter(statement => idOf(statement) === versionId)
@@ -70,14 +75,14 @@ const witnessesIn = (carriers, view, versionId) => {
70
75
  * reported as it stands. A version whose edits are not stated inserts
71
76
  * nothing and is witnessed by statement alone.
72
77
  */
73
- export const witnessesOf = (view, versionId) => view.version(versionId) ? witnessesIn(carriersIn(view), view, versionId) : [];
78
+ export const witnessesOf = (edition, versionId) => versionIn(edition, versionId) ? witnessesIn(carriersIn(edition), edition, versionId) : [];
74
79
  /**
75
80
  * The versions the copy bears witness to, each with how, in the order the
76
81
  * edition lists its versions.
77
82
  */
78
- export const versionsWitnessedBy = (view, copyId) => {
79
- const carriers = carriersIn(view);
80
- return view.edition.versions.flatMap(version => witnessesIn(carriers, view, version.id)
83
+ export const versionsWitnessedBy = (edition, copyId) => {
84
+ const carriers = carriersIn(edition);
85
+ return edition.versions.flatMap(version => witnessesIn(carriers, edition, version.id)
81
86
  .filter(witness => witness.copy === copyId)
82
87
  .map(witness => ({ ...witness, version: version.id })));
83
88
  };
@@ -86,13 +91,13 @@ export const versionsWitnessedBy = (view, copyId) => {
86
91
  * made although the copy's features carry symbols, which say by
87
92
  * themselves what it carries, and one naming a version the edition lacks.
88
93
  */
89
- export const carriageProblems = (view) => {
90
- const carrying = copiesCarrying(view, view.edition.versions.flatMap(insertedBy));
91
- return view.edition.copies.flatMap(copy => (copy.carries ?? []).flatMap((statement) => {
94
+ export const carriageProblems = (edition) => {
95
+ const carrying = copiesCarrying(edition, edition.versions.flatMap(insertedBy));
96
+ return edition.copies.flatMap(copy => (copy.carries ?? []).flatMap((statement) => {
92
97
  const version = idOf(statement);
93
98
  return [
94
99
  ...(carrying.has(copy.id) ? [{ copy: copy.id, version, problem: 'stated-beside-carriers' }] : []),
95
- ...(view.version(version) ? [] : [{ copy: copy.id, version, problem: 'version-missing' }])
100
+ ...(versionIn(edition, version) ? [] : [{ copy: copy.id, version, problem: 'version-missing' }])
96
101
  ];
97
102
  }));
98
103
  };