@memoarchitect/tools 0.7.2 → 0.8.7
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 +1 -1
- package/corpus/README.md +63 -0
- package/corpus/sysml-v2-release/bnf/SysML-graphical-bnf.kgbnf +2264 -0
- package/package.json +4 -3
- package/packages/tools/lib/analysis/dsm.d.ts +1 -1
- package/packages/tools/lib/analysis/dsm.d.ts.map +1 -1
- package/packages/tools/lib/analysis/dsm.js +1 -1
- package/packages/tools/lib/analysis/dsm.js.map +1 -1
- package/packages/tools/lib/bin/memo.js +25 -0
- package/packages/tools/lib/bin/memo.js.map +1 -1
- package/packages/tools/lib/browser/index.d.ts +2 -0
- package/packages/tools/lib/browser/index.d.ts.map +1 -1
- package/packages/tools/lib/browser/index.js +1 -0
- package/packages/tools/lib/browser/index.js.map +1 -1
- package/packages/tools/lib/commands/dev.d.ts.map +1 -1
- package/packages/tools/lib/commands/dev.js +37 -15
- package/packages/tools/lib/commands/dev.js.map +1 -1
- package/packages/tools/lib/commands/dhf-lint.d.ts +10 -0
- package/packages/tools/lib/commands/dhf-lint.d.ts.map +1 -0
- package/packages/tools/lib/commands/dhf-lint.js +72 -0
- package/packages/tools/lib/commands/dhf-lint.js.map +1 -0
- package/packages/tools/lib/commands/dhf.d.ts.map +1 -1
- package/packages/tools/lib/commands/dhf.js +7 -1
- package/packages/tools/lib/commands/dhf.js.map +1 -1
- package/packages/tools/lib/commands/standards.d.ts +28 -0
- package/packages/tools/lib/commands/standards.d.ts.map +1 -0
- package/packages/tools/lib/commands/standards.js +203 -0
- package/packages/tools/lib/commands/standards.js.map +1 -0
- package/packages/tools/lib/commands/validate.d.ts.map +1 -1
- package/packages/tools/lib/commands/validate.js +13 -2
- package/packages/tools/lib/commands/validate.js.map +1 -1
- package/packages/tools/lib/dhf/document-compiler.d.ts.map +1 -1
- package/packages/tools/lib/dhf/document-compiler.js +13 -8
- package/packages/tools/lib/dhf/document-compiler.js.map +1 -1
- package/packages/tools/lib/dhf/document-registry.d.ts +16 -2
- package/packages/tools/lib/dhf/document-registry.d.ts.map +1 -1
- package/packages/tools/lib/dhf/document-registry.js +74 -23
- package/packages/tools/lib/dhf/document-registry.js.map +1 -1
- package/packages/tools/lib/dhf/index.d.ts +3 -0
- package/packages/tools/lib/dhf/index.d.ts.map +1 -1
- package/packages/tools/lib/dhf/index.js +4 -0
- package/packages/tools/lib/dhf/index.js.map +1 -1
- package/packages/tools/lib/dhf/query-engine.d.ts +12 -1
- package/packages/tools/lib/dhf/query-engine.d.ts.map +1 -1
- package/packages/tools/lib/dhf/query-engine.js +2 -1
- package/packages/tools/lib/dhf/query-engine.js.map +1 -1
- package/packages/tools/lib/dhf/query-executor.d.ts +72 -5
- package/packages/tools/lib/dhf/query-executor.d.ts.map +1 -1
- package/packages/tools/lib/dhf/query-executor.js +471 -81
- package/packages/tools/lib/dhf/query-executor.js.map +1 -1
- package/packages/tools/lib/dhf/script-runner.js +6 -6
- package/packages/tools/lib/dhf/script-runner.js.map +1 -1
- package/packages/tools/lib/dhf/standards-block.d.ts +40 -0
- package/packages/tools/lib/dhf/standards-block.d.ts.map +1 -0
- package/packages/tools/lib/dhf/standards-block.js +223 -0
- package/packages/tools/lib/dhf/standards-block.js.map +1 -0
- package/packages/tools/lib/dhf/standards-library.d.ts +123 -0
- package/packages/tools/lib/dhf/standards-library.d.ts.map +1 -0
- package/packages/tools/lib/dhf/standards-library.js +295 -0
- package/packages/tools/lib/dhf/standards-library.js.map +1 -0
- package/packages/tools/lib/dhf/standards-report.d.ts +189 -0
- package/packages/tools/lib/dhf/standards-report.d.ts.map +1 -0
- package/packages/tools/lib/dhf/standards-report.js +384 -0
- package/packages/tools/lib/dhf/standards-report.js.map +1 -0
- package/packages/tools/lib/dhf/template-lint.d.ts +42 -0
- package/packages/tools/lib/dhf/template-lint.d.ts.map +1 -0
- package/packages/tools/lib/dhf/template-lint.js +421 -0
- package/packages/tools/lib/dhf/template-lint.js.map +1 -0
- package/packages/tools/lib/dhf/template-resolver.d.ts.map +1 -1
- package/packages/tools/lib/dhf/template-resolver.js +17 -5
- package/packages/tools/lib/dhf/template-resolver.js.map +1 -1
- package/packages/tools/lib/importer/cameo-importer.js +2 -2
- package/packages/tools/lib/importer/cameo-importer.js.map +1 -1
- package/packages/tools/lib/importer/ea-importer.d.ts.map +1 -1
- package/packages/tools/lib/importer/ea-importer.js +4 -3
- package/packages/tools/lib/importer/ea-importer.js.map +1 -1
- package/packages/tools/lib/importer/owl-importer.d.ts.map +1 -1
- package/packages/tools/lib/importer/owl-importer.js +2 -3
- package/packages/tools/lib/importer/owl-importer.js.map +1 -1
- package/packages/tools/lib/language/generated/ast.d.ts +1155 -55
- package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +922 -9
- package/packages/tools/lib/language/generated/ast.js.map +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/grammar.js +3643 -427
- package/packages/tools/lib/language/generated/grammar.js.map +1 -1
- package/packages/tools/lib/model/builder.d.ts.map +1 -1
- package/packages/tools/lib/model/builder.js +725 -43
- package/packages/tools/lib/model/builder.js.map +1 -1
- package/packages/tools/lib/model/kind-registry.d.ts +20 -0
- package/packages/tools/lib/model/kind-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/kind-registry.js +38 -0
- package/packages/tools/lib/model/kind-registry.js.map +1 -1
- package/packages/tools/lib/model/manifest.d.ts +8 -0
- package/packages/tools/lib/model/manifest.d.ts.map +1 -1
- package/packages/tools/lib/model/manifest.js +3 -0
- package/packages/tools/lib/model/manifest.js.map +1 -1
- package/packages/tools/lib/model/naming.d.ts +57 -0
- package/packages/tools/lib/model/naming.d.ts.map +1 -0
- package/packages/tools/lib/model/naming.js +69 -0
- package/packages/tools/lib/model/naming.js.map +1 -0
- package/packages/tools/lib/model/native-project.d.ts.map +1 -1
- package/packages/tools/lib/model/native-project.js +10 -0
- package/packages/tools/lib/model/native-project.js.map +1 -1
- package/packages/tools/lib/model/ontology-loader.d.ts.map +1 -1
- package/packages/tools/lib/model/ontology-loader.js +3 -0
- package/packages/tools/lib/model/ontology-loader.js.map +1 -1
- package/packages/tools/lib/model/relationship-legality.d.ts +17 -0
- package/packages/tools/lib/model/relationship-legality.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-legality.js.map +1 -1
- package/packages/tools/lib/model/relationship-registry.d.ts +77 -0
- package/packages/tools/lib/model/relationship-registry.d.ts.map +1 -1
- package/packages/tools/lib/model/relationship-registry.js +217 -2
- package/packages/tools/lib/model/relationship-registry.js.map +1 -1
- package/packages/tools/lib/model/semantic.d.ts +46 -0
- package/packages/tools/lib/model/semantic.d.ts.map +1 -1
- package/packages/tools/lib/model/semantic.js +3 -0
- package/packages/tools/lib/model/semantic.js.map +1 -1
- package/packages/tools/lib/model/short-id.d.ts.map +1 -1
- package/packages/tools/lib/model/short-id.js +1 -3
- package/packages/tools/lib/model/short-id.js.map +1 -1
- package/packages/tools/lib/model/view-deriver.d.ts +10 -1
- package/packages/tools/lib/model/view-deriver.d.ts.map +1 -1
- package/packages/tools/lib/model/view-deriver.js +103 -5
- package/packages/tools/lib/model/view-deriver.js.map +1 -1
- package/packages/tools/lib/protocol/messages.d.ts +85 -3
- package/packages/tools/lib/protocol/messages.d.ts.map +1 -1
- package/packages/tools/lib/server/dev-server.d.ts.map +1 -1
- package/packages/tools/lib/server/dev-server.js +158 -5
- package/packages/tools/lib/server/dev-server.js.map +1 -1
- package/packages/tools/lib/server/package-source.d.ts +26 -0
- package/packages/tools/lib/server/package-source.d.ts.map +1 -0
- package/packages/tools/lib/server/package-source.js +81 -0
- package/packages/tools/lib/server/package-source.js.map +1 -0
- package/packages/tools/lib/server/package-writer.d.ts +94 -0
- package/packages/tools/lib/server/package-writer.d.ts.map +1 -0
- package/packages/tools/lib/server/package-writer.js +317 -0
- package/packages/tools/lib/server/package-writer.js.map +1 -0
- package/packages/tools/lib/server/persistor.d.ts +20 -0
- package/packages/tools/lib/server/persistor.d.ts.map +1 -1
- package/packages/tools/lib/server/persistor.js +29 -8
- package/packages/tools/lib/server/persistor.js.map +1 -1
- package/packages/tools/lib/server/relationship-writer.d.ts +13 -1
- package/packages/tools/lib/server/relationship-writer.d.ts.map +1 -1
- package/packages/tools/lib/server/relationship-writer.js +59 -0
- package/packages/tools/lib/server/relationship-writer.js.map +1 -1
- package/packages/tools/lib/server/sysml-notation.d.ts +1 -1
- package/packages/tools/lib/server/sysml-notation.d.ts.map +1 -1
- package/packages/tools/lib/server/sysml-notation.js +17 -0
- package/packages/tools/lib/server/sysml-notation.js.map +1 -1
- package/packages/tools/lib/server/view-layout-store.d.ts.map +1 -1
- package/packages/tools/lib/server/view-layout-store.js +1 -0
- package/packages/tools/lib/server/view-layout-store.js.map +1 -1
- package/packages/tools/lib/validator/behavior-validator.d.ts +1 -1
- package/packages/tools/lib/validator/behavior-validator.js +1 -1
- package/packages/tools/lib/validator/constraint-eval.d.ts +16 -0
- package/packages/tools/lib/validator/constraint-eval.d.ts.map +1 -1
- package/packages/tools/lib/validator/constraint-eval.js +102 -0
- package/packages/tools/lib/validator/constraint-eval.js.map +1 -1
- package/packages/tools/src/grammar/memo-sysml.langium +415 -25
|
@@ -14,10 +14,22 @@
|
|
|
14
14
|
// group_by: layer
|
|
15
15
|
// ```
|
|
16
16
|
//
|
|
17
|
+
// A query selects elements by default. `select: relationships` selects links
|
|
18
|
+
// instead — the shape every clause-traceability and allocation table has, where
|
|
19
|
+
// a row is the edge and the columns come from both of its ends:
|
|
20
|
+
//
|
|
21
|
+
// ```memo-query
|
|
22
|
+
// select: relationships
|
|
23
|
+
// kind: conformsTo
|
|
24
|
+
// where: target.package == "memo_artifacts_standards_iec_62304"
|
|
25
|
+
// columns: source, target.clauseNumber, target.title
|
|
26
|
+
// ```
|
|
27
|
+
//
|
|
17
28
|
// The block is replaced with rendered markdown (table / list / matrix / grouped).
|
|
18
29
|
// Reuses QueryContext from query-engine.ts so all model access is consistent.
|
|
19
30
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
31
|
import * as yaml from 'yaml';
|
|
32
|
+
import { toModelType } from '../model/naming.js';
|
|
21
33
|
// ─── Parse a memo-query block ─────────────────────────────────────────────────
|
|
22
34
|
export function parseMemoQuery(blockContent) {
|
|
23
35
|
try {
|
|
@@ -30,59 +42,349 @@ export function parseMemoQuery(blockContent) {
|
|
|
30
42
|
return null;
|
|
31
43
|
}
|
|
32
44
|
}
|
|
33
|
-
// ───
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
// ─── Strict validation ────────────────────────────────────────────────────────
|
|
46
|
+
//
|
|
47
|
+
// A query directive this engine does not understand must never be ignored.
|
|
48
|
+
// `where:` used to fall through to `return elements` when no pattern matched,
|
|
49
|
+
// so a filter the parser could not read produced the FULL, unfiltered set —
|
|
50
|
+
// a compliance table that looks authoritative and is not. Everything below
|
|
51
|
+
// exists to turn that class of silence into an error.
|
|
52
|
+
//
|
|
53
|
+
// Validation is deliberately separate from execution so `memo dhf lint` can
|
|
54
|
+
// check a template without loading a model.
|
|
55
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
/** A query directive that cannot be executed as written. */
|
|
57
|
+
export class MemoQueryError extends Error {
|
|
58
|
+
source;
|
|
59
|
+
constructor(message, source) {
|
|
60
|
+
super(source ? `${source}: ${message}` : message);
|
|
61
|
+
this.source = source;
|
|
62
|
+
this.name = 'MemoQueryError';
|
|
40
63
|
}
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
}
|
|
65
|
+
const KNOWN_KEYS = new Set([
|
|
66
|
+
'select', 'kind', 'where', 'traverse', 'display', 'columns', 'sort', 'empty',
|
|
67
|
+
'group_by', 'limit', 'row_kind', 'col_kind', 'label', 'value',
|
|
68
|
+
]);
|
|
69
|
+
const KNOWN_DISPLAYS = new Set(['table', 'list', 'matrix', 'grouped', 'count', 'metric']);
|
|
70
|
+
const KNOWN_SELECTS = new Set(['elements', 'relationships']);
|
|
71
|
+
/** True when this spec's rows are relationships rather than elements. */
|
|
72
|
+
export function selectsRelationships(spec) {
|
|
73
|
+
return spec.select === 'relationships';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fields a relationship row answers without consulting its attribute map.
|
|
77
|
+
*
|
|
78
|
+
* `source` and `target` are the endpoint *names*, which is what a table column
|
|
79
|
+
* wants; `source.<field>` / `target.<field>` reach the endpoint element itself.
|
|
80
|
+
*/
|
|
81
|
+
export const RELATIONSHIP_FIELDS = new Set([
|
|
82
|
+
'id', 'type', 'kind', 'source', 'target', 'sourceId', 'targetId',
|
|
83
|
+
'sourceEnd', 'targetEnd', 'file', 'flowItem',
|
|
84
|
+
]);
|
|
85
|
+
/** The `source` / `target` prefix of a dotted path, or undefined if it has none. */
|
|
86
|
+
export function endpointOf(field) {
|
|
87
|
+
const dot = field.indexOf('.');
|
|
88
|
+
if (dot === -1)
|
|
89
|
+
return undefined;
|
|
90
|
+
const head = field.slice(0, dot);
|
|
91
|
+
return head === 'source' || head === 'target' ? head : undefined;
|
|
92
|
+
}
|
|
93
|
+
const WHERE_PATTERNS = [
|
|
94
|
+
{ re: /^([\w.]+)\s*==\s*["']?([^"']*)["']?$/, op: '==' },
|
|
95
|
+
{ re: /^([\w.]+)\s*!=\s*["']?([^"']*)["']?$/, op: '!=' },
|
|
96
|
+
{ re: /^([\w.]+)\s+contains\s+["']?([^"']*)["']?$/i, op: 'contains' },
|
|
97
|
+
];
|
|
98
|
+
/**
|
|
99
|
+
* Parse a `where:` expression, or return null if this engine cannot evaluate it.
|
|
100
|
+
*
|
|
101
|
+
* Only a single comparison is supported. Boolean composition, parenthesised
|
|
102
|
+
* groups and `starts with` are all unsupported — callers must treat null as an
|
|
103
|
+
* error, never as "no filter". A dotted endpoint path (`target.layer`) parses,
|
|
104
|
+
* but means nothing to an element query; `validateQuerySpec` rejects it there.
|
|
105
|
+
*/
|
|
106
|
+
export function parseWhereClause(where) {
|
|
107
|
+
const expr = where.trim();
|
|
108
|
+
for (const { re, op } of WHERE_PATTERNS) {
|
|
109
|
+
const m = expr.match(re);
|
|
110
|
+
if (m)
|
|
111
|
+
return { field: m[1], op, value: m[2] };
|
|
43
112
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
// ─── Enum-qualified values ────────────────────────────────────────────────────
|
|
116
|
+
//
|
|
117
|
+
// `builder.ts` stores an enum attribute as the reference the author wrote, and
|
|
118
|
+
// every enum-typed attribute in the ontology and its exemplars is written
|
|
119
|
+
// qualified — the model holds `"RequirementKind::software"`, never `"software"`.
|
|
120
|
+
// So the qualified form is THE spelling, and a template writes
|
|
121
|
+
// `where: requirementKind == "RequirementKind::software"`.
|
|
122
|
+
//
|
|
123
|
+
// Comparison is exact. The alternative — accepting the bare member name too —
|
|
124
|
+
// buys two spellings for one value, which is the defect this area exists to
|
|
125
|
+
// remove, and it costs the reader the only clue to which enum is meant:
|
|
126
|
+
// `criticality == "high"` and `severity == "high"` are indistinguishable,
|
|
127
|
+
// `CriticalityKind::high` is not.
|
|
128
|
+
//
|
|
129
|
+
// Exact comparison alone would resurrect the silent failure, though: a bare
|
|
130
|
+
// value simply matches nothing and the section renders its `empty:` message.
|
|
131
|
+
// So a bare value that IS a member of the enum actually stored on that field is
|
|
132
|
+
// an error naming the qualified spelling — the filter is not evaluated on a
|
|
133
|
+
// guess. Same rule as everywhere else here: fail, never quietly narrow.
|
|
134
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
/** The member name of an enum reference: `RequirementKind::software` → `software`. */
|
|
136
|
+
export function unqualifyEnum(value) {
|
|
137
|
+
const sep = value.lastIndexOf('::');
|
|
138
|
+
return sep === -1 ? value : value.slice(sep + 2);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The qualified value stored on the compared field whose member name is
|
|
142
|
+
* `queryValue`, if any row has one — i.e. the spelling the author meant to
|
|
143
|
+
* write. Reads through the row's own field resolver so it works the same for a
|
|
144
|
+
* relationship endpoint (`target.requirementKind`) as for an element.
|
|
145
|
+
*/
|
|
146
|
+
function qualifiedSpellingOf(rows, read, queryValue) {
|
|
147
|
+
if (queryValue.includes('::'))
|
|
148
|
+
return undefined;
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
const stored = String(read(row) ?? '');
|
|
151
|
+
if (stored.includes('::') && unqualifyEnum(stored) === queryValue)
|
|
152
|
+
return stored;
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
/** Explain why a `where:` expression is unsupported, for an actionable message. */
|
|
157
|
+
function diagnoseWhere(where) {
|
|
158
|
+
const expr = where.trim();
|
|
159
|
+
if (/\b(and|or)\b/i.test(expr)) {
|
|
160
|
+
return 'boolean composition (`and` / `or`) is not supported — use a single comparison, or narrow with `kind:`';
|
|
47
161
|
}
|
|
162
|
+
if (/\bstarts with\b|\bends with\b|\bmatches\b/i.test(expr)) {
|
|
163
|
+
return 'only `==`, `!=` and `contains` are supported';
|
|
164
|
+
}
|
|
165
|
+
return 'expected `field == value`, `field != value`, or `field contains value`';
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Why a dotted path cannot be used here, or undefined when it can.
|
|
169
|
+
*
|
|
170
|
+
* Dotted paths exist for relationship rows, which have no useful flat fields of
|
|
171
|
+
* their own: everything a compliance table wants to show lives on one of the two
|
|
172
|
+
* ends. An element row has no ends, so a dotted path there is a query that will
|
|
173
|
+
* render "—" in every cell — the silence this engine exists to turn into errors.
|
|
174
|
+
*/
|
|
175
|
+
function diagnoseDottedPath(path, relationships) {
|
|
176
|
+
if (!path.includes('.'))
|
|
177
|
+
return undefined;
|
|
178
|
+
if (!relationships) {
|
|
179
|
+
return 'a dotted path names a relationship endpoint, and this query selects elements — add `select: relationships`';
|
|
180
|
+
}
|
|
181
|
+
if (!endpointOf(path)) {
|
|
182
|
+
return 'a dotted path must start with `source.` or `target.`';
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Validate a query spec against what the engine can execute.
|
|
188
|
+
* Returns a list of human-readable problems; empty means executable.
|
|
189
|
+
*/
|
|
190
|
+
export function validateQuerySpec(spec) {
|
|
191
|
+
const problems = [];
|
|
192
|
+
const relationships = selectsRelationships(spec);
|
|
193
|
+
for (const key of Object.keys(spec)) {
|
|
194
|
+
if (!KNOWN_KEYS.has(key)) {
|
|
195
|
+
problems.push(`unknown directive \`${key}:\` (known: ${[...KNOWN_KEYS].sort().join(', ')})`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (spec.select !== undefined && !KNOWN_SELECTS.has(spec.select)) {
|
|
199
|
+
problems.push(`unknown \`select: ${spec.select}\` (known: ${[...KNOWN_SELECTS].sort().join(', ')})`);
|
|
200
|
+
}
|
|
201
|
+
// A relationship type is stored lower-camel (`AllocatedTo` → `allocatedTo`)
|
|
202
|
+
// and matched exactly, so the PascalCase name of the connection def selects
|
|
203
|
+
// nothing at all. Same rule, and the same message, as `traverse:`.
|
|
204
|
+
if (relationships) {
|
|
205
|
+
for (const type of kindList(spec)) {
|
|
206
|
+
if (/^[A-Z]/.test(type)) {
|
|
207
|
+
problems.push(`\`kind: ${type}\` names a relationship type in PascalCase — use \`${pascalToCamel(type)}\``);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (spec.where !== undefined) {
|
|
212
|
+
if (typeof spec.where !== 'string') {
|
|
213
|
+
problems.push('`where:` must be a string');
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const clause = parseWhereClause(spec.where);
|
|
217
|
+
if (!clause) {
|
|
218
|
+
problems.push(`cannot evaluate \`where: ${spec.where}\` — ${diagnoseWhere(spec.where)}`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const why = diagnoseDottedPath(clause.field, relationships);
|
|
222
|
+
if (why)
|
|
223
|
+
problems.push(`cannot evaluate \`where: ${spec.where}\` — ${why}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (spec.sort !== undefined) {
|
|
228
|
+
if (typeof spec.sort !== 'string') {
|
|
229
|
+
problems.push('`sort:` must be a string');
|
|
230
|
+
}
|
|
231
|
+
else if (/\s/.test(spec.sort.trim())) {
|
|
232
|
+
problems.push(`cannot evaluate \`sort: ${spec.sort}\` — only a bare field name is supported (no \`asc\`/\`desc\`)`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const why = diagnoseDottedPath(spec.sort.trim(), relationships);
|
|
236
|
+
if (why)
|
|
237
|
+
problems.push(`cannot evaluate \`sort: ${spec.sort}\` — ${why}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const col of resolveColumns(spec)) {
|
|
241
|
+
if (/\s+as\s+/i.test(col)) {
|
|
242
|
+
problems.push(`column \`${col}\` uses an alias — \`as\` is not supported`);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const why = diagnoseDottedPath(col, relationships);
|
|
246
|
+
if (why)
|
|
247
|
+
problems.push(`column \`${col}\` — ${why}`);
|
|
248
|
+
}
|
|
249
|
+
if (spec.group_by !== undefined) {
|
|
250
|
+
const why = diagnoseDottedPath(String(spec.group_by), relationships);
|
|
251
|
+
if (why)
|
|
252
|
+
problems.push(`\`group_by: ${spec.group_by}\` — ${why}`);
|
|
253
|
+
}
|
|
254
|
+
if (spec.display !== undefined && !KNOWN_DISPLAYS.has(spec.display)) {
|
|
255
|
+
problems.push(`unknown \`display: ${spec.display}\` (known: ${[...KNOWN_DISPLAYS].sort().join(', ')})`);
|
|
256
|
+
}
|
|
257
|
+
if (spec.traverse !== undefined) {
|
|
258
|
+
const parts = String(spec.traverse).trim().split(/\s+/);
|
|
259
|
+
if (relationships) {
|
|
260
|
+
// Traversal walks *from* an element along an edge. The rows here are
|
|
261
|
+
// already the edges, so there is nothing to walk from — rather than
|
|
262
|
+
// pick an end silently, say so.
|
|
263
|
+
problems.push('`traverse:` selects elements reached from a seed, so it cannot be combined with `select: relationships`');
|
|
264
|
+
}
|
|
265
|
+
else if (parts.length !== 2 || !['outgoing', 'incoming'].includes(parts[0])) {
|
|
266
|
+
problems.push(`cannot evaluate \`traverse: ${spec.traverse}\` — expected \`outgoing <relType>\` or \`incoming <relType>\``);
|
|
267
|
+
}
|
|
268
|
+
else if (parts[1] !== '*' && /^[A-Z]/.test(parts[1])) {
|
|
269
|
+
problems.push(`\`traverse: ${spec.traverse}\` names a relationship type in PascalCase — use \`${pascalToCamel(parts[1])}\``);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return problems;
|
|
273
|
+
}
|
|
274
|
+
/** `kind:` as a list, whichever of the two spellings the block used. */
|
|
275
|
+
export function kindList(spec) {
|
|
276
|
+
if (spec.kind === undefined)
|
|
277
|
+
return [];
|
|
278
|
+
return Array.isArray(spec.kind) ? spec.kind : [spec.kind];
|
|
279
|
+
}
|
|
280
|
+
const pascalToCamel = toModelType;
|
|
281
|
+
// ─── Commented-out blocks ─────────────────────────────────────────────────────
|
|
282
|
+
//
|
|
283
|
+
// A template may park a query it cannot run yet inside an HTML comment, with a
|
|
284
|
+
// visible TODO beside it — the honest way to ship a section whose query shape
|
|
285
|
+
// the engine does not support yet. A commented block renders nothing, so it
|
|
286
|
+
// must neither be executed nor linted; otherwise "park it until the engine
|
|
287
|
+
// catches up" is not available and the alternative is a table that renders
|
|
288
|
+
// empty and reads like a clean audit.
|
|
289
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
290
|
+
const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
291
|
+
/** Character ranges of the HTML comments in `content`. */
|
|
292
|
+
export function htmlCommentRanges(content) {
|
|
293
|
+
const ranges = [];
|
|
294
|
+
for (const m of content.matchAll(HTML_COMMENT_RE)) {
|
|
295
|
+
ranges.push([m.index, m.index + m[0].length]);
|
|
296
|
+
}
|
|
297
|
+
return ranges;
|
|
298
|
+
}
|
|
299
|
+
export function isCommentedOut(ranges, offset) {
|
|
300
|
+
return ranges.some(([start, end]) => offset >= start && offset < end);
|
|
301
|
+
}
|
|
302
|
+
/** Throw if the spec cannot be executed as written. */
|
|
303
|
+
export function assertExecutable(spec, source) {
|
|
304
|
+
const problems = validateQuerySpec(spec);
|
|
305
|
+
if (problems.length > 0) {
|
|
306
|
+
throw new MemoQueryError(problems.join('; '), source);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// ─── Execute a query against the model context ────────────────────────────────
|
|
310
|
+
export function executeQuery(spec, ctx, source) {
|
|
311
|
+
assertExecutable(spec, source);
|
|
312
|
+
return selectsRelationships(spec)
|
|
313
|
+
? executeRelationshipQuery(spec, ctx)
|
|
314
|
+
: executeElementQuery(spec, ctx);
|
|
315
|
+
}
|
|
316
|
+
function executeElementQuery(spec, ctx) {
|
|
317
|
+
// 1. Select by kind(s)
|
|
318
|
+
let elements = spec.kind ? ctx.elementsByKinds(kindList(spec)) : ctx.allElements();
|
|
319
|
+
// 2. Apply where filter
|
|
320
|
+
if (spec.where)
|
|
321
|
+
elements = applyFilter(elements, spec.where, ctx);
|
|
48
322
|
// 3. Traverse relationships
|
|
49
323
|
if (spec.traverse && elements.length > 0) {
|
|
50
324
|
elements = applyTraverse(elements, spec.traverse, ctx);
|
|
51
325
|
}
|
|
52
326
|
// 4. Sort
|
|
53
|
-
if (spec.sort)
|
|
54
|
-
|
|
55
|
-
elements = [...elements].sort((a, b) => {
|
|
56
|
-
const av = getField(a, field) ?? '';
|
|
57
|
-
const bv = getField(b, field) ?? '';
|
|
58
|
-
return String(av).localeCompare(String(bv));
|
|
59
|
-
});
|
|
60
|
-
}
|
|
327
|
+
if (spec.sort)
|
|
328
|
+
elements = sortRows(elements, spec.sort, el => getField(el, spec.sort));
|
|
61
329
|
// 5. Limit
|
|
62
|
-
if (spec.limit && spec.limit > 0)
|
|
330
|
+
if (spec.limit && spec.limit > 0)
|
|
63
331
|
elements = elements.slice(0, spec.limit);
|
|
64
|
-
}
|
|
65
332
|
return elements;
|
|
66
333
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Rows are the links themselves.
|
|
336
|
+
*
|
|
337
|
+
* `kind:` names relationship types here, resolved through the same lower-camel
|
|
338
|
+
* index the builder writes (`AllocatedTo` → `allocatedTo`) — the PascalCase
|
|
339
|
+
* spelling is rejected in validation rather than silently matching nothing.
|
|
340
|
+
*/
|
|
341
|
+
function executeRelationshipQuery(spec, ctx) {
|
|
342
|
+
let rels = spec.kind
|
|
343
|
+
? kindList(spec).flatMap(type => ctx.relationshipsByType(type))
|
|
344
|
+
: ctx.allRelationships();
|
|
345
|
+
if (spec.where)
|
|
346
|
+
rels = applyFilter(rels, spec.where, ctx);
|
|
347
|
+
if (spec.sort)
|
|
348
|
+
rels = sortRows(rels, spec.sort, rel => getRelField(rel, spec.sort, ctx));
|
|
349
|
+
if (spec.limit && spec.limit > 0)
|
|
350
|
+
rels = rels.slice(0, spec.limit);
|
|
351
|
+
return rels;
|
|
352
|
+
}
|
|
353
|
+
function sortRows(rows, _field, read) {
|
|
354
|
+
// Numeric-aware, because the fields documents sort on are numbered: clause
|
|
355
|
+
// "14" belongs after "9", and "REQ-CS-2" before "REQ-CS-10". Plain
|
|
356
|
+
// lexicographic ordering put clause 14 at the top of a conformance matrix,
|
|
357
|
+
// which reads as a broken table long before anyone works out why.
|
|
358
|
+
return [...rows].sort((a, b) => String(read(a) ?? '').localeCompare(String(read(b) ?? ''), undefined, { numeric: true }));
|
|
359
|
+
}
|
|
360
|
+
function applyFilter(rows, where, ctx) {
|
|
361
|
+
const clause = parseWhereClause(where);
|
|
362
|
+
// Never fall through to the unfiltered set: a filter the engine cannot read
|
|
363
|
+
// must fail, not quietly widen the result to everything.
|
|
364
|
+
if (!clause)
|
|
365
|
+
throw new MemoQueryError(`cannot evaluate \`where: ${where}\` — ${diagnoseWhere(where)}`);
|
|
366
|
+
const { field, op, value } = clause;
|
|
367
|
+
const read = (row) => rowField(row, field, ctx);
|
|
368
|
+
// A bare enum member never matches, because the model stores the qualified
|
|
369
|
+
// reference. Say so instead of returning an empty — or, for `!=`, a
|
|
370
|
+
// complete — set that reads like a real answer.
|
|
371
|
+
if (op === '==' || op === '!=') {
|
|
372
|
+
const qualified = qualifiedSpellingOf(rows, read, value);
|
|
373
|
+
if (qualified) {
|
|
374
|
+
throw new MemoQueryError(`\`${field} ${op} "${value}"\` names an enum member without its enum, and the model stores `
|
|
375
|
+
+ `\`${qualified}\` — write \`${field} ${op} "${qualified}"\``);
|
|
376
|
+
}
|
|
79
377
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
378
|
+
switch (op) {
|
|
379
|
+
case '==':
|
|
380
|
+
return rows.filter(row => String(read(row) ?? '') === value);
|
|
381
|
+
case '!=':
|
|
382
|
+
return rows.filter(row => String(read(row) ?? '') !== value);
|
|
383
|
+
case 'contains': {
|
|
384
|
+
const lv = value.toLowerCase();
|
|
385
|
+
return rows.filter(row => String(read(row) ?? '').toLowerCase().includes(lv));
|
|
386
|
+
}
|
|
84
387
|
}
|
|
85
|
-
return elements;
|
|
86
388
|
}
|
|
87
389
|
function applyTraverse(seeds, traverse, ctx) {
|
|
88
390
|
// "outgoing mitigates" | "incoming derivedFrom" | "outgoing *"
|
|
@@ -116,6 +418,13 @@ function getField(el, field) {
|
|
|
116
418
|
return el.kind;
|
|
117
419
|
if (field === 'layer')
|
|
118
420
|
return el.layer;
|
|
421
|
+
// The declaring package. Structural, one per source file, and the fact that
|
|
422
|
+
// says which standard a clause belongs to — the standards library is one
|
|
423
|
+
// package per standard, so `target.package == "…_iec_62304"` is the exact
|
|
424
|
+
// per-standard selector a clause-conformance table needs. Clause *numbers*
|
|
425
|
+
// are not: "5" is a clause of seven different standards in this library.
|
|
426
|
+
if (field === 'package')
|
|
427
|
+
return el.package || '';
|
|
119
428
|
if (field === 'doc' || field === 'description')
|
|
120
429
|
return el.doc || '';
|
|
121
430
|
// Try attributes map
|
|
@@ -123,20 +432,61 @@ function getField(el, field) {
|
|
|
123
432
|
return el.attributes[field];
|
|
124
433
|
return undefined;
|
|
125
434
|
}
|
|
435
|
+
/** True when a row is a relationship rather than an element. */
|
|
436
|
+
function isRelationshipRow(row) {
|
|
437
|
+
return 'sourceId' in row;
|
|
438
|
+
}
|
|
439
|
+
/** Read `field` off a row, whichever kind of row it is. */
|
|
440
|
+
function rowField(row, field, ctx) {
|
|
441
|
+
return isRelationshipRow(row) ? getRelField(row, field, ctx) : getField(row, field);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Read a field off a relationship.
|
|
445
|
+
*
|
|
446
|
+
* Bare `source` / `target` are the endpoint names, which is what a table cell
|
|
447
|
+
* wants. `source.<field>` / `target.<field>` resolve the endpoint element and
|
|
448
|
+
* read the field off it — the reason relationship rows need dotted paths at
|
|
449
|
+
* all, since a link carries almost nothing on itself.
|
|
450
|
+
*/
|
|
451
|
+
function getRelField(rel, field, ctx) {
|
|
452
|
+
const endpoint = endpointOf(field);
|
|
453
|
+
if (endpoint) {
|
|
454
|
+
const el = ctx.element(endpoint === 'source' ? rel.sourceId : rel.targetId);
|
|
455
|
+
return el ? getField(el, field.slice(field.indexOf('.') + 1)) : undefined;
|
|
456
|
+
}
|
|
457
|
+
switch (field) {
|
|
458
|
+
case 'source': return ctx.elementName(rel.sourceId);
|
|
459
|
+
case 'target': return ctx.elementName(rel.targetId);
|
|
460
|
+
case 'id': return rel.id;
|
|
461
|
+
// `kind` is the element word for "what is this"; a template that writes
|
|
462
|
+
// it against a link means the link's type, and answering is kinder than
|
|
463
|
+
// rendering a dash.
|
|
464
|
+
case 'kind':
|
|
465
|
+
case 'type': return rel.type;
|
|
466
|
+
case 'sourceId': return rel.sourceId;
|
|
467
|
+
case 'targetId': return rel.targetId;
|
|
468
|
+
case 'sourceEnd': return rel.sourceEnd;
|
|
469
|
+
case 'targetEnd': return rel.targetEnd;
|
|
470
|
+
case 'file': return rel.file;
|
|
471
|
+
case 'flowItem': return rel.flowItem;
|
|
472
|
+
default: return rel.attributes?.[field];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
126
475
|
// ─── Render query results as markdown ─────────────────────────────────────────
|
|
127
|
-
export function renderQueryResult(spec,
|
|
128
|
-
if (
|
|
476
|
+
export function renderQueryResult(spec, rows, ctx) {
|
|
477
|
+
if (rows.length === 0) {
|
|
129
478
|
return spec.empty ? `\n_${spec.empty}_\n` : '\n_No results found._\n';
|
|
130
479
|
}
|
|
480
|
+
const read = (row, field) => rowField(row, field, ctx);
|
|
131
481
|
const display = spec.display || 'table';
|
|
132
482
|
switch (display) {
|
|
133
|
-
case 'table': return renderTable(spec,
|
|
134
|
-
case 'list': return renderList(spec,
|
|
135
|
-
case 'grouped': return renderGrouped(spec,
|
|
136
|
-
case 'matrix': return renderMatrix(spec,
|
|
137
|
-
case 'count': return `\n**${
|
|
138
|
-
case 'metric': return renderMetric(spec,
|
|
139
|
-
default: return renderTable(spec,
|
|
483
|
+
case 'table': return renderTable(spec, rows, read);
|
|
484
|
+
case 'list': return renderList(spec, rows, read);
|
|
485
|
+
case 'grouped': return renderGrouped(spec, rows, read);
|
|
486
|
+
case 'matrix': return renderMatrix(spec, rows, read);
|
|
487
|
+
case 'count': return `\n**${rows.length}** ${spec.kind || (selectsRelationships(spec) ? 'relationships' : 'elements')}\n`;
|
|
488
|
+
case 'metric': return renderMetric(spec, rows, read);
|
|
489
|
+
default: return renderTable(spec, rows, read);
|
|
140
490
|
}
|
|
141
491
|
}
|
|
142
492
|
function resolveColumns(spec) {
|
|
@@ -145,78 +495,118 @@ function resolveColumns(spec) {
|
|
|
145
495
|
return spec.columns;
|
|
146
496
|
return spec.columns.split(',').map(c => c.trim());
|
|
147
497
|
}
|
|
148
|
-
|
|
498
|
+
// A relationship row has no name/layer of its own; what it has is two ends.
|
|
499
|
+
return selectsRelationships(spec)
|
|
500
|
+
? ['source', 'type', 'target']
|
|
501
|
+
: ['name', 'kind', 'layer', 'doc'];
|
|
149
502
|
}
|
|
150
|
-
function renderTable(spec,
|
|
503
|
+
function renderTable(spec, rows, read) {
|
|
151
504
|
const cols = resolveColumns(spec);
|
|
152
|
-
const header = '| ' + cols.map(c =>
|
|
505
|
+
const header = '| ' + cols.map(c => columnHeader(c)).join(' | ') + ' |';
|
|
153
506
|
const sep = '| ' + cols.map(() => '---').join(' | ') + ' |';
|
|
154
|
-
const
|
|
507
|
+
const body = rows.map(row => {
|
|
155
508
|
const cells = cols.map(c => {
|
|
156
|
-
const val =
|
|
509
|
+
const val = read(row, c);
|
|
157
510
|
return val ? String(val).replace(/\|/g, '\\|') : '—';
|
|
158
511
|
});
|
|
159
512
|
return '| ' + cells.join(' | ') + ' |';
|
|
160
513
|
});
|
|
161
|
-
return '\n' + [header, sep, ...
|
|
514
|
+
return '\n' + [header, sep, ...body].join('\n') + '\n';
|
|
162
515
|
}
|
|
163
|
-
function renderList(spec,
|
|
164
|
-
|
|
516
|
+
function renderList(spec, rows, read) {
|
|
517
|
+
const line = (row) => selectsRelationships(spec)
|
|
518
|
+
? `- **${read(row, 'source')}** → **${read(row, 'target')}** _(${read(row, 'type')})_`
|
|
519
|
+
: `- **${read(row, 'name')}** _(${read(row, 'kind')})_ — ${read(row, 'doc') || read(row, 'layer') || ''}`;
|
|
520
|
+
return '\n' + rows.map(line).join('\n') + '\n';
|
|
165
521
|
}
|
|
166
|
-
function renderGrouped(spec,
|
|
167
|
-
const groupField = spec.group_by || 'layer';
|
|
522
|
+
function renderGrouped(spec, rows, read) {
|
|
523
|
+
const groupField = spec.group_by || (selectsRelationships(spec) ? 'type' : 'layer');
|
|
168
524
|
const groups = new Map();
|
|
169
|
-
for (const
|
|
170
|
-
const key = String(
|
|
525
|
+
for (const row of rows) {
|
|
526
|
+
const key = String(read(row, groupField) ?? 'Other');
|
|
171
527
|
if (!groups.has(key))
|
|
172
528
|
groups.set(key, []);
|
|
173
|
-
groups.get(key).push(
|
|
529
|
+
groups.get(key).push(row);
|
|
174
530
|
}
|
|
175
531
|
const parts = [];
|
|
176
|
-
for (const [key,
|
|
177
|
-
parts.push(`\n**${key}** (${
|
|
178
|
-
parts.push(renderTable(spec,
|
|
532
|
+
for (const [key, group] of groups) {
|
|
533
|
+
parts.push(`\n**${key}** (${group.length})\n`);
|
|
534
|
+
parts.push(renderTable(spec, group, read));
|
|
179
535
|
}
|
|
180
536
|
return parts.join('');
|
|
181
537
|
}
|
|
182
|
-
function renderMatrix(spec,
|
|
538
|
+
function renderMatrix(spec, rows, read) {
|
|
183
539
|
// Row/col breakdown by two fields
|
|
184
540
|
const rowField = spec.row_kind || 'kind';
|
|
185
541
|
const colField = spec.col_kind || 'layer';
|
|
186
|
-
const rowVals = [...new Set(
|
|
187
|
-
const colVals = [...new Set(
|
|
542
|
+
const rowVals = [...new Set(rows.map(r => String(read(r, rowField) ?? '?')))].sort();
|
|
543
|
+
const colVals = [...new Set(rows.map(r => String(read(r, colField) ?? '?')))].sort();
|
|
188
544
|
const header = '| | ' + colVals.join(' | ') + ' |';
|
|
189
545
|
const sep = '| --- | ' + colVals.map(() => '---').join(' | ') + ' |';
|
|
190
|
-
const
|
|
546
|
+
const body = rowVals.map(rv => {
|
|
191
547
|
const cells = colVals.map(cv => {
|
|
192
|
-
const count =
|
|
193
|
-
String(
|
|
548
|
+
const count = rows.filter(r => String(read(r, rowField) ?? '?') === rv &&
|
|
549
|
+
String(read(r, colField) ?? '?') === cv).length;
|
|
194
550
|
return count > 0 ? String(count) : '—';
|
|
195
551
|
});
|
|
196
552
|
return `| **${rv}** | ${cells.join(' | ')} |`;
|
|
197
553
|
});
|
|
198
|
-
return '\n' + [header, sep, ...
|
|
554
|
+
return '\n' + [header, sep, ...body].join('\n') + '\n';
|
|
199
555
|
}
|
|
200
|
-
function renderMetric(spec,
|
|
556
|
+
function renderMetric(spec, rows, read) {
|
|
201
557
|
const label = spec.label || 'Count';
|
|
202
|
-
const value = spec.value ?
|
|
203
|
-
const v = Number(
|
|
558
|
+
const value = spec.value ? rows.reduce((sum, row) => {
|
|
559
|
+
const v = Number(read(row, spec.value) ?? 0);
|
|
204
560
|
return sum + (isNaN(v) ? 0 : v);
|
|
205
|
-
}, 0) :
|
|
561
|
+
}, 0) : rows.length;
|
|
206
562
|
return `\n**${label}:** ${value}\n`;
|
|
207
563
|
}
|
|
208
564
|
function capitalize(s) {
|
|
209
565
|
return s.charAt(0).toUpperCase() + s.slice(1).replace(/_/g, ' ');
|
|
210
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* Header text for a column. A dotted endpoint path becomes readable words
|
|
569
|
+
* ("target.clauseNumber" → "Target Clause Number"), which is why relationship
|
|
570
|
+
* tables need no column aliases. Flat column headers are left exactly as they
|
|
571
|
+
* were, so no existing document's tables change.
|
|
572
|
+
*/
|
|
573
|
+
function columnHeader(col) {
|
|
574
|
+
if (!col.includes('.'))
|
|
575
|
+
return capitalize(col);
|
|
576
|
+
return col
|
|
577
|
+
.split('.')
|
|
578
|
+
.map(seg => capitalize(seg.replace(/([a-z0-9])([A-Z])/g, '$1 $2')))
|
|
579
|
+
.join(' ');
|
|
580
|
+
}
|
|
211
581
|
// ─── Process all memo-query blocks in a markdown string ───────────────────────
|
|
212
582
|
const QUERY_BLOCK_RE = /```memo-query\n([\s\S]*?)```/g;
|
|
213
|
-
export function processMemoQueryBlocks(content, ctx) {
|
|
214
|
-
|
|
583
|
+
export function processMemoQueryBlocks(content, ctx, options = {}) {
|
|
584
|
+
const { source, onError = 'throw' } = options;
|
|
585
|
+
const commented = htmlCommentRanges(content);
|
|
586
|
+
let blockIndex = 0;
|
|
587
|
+
return content.replace(QUERY_BLOCK_RE, (match, blockContent, offset) => {
|
|
588
|
+
// Leave a parked block exactly as written — it is documentation of a
|
|
589
|
+
// query the engine cannot run yet, not a query to run.
|
|
590
|
+
if (isCommentedOut(commented, offset))
|
|
591
|
+
return match;
|
|
592
|
+
blockIndex++;
|
|
593
|
+
const where = source ? `${source} (memo-query block ${blockIndex})` : `memo-query block ${blockIndex}`;
|
|
215
594
|
const spec = parseMemoQuery(blockContent);
|
|
216
|
-
if (!spec)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
595
|
+
if (!spec) {
|
|
596
|
+
if (onError === 'throw')
|
|
597
|
+
throw new MemoQueryError('block is not valid YAML', where);
|
|
598
|
+
return `\n> ⚠️ **${where}** — block is not valid YAML\n`;
|
|
599
|
+
}
|
|
600
|
+
try {
|
|
601
|
+
const elements = executeQuery(spec, ctx, where);
|
|
602
|
+
return renderQueryResult(spec, elements, ctx);
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
if (onError === 'throw')
|
|
606
|
+
throw error;
|
|
607
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
608
|
+
return `\n> ⚠️ **${message}**\n`;
|
|
609
|
+
}
|
|
220
610
|
});
|
|
221
611
|
}
|
|
222
612
|
//# sourceMappingURL=query-executor.js.map
|