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.
Files changed (53) hide show
  1. package/README.md +13 -5
  2. package/lib/analysis/constraints.d.ts +2 -2
  3. package/lib/analysis/constraints.js +17 -14
  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 +22 -20
  16. package/lib/collation/scatter.d.ts +4 -4
  17. package/lib/collation/scatter.js +15 -13
  18. package/lib/emulation/Emulation.d.ts +7 -7
  19. package/lib/emulation/Emulation.js +29 -27
  20. package/lib/emulation/negotiation.d.ts +2 -2
  21. package/lib/emulation/negotiation.js +3 -2
  22. package/lib/index.d.ts +4 -1
  23. package/lib/index.js +4 -1
  24. package/lib/io/asJsonLd.js +11 -1
  25. package/lib/io/importJsonLd.js +11 -1
  26. package/lib/lookup/lookup.d.ts +32 -0
  27. package/lib/lookup/lookup.js +40 -0
  28. package/lib/lookup/paths.d.ts +11 -0
  29. package/lib/lookup/paths.js +78 -0
  30. package/lib/lookup/perState.d.ts +32 -0
  31. package/lib/lookup/perState.js +63 -0
  32. package/lib/model/Edit.d.ts +4 -0
  33. package/lib/model/Version.d.ts +7 -1
  34. package/lib/ops/beliefs.d.ts +1 -1
  35. package/lib/ops/beliefs.js +1 -1
  36. package/lib/ops/collation.d.ts +3 -4
  37. package/lib/ops/collation.js +25 -23
  38. package/lib/ops/commands.d.ts +4 -5
  39. package/lib/ops/commands.js +10 -10
  40. package/lib/ops/draft.d.ts +12 -12
  41. package/lib/ops/draft.js +11 -13
  42. package/lib/ops/edits.d.ts +1 -2
  43. package/lib/ops/edits.js +13 -10
  44. package/lib/ops/features.d.ts +2 -2
  45. package/lib/ops/features.js +3 -2
  46. package/lib/ops/index.d.ts +1 -1
  47. package/lib/ops/index.js +6 -0
  48. package/lib/ops/versions.d.ts +2 -3
  49. package/lib/ops/versions.js +6 -5
  50. package/lib/schema.json +2 -2
  51. package/package.json +1 -1
  52. package/lib/view/EditionView.d.ts +0 -103
  53. package/lib/view/EditionView.js +0 -329
@@ -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 = (view, copies) => (feature) => {
12
- const copy = view.copyOf(feature.id);
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 = (view, symbols, copies) => {
38
- const tested = sitsOn(view, copies);
39
+ export const readingsOf = (edition, symbols, copies) => {
40
+ const tested = sitsOn(edition, copies);
39
41
  return symbols.flatMap((symbol) => {
40
- const carriers = view.placedCarriersOf(symbol);
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 = (view, versionId, copyId, options = {}) => scatterOf(readingsOf(view, view.snapshot(versionId), new Set([copyId])), options);
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 = (view, symbols) => {
194
+ const copiesBearing = (edition, symbols) => {
193
195
  const tally = symbols.reduce((counts, symbol) => {
194
- const bearers = new Set(view.carriersOf(symbol).flatMap(carrier => {
195
- const copy = view.copyOf(carrier.id);
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 = (view, versionId) => {
223
- const version = view.version(versionId);
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(view, insertedBy(version)),
228
- parent: copiesBearing(view, view.symbols(deletedBy(version)))
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 view supplies the copies, whose measurements
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: (view: EditionView, events: readonly NegotiatedEvent[]) => NegotiatedEvent[];
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: (view: EditionView, version: Readonly<Version>, bar: TrackerBar, { range }?: Pick<EmulationScope, 'range'>) => NegotiatedEvent[];
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>, view: EditionView, options?: Options, { range, skipToFirstNote }?: EmulationScope) => Emulated;
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(view: EditionView): void;
61
- emulateVersion(version: Version, view: EditionView, scope?: EmulationScope): AnyPerformedRollFeature[];
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 = (view) => {
10
- const measured = view.edition.copies
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 = (view, version) => ({
16
- punchDiameter: meanPunchDiameterOf(view),
17
- tempo: view.edition.tempoAdjustment,
18
- toOwnPaper: toOwnPaperOf(view, version)
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 = (view, symbolId) => {
22
- const symbol = view.symbol(symbolId);
23
+ const onsetsByCopy = (edition, symbolId) => {
24
+ const symbol = symbolIn(edition, symbolId);
23
25
  if (!symbol)
24
26
  return new Map();
25
- const onsets = view.placedCarriersOf(symbol).flatMap((carrier) => {
26
- const copy = view.copyOf(carrier.id);
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 = (view, followerId, referenceId) => {
38
- const references = onsetsByCopy(view, referenceId);
39
- return [...onsetsByCopy(view, followerId)].flatMap(([copy, onset]) => {
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 view supplies the copies, whose measurements
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 = (view, events) => {
99
- const gap = meanPunchDiameterOf(view) ?? mm(1);
100
- const offsets = (follower, reference) => offsetsBetween(view, follower, reference);
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 = (view, version, bar, { range } = {}) => {
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(view, view.snapshot(version.id)
119
+ return withPlacementsApplied(edition, snapshotOf(edition, version.id)
118
120
  .filter(isCommand)
119
- .map(symbol => negotiatedEventOf(view, symbol, bar))
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, view, options = system.defaultOptions, { range, skipToFirstNote = false } = {}) => {
128
- const negotiated = negotiatedEventsOf(view, version, system.trackerBar, { range });
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(view, version));
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(view) {
266
- this.negotiatedEvents = withPlacementsApplied(view, this.negotiatedEvents);
267
+ applyConstraints(edition) {
268
+ this.negotiatedEvents = withPlacementsApplied(edition, this.negotiatedEvents);
267
269
  }
268
- emulateVersion(version, view, scope = {}) {
269
- const emulated = emulate(this.system, version, view, this.options, scope);
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: (view: EditionView, symbol: Note | Expression, bar: TrackerBar) => NegotiatedEvent | null;
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 = (view, symbol, bar) => {
11
- const horizontal = view.placeOf(symbol);
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 './view/EditionView.js';
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 './view/EditionView.js';
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';
@@ -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 {
@@ -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
+ };
@@ -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 {
@@ -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;