@rse/specbook 1.2.3 → 1.2.4
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/README.md +112 -204
- package/dst/specbook-cmd-export.js +4 -1
- package/dst/specbook-config.js +6 -6
- package/dst/specbook-diagram.js +3 -3
- package/dst/specbook-export-ast.js +1 -1
- package/dst/specbook-export-common.js +1 -1
- package/dst/specbook-export-html.d.ts +1 -1
- package/dst/specbook-export-html.js +38 -38
- package/dst/specbook-export-md.js +9 -9
- package/dst/specbook-export-pdf.js +7 -7
- package/dst/specbook-format-schema.d.ts +1 -1
- package/dst/specbook-format-schema.js +2 -2
- package/dst/specbook-format-spec.d.ts +1 -1
- package/dst/specbook-format-spec.js +2 -2
- package/dst/specbook-format.md +4 -4
- package/dst/specbook-format.yaml +40 -40
- package/dst/specbook-link.js +2 -2
- package/dst/specbook-parse-semantic.js +22 -22
- package/dst/specbook-parse-syntax.js +5 -5
- package/dst/specbook-parse.js +1 -1
- package/package.json +2 -2
package/dst/specbook-format.yaml
CHANGED
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
size-node-width-max: 440
|
|
233
233
|
size-node-height-scale: 1.75
|
|
234
234
|
grid-columns-max: 5
|
|
235
|
-
|
|
235
|
+
children:
|
|
236
236
|
- kind: PREMISE
|
|
237
237
|
desc: >
|
|
238
238
|
A single premise, named with a short (3-8 word) summary phrased as the fact taken
|
|
@@ -307,7 +307,7 @@
|
|
|
307
307
|
config:
|
|
308
308
|
size-node-width-max: 440
|
|
309
309
|
grid-columns-max: 4
|
|
310
|
-
|
|
310
|
+
children:
|
|
311
311
|
- kind: TERM
|
|
312
312
|
name: "[A-Z][A-Za-z0-9-]*( [A-Z][A-Za-z0-9-]*)*"
|
|
313
313
|
desc: >
|
|
@@ -399,7 +399,7 @@
|
|
|
399
399
|
config:
|
|
400
400
|
size-node-width-max: 440
|
|
401
401
|
grid-columns-max: 4
|
|
402
|
-
|
|
402
|
+
children:
|
|
403
403
|
- kind: PERSONA
|
|
404
404
|
desc: >
|
|
405
405
|
An archetypal user profile, named with a per-artifact unique first name of the
|
|
@@ -490,7 +490,7 @@
|
|
|
490
490
|
nodes: "[[JOURNEY:*]]"
|
|
491
491
|
properties:
|
|
492
492
|
- ACTORS
|
|
493
|
-
|
|
493
|
+
children:
|
|
494
494
|
- kind: JOURNEY
|
|
495
495
|
desc: >
|
|
496
496
|
The end-to-end journey of a single persona (or of multiple personas sharing the same
|
|
@@ -501,7 +501,7 @@
|
|
|
501
501
|
Comma-separated references to the User Personas objects experiencing the
|
|
502
502
|
journey.
|
|
503
503
|
value: list([[PERSONA:*]])
|
|
504
|
-
|
|
504
|
+
children:
|
|
505
505
|
- kind: STEP
|
|
506
506
|
desc: >
|
|
507
507
|
A single step of the journey, named with a 5-10 word summary of the step at
|
|
@@ -569,7 +569,7 @@
|
|
|
569
569
|
config:
|
|
570
570
|
size-node-width-max: 440
|
|
571
571
|
grid-columns-max: 5
|
|
572
|
-
|
|
572
|
+
children:
|
|
573
573
|
- kind: REQUIREMENT
|
|
574
574
|
desc: >
|
|
575
575
|
A single functional requirement, named with a short (3-8 word) summary and described
|
|
@@ -628,7 +628,7 @@
|
|
|
628
628
|
config:
|
|
629
629
|
size-node-width-max: 440
|
|
630
630
|
grid-columns-max: 5
|
|
631
|
-
|
|
631
|
+
children:
|
|
632
632
|
- kind: REQUIREMENT
|
|
633
633
|
desc: >
|
|
634
634
|
A single non-functional requirement, named with a short (3-8 word) summary and
|
|
@@ -703,7 +703,7 @@
|
|
|
703
703
|
config:
|
|
704
704
|
size-node-width-max: 440
|
|
705
705
|
grid-columns-max: 5
|
|
706
|
-
|
|
706
|
+
children:
|
|
707
707
|
- kind: RULE
|
|
708
708
|
desc: >
|
|
709
709
|
A single domain rule, named with a short (3-8 word) summary and described in a
|
|
@@ -806,7 +806,7 @@
|
|
|
806
806
|
size-node-width-max: 440
|
|
807
807
|
size-node-height-scale: 2
|
|
808
808
|
grid-columns-max: 4
|
|
809
|
-
|
|
809
|
+
children:
|
|
810
810
|
- kind: WORKFLOW
|
|
811
811
|
desc: >
|
|
812
812
|
A single workflow, named with a short (2-5 word) summary phrased as the outcome
|
|
@@ -881,7 +881,7 @@
|
|
|
881
881
|
checked at a single handoff is referenced from the handoff instead.
|
|
882
882
|
value: list([[RULE:*]])
|
|
883
883
|
optional: true
|
|
884
|
-
|
|
884
|
+
children:
|
|
885
885
|
- kind: ACTIVITY
|
|
886
886
|
desc: >
|
|
887
887
|
A single unit of work within the workflow, performed by one party without a
|
|
@@ -974,7 +974,7 @@
|
|
|
974
974
|
config:
|
|
975
975
|
size-node-height-scale: 4
|
|
976
976
|
grid-columns-max: 4
|
|
977
|
-
|
|
977
|
+
children:
|
|
978
978
|
- kind: USE-CASE
|
|
979
979
|
desc: >
|
|
980
980
|
A discrete goal a user pursues with the solution, named with a short (3-8 word)
|
|
@@ -1056,7 +1056,7 @@
|
|
|
1056
1056
|
desc: >
|
|
1057
1057
|
The condition that holds after the use case completes successfully.
|
|
1058
1058
|
optional: true
|
|
1059
|
-
|
|
1059
|
+
children:
|
|
1060
1060
|
- kind: SCENARIO
|
|
1061
1061
|
desc: >
|
|
1062
1062
|
A concrete step-by-step flow of the use case, named with a short (3-8 word)
|
|
@@ -1122,7 +1122,7 @@
|
|
|
1122
1122
|
type: graph
|
|
1123
1123
|
nodes: "[[DM.ENTITY:*]]"
|
|
1124
1124
|
edges: "[[RELATION:*]]"
|
|
1125
|
-
|
|
1125
|
+
children:
|
|
1126
1126
|
- kind: ENTITY
|
|
1127
1127
|
name: "[A-Z][a-zA-Z0-9]*"
|
|
1128
1128
|
desc: >
|
|
@@ -1151,7 +1151,7 @@
|
|
|
1151
1151
|
is anchored in the ubiquitous language and every term shows its data.
|
|
1152
1152
|
value: list([[TERM:*]])
|
|
1153
1153
|
optional: true
|
|
1154
|
-
|
|
1154
|
+
children:
|
|
1155
1155
|
- kind: ATTRIBUTE
|
|
1156
1156
|
name: "[a-z][a-zA-Z0-9]*"
|
|
1157
1157
|
desc: >
|
|
@@ -1264,7 +1264,7 @@
|
|
|
1264
1264
|
size-node-width-max: 440
|
|
1265
1265
|
size-node-height-scale: 2
|
|
1266
1266
|
grid-columns-max: 5
|
|
1267
|
-
|
|
1267
|
+
children:
|
|
1268
1268
|
- kind: LIFECYCLE
|
|
1269
1269
|
desc: >
|
|
1270
1270
|
The lifecycle of a single entity, named with the short (1-3 word) name of the
|
|
@@ -1297,7 +1297,7 @@
|
|
|
1297
1297
|
lifecycle per entity (an entity without a lifecycle has a trivial one).
|
|
1298
1298
|
value: "[[DM.ENTITY:*]]"
|
|
1299
1299
|
unique: true
|
|
1300
|
-
|
|
1300
|
+
children:
|
|
1301
1301
|
- kind: STATE
|
|
1302
1302
|
name: "[A-Z][a-zA-Z0-9]*"
|
|
1303
1303
|
desc: >
|
|
@@ -1415,7 +1415,7 @@
|
|
|
1415
1415
|
config:
|
|
1416
1416
|
size-node-width-max: 440
|
|
1417
1417
|
graph-channel-width-max: 340
|
|
1418
|
-
|
|
1418
|
+
children:
|
|
1419
1419
|
- kind: ROLE
|
|
1420
1420
|
desc: >
|
|
1421
1421
|
A single role, named with the canonical name of the domain role it grants (e.g.
|
|
@@ -1461,7 +1461,7 @@
|
|
|
1461
1461
|
value: list([[ROLE:*]])
|
|
1462
1462
|
optional: true
|
|
1463
1463
|
acyclic: true
|
|
1464
|
-
|
|
1464
|
+
children:
|
|
1465
1465
|
- kind: PERMISSION
|
|
1466
1466
|
desc: >
|
|
1467
1467
|
A single right of the role on a single entity, named with a short (2-5
|
|
@@ -1544,7 +1544,7 @@
|
|
|
1544
1544
|
properties:
|
|
1545
1545
|
- KIND
|
|
1546
1546
|
- DIRECTION
|
|
1547
|
-
|
|
1547
|
+
children:
|
|
1548
1548
|
- kind: INTERFACE
|
|
1549
1549
|
desc: >
|
|
1550
1550
|
A single interface, named with a short (2-5 word) summary (e.g. `Public REST API`,
|
|
@@ -1629,7 +1629,7 @@
|
|
|
1629
1629
|
qualified: true
|
|
1630
1630
|
config:
|
|
1631
1631
|
size-node-width-max: 440
|
|
1632
|
-
|
|
1632
|
+
children:
|
|
1633
1633
|
- kind: TYPE
|
|
1634
1634
|
name: "[A-Z][A-Za-z0-9_]*"
|
|
1635
1635
|
desc: >
|
|
@@ -1691,7 +1691,7 @@
|
|
|
1691
1691
|
entities), tracing the interface into the data model.
|
|
1692
1692
|
value: list([[DM.ENTITY:*]])
|
|
1693
1693
|
optional: true
|
|
1694
|
-
|
|
1694
|
+
children:
|
|
1695
1695
|
- kind: FIELD
|
|
1696
1696
|
name: "[A-Za-z_][A-Za-z0-9_-]*"
|
|
1697
1697
|
desc: >
|
|
@@ -1792,7 +1792,7 @@
|
|
|
1792
1792
|
qualified: true
|
|
1793
1793
|
config:
|
|
1794
1794
|
size-node-width-max: 440
|
|
1795
|
-
|
|
1795
|
+
children:
|
|
1796
1796
|
- kind: ENDPOINT
|
|
1797
1797
|
name: "[A-Za-z_][A-Za-z0-9_-]*"
|
|
1798
1798
|
desc: >
|
|
@@ -1872,7 +1872,7 @@
|
|
|
1872
1872
|
realizes, tracing the requirements into the interface.
|
|
1873
1873
|
value: list([[USE-CASE:*]])
|
|
1874
1874
|
optional: true
|
|
1875
|
-
|
|
1875
|
+
children:
|
|
1876
1876
|
- kind: OPTION
|
|
1877
1877
|
name: "-{0,2}[A-Za-z_][A-Za-z0-9_-]*"
|
|
1878
1878
|
desc: >
|
|
@@ -2034,7 +2034,7 @@
|
|
|
2034
2034
|
size-node-width-max: 440
|
|
2035
2035
|
size-node-height-scale: 2
|
|
2036
2036
|
grid-columns-max: 4
|
|
2037
|
-
|
|
2037
|
+
children:
|
|
2038
2038
|
- kind: PRINCIPLE
|
|
2039
2039
|
desc: >
|
|
2040
2040
|
A single interaction principle, named with a short (2-5 word) summary (e.g.
|
|
@@ -2115,7 +2115,7 @@
|
|
|
2115
2115
|
size-node-width-max: 440
|
|
2116
2116
|
size-node-height-scale: 2
|
|
2117
2117
|
grid-columns-max: 4
|
|
2118
|
-
|
|
2118
|
+
children:
|
|
2119
2119
|
- kind: CONVENTION
|
|
2120
2120
|
desc: >
|
|
2121
2121
|
A single language convention, named with a short (2-5 word) summary (e.g. `Neutral
|
|
@@ -2186,7 +2186,7 @@
|
|
|
2186
2186
|
size-node-width-max: 440
|
|
2187
2187
|
size-node-height-scale: 2
|
|
2188
2188
|
grid-columns-max: 4
|
|
2189
|
-
|
|
2189
|
+
children:
|
|
2190
2190
|
- kind: PATTERN
|
|
2191
2191
|
desc: >
|
|
2192
2192
|
A single reusable dialog pattern, named with a short (2-5 word) summary (e.g.
|
|
@@ -2280,7 +2280,7 @@
|
|
|
2280
2280
|
size-node-width-max: 440
|
|
2281
2281
|
size-node-height-scale: 2
|
|
2282
2282
|
grid-columns-max: 4
|
|
2283
|
-
|
|
2283
|
+
children:
|
|
2284
2284
|
- kind: STORYBOARD
|
|
2285
2285
|
desc: >
|
|
2286
2286
|
A single depicted dialog flow, named with a short (3-8 word) summary of the flow and
|
|
@@ -2312,7 +2312,7 @@
|
|
|
2312
2312
|
interface, omitted for a storyboard of a flow no scenario covers.
|
|
2313
2313
|
value: "[[SCENARIO:*]]"
|
|
2314
2314
|
optional: true
|
|
2315
|
-
|
|
2315
|
+
children:
|
|
2316
2316
|
- kind: FRAME
|
|
2317
2317
|
desc: >
|
|
2318
2318
|
A single frame of the storyboard, named with a short (2-5 word) label for
|
|
@@ -2385,7 +2385,7 @@
|
|
|
2385
2385
|
size-node-width-max: 440
|
|
2386
2386
|
size-node-height-scale: 2
|
|
2387
2387
|
grid-columns-max: 4
|
|
2388
|
-
|
|
2388
|
+
children:
|
|
2389
2389
|
- kind: ELEMENT
|
|
2390
2390
|
desc: >
|
|
2391
2391
|
A single visual design element, named with a short (2-5 word) summary (e.g. `Brand
|
|
@@ -2515,7 +2515,7 @@
|
|
|
2515
2515
|
qualified: true
|
|
2516
2516
|
properties:
|
|
2517
2517
|
- KIND
|
|
2518
|
-
|
|
2518
|
+
children:
|
|
2519
2519
|
- kind: ENTITY
|
|
2520
2520
|
desc: >
|
|
2521
2521
|
A single external entity the solution interacts with, named with a short (2-5 word)
|
|
@@ -2614,7 +2614,7 @@
|
|
|
2614
2614
|
labeled: true
|
|
2615
2615
|
properties:
|
|
2616
2616
|
- KIND
|
|
2617
|
-
|
|
2617
|
+
children:
|
|
2618
2618
|
- kind: COMPONENT
|
|
2619
2619
|
desc: >
|
|
2620
2620
|
A single functional component, named with a short (2-5 word) summary (e.g.
|
|
@@ -2718,7 +2718,7 @@
|
|
|
2718
2718
|
diagram:
|
|
2719
2719
|
type: grid
|
|
2720
2720
|
nodes: "[[TS.TIER:*]]"
|
|
2721
|
-
|
|
2721
|
+
children:
|
|
2722
2722
|
- kind: TIER
|
|
2723
2723
|
desc: |
|
|
2724
2724
|
The architectural tier grouping the technology components belonging to it, named
|
|
@@ -2742,7 +2742,7 @@
|
|
|
2742
2742
|
config:
|
|
2743
2743
|
size-node-width-max: 440
|
|
2744
2744
|
size-node-height-scale: 1.75
|
|
2745
|
-
|
|
2745
|
+
children:
|
|
2746
2746
|
- kind: COMPONENT
|
|
2747
2747
|
desc: >
|
|
2748
2748
|
A single technology component, named with a short (2-5 word) summary (e.g.
|
|
@@ -3070,7 +3070,7 @@
|
|
|
3070
3070
|
nodes: "[[ASPECT:*]]"
|
|
3071
3071
|
properties:
|
|
3072
3072
|
- CATEGORY
|
|
3073
|
-
|
|
3073
|
+
children:
|
|
3074
3074
|
- kind: ASPECT
|
|
3075
3075
|
desc: >
|
|
3076
3076
|
A single development aspect, named with a short (2-5 word) summary (e.g. `Module
|
|
@@ -3118,7 +3118,7 @@
|
|
|
3118
3118
|
labeled: true
|
|
3119
3119
|
properties:
|
|
3120
3120
|
- KIND
|
|
3121
|
-
|
|
3121
|
+
children:
|
|
3122
3122
|
- kind: UNIT
|
|
3123
3123
|
desc: >
|
|
3124
3124
|
A single concurrency unit, named with a short (2-5 word) summary (e.g. `Web Worker
|
|
@@ -3214,7 +3214,7 @@
|
|
|
3214
3214
|
- PLATFORM
|
|
3215
3215
|
config:
|
|
3216
3216
|
size-node-width-max: 350
|
|
3217
|
-
|
|
3217
|
+
children:
|
|
3218
3218
|
- kind: TIER
|
|
3219
3219
|
desc: >
|
|
3220
3220
|
A single deployment tier, named with a short (1-3 word) summary (e.g. `Client`,
|
|
@@ -3455,7 +3455,7 @@
|
|
|
3455
3455
|
nodes: "[[CONCERN:*]]"
|
|
3456
3456
|
properties:
|
|
3457
3457
|
- CATEGORY
|
|
3458
|
-
|
|
3458
|
+
children:
|
|
3459
3459
|
- kind: CONCERN
|
|
3460
3460
|
desc: >
|
|
3461
3461
|
A single operational concern, named with a short (2-5 word) summary (e.g. `Rolling
|
|
@@ -3567,7 +3567,7 @@
|
|
|
3567
3567
|
nodes: "[[TACTIC:*]]"
|
|
3568
3568
|
properties:
|
|
3569
3569
|
- QUALITY
|
|
3570
|
-
|
|
3570
|
+
children:
|
|
3571
3571
|
- kind: TACTIC
|
|
3572
3572
|
desc: >
|
|
3573
3573
|
A single quality tactic, one architectural tactic applied to one quality attribute
|
|
@@ -3691,7 +3691,7 @@
|
|
|
3691
3691
|
config:
|
|
3692
3692
|
size-node-width-max: 440
|
|
3693
3693
|
grid-columns-max: 5
|
|
3694
|
-
|
|
3694
|
+
children:
|
|
3695
3695
|
- kind: DECISION
|
|
3696
3696
|
desc: |
|
|
3697
3697
|
A single architecture decision, named with a short summary of the decision, not
|
|
@@ -3931,7 +3931,7 @@
|
|
|
3931
3931
|
size-node-width-max: 440
|
|
3932
3932
|
size-node-height-scale: 2
|
|
3933
3933
|
grid-columns-max: 4
|
|
3934
|
-
|
|
3934
|
+
children:
|
|
3935
3935
|
- kind: TEST-CASE
|
|
3936
3936
|
desc: >
|
|
3937
3937
|
A single verifiable test case, named with a short (3-8 word) summary and defined by
|
package/dst/specbook-link.js
CHANGED
|
@@ -55,7 +55,7 @@ export const buildLinkIndex = (specification) => {
|
|
|
55
55
|
const walk = (object, parent) => {
|
|
56
56
|
const node = { object, parent, pos: index.length, name: plainText(object.name) };
|
|
57
57
|
index.push(node);
|
|
58
|
-
for (const child of object.
|
|
58
|
+
for (const child of object.children)
|
|
59
59
|
walk(child, node);
|
|
60
60
|
};
|
|
61
61
|
for (const artifact of specification.artifacts)
|
|
@@ -173,7 +173,7 @@ export const resolveUnique = (index, reference, from) => {
|
|
|
173
173
|
};
|
|
174
174
|
/* derive the fully-qualified anchor path of every object: the
|
|
175
175
|
"<kind>-<id>" segments (whitespace in kinds dashed) of the ancestor
|
|
176
|
-
chain joined with "." (the index lists parents before
|
|
176
|
+
chain joined with "." (the index lists parents before children) */
|
|
177
177
|
export const anchorPaths = (index) => {
|
|
178
178
|
const paths = new Map();
|
|
179
179
|
for (const node of index) {
|
|
@@ -129,11 +129,11 @@ const parenProp = (object, props) => {
|
|
|
129
129
|
values matching the regexp or enum expression of the flag), where
|
|
130
130
|
a kind without any sibling fails a present flag, too, but only for
|
|
131
131
|
an optional kind, as the missing-kind check reports a required one */
|
|
132
|
-
const checkSiblingFlags = (ctx, object,
|
|
132
|
+
const checkSiblingFlags = (ctx, object, children, meta) => {
|
|
133
133
|
const marker = (flag) => flag === undefined || flag === false ? undefined :
|
|
134
134
|
{ filter: typeof flag === "string" ? compileValueExpr(flag) : undefined };
|
|
135
|
-
for (const child of
|
|
136
|
-
const siblings = object.
|
|
135
|
+
for (const child of children) {
|
|
136
|
+
const siblings = object.children.filter((c) => c.kind === child.kind);
|
|
137
137
|
for (const prop of child.props ?? []) {
|
|
138
138
|
const unique = marker(prop.unique);
|
|
139
139
|
const present = marker(prop.present);
|
|
@@ -165,7 +165,7 @@ const checkSiblingFlags = (ctx, object, childs, meta) => {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
|
-
/* validate a single object (and recursively its
|
|
168
|
+
/* validate a single object (and recursively its children) against its schema */
|
|
169
169
|
const validateObject = (ctx, object, schema, level) => {
|
|
170
170
|
const meta = ctx.metaOf(object);
|
|
171
171
|
const props = schema.props ?? [];
|
|
@@ -199,10 +199,10 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
199
199
|
if (!props.some((p) => p.name === property.key))
|
|
200
200
|
ctx.diagnose(meta.file, ctx.propMeta.get(property)?.line ?? meta.line, `unknown property "${property.key}" on ${object.kind} "${object.name}"`);
|
|
201
201
|
/* check the configured child objects */
|
|
202
|
-
const
|
|
203
|
-
for (const child of object.
|
|
202
|
+
const children = schema.children ?? [];
|
|
203
|
+
for (const child of object.children) {
|
|
204
204
|
const childMeta = ctx.metaOf(child);
|
|
205
|
-
const childSchema =
|
|
205
|
+
const childSchema = children.find((c) => c.kind === child.kind);
|
|
206
206
|
if (childSchema === undefined) {
|
|
207
207
|
ctx.diagnose(childMeta.file, childMeta.line, `unknown object kind "${child.kind}" below ${object.kind} "${object.name}"`);
|
|
208
208
|
continue;
|
|
@@ -210,20 +210,20 @@ const validateObject = (ctx, object, schema, level) => {
|
|
|
210
210
|
validateObject(ctx, child, childSchema, level + 1);
|
|
211
211
|
}
|
|
212
212
|
/* check the sibling-scoped property flags of the child kinds */
|
|
213
|
-
checkSiblingFlags(ctx, object,
|
|
213
|
+
checkSiblingFlags(ctx, object, children, meta);
|
|
214
214
|
/* report the configured child object kinds which are missing */
|
|
215
|
-
for (const child of
|
|
215
|
+
for (const child of children)
|
|
216
216
|
if (child.optional !== true
|
|
217
|
-
&& !object.
|
|
217
|
+
&& !object.children.some((c) => c.kind === child.kind))
|
|
218
218
|
ctx.diagnose(meta.file, meta.line, `required object kind "${child.kind}" missing below ${object.kind} "${object.name}"`);
|
|
219
|
-
/* order the
|
|
219
|
+
/* order the children and properties exactly along the schema
|
|
220
220
|
definition (the sort is stable, so objects of the same kind
|
|
221
221
|
and unknown items keep their document order) */
|
|
222
222
|
const kindPos = (kind) => {
|
|
223
|
-
const i =
|
|
224
|
-
return i >= 0 ? i :
|
|
223
|
+
const i = children.findIndex((c) => c.kind === kind);
|
|
224
|
+
return i >= 0 ? i : children.length;
|
|
225
225
|
};
|
|
226
|
-
object.
|
|
226
|
+
object.children.sort((a, b) => kindPos(a.kind) - kindPos(b.kind));
|
|
227
227
|
const propPos = (key) => {
|
|
228
228
|
const i = props.findIndex((p) => p.name === key);
|
|
229
229
|
return i >= 0 ? i : props.length;
|
|
@@ -242,8 +242,8 @@ export const collectSchemas = (specification, config) => {
|
|
|
242
242
|
const schemas = new Map();
|
|
243
243
|
const walk = (object, schema) => {
|
|
244
244
|
schemas.set(object, schema);
|
|
245
|
-
for (const child of object.
|
|
246
|
-
const childSchema = (schema.
|
|
245
|
+
for (const child of object.children) {
|
|
246
|
+
const childSchema = (schema.children ?? []).find((c) => c.kind === child.kind);
|
|
247
247
|
if (childSchema !== undefined)
|
|
248
248
|
walk(child, childSchema);
|
|
249
249
|
}
|
|
@@ -284,7 +284,7 @@ const checkIds = (ctx, objects) => {
|
|
|
284
284
|
const meta = ctx.metaOf(object);
|
|
285
285
|
ctx.diagnose(meta.file, meta.line, `id "${object.id}" of ${object.kind} "${object.name}" collides with preceding ${object.kind} "${first.name}"`);
|
|
286
286
|
}
|
|
287
|
-
checkIds(ctx, object.
|
|
287
|
+
checkIds(ctx, object.children);
|
|
288
288
|
}
|
|
289
289
|
};
|
|
290
290
|
/* the objects a property of an object references (resolved and
|
|
@@ -317,7 +317,7 @@ const checkAutomata = (ctx, schemas) => {
|
|
|
317
317
|
const automaton = schema.automaton;
|
|
318
318
|
if (automaton === undefined)
|
|
319
319
|
continue;
|
|
320
|
-
const nodes = object.
|
|
320
|
+
const nodes = object.children.filter((child) => child.kind === automaton.nodes);
|
|
321
321
|
const nodeSet = new Set(nodes);
|
|
322
322
|
const flagged = (node, name) => {
|
|
323
323
|
const property = findProp(ctx, node, name);
|
|
@@ -329,7 +329,7 @@ const checkAutomata = (ctx, schemas) => {
|
|
|
329
329
|
the node set is skipped, as the property checks report it) */
|
|
330
330
|
const succs = new Map(nodes.map((node) => [node, new Set()]));
|
|
331
331
|
const preds = new Map(nodes.map((node) => [node, new Set()]));
|
|
332
|
-
for (const edge of object.
|
|
332
|
+
for (const edge of object.children.filter((child) => child.kind === automaton.edges)) {
|
|
333
333
|
const source = referencedObjects(ctx, edge, automaton.source)[0];
|
|
334
334
|
const target = referencedObjects(ctx, edge, automaton.target)[0];
|
|
335
335
|
if (source !== undefined && target !== undefined && nodeSet.has(source) && nodeSet.has(target)) {
|
|
@@ -446,7 +446,7 @@ const checkReferenced = (ctx, specification, schemas) => {
|
|
|
446
446
|
if (target !== undefined && !chain.includes(target))
|
|
447
447
|
referrers.set(target, (referrers.get(target) ?? new Set()).add(object));
|
|
448
448
|
}
|
|
449
|
-
object.
|
|
449
|
+
object.children.forEach(walk);
|
|
450
450
|
};
|
|
451
451
|
for (const artifact of specification.artifacts)
|
|
452
452
|
artifact.objects.forEach(walk);
|
|
@@ -494,7 +494,7 @@ export const validate = (ctx, specification, config) => {
|
|
|
494
494
|
references all over the corpus */
|
|
495
495
|
if (object.paren !== undefined && object.anchor === undefined && !ctx.parenProps.has(object))
|
|
496
496
|
assignId(ctx.linkIndex, object, object.paren);
|
|
497
|
-
object.
|
|
497
|
+
object.children.forEach(promote);
|
|
498
498
|
};
|
|
499
499
|
for (const artifact of specification.artifacts)
|
|
500
500
|
artifact.objects.forEach(promote);
|
|
@@ -554,7 +554,7 @@ export const validateReferences = (ctx, specification) => {
|
|
|
554
554
|
if (object.description.rationale !== undefined)
|
|
555
555
|
check(object, object.description.rationale, meta.file, meta.line);
|
|
556
556
|
}
|
|
557
|
-
object.
|
|
557
|
+
object.children.forEach(walk);
|
|
558
558
|
};
|
|
559
559
|
for (const artifact of specification.artifacts)
|
|
560
560
|
artifact.objects.forEach(walk);
|
|
@@ -150,7 +150,7 @@ const embed = (ctx, object, file) => {
|
|
|
150
150
|
load(object.description, object.description.description, line);
|
|
151
151
|
for (const property of object.properties)
|
|
152
152
|
load(property, property.value, ctx.propMeta.get(property)?.line ?? line);
|
|
153
|
-
for (const child of object.
|
|
153
|
+
for (const child of object.children)
|
|
154
154
|
embed(ctx, child, file);
|
|
155
155
|
};
|
|
156
156
|
/* parse an unordered list of key-values and/or concise-format objects */
|
|
@@ -229,7 +229,7 @@ const parseConcise = (ctx, item, parent, file, line, group) => {
|
|
|
229
229
|
id: anchor ?? slugify(name),
|
|
230
230
|
name,
|
|
231
231
|
properties: [],
|
|
232
|
-
|
|
232
|
+
children: []
|
|
233
233
|
};
|
|
234
234
|
if (anchor !== undefined)
|
|
235
235
|
object.anchor = anchor;
|
|
@@ -267,7 +267,7 @@ const parseConcise = (ctx, item, parent, file, line, group) => {
|
|
|
267
267
|
ctx.diagnose(file, subLine, `nested ordered list below ${kind} "${name}" ignored`);
|
|
268
268
|
subLine += (sub.raw.match(/\n/g) ?? []).length;
|
|
269
269
|
}
|
|
270
|
-
parent.
|
|
270
|
+
parent.children.push(object);
|
|
271
271
|
};
|
|
272
272
|
/* parse a heading token into either a grouping container (e.g. "### STATE",
|
|
273
273
|
collecting objects of its kind below the parent object) or a new object
|
|
@@ -298,7 +298,7 @@ const parseHeading = (ctx, token, state, stamps, file, line) => {
|
|
|
298
298
|
id: heading.id ?? (depth === 1 ? heading.paren : undefined) ?? slugify(heading.name),
|
|
299
299
|
name: heading.name,
|
|
300
300
|
properties: [],
|
|
301
|
-
|
|
301
|
+
children: []
|
|
302
302
|
};
|
|
303
303
|
if (heading.id !== undefined)
|
|
304
304
|
object.anchor = heading.id;
|
|
@@ -310,7 +310,7 @@ const parseHeading = (ctx, token, state, stamps, file, line) => {
|
|
|
310
310
|
if (parent === undefined)
|
|
311
311
|
state.artifacts.push({ ...stamps, objects: [object] });
|
|
312
312
|
else
|
|
313
|
-
parent.
|
|
313
|
+
parent.children.push(object);
|
|
314
314
|
state.stack.length = depth - 1;
|
|
315
315
|
state.stack[depth - 1] = object;
|
|
316
316
|
state.current = object;
|
package/dst/specbook-parse.js
CHANGED
|
@@ -51,7 +51,7 @@ export const specStatistics = (specification) => {
|
|
|
51
51
|
texts.push(object.description.description, object.description.rationale ?? "");
|
|
52
52
|
for (const text of texts)
|
|
53
53
|
links += (plainText(text).match(referenceRegex) ?? []).length;
|
|
54
|
-
object.
|
|
54
|
+
object.children.forEach(walk);
|
|
55
55
|
};
|
|
56
56
|
for (const artifact of specification.artifacts)
|
|
57
57
|
artifact.objects.forEach(walk);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rse/specbook",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"publishConfig": { "access": "public" },
|
|
5
5
|
"description": "Markdown-based Specification Format (API, CLI and MCP service)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"source-code-error": "1.2.0",
|
|
37
37
|
"commander": "15.0.0",
|
|
38
38
|
"chalk": "6.0.0",
|
|
39
|
-
"
|
|
39
|
+
"chokidar5": "npm:chokidar@5.0.0",
|
|
40
40
|
"glob": "13.0.6",
|
|
41
41
|
"es-toolkit": "1.52.0",
|
|
42
42
|
"fastify": "5.12.3",
|