linked-rolls 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/asJsonLd.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { systemIdOf } from "./TrackerBar";
1
2
  export const exportDate = (date) => {
2
3
  const year = date.getFullYear();
3
4
  const month = String(date.getMonth() + 1).padStart(2, "0");
@@ -32,13 +33,26 @@ const asJsonLdEntity = (obj) => {
32
33
  result[key] = value;
33
34
  }
34
35
  }
36
+ if ('@value' in obj && obj['@value'] instanceof Date) {
37
+ result['@type'] = 'xsd:date';
38
+ }
35
39
  return result;
36
40
  };
41
+ /**
42
+ * The context of the roll's reproducing system, which reads the
43
+ * expression types as that system's terms.
44
+ */
45
+ const systemContextOf = (edition) => {
46
+ const system = systemIdOf(edition.roll?.system);
47
+ return system ? [`https://w3id.org/reo/${system}/context.jsonld`] : [];
48
+ };
37
49
  export const asJsonLd = (edition) => {
38
- const { base, copies, ...rest } = asJsonLdEntity(edition);
50
+ // The context is the export's own; one carried in from an import must not override it.
51
+ const { base, copies, '@context': carried, ...rest } = asJsonLdEntity(edition);
39
52
  return {
40
53
  '@context': [
41
- 'https://linked-rolls.org/rollo/1.0/edition.jsonld',
54
+ 'https://w3id.org/reo/context.jsonld',
55
+ ...systemContextOf(edition),
42
56
  {
43
57
  '@base': edition.base
44
58
  }
package/lib/context.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  export { default as jsonLdContext } from './spec/context.json';
2
+ /** The context of the Welte-Mignon T-100, added to an edition of a T-100 roll. */
3
+ export { default as welteT100JsonLdContext } from './spec/welte-t100.context.json';
package/lib/context.js CHANGED
@@ -1 +1,3 @@
1
1
  export { default as jsonLdContext } from './spec/context.json';
2
+ /** The context of the Welte-Mignon T-100, added to an edition of a T-100 roll. */
3
+ export { default as welteT100JsonLdContext } from './spec/welte-t100.context.json';
@@ -1,3 +1,4 @@
1
+ import { migrate } from "./migrate";
1
2
  const isDate = (value) => {
2
3
  const datePattern = /^\d{4}-\d{1,2}-\d{1,2}$/;
3
4
  return datePattern.test(value);
@@ -14,7 +15,11 @@ const fromJsonLdEntity = (json) => {
14
15
  return json;
15
16
  }
16
17
  let result = json;
17
- if ('@type' in json) {
18
+ if ('@value' in json) {
19
+ // the datatype of a value object, not a class
20
+ delete result['@type'];
21
+ }
22
+ else if ('@type' in json) {
18
23
  result['type'] = json['@type'];
19
24
  delete result['@type'];
20
25
  }
@@ -49,10 +54,19 @@ const fromJsonLdEntity = (json) => {
49
54
  }
50
55
  return result;
51
56
  };
57
+ // The export prefixes copy identifiers with `copy/`; this is its inverse.
58
+ const withPlainCopyIds = (json) => ({
59
+ ...json,
60
+ copies: (json.copies ?? []).map((copy) => ({
61
+ ...copy,
62
+ '@id': typeof copy['@id'] === 'string' ? copy['@id'].replace(/^copy\//, '') : copy['@id']
63
+ }))
64
+ });
52
65
  export const importJsonLd = (json) => {
53
- const edition = fromJsonLdEntity(json);
54
- if (Array.isArray(json['@context'])) {
55
- edition.base = json['@context'].find((c) => c['@base'])?.['@base'] || '';
56
- }
66
+ const { '@context': context, ...document } = withPlainCopyIds(migrate(json));
67
+ const edition = fromJsonLdEntity(document);
68
+ edition.base = Array.isArray(context)
69
+ ? context.find((c) => c['@base'])?.['@base'] || ''
70
+ : '';
57
71
  return edition;
58
72
  };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Brings the JSON of an edition written by an earlier release of the
3
+ * format up to the current shape. The shapes are recognised
4
+ * structurally, so a file that is current already passes through
5
+ * unchanged, and a file may be migrated any number of times.
6
+ *
7
+ * Format 0.1 used the type discriminator of versions and conditions
8
+ * for their typology, held the keeper and the production metadata as
9
+ * strings, and named the roll system on each copy. Some 0.1 files
10
+ * also carry references written as values and two keys the format
11
+ * had renamed before.
12
+ */
13
+ type Json = any;
14
+ export declare const migrate: (edition: Json) => Json;
15
+ export {};
package/lib/migrate.js ADDED
@@ -0,0 +1,77 @@
1
+ import { conditions } from "./Feature";
2
+ import { rollConditions } from "./RollCopy";
3
+ import { systemOf, welteT100 } from "./TrackerBar";
4
+ import { versionTypes } from "./Version";
5
+ const versionTypeValues = new Set(versionTypes);
6
+ const conditionTypeValues = new Set([...rollConditions, ...Object.values(conditions).flat()]);
7
+ const renamedKeys = {
8
+ productionEvent: 'production',
9
+ annotates: 'depiction',
10
+ classification: 'editType'
11
+ };
12
+ const referenceKeys = ['alignedWith', 'pairedWith', 'basedOn'];
13
+ const named = (name) => ({ name, sameAs: [] });
14
+ const withRenamedKeys = (node) => Object.fromEntries(Object.entries(node).map(([key, value]) => [renamedKeys[key] ?? key, value]));
15
+ const withTypology = (node) => {
16
+ if (versionTypeValues.has(node['@type'])) {
17
+ return { ...node, '@type': 'Version', versionType: node['@type'] };
18
+ }
19
+ if (conditionTypeValues.has(node['@type'])) {
20
+ return { ...node, '@type': 'ConditionState', conditionType: node['@type'] };
21
+ }
22
+ return node;
23
+ };
24
+ const withReferences = (node) => referenceKeys.reduce((result, key) => {
25
+ const reference = result[key];
26
+ if (reference && typeof reference === 'object' && '@value' in reference) {
27
+ const { '@value': id, ...rest } = reference;
28
+ return { ...result, [key]: { '@id': id, ...rest } };
29
+ }
30
+ return result;
31
+ }, node);
32
+ const withKeeper = (node) => {
33
+ if (typeof node.location !== 'string')
34
+ return node;
35
+ const { location, ...rest } = node;
36
+ return { ...rest, keeper: named(location) };
37
+ };
38
+ const withProductionNodes = (node) => {
39
+ const production = node.production;
40
+ if (!production || typeof production !== 'object')
41
+ return node;
42
+ const { company, paper, system, ...rest } = production;
43
+ return {
44
+ ...node,
45
+ production: {
46
+ ...rest,
47
+ ...(typeof company === 'string' ? (company && { company: named(company) }) : { company }),
48
+ ...(typeof paper === 'string' ? (paper && { paper: named(paper) }) : { paper }),
49
+ }
50
+ };
51
+ };
52
+ const migrateNode = (node) => [withRenamedKeys, withTypology, withReferences, withKeeper, withProductionNodes]
53
+ .reduce((result, step) => step(result), node);
54
+ const walk = (value) => {
55
+ if (Array.isArray(value))
56
+ return value.map(walk);
57
+ if (value && typeof value === 'object') {
58
+ return Object.fromEntries(Object.entries(migrateNode(value)).map(([key, child]) => [key, walk(child)]));
59
+ }
60
+ return value;
61
+ };
62
+ /**
63
+ * Every 0.1 edition was read with the T-100 tracker bar, so a roll
64
+ * without a system is a T-100 roll. The text a copy's production
65
+ * gave for the system is kept as the name.
66
+ */
67
+ const withRollSystem = (edition) => {
68
+ if (!edition.roll || edition.roll.system)
69
+ return edition;
70
+ const stated = (edition.copies ?? [])
71
+ .map((copy) => copy.production?.system ?? copy.productionEvent?.system)
72
+ .find((system) => typeof system === 'string' && system !== '');
73
+ const { id, ...concept } = systemOf(welteT100);
74
+ const system = { '@id': id, ...concept, ...(stated && { name: stated }) };
75
+ return { ...edition, roll: { ...edition.roll, system } };
76
+ };
77
+ export const migrate = (edition) => walk(withRollSystem(edition));