@heroiclands/package-build 9.0.0 → 10.0.1
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/CHANGELOG.md +721 -0
- package/CONTENT.md +273 -13
- package/bin/content-build.mjs +437 -7
- package/content-config.mjs +259 -28
- package/docs/content-format.md +1418 -0
- package/engine/address-charset.mjs +62 -0
- package/engine/alias-index.mjs +153 -0
- package/engine/base-compiler.mjs +194 -4
- package/engine/content-address.mjs +4 -4
- package/engine/content-format-check.mjs +570 -0
- package/engine/content-format.mjs +253 -0
- package/engine/content-links.mjs +132 -56
- package/engine/content-lint.mjs +8 -1
- package/engine/diagnostics.mjs +33 -0
- package/engine/document-subtypes.mjs +440 -0
- package/engine/field-spec.mjs +49 -43
- package/engine/frontmatter-lint.mjs +351 -28
- package/engine/generate.mjs +32 -4
- package/engine/helpers.mjs +41 -29
- package/engine/ids.mjs +19 -1
- package/engine/index.mjs +15 -0
- package/engine/item-registry.mjs +72 -5
- package/engine/kb-manifest.mjs +36 -7
- package/engine/map-notes.mjs +34 -18
- package/engine/note-claims.mjs +383 -0
- package/engine/note-vocabulary.mjs +678 -0
- package/engine/pack-config.mjs +39 -22
- package/engine/pack-router.mjs +17 -6
- package/engine/prose-lint.mjs +55 -3
- package/engine/retired-fields.mjs +117 -3
- package/engine/scenes.mjs +19 -1
- package/engine/schema-check.mjs +347 -3
- package/engine/site-build.mjs +1 -1
- package/engine/site-index.mjs +38 -21
- package/engine/system-block.mjs +513 -0
- package/engine/web-wikilinks.mjs +112 -80
- package/engine/wikilink-syntax.mjs +30 -0
- package/engine/wikilinks.mjs +67 -51
- package/package.json +6 -2
- package/sohl/actors.mjs +249 -36
- package/sohl/document-subtypes.mjs +82 -0
- package/sohl/index.mjs +3 -0
- package/sohl/items.mjs +110 -14
- package/sohl/note-schemas.mjs +11 -7
- package/types/content-config.d.mts +48 -4
- package/types/engine/address-charset.d.mts +45 -0
- package/types/engine/alias-index.d.mts +122 -0
- package/types/engine/base-compiler.d.mts +132 -4
- package/types/engine/content-address.d.mts +2 -2
- package/types/engine/content-format-check.d.mts +163 -0
- package/types/engine/content-format.d.mts +101 -0
- package/types/engine/content-links.d.mts +16 -1
- package/types/engine/content-lint.d.mts +6 -0
- package/types/engine/diagnostics.d.mts +29 -0
- package/types/engine/document-subtypes.d.mts +233 -0
- package/types/engine/field-spec.d.mts +76 -23
- package/types/engine/frontmatter-lint.d.mts +47 -2
- package/types/engine/generate.d.mts +14 -1
- package/types/engine/helpers.d.mts +21 -13
- package/types/engine/ids.d.mts +10 -0
- package/types/engine/index.d.mts +5 -0
- package/types/engine/item-registry.d.mts +21 -2
- package/types/engine/kb-manifest.d.mts +35 -8
- package/types/engine/map-notes.d.mts +21 -11
- package/types/engine/note-claims.d.mts +113 -0
- package/types/engine/note-vocabulary.d.mts +251 -0
- package/types/engine/pack-config.d.mts +4 -3
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/prose-lint.d.mts +6 -2
- package/types/engine/retired-fields.d.mts +73 -2
- package/types/engine/schema-check.d.mts +182 -0
- package/types/engine/system-block.d.mts +281 -0
- package/types/engine/web-wikilinks.d.mts +23 -12
- package/types/engine/wikilink-syntax.d.mts +29 -0
- package/types/sohl/actors.d.mts +62 -6
- package/types/sohl/document-subtypes.d.mts +14 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/items.d.mts +21 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every field path any subtype of a published schema declares.
|
|
3
|
+
*
|
|
4
|
+
* `own` and `inherited` are collapsed here, unlike
|
|
5
|
+
* {@link module:engine/schema-check}, and for the same reason that module keeps
|
|
6
|
+
* them apart: it asks two questions of one artifact and only one of them wants
|
|
7
|
+
* the inherited set. This asks the single question "is this field defined
|
|
8
|
+
* anywhere in the system", for which an inherited field is defined.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} artifact - A `version: 1` schema artifact.
|
|
11
|
+
* @returns {Set<string>} The paths, `system.` prefix stripped as the artifact
|
|
12
|
+
* stores them.
|
|
13
|
+
*/
|
|
14
|
+
export function declaredPaths(artifact: object): Set<string>;
|
|
15
|
+
/**
|
|
16
|
+
* What an author is told about a target no schema declares.
|
|
17
|
+
*
|
|
18
|
+
* Names the version, because a field may be perfectly well defined on the
|
|
19
|
+
* system's `main` and simply unreleased — the same distinction
|
|
20
|
+
* {@link undeclaredMessage} draws, and the difference between "the
|
|
21
|
+
* specification is wrong" and "the schema has not caught up".
|
|
22
|
+
*
|
|
23
|
+
* @param {object} finding - `{system, systemVersion, noteType, source, target}`.
|
|
24
|
+
* @returns {string} The message.
|
|
25
|
+
*/
|
|
26
|
+
export function undeclaredTargetMessage({ system, systemVersion, noteType, source, target }: object): string;
|
|
27
|
+
/**
|
|
28
|
+
* Check every `system.*` target the specification names.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} opts
|
|
31
|
+
* @param {import("./content-format.mjs").ContentFormat} opts.format - The
|
|
32
|
+
* parsed specification.
|
|
33
|
+
* @param {Record<string, object>} opts.schemas - System id → its published
|
|
34
|
+
* `version: 1` schema artifact. A system absent from this map is counted
|
|
35
|
+
* `unchecked` rather than passed.
|
|
36
|
+
* @returns {{findings: object[], checked: number, unchecked: Record<string, number>}}
|
|
37
|
+
* Findings ready for `emitDiagnostic`, how many claims were resolved, and how
|
|
38
|
+
* many were left unresolved per system.
|
|
39
|
+
*/
|
|
40
|
+
export function checkSchemaTargets({ format, schemas }: {
|
|
41
|
+
format: import("./content-format.mjs").ContentFormat;
|
|
42
|
+
schemas: Record<string, object>;
|
|
43
|
+
}): {
|
|
44
|
+
findings: object[];
|
|
45
|
+
checked: number;
|
|
46
|
+
unchecked: Record<string, number>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The systems the specification names.
|
|
50
|
+
*
|
|
51
|
+
* Taken from the columns of its mapping tables rather than from a list written
|
|
52
|
+
* here, so a system added to the document is measured with no code change.
|
|
53
|
+
* Cached per parsed document, since the answer is the same for every note.
|
|
54
|
+
*
|
|
55
|
+
* @param {import("./content-format.mjs").ContentFormat} format - The parsed
|
|
56
|
+
* specification.
|
|
57
|
+
* @returns {ReadonlySet<string>} The system ids.
|
|
58
|
+
*/
|
|
59
|
+
export function systemsNamed(format: import("./content-format.mjs").ContentFormat): ReadonlySet<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Measure one note against the vocabulary the format declares for its type.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} note - `{file, raw, fm}` — as the link index hands one over.
|
|
64
|
+
* @param {import("./content-format.mjs").ContentFormat} format - The parsed
|
|
65
|
+
* specification.
|
|
66
|
+
* @param {object} [opts]
|
|
67
|
+
* @param {"warning"|"error"} [opts.severity="warning"] - What the findings are.
|
|
68
|
+
* A report by default; `--strict` raises it.
|
|
69
|
+
* @returns {object[]} Findings, each carrying the `class` it belongs to
|
|
70
|
+
* alongside the fields `emitDiagnostic` reads.
|
|
71
|
+
*/
|
|
72
|
+
export function measureNote(note: object, format: import("./content-format.mjs").ContentFormat, { severity }?: {
|
|
73
|
+
severity?: "error" | "warning" | undefined;
|
|
74
|
+
}): object[];
|
|
75
|
+
/**
|
|
76
|
+
* Measure a corpus, and count what it finds by class.
|
|
77
|
+
*
|
|
78
|
+
* The counts are the point as much as the findings: #127 promotes a class to
|
|
79
|
+
* fatal when its count reaches zero, so a run that prints them is the epic's
|
|
80
|
+
* progress bar.
|
|
81
|
+
*
|
|
82
|
+
* @param {Iterable<object>} notes - `{file, raw, fm}` for each authored note.
|
|
83
|
+
* @param {import("./content-format.mjs").ContentFormat} format - The parsed
|
|
84
|
+
* specification.
|
|
85
|
+
* @param {object} [opts]
|
|
86
|
+
* @param {boolean} [opts.strict=false] - Report the findings as errors rather
|
|
87
|
+
* than warnings. #127 turns this on one slice at a time.
|
|
88
|
+
* @returns {{findings: object[], notes: number, byClass: Record<string, number>}}
|
|
89
|
+
*/
|
|
90
|
+
export function measureCorpus(notes: Iterable<object>, format: import("./content-format.mjs").ContentFormat, { strict }?: {
|
|
91
|
+
strict?: boolean | undefined;
|
|
92
|
+
}): {
|
|
93
|
+
findings: object[];
|
|
94
|
+
notes: number;
|
|
95
|
+
byClass: Record<string, number>;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* What an author is told when the specification and the declaration disagree.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} finding - `{noteType, source, target, name, to}`.
|
|
101
|
+
* @returns {string} The message.
|
|
102
|
+
*/
|
|
103
|
+
export function fieldDriftMessage({ noteType, source, target, name, to }: object): string;
|
|
104
|
+
/**
|
|
105
|
+
* Check the specification's per-type tables against the field declarations that
|
|
106
|
+
* compile them (#136).
|
|
107
|
+
*
|
|
108
|
+
* @param {object} opts
|
|
109
|
+
* @param {import("./content-format.mjs").ContentFormat} opts.format - The
|
|
110
|
+
* parsed specification.
|
|
111
|
+
* @param {Record<string, readonly object[]>} opts.itemFields - Item type → its
|
|
112
|
+
* `fields` declaration, as `itemBuilders` carries it.
|
|
113
|
+
* @param {string} opts.system - Which system column of the mapping tables these
|
|
114
|
+
* declarations compile. Supplied by the caller rather than assumed here: the
|
|
115
|
+
* declarations belong to one system, and the document maps onto several.
|
|
116
|
+
* @param {"warning"|"error"} [opts.severity="error"] - What a contradiction is.
|
|
117
|
+
* @returns {{findings: object[], coverage: object[], checked: string[],
|
|
118
|
+
* skipped: {spec: string[], registry: string[]}, fields: number}}
|
|
119
|
+
* Contradictions ready for `emitDiagnostic`, the per-type coverage, the types
|
|
120
|
+
* compared, the types out of reach on each side, and how many field pairs
|
|
121
|
+
* were compared.
|
|
122
|
+
*/
|
|
123
|
+
export function checkDeclaredFields({ format, itemFields, system, severity }: {
|
|
124
|
+
format: import("./content-format.mjs").ContentFormat;
|
|
125
|
+
itemFields: Record<string, readonly object[]>;
|
|
126
|
+
system: string;
|
|
127
|
+
severity?: "error" | "warning" | undefined;
|
|
128
|
+
}): {
|
|
129
|
+
findings: object[];
|
|
130
|
+
coverage: object[];
|
|
131
|
+
checked: string[];
|
|
132
|
+
skipped: {
|
|
133
|
+
spec: string[];
|
|
134
|
+
registry: string[];
|
|
135
|
+
};
|
|
136
|
+
fields: number;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Top-level keys the format names as the note's own, whatever its type.
|
|
140
|
+
*
|
|
141
|
+
* Top level is otherwise **open** — an unrecognised key there is a Hugo or
|
|
142
|
+
* theme parameter this build has no standing to refuse — so this set exists
|
|
143
|
+
* only to keep a note's own identity fields from being mistaken for a
|
|
144
|
+
* misplaced `data` property where the two share a name. `type` and `subType`
|
|
145
|
+
* are the pair that matters: several mapping tables name `subType` as a shared
|
|
146
|
+
* source, and it is authored at top level by design.
|
|
147
|
+
*
|
|
148
|
+
* @type {ReadonlySet<string>}
|
|
149
|
+
*/
|
|
150
|
+
export const NOTE_LEVEL_KEYS: ReadonlySet<string>;
|
|
151
|
+
/**
|
|
152
|
+
* Where one declared field sends one authored value.
|
|
153
|
+
*/
|
|
154
|
+
export type FieldPair = {
|
|
155
|
+
/**
|
|
156
|
+
* - The authored key, as the declaration spells it.
|
|
157
|
+
*/
|
|
158
|
+
name: string;
|
|
159
|
+
/**
|
|
160
|
+
* - The emitted path, `system.` prefix stripped.
|
|
161
|
+
*/
|
|
162
|
+
to: string;
|
|
163
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the specification's tables.
|
|
3
|
+
*
|
|
4
|
+
* Pure: text in, model out, so a test states a miniature document rather than
|
|
5
|
+
* asserting against the real one and its 1,100 lines of prose.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} text - The document's contents.
|
|
8
|
+
* @param {object} [opts]
|
|
9
|
+
* @param {string} [opts.file] - Path recorded on the result, for diagnostics.
|
|
10
|
+
* @returns {ContentFormat} What the document declares.
|
|
11
|
+
*/
|
|
12
|
+
export function parseContentFormat(text: string, { file }?: {
|
|
13
|
+
file?: string | undefined;
|
|
14
|
+
}): ContentFormat;
|
|
15
|
+
/**
|
|
16
|
+
* Read and parse the specification from disk.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} [file] - The document. Defaults to {@link CONTENT_FORMAT_PATH}.
|
|
19
|
+
* @returns {ContentFormat} What it declares.
|
|
20
|
+
*/
|
|
21
|
+
export function loadContentFormat(file?: string): ContentFormat;
|
|
22
|
+
/**
|
|
23
|
+
* The specification this package ships.
|
|
24
|
+
*
|
|
25
|
+
* Resolved from this module rather than from the working directory: a consumer
|
|
26
|
+
* runs `content-build content-format` inside its own repository, and the
|
|
27
|
+
* document it should be checked against is the one that came with the toolchain
|
|
28
|
+
* version it resolved — the same rule `--version` follows.
|
|
29
|
+
*
|
|
30
|
+
* @type {string}
|
|
31
|
+
*/
|
|
32
|
+
export const CONTENT_FORMAT_PATH: string;
|
|
33
|
+
/**
|
|
34
|
+
* What one note type's section declares.
|
|
35
|
+
*/
|
|
36
|
+
export type TypeSpec = {
|
|
37
|
+
/**
|
|
38
|
+
* - The note type, as the `### type:` heading spells it.
|
|
39
|
+
*/
|
|
40
|
+
name: string;
|
|
41
|
+
/**
|
|
42
|
+
* - 1-based line of that heading.
|
|
43
|
+
*/
|
|
44
|
+
line: number;
|
|
45
|
+
/**
|
|
46
|
+
* - The head segment of each declared `data`
|
|
47
|
+
* property — what a note actually writes. `appearance.eye_color` is authored
|
|
48
|
+
* as `appearance`, so that is the key recorded.
|
|
49
|
+
*/
|
|
50
|
+
dataKeys: Set<string>;
|
|
51
|
+
/**
|
|
52
|
+
* - The declared paths, whole.
|
|
53
|
+
*/
|
|
54
|
+
dataPaths: Set<string>;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* One `system.*` target the specification names for one note type.
|
|
58
|
+
*/
|
|
59
|
+
export type MappingClaim = {
|
|
60
|
+
/**
|
|
61
|
+
* - The type whose section makes the claim.
|
|
62
|
+
*/
|
|
63
|
+
noteType: string;
|
|
64
|
+
/**
|
|
65
|
+
* - The system column it sits under, from the header.
|
|
66
|
+
*/
|
|
67
|
+
system: string;
|
|
68
|
+
/**
|
|
69
|
+
* - The shared source cell, stripped of its backticks.
|
|
70
|
+
*/
|
|
71
|
+
source: string;
|
|
72
|
+
/**
|
|
73
|
+
* - The dotted path, `system.` prefix included.
|
|
74
|
+
*/
|
|
75
|
+
target: string;
|
|
76
|
+
/**
|
|
77
|
+
* - 1-based line of the row.
|
|
78
|
+
*/
|
|
79
|
+
line: number;
|
|
80
|
+
/**
|
|
81
|
+
* - 1-based column of the cell's first character.
|
|
82
|
+
*/
|
|
83
|
+
column: number;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* The specification, as data.
|
|
87
|
+
*/
|
|
88
|
+
export type ContentFormat = {
|
|
89
|
+
/**
|
|
90
|
+
* - Where it was read from, for diagnostics.
|
|
91
|
+
*/
|
|
92
|
+
file: string;
|
|
93
|
+
/**
|
|
94
|
+
* - Note type → what its section declares.
|
|
95
|
+
*/
|
|
96
|
+
types: Map<string, TypeSpec>;
|
|
97
|
+
/**
|
|
98
|
+
* - Every `system.*` target, in document order.
|
|
99
|
+
*/
|
|
100
|
+
claims: MappingClaim[];
|
|
101
|
+
};
|
|
@@ -86,15 +86,30 @@ export function auditHomepageLinks(index: ReturnType<typeof buildLinkIndex>): Ar
|
|
|
86
86
|
/**
|
|
87
87
|
* Every link in a tree that lands nowhere.
|
|
88
88
|
*
|
|
89
|
+
* **The two failure modes are separate findings, because they are separate
|
|
90
|
+
* problems** (#131). A piped target the author declared to be an address, and
|
|
91
|
+
* which resolves nowhere, is a typo: every package it could name is either
|
|
92
|
+
* built here or vendored, so there is no third possibility. An unpiped target
|
|
93
|
+
* naming no note of the source's type may be exactly that typo — or a
|
|
94
|
+
* worldbuilding placeholder, which is a long-standing convention in the
|
|
95
|
+
* setting trees. So the first is an error and the second a warning, and the
|
|
96
|
+
* caller can tell them apart without parsing a message.
|
|
97
|
+
*
|
|
89
98
|
* @param {ReturnType<typeof buildLinkIndex>} index - The built index.
|
|
90
99
|
* @returns {{deadAnchors: object[], deadAddresses: object[],
|
|
100
|
+
* deadAliases: object[], aliasCollisions: object[],
|
|
91
101
|
* frontmatterLinks: object[], homepageLinks: object[],
|
|
92
102
|
* usedManifest: Set<string>}} The findings, and which addresses a foreign
|
|
93
|
-
* manifest answered.
|
|
103
|
+
* manifest answered. Each `deadAddresses` entry carries a `reason`:
|
|
104
|
+
* `"not-an-address"` when the target does not parse as one at all,
|
|
105
|
+
* `"unknown-type"` when it is qualified but names no known type, and
|
|
106
|
+
* `"unresolved"` when it parses and nothing answers it.
|
|
94
107
|
*/
|
|
95
108
|
export function auditLinks(index: ReturnType<typeof buildLinkIndex>): {
|
|
96
109
|
deadAnchors: object[];
|
|
97
110
|
deadAddresses: object[];
|
|
111
|
+
deadAliases: object[];
|
|
112
|
+
aliasCollisions: object[];
|
|
98
113
|
frontmatterLinks: object[];
|
|
99
114
|
homepageLinks: object[];
|
|
100
115
|
usedManifest: Set<string>;
|
|
@@ -39,6 +39,12 @@ export function lintContentTree(contentBase: string, { skipDirectories, contentP
|
|
|
39
39
|
/**
|
|
40
40
|
* The shape every `shortcode` must match: ASCII letters and digits only.
|
|
41
41
|
*
|
|
42
|
+
* This is {@link ADDRESS_SEGMENT_PATTERN}, not a second copy of it. A shortcode
|
|
43
|
+
* is the last segment of a canonical address, and the rule it is held to is the
|
|
44
|
+
* rule *every* segment is held to — so the two are one constant rather than two
|
|
45
|
+
* free to drift apart (#59). The name survives because this is where the rule
|
|
46
|
+
* is applied to a note.
|
|
47
|
+
*
|
|
42
48
|
* Case is deliberately **not** constrained: hundreds of authored shortcodes are
|
|
43
49
|
* mixed-case and collide with nothing, so tightening that is a separate
|
|
44
50
|
* decision from this one.
|
|
@@ -210,3 +210,32 @@ export function positionOfYamlPath(text: string, keyPath: ReadonlyArray<string |
|
|
|
210
210
|
* @returns {Array<string|number>} Its segments, sequence indices as numbers.
|
|
211
211
|
*/
|
|
212
212
|
export function yamlKeyPath(field: string): Array<string | number>;
|
|
213
|
+
/**
|
|
214
|
+
* Where a key sits **inside a note's frontmatter fence**, addressed by path.
|
|
215
|
+
*
|
|
216
|
+
* {@link positionInFrontmatter} searches the fence for a key by name, which is
|
|
217
|
+
* the right tool while a note's vocabulary is flat: a key appears once and the
|
|
218
|
+
* first line matching it is the one. It stops being the right tool the moment
|
|
219
|
+
* the same name is legal in two regions — `data.weight` and a top-level
|
|
220
|
+
* `weight` are different keys, and a search finds whichever comes first.
|
|
221
|
+
*
|
|
222
|
+
* So this is the located form for the closed regions (#128). It reuses
|
|
223
|
+
* {@link positionOfYamlPath} rather than repeating its parse, which is what
|
|
224
|
+
* keeps a finding about `data.weight` pointing at `data.weight`.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} raw - The note's full contents, frontmatter included.
|
|
227
|
+
* @param {ReadonlyArray<string|number>} keyPath - Path to the node, from the
|
|
228
|
+
* top of the frontmatter.
|
|
229
|
+
* @param {object} [opts]
|
|
230
|
+
* @param {boolean} [opts.key=false] - Report where the last segment is
|
|
231
|
+
* *declared* rather than where its value sits.
|
|
232
|
+
* @returns {{line?: number, column?: number}} Spreadable position fields, empty
|
|
233
|
+
* when there is no fence or the path resolves to nothing — dropped rather
|
|
234
|
+
* than guessed.
|
|
235
|
+
*/
|
|
236
|
+
export function positionOfFrontmatterPath(raw: string, keyPath: ReadonlyArray<string | number>, { key }?: {
|
|
237
|
+
key?: boolean | undefined;
|
|
238
|
+
}): {
|
|
239
|
+
line?: number;
|
|
240
|
+
column?: number;
|
|
241
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One row of a system's map: what a note of this type becomes.
|
|
3
|
+
*
|
|
4
|
+
* @typedef {object} DocumentSubtypeRow
|
|
5
|
+
* @property {string} document - The Foundry document class — `"Item"`,
|
|
6
|
+
* `"Actor"`, and so on. It must agree with what the engine's own
|
|
7
|
+
* {@link packForType} routes the type to; they are two statements about one
|
|
8
|
+
* fact, and a test holds them together.
|
|
9
|
+
* @property {string} [subType] - The document subtype, for a one-to-one row.
|
|
10
|
+
* Mutually exclusive with `discriminator`.
|
|
11
|
+
* @property {string} [discriminator] - For a one-to-many row: the key the note
|
|
12
|
+
* supplies in this system's own frontmatter block to say which subtype it
|
|
13
|
+
* is.
|
|
14
|
+
* @property {readonly string[]} [subTypes] - The subtypes `discriminator` may
|
|
15
|
+
* name. Required with it, and never empty — a row permitting nothing is a
|
|
16
|
+
* row that can never resolve.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* A system's whole declaration: which system it belongs to, which frontmatter
|
|
20
|
+
* block its notes write, and every note type it maps.
|
|
21
|
+
*
|
|
22
|
+
* @typedef {object} DocumentSubtypeMap
|
|
23
|
+
* @property {string} system - The system this map belongs to (`"sohl"`).
|
|
24
|
+
* @property {string} block - The note frontmatter block this system's notes
|
|
25
|
+
* write, and the only place a discriminator is read from. Defaults to
|
|
26
|
+
* `system`.
|
|
27
|
+
* @property {Readonly<Record<string, DocumentSubtypeRow>>} types - Note type →
|
|
28
|
+
* row.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Declare one system's note-type → document-subtype map.
|
|
32
|
+
*
|
|
33
|
+
* Every malformed row is refused **here**, at module evaluation, rather than
|
|
34
|
+
* when some note happens to reach it: a map is a small piece of authored data
|
|
35
|
+
* loaded once per build, so a mistake in it should stop the build immediately
|
|
36
|
+
* and name the row, not surface as a missing document a thousand notes later.
|
|
37
|
+
*
|
|
38
|
+
* @param {object} declaration - The system's declaration.
|
|
39
|
+
* @param {string} declaration.system - The system this map belongs to.
|
|
40
|
+
* @param {string} [declaration.block] - The frontmatter block its notes write.
|
|
41
|
+
* Defaults to `system`.
|
|
42
|
+
* @param {Readonly<Record<string, DocumentSubtypeRow>>} declaration.types -
|
|
43
|
+
* Note type → row.
|
|
44
|
+
* @returns {DocumentSubtypeMap} The frozen map, rows and all.
|
|
45
|
+
* @throws {Error} When the declaration names no system, or any row is neither
|
|
46
|
+
* of the two permitted shapes.
|
|
47
|
+
*/
|
|
48
|
+
export function defineDocumentSubtypes({ system, block, types }?: {
|
|
49
|
+
system: string;
|
|
50
|
+
block?: string | undefined;
|
|
51
|
+
types: Readonly<Record<string, DocumentSubtypeRow>>;
|
|
52
|
+
}): DocumentSubtypeMap;
|
|
53
|
+
/**
|
|
54
|
+
* The row a system declares for a note type, or nothing.
|
|
55
|
+
*
|
|
56
|
+
* @param {DocumentSubtypeMap} map - The system's map.
|
|
57
|
+
* @param {string|undefined} noteType - The note's declared `type`.
|
|
58
|
+
* @returns {Readonly<DocumentSubtypeRow>|undefined} The row, or `undefined`
|
|
59
|
+
* where this system maps the type at all.
|
|
60
|
+
*/
|
|
61
|
+
export function subtypeRow(map: DocumentSubtypeMap, noteType: string | undefined): Readonly<DocumentSubtypeRow> | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Whether a system maps a note type — optionally, onto one document class.
|
|
64
|
+
*
|
|
65
|
+
* This is what a pass asks to decide whether it claims a note, which is the
|
|
66
|
+
* whole of the "no mapping, no document" property: a type the map does not
|
|
67
|
+
* carry is skipped exactly as quietly as the thousands of notes that belong to
|
|
68
|
+
* another pass.
|
|
69
|
+
*
|
|
70
|
+
* @param {DocumentSubtypeMap} map - The system's map.
|
|
71
|
+
* @param {string|undefined} noteType - The note's declared `type`.
|
|
72
|
+
* @param {string} [document] - Restrict the question to one document class.
|
|
73
|
+
* @returns {boolean} True when the map carries a matching row.
|
|
74
|
+
*/
|
|
75
|
+
export function mapsNoteType(map: DocumentSubtypeMap, noteType: string | undefined, document?: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Every note type a system maps onto one document class, sorted.
|
|
78
|
+
*
|
|
79
|
+
* A pass reports what it skipped by naming what it would have claimed, and
|
|
80
|
+
* that list is the map's — never a constant restating it.
|
|
81
|
+
*
|
|
82
|
+
* @param {DocumentSubtypeMap} map - The system's map.
|
|
83
|
+
* @param {string} document - The Foundry document class.
|
|
84
|
+
* @returns {string[]} The note types, in sorted order.
|
|
85
|
+
*/
|
|
86
|
+
export function noteTypesFor(map: DocumentSubtypeMap, document: string): string[];
|
|
87
|
+
/**
|
|
88
|
+
* The document subtype a note compiles into for one system.
|
|
89
|
+
*
|
|
90
|
+
* @param {DocumentSubtypeMap} map - The system's map.
|
|
91
|
+
* @param {string|undefined} noteType - The note's declared `type`.
|
|
92
|
+
* @param {object} fm - The note's frontmatter, read only for a one-to-many
|
|
93
|
+
* row's discriminator.
|
|
94
|
+
* @param {object} [options] - Options.
|
|
95
|
+
* @param {string} [options.file] - The note's path, appended to the message.
|
|
96
|
+
* Omit it where the caller emits through a diagnostic, which already puts
|
|
97
|
+
* the locator at the start of the line.
|
|
98
|
+
* @param {string} [options.absPath] - The note's file on disk, read only on
|
|
99
|
+
* the failing path to locate the offending line. The position rides on the
|
|
100
|
+
* thrown error as `position`, for a caller that emits a diagnostic.
|
|
101
|
+
* @returns {string|undefined} The subtype, or `undefined` where this system
|
|
102
|
+
* maps nothing for the type — which means no document, not an error.
|
|
103
|
+
* @throws {Error} When a one-to-many row's discriminator is absent, blank, or
|
|
104
|
+
* names a value the row does not permit.
|
|
105
|
+
*/
|
|
106
|
+
export function documentSubtype(map: DocumentSubtypeMap, noteType: string | undefined, fm: object, { file, absPath }?: {
|
|
107
|
+
file?: string | undefined;
|
|
108
|
+
absPath?: string | undefined;
|
|
109
|
+
}): string | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* The answer {@link referencedSubtype} gives: a subtype, or why there is none.
|
|
112
|
+
*
|
|
113
|
+
* Exactly one of the two fields is set. A `problem` is a sentence a caller
|
|
114
|
+
* prefixes with its own context and emits as a finding — it never throws,
|
|
115
|
+
* because the caller resolving a reference is walking a list and has to report
|
|
116
|
+
* this one and carry on.
|
|
117
|
+
*
|
|
118
|
+
* @typedef {object} ReferencedSubtype
|
|
119
|
+
* @property {string} [subType] - The document subtype the reference addresses.
|
|
120
|
+
* @property {string} [problem] - Why the reference names no document subtype.
|
|
121
|
+
*/
|
|
122
|
+
/**
|
|
123
|
+
* The document subtype a `(type, shortcode)` **reference** addresses (#140).
|
|
124
|
+
*
|
|
125
|
+
* A being's frontmatter names each embedded item by the *note's* type — the
|
|
126
|
+
* vocabulary an author writes — while the predefined items it resolves against
|
|
127
|
+
* are compiled documents, which carry only the *subtype*. One side has to
|
|
128
|
+
* translate, and it is this one: the map is a function from note type to
|
|
129
|
+
* subtype by construction, whereas the reverse is not — two note types may
|
|
130
|
+
* compile into one subtype, and a compiled document records nothing about the
|
|
131
|
+
* note that produced it. So the addresses stay keyed on the **document
|
|
132
|
+
* subtype**, which is the only vocabulary both a local pack and an extracted
|
|
133
|
+
* dependency catalogue actually carry, and a reference is translated forward
|
|
134
|
+
* here before it is looked up.
|
|
135
|
+
*
|
|
136
|
+
* Four answers, and only the first resolves:
|
|
137
|
+
*
|
|
138
|
+
* - _A one-to-one row_ → the subtype it declares. `armor` addresses an
|
|
139
|
+
* `armorgear`.
|
|
140
|
+
* - _No row at all_ → the note type itself. A consumer declares its own item
|
|
141
|
+
* types in its `itemBuilders` table rather than in this system's map, and the
|
|
142
|
+
* Item pass stamps such a document with the note type; the reference has to
|
|
143
|
+
* agree, or a consumer's own items would stop resolving the moment a map
|
|
144
|
+
* existed.
|
|
145
|
+
* - _A row for another document class_ → a problem. A being is not an item,
|
|
146
|
+
* however the address is spelled.
|
|
147
|
+
* - _A one-to-many row_ → a problem naming the candidates. The note that owns
|
|
148
|
+
* such a row resolves it from its own frontmatter block; a reference has no
|
|
149
|
+
* block, so nothing here can choose, and choosing anyway would be right about
|
|
150
|
+
* half the time. No system declares a one-to-many **Item** row today, so this
|
|
151
|
+
* is a guard rather than a behaviour — but it is a loud one, which is the
|
|
152
|
+
* whole point of the issue.
|
|
153
|
+
*
|
|
154
|
+
* A **retired** spelling is refused by name before any of that. Without it a
|
|
155
|
+
* reference left behind by a rename would take the unmapped fallback and
|
|
156
|
+
* address a document of the old name — resolving silently, which is precisely
|
|
157
|
+
* what a retirement exists to stop (#78).
|
|
158
|
+
*
|
|
159
|
+
* @param {DocumentSubtypeMap} map - The system's map.
|
|
160
|
+
* @param {string|undefined} noteType - The type the reference names.
|
|
161
|
+
* @param {string} document - The Foundry document class the reference must
|
|
162
|
+
* address — `"Item"` for a being's embedded items.
|
|
163
|
+
* @returns {ReferencedSubtype} The subtype, or why there is none.
|
|
164
|
+
*/
|
|
165
|
+
export function referencedSubtype(map: DocumentSubtypeMap, noteType: string | undefined, document: string): ReferencedSubtype;
|
|
166
|
+
/**
|
|
167
|
+
* One row of a system's map: what a note of this type becomes.
|
|
168
|
+
*/
|
|
169
|
+
export type DocumentSubtypeRow = {
|
|
170
|
+
/**
|
|
171
|
+
* - The Foundry document class — `"Item"`,
|
|
172
|
+
* `"Actor"`, and so on. It must agree with what the engine's own
|
|
173
|
+
* {@link packForType} routes the type to; they are two statements about one
|
|
174
|
+
* fact, and a test holds them together.
|
|
175
|
+
*/
|
|
176
|
+
document: string;
|
|
177
|
+
/**
|
|
178
|
+
* - The document subtype, for a one-to-one row.
|
|
179
|
+
* Mutually exclusive with `discriminator`.
|
|
180
|
+
*/
|
|
181
|
+
subType?: string | undefined;
|
|
182
|
+
/**
|
|
183
|
+
* - For a one-to-many row: the key the note
|
|
184
|
+
* supplies in this system's own frontmatter block to say which subtype it
|
|
185
|
+
* is.
|
|
186
|
+
*/
|
|
187
|
+
discriminator?: string | undefined;
|
|
188
|
+
/**
|
|
189
|
+
* - The subtypes `discriminator` may
|
|
190
|
+
* name. Required with it, and never empty — a row permitting nothing is a
|
|
191
|
+
* row that can never resolve.
|
|
192
|
+
*/
|
|
193
|
+
subTypes?: readonly string[] | undefined;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* A system's whole declaration: which system it belongs to, which frontmatter
|
|
197
|
+
* block its notes write, and every note type it maps.
|
|
198
|
+
*/
|
|
199
|
+
export type DocumentSubtypeMap = {
|
|
200
|
+
/**
|
|
201
|
+
* - The system this map belongs to (`"sohl"`).
|
|
202
|
+
*/
|
|
203
|
+
system: string;
|
|
204
|
+
/**
|
|
205
|
+
* - The note frontmatter block this system's notes
|
|
206
|
+
* write, and the only place a discriminator is read from. Defaults to
|
|
207
|
+
* `system`.
|
|
208
|
+
*/
|
|
209
|
+
block: string;
|
|
210
|
+
/**
|
|
211
|
+
* - Note type →
|
|
212
|
+
* row.
|
|
213
|
+
*/
|
|
214
|
+
types: Readonly<Record<string, DocumentSubtypeRow>>;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* The answer {@link referencedSubtype} gives: a subtype, or why there is none.
|
|
218
|
+
*
|
|
219
|
+
* Exactly one of the two fields is set. A `problem` is a sentence a caller
|
|
220
|
+
* prefixes with its own context and emits as a finding — it never throws,
|
|
221
|
+
* because the caller resolving a reference is walking a list and has to report
|
|
222
|
+
* this one and carry on.
|
|
223
|
+
*/
|
|
224
|
+
export type ReferencedSubtype = {
|
|
225
|
+
/**
|
|
226
|
+
* - The document subtype the reference addresses.
|
|
227
|
+
*/
|
|
228
|
+
subType?: string | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* - Why the reference names no document subtype.
|
|
231
|
+
*/
|
|
232
|
+
problem?: string | undefined;
|
|
233
|
+
};
|