linked-rolls 0.62.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 (71) hide show
  1. package/README.md +26 -7
  2. package/lib/analysis/constraints.d.ts +2 -2
  3. package/lib/analysis/constraints.js +30 -21
  4. package/lib/analysis/ownPaper.d.ts +11 -16
  5. package/lib/analysis/ownPaper.js +13 -31
  6. package/lib/analysis/paper.d.ts +129 -0
  7. package/lib/analysis/paper.js +260 -0
  8. package/lib/analysis/reservations.d.ts +2 -2
  9. package/lib/analysis/reservations.js +6 -6
  10. package/lib/analysis/sigla.d.ts +4 -5
  11. package/lib/analysis/sigla.js +4 -4
  12. package/lib/analysis/stemma.d.ts +22 -0
  13. package/lib/analysis/stemma.js +38 -0
  14. package/lib/analysis/text.d.ts +31 -0
  15. package/lib/analysis/text.js +62 -0
  16. package/lib/analysis/witnesses.d.ts +5 -5
  17. package/lib/analysis/witnesses.js +22 -20
  18. package/lib/collation/alignment.d.ts +46 -8
  19. package/lib/collation/alignment.js +137 -47
  20. package/lib/collation/scatter.d.ts +4 -4
  21. package/lib/collation/scatter.js +15 -13
  22. package/lib/emulation/Emulation.d.ts +7 -7
  23. package/lib/emulation/Emulation.js +29 -27
  24. package/lib/emulation/negotiation.d.ts +2 -2
  25. package/lib/emulation/negotiation.js +3 -2
  26. package/lib/index.d.ts +6 -2
  27. package/lib/index.js +6 -2
  28. package/lib/io/asJsonLd.js +16 -1
  29. package/lib/io/importJsonLd.d.ts +5 -0
  30. package/lib/io/importJsonLd.js +18 -1
  31. package/lib/io/migrate.js +94 -2
  32. package/lib/lookup/lookup.d.ts +32 -0
  33. package/lib/lookup/lookup.js +40 -0
  34. package/lib/lookup/paths.d.ts +11 -0
  35. package/lib/lookup/paths.js +78 -0
  36. package/lib/lookup/perState.d.ts +32 -0
  37. package/lib/lookup/perState.js +63 -0
  38. package/lib/model/Assumption.d.ts +21 -9
  39. package/lib/model/Edit.d.ts +4 -0
  40. package/lib/model/Edition.d.ts +19 -0
  41. package/lib/model/Edition.js +9 -1
  42. package/lib/model/Quantity.d.ts +4 -1
  43. package/lib/model/Quantity.js +1 -0
  44. package/lib/model/RollCopy.d.ts +144 -48
  45. package/lib/model/RollCopy.js +9 -6
  46. package/lib/model/Version.d.ts +7 -1
  47. package/lib/ops/beliefs.d.ts +1 -1
  48. package/lib/ops/beliefs.js +1 -1
  49. package/lib/ops/collation.d.ts +3 -4
  50. package/lib/ops/collation.js +25 -23
  51. package/lib/ops/commands.d.ts +4 -5
  52. package/lib/ops/commands.js +10 -10
  53. package/lib/ops/copies.d.ts +47 -10
  54. package/lib/ops/copies.js +92 -26
  55. package/lib/ops/draft.d.ts +12 -12
  56. package/lib/ops/draft.js +11 -13
  57. package/lib/ops/edits.d.ts +1 -2
  58. package/lib/ops/edits.js +13 -10
  59. package/lib/ops/features.d.ts +2 -2
  60. package/lib/ops/features.js +3 -2
  61. package/lib/ops/index.d.ts +2 -2
  62. package/lib/ops/index.js +7 -1
  63. package/lib/ops/versions.d.ts +2 -3
  64. package/lib/ops/versions.js +6 -5
  65. package/lib/readers/stanfordAton.js +28 -1
  66. package/lib/schema.json +185 -26
  67. package/lib/spec/context.json +33 -1
  68. package/lib/systems/ReproducingSystem.d.ts +7 -6
  69. package/package.json +1 -1
  70. package/lib/view/EditionView.d.ts +0 -103
  71. package/lib/view/EditionView.js +0 -329
@@ -1,6 +1,7 @@
1
1
  import { derivedKeys } from "./asJsonLd.js";
2
2
  import { migrate, plainCopyId } from "./migrate.js";
3
3
  import { isDateString } from "../shared/utils.js";
4
+ import { putOnAxis } from "../collation/alignment.js";
4
5
  import schema from "../schema.json" with { type: 'json' };
5
6
  export const importDate = (str) => {
6
7
  const [y, m, d] = str.split('-').map(s => parseInt(s, 10));
@@ -70,6 +71,16 @@ const withPlainCopyIds = (json) => ({
70
71
  '@id': typeof copy['@id'] === 'string' ? plainCopyId(copy['@id']) : copy['@id']
71
72
  }))
72
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;
73
84
  /** A statement an export quoted rather than stated: an included node whose id is a triple. */
74
85
  const isQuotedStatement = (node) => node !== null && typeof node === 'object' && node['@id'] !== null && typeof node['@id'] === 'object';
75
86
  /** The reference a quoted statement made, annotated again with the belief the export set beside it. */
@@ -118,11 +129,17 @@ const withQuotedStatementsInPlace = (edition) => {
118
129
  const bySubject = Map.groupBy(statements.map(referenceOf), ({ subject }) => subject);
119
130
  return withReferencesOn({ ...rest, ...(others.length > 0 && { '@included': others }) }, bySubject);
120
131
  };
132
+ /**
133
+ * Reads an edition. A document holds each copy's features and tears at
134
+ * the copy's own places, as they were read; the edition holds them on
135
+ * its axis, which is where the copy's alignment carries them.
136
+ */
121
137
  export const importJsonLd = (json) => {
122
- const { '@context': context, ...document } = withPlainCopyIds(migrate(withQuotedStatementsInPlace(json)));
138
+ const { '@context': context, ...document } = withoutEmptyCreations(withPlainCopyIds(migrate(withQuotedStatementsInPlace(json))));
123
139
  const edition = fromJsonLdEntity(document);
124
140
  edition.base = Array.isArray(context)
125
141
  ? context.find((c) => c['@base'])?.['@base'] || ''
126
142
  : '';
143
+ (edition.copies ?? []).forEach(putOnAxis);
127
144
  return edition;
128
145
  };
package/lib/io/migrate.js CHANGED
@@ -360,6 +360,78 @@ const withFeaturesInActs = (node) => {
360
360
  modifications: [...acts, ...(loose.length > 0 ? [{ '@type': 'Attachment', added: loose }] : [])]
361
361
  };
362
362
  };
363
+ /** A place written to the nanometre, as the export writes places it has carried off the axis. */
364
+ const writtenPlace = (place) => Math.round(place * 1e6) / 1e6;
365
+ /** The span with both ends taken through the mapping, the far end only where it has one. */
366
+ const spanThrough = (span, through) => span && typeof span.from === 'number'
367
+ ? { ...span, from: through(span.from), ...(typeof span.to === 'number' && { to: through(span.to) }) }
368
+ : span;
369
+ const isLegacyAligned = (copy) => copy?.measurements?.shift !== undefined || copy?.measurements?.scale !== undefined;
370
+ const isLegacyPaperStretch = (condition) => isPaperStretch(condition) && Object.hasOwn(condition, 'factor');
371
+ const conditionTypeOf = (condition) => condition?.conditionType ?? condition?.['@type'];
372
+ /**
373
+ * A copy's features once stood where its alignment had put them, on the
374
+ * edition's axis, with the shift and the scale that had done it recorded
375
+ * beside them. They stand at the copy's own places now, as they were
376
+ * read, and the alignment says how they go onto the axis; so they are
377
+ * taken back by it, and so are the tears, which were measured as the
378
+ * features are. The features are in their acts here, `withFeaturesInActs`
379
+ * having run.
380
+ *
381
+ * The factor of a paper-stretch condition was the alignment's scale
382
+ * stated again, which is how every such condition came about. It is
383
+ * dropped: the scale stays with the alignment, and what it says about the
384
+ * paper is worked out from all the alignments together.
385
+ */
386
+ const withOwnPlaces = (node) => {
387
+ if (node['@type'] !== 'RollCopy' || !isLegacyAligned(node))
388
+ return node;
389
+ const { shift: legacyShift, scale: legacyScale, ...measurements } = node.measurements;
390
+ const shift = {
391
+ horizontal: legacyShift?.horizontal ?? 0,
392
+ vertical: legacyShift?.vertical ?? 0
393
+ };
394
+ const scale = legacyScale ?? 1;
395
+ const along = (place) => writtenPlace(place / scale - shift.horizontal);
396
+ const across = (position) => position - shift.vertical;
397
+ const ownFeature = (feature) => {
398
+ if (!feature || typeof feature !== 'object' || !feature.horizontal)
399
+ return feature;
400
+ return {
401
+ ...feature,
402
+ horizontal: spanThrough(feature.horizontal, along),
403
+ ...(feature.vertical?.unit === 'track' && shift.vertical !== 0 && { vertical: spanThrough(feature.vertical, across) })
404
+ };
405
+ };
406
+ const ownFeatures = (features) => Array.isArray(features) ? features.map(ownFeature) : features;
407
+ const production = node.production && {
408
+ ...node.production,
409
+ ...(Array.isArray(node.production.produced) && { produced: ownFeatures(node.production.produced) })
410
+ };
411
+ const modifications = Array.isArray(node.modifications)
412
+ ? node.modifications.map((act) => act && typeof act === 'object'
413
+ ? {
414
+ ...act,
415
+ ...(Array.isArray(act.produced) && { produced: ownFeatures(act.produced) }),
416
+ ...(Array.isArray(act.added) && { added: ownFeatures(act.added) })
417
+ }
418
+ : act)
419
+ : node.modifications;
420
+ const conditions = Array.isArray(node.conditions)
421
+ ? node.conditions
422
+ .filter((condition) => !isLegacyPaperStretch(condition))
423
+ .map((condition) => conditionTypeOf(condition) === 'torn' && condition.horizontal
424
+ ? { ...condition, horizontal: spanThrough(condition.horizontal, along) }
425
+ : condition)
426
+ : node.conditions;
427
+ return {
428
+ ...node,
429
+ measurements: { ...measurements, alignment: { shift, scale } },
430
+ ...(production && { production }),
431
+ ...(modifications && { modifications }),
432
+ ...(conditions && { conditions })
433
+ };
434
+ };
363
435
  /** A keeper nobody could name was written as an empty one before a copy could leave it out. */
364
436
  const withoutEmptyKeeper = (node) => {
365
437
  if (node.keeper?.name !== '' || node.keeper.sameAs?.length)
@@ -380,7 +452,7 @@ const withTimeSpanDates = (node) => {
380
452
  };
381
453
  const migrateNode = (node) => [withRenamedKeys, withRenamedEditType, withTypology, withRenamedType, withoutVersionType, withoutVersionSiglum, withLowerCaseTerms, withSplitMethod, withReferences, withKeeper, withoutEmptyKeeper,
382
454
  withPerforator, withProductionNodes, withTypedPerforator, withScale, withoutOps, withDerivationList, withReadingKind, withTimeSpanDates,
383
- withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType]
455
+ withoutFeatureKind, withBorneFeaturesNamed, withFeaturesInActs, withOwnPlaces, withDriveOfStaggering, withoutPattern, withoutBearings, withoutEntailedType]
384
456
  .reduce((result, step) => step(result), node);
385
457
  /** The items each walked, or the very same list where the walk changed none. */
386
458
  const walked = (items) => {
@@ -525,5 +597,25 @@ const withoutFormatVersion = (edition) => {
525
597
  const { formatVersion: _stated, ...rest } = edition;
526
598
  return rest;
527
599
  };
528
- const editionSteps = [withoutFormatVersion, withoutEditionType, withSystems, withEditors, withDerivationTolerance];
600
+ /** Whether the copy states features, in either shape. */
601
+ const hasFeatures = (copy) => [copy?.features, copy?.production?.produced, copy?.productionEvent?.produced]
602
+ .some(features => Array.isArray(features) && features.length > 0);
603
+ const statesLegacyAlignment = (copy) => isLegacyAligned(copy)
604
+ || (Array.isArray(copy?.ops) && (copy.ops.includes('shifted') || copy.ops.includes('stretched')));
605
+ /**
606
+ * The copy whose millimetres were the edition's axis went unnamed while
607
+ * the copies aligned onto it recorded only what had been done to them.
608
+ * It was the copy nothing had been done to, and of those the first with
609
+ * features, so that is the one named.
610
+ */
611
+ const withReferenceCopy = (edition) => {
612
+ const copies = listAt(edition, 'copies');
613
+ if (edition.referenceCopy !== undefined || !copies?.some(statesLegacyAlignment))
614
+ return edition;
615
+ const reference = copies.find(copy => !statesLegacyAlignment(copy) && hasFeatures(copy));
616
+ return typeof reference?.['@id'] === 'string'
617
+ ? { ...edition, referenceCopy: plainCopyId(reference['@id']) }
618
+ : edition;
619
+ };
620
+ const editionSteps = [withoutFormatVersion, withoutEditionType, withSystems, withEditors, withDerivationTolerance, withReferenceCopy];
529
621
  export const migrate = (edition) => walk(editionSteps.reduce((result, step) => step(result), edition));
@@ -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
+ };
@@ -46,22 +46,35 @@ export interface Inference extends Argumentation<'inference'> {
46
46
  /**
47
47
  * References (by `@id`) to the beliefs
48
48
  * from which the conclusion is drawn.
49
+ *
50
+ * A belief the editor holds and publishes elsewhere under an IRI of
51
+ * its own, such as a bound from a catalogue of premises the editor
52
+ * keeps, is named by that IRI. Someone else's belief is not: it is
53
+ * adopted first, and the belief of the edition's own that the
54
+ * adoption concludes is named instead.
49
55
  * @see crminf:J1 used as premise
50
56
  */
51
57
  premises: string[];
52
58
  /**
53
59
  * References (by `@id`) to what the inference worked on besides
54
- * beliefs, such as the versions it compared or the analysis output
55
- * it cites.
60
+ * beliefs, such as the versions it compared, the analysis output it
61
+ * cites, or the document holding a premise named by IRI, in the
62
+ * state it was read in, since a belief published elsewhere may move
63
+ * after the edition has used it.
56
64
  * @see crm:P16 used specific object
57
65
  */
58
66
  used?: string[];
59
67
  }
60
68
  /**
61
- * A belief adoption adopts someone else's belief. This type is used
62
- * to indicate e.g. knowledge through private communication or
63
- * from secondary literature, or a belief published elsewhere under
64
- * an IRI of its own, such as a premise from a catalogue of premises.
69
+ * A belief adoption takes over someone else's belief without checking it:
70
+ * knowledge through private communication, from the secondary
71
+ * literature, or from another edition. Like every reason, it concludes
72
+ * the belief it is a reason of, which is the edition's own and holds of
73
+ * the edition's statement what the adopted belief held. An inference that
74
+ * relies on it names that belief among its premises.
75
+ *
76
+ * The editor's own beliefs need no adoption, wherever they are
77
+ * published: an inference names them directly (see `Inference`).
65
78
  * @see crminf:I7 Belief Adoption
66
79
  */
67
80
  export interface BeliefAdoption extends Argumentation<'beliefAdoption'> {
@@ -76,9 +89,8 @@ export interface BeliefAdoption extends Argumentation<'beliefAdoption'> {
76
89
  note: string;
77
90
  /**
78
91
  * The beliefs this adoption takes over, by IRI, where they are
79
- * published with one. An inference that uses such a belief names it
80
- * among its premises by the same IRI; the adoption says where it
81
- * comes from.
92
+ * published with one. They are someone else's, and stay theirs: what
93
+ * the edition holds is the belief the adoption concludes.
82
94
  * @see crminf:J6 adopted
83
95
  */
84
96
  adopted?: string[];
@@ -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 {
@@ -140,6 +140,18 @@ export interface Edition {
140
140
  * @see reo:witness
141
141
  */
142
142
  copies: RollCopy[];
143
+ /**
144
+ * The copy whose millimetres are the edition's axis, by id: every
145
+ * place the edition gives along the roll is a place on this copy's
146
+ * paper, counted from where its scan begins, and every other copy is
147
+ * aligned onto it. Choosing it is the one editorial decision the
148
+ * alignment leaves; a copy scanned whole and without tears serves
149
+ * best, since its places are what notes and readers cite. Left out,
150
+ * it is the first copy with features that is not aligned
151
+ * (`referenceCopyOf`).
152
+ * Not exported to RDF.
153
+ */
154
+ referenceCopy?: string;
143
155
  /**
144
156
  * The different versions of the roll on which
145
157
  * this edition is based.
@@ -154,3 +166,10 @@ export interface Edition {
154
166
  tempoAdjustment?: ObjectAssumption<RollTempo>;
155
167
  }
156
168
  export type EditionMetadata = Pick<Edition, 'base' | 'title' | 'license' | 'creation' | 'roll'>;
169
+ /**
170
+ * The copy whose millimetres are the edition's axis: the one it names,
171
+ * or where it names none or one it no longer has, the first copy with
172
+ * features that is not aligned, which is how an edition stood before it
173
+ * named one.
174
+ */
175
+ export declare const referenceCopyOf: (edition: Pick<Edition, 'copies' | 'referenceCopy'>) => RollCopy | undefined;
@@ -1 +1,9 @@
1
- export {};
1
+ import { featuresOf } from "./RollCopy.js";
2
+ /**
3
+ * The copy whose millimetres are the edition's axis: the one it names,
4
+ * or where it names none or one it no longer has, the first copy with
5
+ * features that is not aligned, which is how an edition stood before it
6
+ * named one.
7
+ */
8
+ export const referenceCopyOf = (edition) => edition.copies.find(copy => copy.id === edition.referenceCopy)
9
+ ?? edition.copies.find(copy => !copy.measurements.alignment && featuresOf(copy).length > 0);
@@ -11,7 +11,7 @@ declare const unit: unique symbol;
11
11
  * The units a record may state. A `Measure` is limited to these, since
12
12
  * they are what the `unit` field is allowed to say.
13
13
  */
14
- export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min' | 'px/in' | 'deg';
14
+ export type Unit = 'mm' | 'cm' | 'px' | 'track' | 's' | 'ms' | 'ft/min' | 'm/min' | 'px/in' | 'deg' | 'percent';
15
15
  export type Quantity<U extends string> = number & {
16
16
  readonly [unit]: U;
17
17
  };
@@ -30,6 +30,8 @@ export type MetersPerMinute = Quantity<'m/min'>;
30
30
  export type Resolution = Quantity<'px/in'>;
31
31
  /** An angle on the roll, turning from the line across the paper towards the end of the roll. */
32
32
  export type Degrees = Quantity<'deg'>;
33
+ /** A share of a length, such as how far the paper has stretched: dimensionless, in hundredths. */
34
+ export type Percent = Quantity<'percent'>;
33
35
  /** Names a number in a unit. Partially apply it to make a constructor. */
34
36
  export declare const quantity: <U extends string>(value: number) => Quantity<U>;
35
37
  export declare const mm: (value: number) => Quantity<"mm">;
@@ -42,6 +44,7 @@ export declare const feetPerMinute: (value: number) => Quantity<"ft/min">;
42
44
  export declare const metersPerMinute: (value: number) => Quantity<"m/min">;
43
45
  export declare const pixelsPerInch: (value: number) => Quantity<"px/in">;
44
46
  export declare const degrees: (value: number) => Quantity<"deg">;
47
+ export declare const percent: (value: number) => Quantity<"percent">;
45
48
  /**
46
49
  * A value together with the unit it was measured in, as a record
47
50
  * states it.
@@ -10,6 +10,7 @@ export const feetPerMinute = quantity;
10
10
  export const metersPerMinute = quantity;
11
11
  export const pixelsPerInch = quantity;
12
12
  export const degrees = quantity;
13
+ export const percent = quantity;
13
14
  // The second operand takes its unit from the first. Inferred from both,
14
15
  // TypeScript would unite two units rather than reject them.
15
16
  export const add = (a, b) => quantity(a + b);