@metaobjectsdev/metadata 1.0.2 → 1.0.4-rc.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/dist/core/parser-yaml.d.ts.map +1 -1
- package/dist/core/parser-yaml.js +4 -0
- package/dist/core/parser-yaml.js.map +1 -1
- package/dist/core/relationship/derive-m2m-fields.d.ts +3 -1
- package/dist/core/relationship/derive-m2m-fields.d.ts.map +1 -1
- package/dist/core/relationship/derive-m2m-fields.js +110 -15
- package/dist/core/relationship/derive-m2m-fields.js.map +1 -1
- package/dist/core/relationship/find-reference.d.ts +21 -5
- package/dist/core/relationship/find-reference.d.ts.map +1 -1
- package/dist/core/relationship/find-reference.js +29 -10
- package/dist/core/relationship/find-reference.js.map +1 -1
- package/dist/core/relationship/relationship-definition.embedded.js +8 -8
- package/dist/core/relationship/relationship-definition.embedded.js.map +1 -1
- package/dist/core/relationship/resolve-relationship-reference.d.ts +27 -0
- package/dist/core/relationship/resolve-relationship-reference.d.ts.map +1 -0
- package/dist/core/relationship/resolve-relationship-reference.js +84 -0
- package/dist/core/relationship/resolve-relationship-reference.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +31 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/json-path.d.ts +14 -0
- package/dist/json-path.d.ts.map +1 -1
- package/dist/json-path.js +13 -0
- package/dist/json-path.js.map +1 -1
- package/dist/library/embedded-library.generated.d.ts +2 -0
- package/dist/library/embedded-library.generated.d.ts.map +1 -1
- package/dist/library/embedded-library.generated.js +11 -1
- package/dist/library/embedded-library.generated.js.map +1 -1
- package/dist/library/index.d.ts +2 -0
- package/dist/library/index.d.ts.map +1 -0
- package/dist/library/index.js +24 -0
- package/dist/library/index.js.map +1 -0
- package/dist/library/library-sources.d.ts +99 -6
- package/dist/library/library-sources.d.ts.map +1 -1
- package/dist/library/library-sources.js +165 -43
- package/dist/library/library-sources.js.map +1 -1
- package/dist/loader/meta-data-loader.d.ts +44 -21
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +118 -88
- package/dist/loader/meta-data-loader.js.map +1 -1
- package/dist/loader/sources/file-source.d.ts +7 -1
- package/dist/loader/sources/file-source.d.ts.map +1 -1
- package/dist/loader/sources/file-source.js +3 -2
- package/dist/loader/sources/file-source.js.map +1 -1
- package/dist/loader/validation-passes.d.ts +2 -0
- package/dist/loader/validation-passes.d.ts.map +1 -1
- package/dist/loader/validation-passes.js +162 -22
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/naming.d.ts +7 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +10 -0
- package/dist/naming.js.map +1 -1
- package/dist/parser-core.d.ts +103 -0
- package/dist/parser-core.d.ts.map +1 -1
- package/dist/parser-core.js +202 -18
- package/dist/parser-core.js.map +1 -1
- package/dist/scope.d.ts +16 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +78 -0
- package/dist/scope.js.map +1 -0
- package/dist/serializer-json.d.ts +1 -0
- package/dist/serializer-json.d.ts.map +1 -1
- package/dist/serializer-json.js +43 -1
- package/dist/serializer-json.js.map +1 -1
- package/dist/vocabulary-rewrite-yaml.d.ts +2 -0
- package/dist/vocabulary-rewrite-yaml.d.ts.map +1 -0
- package/dist/vocabulary-rewrite-yaml.js +22 -0
- package/dist/vocabulary-rewrite-yaml.js.map +1 -0
- package/package.json +7 -7
- package/src/core/parser-yaml.ts +4 -0
- package/src/core/relationship/derive-m2m-fields.ts +115 -15
- package/src/core/relationship/find-reference.ts +34 -11
- package/src/core/relationship/relationship-definition.embedded.ts +8 -8
- package/src/core/relationship/resolve-relationship-reference.ts +99 -0
- package/src/errors.ts +31 -0
- package/src/index.ts +18 -5
- package/src/json-path.ts +16 -1
- package/src/library/embedded-library.generated.ts +12 -1
- package/src/library/index.ts +23 -0
- package/src/library/library-sources.ts +197 -47
- package/src/loader/meta-data-loader.ts +154 -102
- package/src/loader/sources/file-source.ts +10 -2
- package/src/loader/validation-passes.ts +181 -23
- package/src/naming.ts +11 -0
- package/src/parser-core.ts +319 -22
- package/src/scope.ts +97 -0
- package/src/serializer-json.ts +46 -1
- package/src/vocabulary-rewrite-yaml.ts +21 -0
|
@@ -9,19 +9,30 @@ import { join, dirname } from "node:path";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { FileSource } from "../loader/sources/file-source.js";
|
|
11
11
|
import { InMemoryStringSource } from "../loader/meta-data-source.js";
|
|
12
|
-
import { EMBEDDED_LIBRARY } from "./embedded-library.generated.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
for (const ref of Object.keys(EMBEDDED_LIBRARY).sort()) {
|
|
18
|
-
const pkg = ref.split("/")[0];
|
|
19
|
-
if (pkg === undefined || pkg === "")
|
|
20
|
-
continue;
|
|
21
|
-
(map[pkg] ??= []).push(ref);
|
|
12
|
+
import { EMBEDDED_LIBRARY, EMBEDDED_LIBRARY_MANIFESTS } from "./embedded-library.generated.js";
|
|
13
|
+
const MANIFESTS = (() => {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [name, text] of Object.entries(EMBEDDED_LIBRARY_MANIFESTS)) {
|
|
16
|
+
out[name] = JSON.parse(text);
|
|
22
17
|
}
|
|
23
|
-
return
|
|
18
|
+
return out;
|
|
24
19
|
})();
|
|
20
|
+
/** Every shipped library's parsed manifest, keyed by name. */
|
|
21
|
+
export function libraryManifests() {
|
|
22
|
+
return MANIFESTS;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Split a selection token into `[library, layer]` — `"iam"` → `["iam", ""]`,
|
|
26
|
+
* `"iam/db"` → `["iam", "db"]`.
|
|
27
|
+
*
|
|
28
|
+
* Path-like, so `libraries` stays `string[]` and no config schema moves. Only ONE
|
|
29
|
+
* separator is meaningful; anything after a second is part of the layer token, which
|
|
30
|
+
* keeps a typo failing loudly rather than resolving to a prefix.
|
|
31
|
+
*/
|
|
32
|
+
export function splitLayerToken(token) {
|
|
33
|
+
const i = token.indexOf("/");
|
|
34
|
+
return i === -1 ? [token, ""] : [token.slice(0, i), token.slice(i + 1)];
|
|
35
|
+
}
|
|
25
36
|
/**
|
|
26
37
|
* Locate the repo-root `library/` directory by walking up from this module's
|
|
27
38
|
* location until a directory contains BOTH `library/` and `server/` (the two
|
|
@@ -57,45 +68,156 @@ function getLibraryDir() {
|
|
|
57
68
|
* available (Python's `project_config` draws the same line, in the same place).
|
|
58
69
|
*/
|
|
59
70
|
export function knownLibraryPackages() {
|
|
60
|
-
return Object.keys(
|
|
71
|
+
return Object.keys(MANIFESTS).sort();
|
|
61
72
|
}
|
|
62
73
|
/**
|
|
63
|
-
*
|
|
74
|
+
* Every package name a shipped library OWNS, across every library and layer.
|
|
64
75
|
*
|
|
65
|
-
* -
|
|
66
|
-
*
|
|
67
|
-
*
|
|
76
|
+
* The provenance key for FR-043 §5.4 — object coverage activates on adopter-authored
|
|
77
|
+
* requirements only, and "adopter-authored" means "declared outside every library
|
|
78
|
+
* package". It reads the manifests rather than node source ids deliberately: `packages`
|
|
79
|
+
* is a manifest fact the standalone gate resolves against the library loaded alone,
|
|
80
|
+
* while a source id differs between the on-disk dev layout (an absolute path) and the
|
|
81
|
+
* embedded one (`library:<ref>.yaml`), so a rule keyed on that would hold here and stop
|
|
82
|
+
* holding in an installed build.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* The source id a library file loads under, in EVERY build — `library:iam/model.yaml`.
|
|
68
86
|
*
|
|
69
|
-
*
|
|
87
|
+
* Stable rather than path-derived so a library node's ADR-0009 provenance envelope reads
|
|
88
|
+
* the same from a checkout and from an installed package, carries no absolute path, and
|
|
89
|
+
* cannot be confused with an adopter file that happens to share a basename. The
|
|
90
|
+
* `library:` prefix is the discriminator {@link isLibraryFileId} reads.
|
|
70
91
|
*/
|
|
71
|
-
export function
|
|
72
|
-
|
|
92
|
+
export function libraryFileId(ref) {
|
|
93
|
+
return `${LIBRARY_FILE_ID_PREFIX}${ref}.yaml`;
|
|
94
|
+
}
|
|
95
|
+
/** The prefix every library source id carries. */
|
|
96
|
+
export const LIBRARY_FILE_ID_PREFIX = "library:";
|
|
97
|
+
/** True when a source id names a file a shipped library contributed. */
|
|
98
|
+
export function isLibraryFileId(id) {
|
|
99
|
+
return id.startsWith(LIBRARY_FILE_ID_PREFIX);
|
|
100
|
+
}
|
|
101
|
+
export function libraryPackages() {
|
|
102
|
+
const out = new Set();
|
|
103
|
+
for (const manifest of Object.values(MANIFESTS)) {
|
|
104
|
+
for (const pkg of manifest.packages ?? [])
|
|
105
|
+
out.add(pkg);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Every selection token this build accepts, sorted — `["ai", "ai/db", "iam", "iam/db"]`.
|
|
111
|
+
*
|
|
112
|
+
* What a config error message should print, so an adopter who typed `iam/database` is
|
|
113
|
+
* shown the layer they meant rather than only the library they got right.
|
|
114
|
+
*/
|
|
115
|
+
export function knownLibraryTokens() {
|
|
73
116
|
const out = [];
|
|
74
|
-
for (const
|
|
75
|
-
const refs
|
|
76
|
-
|
|
77
|
-
continue; // unknown package — no sources
|
|
78
|
-
for (const ref of refs) {
|
|
79
|
-
if (dir !== undefined) {
|
|
80
|
-
const path = join(dir, `${ref}.yaml`);
|
|
81
|
-
if (existsSync(path)) {
|
|
82
|
-
out.push(new FileSource(path));
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const embedded = EMBEDDED_LIBRARY[ref];
|
|
87
|
-
if (embedded !== undefined) {
|
|
88
|
-
out.push(new InMemoryStringSource(embedded, {
|
|
89
|
-
id: `library:${ref}.yaml`,
|
|
90
|
-
format: "yaml",
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
throw new Error(`library ref "${ref}" (package "${pkg}") has no on-disk file and no embedded entry — ` +
|
|
95
|
-
`the embedded library module is stale; run scripts/generate-embedded-library.ts`);
|
|
96
|
-
}
|
|
117
|
+
for (const [name, manifest] of Object.entries(MANIFESTS)) {
|
|
118
|
+
for (const layer of Object.keys(manifest.layers ?? { "": { refs: [] } })) {
|
|
119
|
+
out.push(layer === "" ? name : `${name}/${layer}`);
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
|
-
return out;
|
|
122
|
+
return out.sort();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* `MetaDataSource` instances for the requested library selection.
|
|
126
|
+
*
|
|
127
|
+
* **Layer-granular.** A token is `<library>` or `<library>/<layer>`; the CORE layer is
|
|
128
|
+
* the bare name. This used to be package-granular — every ref under a library came back
|
|
129
|
+
* for a bare `"iam"` — which under the layered design would have handed an adopter the
|
|
130
|
+
* db and ui layers they did not ask for, and with them a migration proposing nine tables.
|
|
131
|
+
*
|
|
132
|
+
* **`"iam/db"` IMPLIES `"iam"`**, and the implication is not a convenience: a db layer is
|
|
133
|
+
* nothing but `overlay: true` redeclarations, and an overlay whose target was never
|
|
134
|
+
* declared is `ERR_OVERLAY_NO_TARGET`. Resolving the layer without its core would produce
|
|
135
|
+
* exactly that error, so implying it is the only coherent reading.
|
|
136
|
+
*
|
|
137
|
+
* Refs are de-duplicated and returned in a stable order — core first, then each requested
|
|
138
|
+
* layer in the manifest's own order — because an overlay must be parsed after its base
|
|
139
|
+
* even though ADR-0055 applies overlays in a deferred pass.
|
|
140
|
+
*
|
|
141
|
+
* An unrecognised token contributes nothing and is skipped silently: that is right for a
|
|
142
|
+
* programmatic caller asking for something a given version may not ship. A name a HUMAN
|
|
143
|
+
* typed is a different case and is refused by the config readers, which call
|
|
144
|
+
* {@link knownLibraryTokens} to say what is available.
|
|
145
|
+
*
|
|
146
|
+
* @param selection - Tokens, e.g. `["iam", "iam/db"]`.
|
|
147
|
+
*/
|
|
148
|
+
export function librarySources(selection) {
|
|
149
|
+
const refs = [];
|
|
150
|
+
const seen = new Set();
|
|
151
|
+
const add = (ref) => {
|
|
152
|
+
if (seen.has(ref))
|
|
153
|
+
return;
|
|
154
|
+
seen.add(ref);
|
|
155
|
+
refs.push(ref);
|
|
156
|
+
};
|
|
157
|
+
// Core layers first, across every requested library, so a db layer named before its
|
|
158
|
+
// core in the config still parses after it.
|
|
159
|
+
//
|
|
160
|
+
// A token whose LAYER is unknown is dropped whole, not reduced to its core. The core is
|
|
161
|
+
// implied by a VALID layer token; implying it from an invalid one would answer a
|
|
162
|
+
// mistyped `iam/database` with an inert core and no tables — "I asked for the db layer
|
|
163
|
+
// and got nothing" with no diagnostic, which is the worst of the available outcomes.
|
|
164
|
+
const wanted = selection
|
|
165
|
+
.map(splitLayerToken)
|
|
166
|
+
.filter(([lib, layer]) => lib in MANIFESTS && (MANIFESTS[lib].layers ?? {})[layer] !== undefined);
|
|
167
|
+
for (const [lib] of wanted) {
|
|
168
|
+
for (const ref of MANIFESTS[lib].layers?.[""]?.refs ?? [])
|
|
169
|
+
add(ref);
|
|
170
|
+
}
|
|
171
|
+
for (const [lib, layer] of wanted) {
|
|
172
|
+
if (layer === "")
|
|
173
|
+
continue;
|
|
174
|
+
for (const ref of MANIFESTS[lib].layers?.[layer]?.refs ?? [])
|
|
175
|
+
add(ref);
|
|
176
|
+
}
|
|
177
|
+
return refs.map(libraryRefSource);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* One library ref as a source — on-disk first, embedded otherwise.
|
|
181
|
+
*
|
|
182
|
+
* Factored out of {@link librarySources} because `meta eject <library>` needs the TEXT
|
|
183
|
+
* of one ref and must resolve it exactly the way a load does: an adopter ejecting from a
|
|
184
|
+
* checkout must get the file they can see, and from an installed package the embedded
|
|
185
|
+
* copy, with no third rule to keep in step.
|
|
186
|
+
*/
|
|
187
|
+
export function libraryRefSource(ref) {
|
|
188
|
+
const dir = getLibraryDir();
|
|
189
|
+
if (dir !== undefined) {
|
|
190
|
+
const path = join(dir, `${ref}.yaml`);
|
|
191
|
+
if (existsSync(path)) {
|
|
192
|
+
// The SAME id the embedded branch below uses, deliberately. A `FileSource`
|
|
193
|
+
// defaults its id to the file's BASENAME, which would make a library node's error
|
|
194
|
+
// envelope read `model.yaml` in a checkout and `library:iam/model.yaml` in an
|
|
195
|
+
// installed build — and would collide outright with an adopter file of that name.
|
|
196
|
+
// One stable id makes the two builds report identically and gives anything asking
|
|
197
|
+
// "did a library declare this node" an unambiguous answer.
|
|
198
|
+
return new FileSource(path, { id: libraryFileId(ref) });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const embedded = EMBEDDED_LIBRARY[ref];
|
|
202
|
+
if (embedded === undefined) {
|
|
203
|
+
throw new Error(`library ref "${ref}" has no on-disk file and no embedded entry — ` +
|
|
204
|
+
`the embedded library module is stale; run scripts/generate-embedded-library.ts`);
|
|
205
|
+
}
|
|
206
|
+
return new InMemoryStringSource(embedded, { id: libraryFileId(ref), format: "yaml" });
|
|
207
|
+
}
|
|
208
|
+
/** Every ref one library contributes, core layer first — what `meta eject` copies. */
|
|
209
|
+
export function libraryRefs(name) {
|
|
210
|
+
const layers = MANIFESTS[name]?.layers ?? {};
|
|
211
|
+
const refs = [];
|
|
212
|
+
const seen = new Set();
|
|
213
|
+
for (const token of ["", ...Object.keys(layers).filter((k) => k !== "")]) {
|
|
214
|
+
for (const ref of layers[token]?.refs ?? []) {
|
|
215
|
+
if (seen.has(ref))
|
|
216
|
+
continue;
|
|
217
|
+
seen.add(ref);
|
|
218
|
+
refs.push(ref);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return refs;
|
|
100
222
|
}
|
|
101
223
|
//# sourceMappingURL=library-sources.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"library-sources.js","sourceRoot":"","sources":["../../src/library/library-sources.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,EAAE;AACF,0FAA0F;AAC1F,4FAA4F;AAC5F,2FAA2F;AAC3F,4EAA4E;AAE5E,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"library-sources.js","sourceRoot":"","sources":["../../src/library/library-sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,6 +38,50 @@ export interface LoadResult {
|
|
|
38
38
|
warnings: LoaderWarning[];
|
|
39
39
|
errors: Error[];
|
|
40
40
|
}
|
|
41
|
+
/** One root-level declaration as `declaredTopLevelKeys` structurally scans it —
|
|
42
|
+
* before registry-driven parsing, super resolution, or merge. */
|
|
43
|
+
export interface DeclaredTopLevelKey {
|
|
44
|
+
/** The wrapper key's TYPE segment (e.g. "object" for "object.entity"). A bare
|
|
45
|
+
* wrapper key with no "." is its own type — this walk never consults the
|
|
46
|
+
* registry for a default subType, because it never needs the subType. */
|
|
47
|
+
type: string;
|
|
48
|
+
/** The resolution key the declaration would carry once parsed: the child's
|
|
49
|
+
* own `package` if set (expanded against the root's `package` via
|
|
50
|
+
* `expandPackageForPath` when it's a relative, `::`-prefixed path — e.g.
|
|
51
|
+
* root `acme` + own `::garage` → `acme::garage`), else the root's
|
|
52
|
+
* `package` verbatim, then `::`, then its `name`. Exactly
|
|
53
|
+
* `rootChildResolutionKey` (parser-core.ts) — including the relative-path
|
|
54
|
+
* expansion, reusing `expandPackageForPath` rather than reimplementing it,
|
|
55
|
+
* because a second copy is exactly how this walk and the real parser
|
|
56
|
+
* silently disagreed on a relative package (task 17 fix-round-1: this
|
|
57
|
+
* function used to return `::garage::Garage` for a fixture the real
|
|
58
|
+
* loader resolves to `acme::garage::Garage`). */
|
|
59
|
+
key: string;
|
|
60
|
+
/** Whether the declaration's body carries `overlay: true`. */
|
|
61
|
+
overlay: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Structurally scan a source's raw content (JSON via `JSON.parse`; sigil-free
|
|
65
|
+
* authoring YAML via the raw YAML walker — `overlay: true` is a bare key
|
|
66
|
+
* before desugar) and report every top-level declaration under
|
|
67
|
+
* `metadata.root.children`: its type, the resolution key it would carry once
|
|
68
|
+
* parsed, and whether it carries `overlay: true`.
|
|
69
|
+
*
|
|
70
|
+
* A declaration with no (string, non-empty) `name` is skipped — a resolution
|
|
71
|
+
* key can't be computed for it, and the real parser will report it as a
|
|
72
|
+
* loader error rather than silently reusing or creating a node. Malformed
|
|
73
|
+
* root shapes (root missing, not an object, `children` absent or not an
|
|
74
|
+
* array) return `[]` rather than throwing — the real parse loop is where a
|
|
75
|
+
* genuine structural error surfaces; this walk must never crash a caller
|
|
76
|
+
* that is only trying to answer "what does this file declare".
|
|
77
|
+
*
|
|
78
|
+
* Written for the #160 overlay-only source partition, which ADR-0055 retired —
|
|
79
|
+
* overlays are now applied in a post-parse pass, so no ordering decision needs
|
|
80
|
+
* this walk. Its remaining caller is the `meta verify` overlay authoring lint,
|
|
81
|
+
* which needs per-file declaration provenance precisely because the MERGED tree
|
|
82
|
+
* has already lost which file contributed which declaration.
|
|
83
|
+
*/
|
|
84
|
+
export declare function declaredTopLevelKeys(content: string, format: MetaDataFormat): Promise<ReadonlyArray<DeclaredTopLevelKey>>;
|
|
41
85
|
export declare class MetaDataLoader {
|
|
42
86
|
private readonly _registry;
|
|
43
87
|
private readonly _freeze;
|
|
@@ -120,27 +164,6 @@ export declare class MetaDataLoader {
|
|
|
120
164
|
protected parseSource(content: string, source: MetaDataSource, parseOpts: ParseOptions): ParseResult;
|
|
121
165
|
private static _yamlParser;
|
|
122
166
|
private static _ensureYamlParser;
|
|
123
|
-
/**
|
|
124
|
-
* Stable-partition `sources` so that "overlay-only" sources — every top-level
|
|
125
|
-
* object declaration carries `overlay: true`, i.e. the source declares no base
|
|
126
|
-
* objects of its own and only re-opens objects declared elsewhere — are parsed
|
|
127
|
-
* LAST. Preserves original order within each group.
|
|
128
|
-
*
|
|
129
|
-
* A source whose content can't be read or structurally scanned stays in the
|
|
130
|
-
* base group (never overlay-only) — this partition must never crash the loader;
|
|
131
|
-
* any genuine read/parse failure surfaces later, in the real parse loop.
|
|
132
|
-
*/
|
|
133
|
-
private static _partitionOverlayLast;
|
|
134
|
-
/**
|
|
135
|
-
* Structurally scan a source's raw content (JSON via JSON.parse; sigil-free
|
|
136
|
-
* authoring YAML via the raw YAML walker — `overlay: true` is a bare key
|
|
137
|
-
* before desugar) and report whether every top-level object declaration under
|
|
138
|
-
* `metadata.root.children` carries `overlay: true` (and there is at least one).
|
|
139
|
-
*/
|
|
140
|
-
private static _isOverlayOnlySource;
|
|
141
|
-
/** True when the structurally-parsed root has ≥1 child and every top-level
|
|
142
|
-
* child node carries `overlay: true` (declares no base objects). */
|
|
143
|
-
private static _rootIsOverlayOnly;
|
|
144
167
|
/**
|
|
145
168
|
* Load metadata from one or more MetaDataSource instances. Sources are read
|
|
146
169
|
* in order; each source's content is parsed and merged into the accumulating
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-data-loader.d.ts","sourceRoot":"","sources":["../../src/loader/meta-data-loader.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAU,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"meta-data-loader.d.ts","sourceRoot":"","sources":["../../src/loader/meta-data-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,11 +11,11 @@ import { TypeId, TypeRegistry } from "../registry.js";
|
|
|
11
11
|
import { coreProviders } from "../core-types.js";
|
|
12
12
|
import { composeRegistry } from "../provider.js";
|
|
13
13
|
import { TYPE_METADATA, SUBTYPE_ROOT } from "../shared/base-types.js";
|
|
14
|
-
import { RESERVED_KEY_CHILDREN, RESERVED_KEY_OVERLAY } from "../shared/structural.js";
|
|
14
|
+
import { PACKAGE_SEPARATOR, RESERVED_KEY_CHILDREN, RESERVED_KEY_NAME, RESERVED_KEY_OVERLAY, RESERVED_KEY_PACKAGE, TYPE_SUBTYPE_SEPARATOR, } from "../shared/structural.js";
|
|
15
15
|
import { ParseError } from "../errors.js";
|
|
16
16
|
import { codeSource, resolvedSource } from "../source.js";
|
|
17
17
|
import { parseJson } from "../parser-json.js";
|
|
18
|
-
import { validateDataGridSortFields, validateFilterableHasIndex, validateFilterableHasSupportedOps, validateSortableHasSupportedSubtype, validateOriginPaths, validateDerivedFieldProvidability, validateDataGridFilterValues, validateFieldObjectStorage, validateFieldMap, validateTemplatePayloadRefs, validateFieldDefaults, validateRelationships, validateIndexLookupFields, validateProjectionFilter, validateRetiredRequirementLinks } from "./validation-passes.js";
|
|
18
|
+
import { validateDataGridSortFields, validateFilterableHasIndex, validateFilterableHasSupportedOps, validateSortableHasSupportedSubtype, validateOriginPaths, validateDerivedFieldProvidability, validateDataGridFilterValues, validateFieldObjectStorage, validateFieldMap, validateTemplatePayloadRefs, validateFieldDefaults, validateRelationships, validateOneSideReferenceResolution, validateIndexLookupFields, validateProjectionFilter, validateRetiredRequirementLinks } from "./validation-passes.js";
|
|
19
19
|
import { runRegisteredValidation } from "./validation-registry.js";
|
|
20
20
|
import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
|
|
21
21
|
import { validateSourceEscapes } from "../persistence/source/validate-source-escapes.js";
|
|
@@ -30,12 +30,94 @@ import { validateMaxOccurs } from "../validate-max-occurs.js";
|
|
|
30
30
|
import { validateIdentityPassthrough } from "../core/identity/validate-identity-passthrough.js";
|
|
31
31
|
import { validateAttrSchema } from "../attr-schema-validate.js";
|
|
32
32
|
import { InMemoryStringSource } from "./meta-data-source.js";
|
|
33
|
+
import { expandPackageForPath, applyPendingOverlays } from "../parser-core.js";
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
// Synthetic empty root (used when all sources fail to parse)
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
36
37
|
function makeSyntheticRoot() {
|
|
37
38
|
return new MetaRoot(new TypeId(TYPE_METADATA, SUBTYPE_ROOT), "");
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Structurally scan a source's raw content (JSON via `JSON.parse`; sigil-free
|
|
42
|
+
* authoring YAML via the raw YAML walker — `overlay: true` is a bare key
|
|
43
|
+
* before desugar) and report every top-level declaration under
|
|
44
|
+
* `metadata.root.children`: its type, the resolution key it would carry once
|
|
45
|
+
* parsed, and whether it carries `overlay: true`.
|
|
46
|
+
*
|
|
47
|
+
* A declaration with no (string, non-empty) `name` is skipped — a resolution
|
|
48
|
+
* key can't be computed for it, and the real parser will report it as a
|
|
49
|
+
* loader error rather than silently reusing or creating a node. Malformed
|
|
50
|
+
* root shapes (root missing, not an object, `children` absent or not an
|
|
51
|
+
* array) return `[]` rather than throwing — the real parse loop is where a
|
|
52
|
+
* genuine structural error surfaces; this walk must never crash a caller
|
|
53
|
+
* that is only trying to answer "what does this file declare".
|
|
54
|
+
*
|
|
55
|
+
* Written for the #160 overlay-only source partition, which ADR-0055 retired —
|
|
56
|
+
* overlays are now applied in a post-parse pass, so no ordering decision needs
|
|
57
|
+
* this walk. Its remaining caller is the `meta verify` overlay authoring lint,
|
|
58
|
+
* which needs per-file declaration provenance precisely because the MERGED tree
|
|
59
|
+
* has already lost which file contributed which declaration.
|
|
60
|
+
*/
|
|
61
|
+
export async function declaredTopLevelKeys(content, format) {
|
|
62
|
+
// Strip UTF-8 BOM if present (mirrors parseJson / parseYaml).
|
|
63
|
+
const normalized = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
64
|
+
let parsed;
|
|
65
|
+
if (format === "json") {
|
|
66
|
+
parsed = JSON.parse(normalized);
|
|
67
|
+
}
|
|
68
|
+
else if (format === "yaml") {
|
|
69
|
+
const { parseYamlWithPositions } = await import("../core/yaml-positions-walker.js");
|
|
70
|
+
parsed = parseYamlWithPositions(normalized).value;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
return declaredTopLevelKeysFromParsedRoot(parsed);
|
|
76
|
+
}
|
|
77
|
+
/** The structural walk `declaredTopLevelKeys` performs once content is
|
|
78
|
+
* structurally parsed — split out so it takes no format/BOM concerns. */
|
|
79
|
+
function declaredTopLevelKeysFromParsedRoot(parsed) {
|
|
80
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
81
|
+
return [];
|
|
82
|
+
const parsedRecord = parsed;
|
|
83
|
+
// Canonical JSON always fuses the subType onto the root wrapper key
|
|
84
|
+
// ("metadata.root"); sigil-free YAML authoring writes the bare type
|
|
85
|
+
// ("metadata") and leaves the default subType to the registry — this
|
|
86
|
+
// structural walk runs BEFORE desugar/registry resolution, so it must
|
|
87
|
+
// accept both spellings rather than only ever matching JSON's.
|
|
88
|
+
const explicitRootKey = `${TYPE_METADATA}${TYPE_SUBTYPE_SEPARATOR}${SUBTYPE_ROOT}`; // "metadata.root"
|
|
89
|
+
const rootBody = parsedRecord[explicitRootKey] ?? parsedRecord[TYPE_METADATA];
|
|
90
|
+
if (typeof rootBody !== "object" || rootBody === null)
|
|
91
|
+
return [];
|
|
92
|
+
const rawRootPkg = rootBody[RESERVED_KEY_PACKAGE];
|
|
93
|
+
const rootPkg = typeof rawRootPkg === "string" ? rawRootPkg : "";
|
|
94
|
+
const children = rootBody[RESERVED_KEY_CHILDREN];
|
|
95
|
+
if (!Array.isArray(children))
|
|
96
|
+
return [];
|
|
97
|
+
const declared = [];
|
|
98
|
+
for (const child of children) {
|
|
99
|
+
if (typeof child !== "object" || child === null)
|
|
100
|
+
continue;
|
|
101
|
+
// Each child is a single-key wrapper: { "object.entity": { ... } }.
|
|
102
|
+
for (const [wrapperKey, body] of Object.entries(child)) {
|
|
103
|
+
if (typeof body !== "object" || body === null)
|
|
104
|
+
continue;
|
|
105
|
+
const bodyRecord = body;
|
|
106
|
+
const name = bodyRecord[RESERVED_KEY_NAME];
|
|
107
|
+
if (typeof name !== "string" || name === "")
|
|
108
|
+
continue;
|
|
109
|
+
const dotIdx = wrapperKey.indexOf(TYPE_SUBTYPE_SEPARATOR);
|
|
110
|
+
const type = dotIdx < 0 ? wrapperKey : wrapperKey.slice(0, dotIdx);
|
|
111
|
+
const rawOwnPkg = bodyRecord[RESERVED_KEY_PACKAGE];
|
|
112
|
+
const pkg = typeof rawOwnPkg === "string" && rawOwnPkg !== ""
|
|
113
|
+
? expandPackageForPath(rootPkg, rawOwnPkg)
|
|
114
|
+
: rootPkg;
|
|
115
|
+
const key = pkg !== "" ? `${pkg}${PACKAGE_SEPARATOR}${name}` : name;
|
|
116
|
+
declared.push({ type, key, overlay: bodyRecord[RESERVED_KEY_OVERLAY] === true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return declared;
|
|
120
|
+
}
|
|
39
121
|
// ---------------------------------------------------------------------------
|
|
40
122
|
// MetaDataLoader class
|
|
41
123
|
// ---------------------------------------------------------------------------
|
|
@@ -230,80 +312,6 @@ export class MetaDataLoader {
|
|
|
230
312
|
MetaDataLoader._yamlParser = mod.parseYaml;
|
|
231
313
|
}
|
|
232
314
|
// ---------------------------------------------------------------------------
|
|
233
|
-
// #160 — overlay-only source partition (stable, overlay-only sources last)
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
/**
|
|
236
|
-
* Stable-partition `sources` so that "overlay-only" sources — every top-level
|
|
237
|
-
* object declaration carries `overlay: true`, i.e. the source declares no base
|
|
238
|
-
* objects of its own and only re-opens objects declared elsewhere — are parsed
|
|
239
|
-
* LAST. Preserves original order within each group.
|
|
240
|
-
*
|
|
241
|
-
* A source whose content can't be read or structurally scanned stays in the
|
|
242
|
-
* base group (never overlay-only) — this partition must never crash the loader;
|
|
243
|
-
* any genuine read/parse failure surfaces later, in the real parse loop.
|
|
244
|
-
*/
|
|
245
|
-
static async _partitionOverlayLast(sources) {
|
|
246
|
-
const base = [];
|
|
247
|
-
const overlayOnly = [];
|
|
248
|
-
for (const source of sources) {
|
|
249
|
-
let isOverlayOnly = false;
|
|
250
|
-
try {
|
|
251
|
-
const content = await source.read();
|
|
252
|
-
isOverlayOnly = await MetaDataLoader._isOverlayOnlySource(content, source.format);
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
isOverlayOnly = false;
|
|
256
|
-
}
|
|
257
|
-
(isOverlayOnly ? overlayOnly : base).push(source);
|
|
258
|
-
}
|
|
259
|
-
return [...base, ...overlayOnly];
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Structurally scan a source's raw content (JSON via JSON.parse; sigil-free
|
|
263
|
-
* authoring YAML via the raw YAML walker — `overlay: true` is a bare key
|
|
264
|
-
* before desugar) and report whether every top-level object declaration under
|
|
265
|
-
* `metadata.root.children` carries `overlay: true` (and there is at least one).
|
|
266
|
-
*/
|
|
267
|
-
static async _isOverlayOnlySource(content, format) {
|
|
268
|
-
// Strip UTF-8 BOM if present (mirrors parseJson / parseYaml).
|
|
269
|
-
const normalized = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
|
|
270
|
-
let parsed;
|
|
271
|
-
if (format === "json") {
|
|
272
|
-
parsed = JSON.parse(normalized);
|
|
273
|
-
}
|
|
274
|
-
else if (format === "yaml") {
|
|
275
|
-
const { parseYamlWithPositions } = await import("../core/yaml-positions-walker.js");
|
|
276
|
-
parsed = parseYamlWithPositions(normalized).value;
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
return MetaDataLoader._rootIsOverlayOnly(parsed);
|
|
282
|
-
}
|
|
283
|
-
/** True when the structurally-parsed root has ≥1 child and every top-level
|
|
284
|
-
* child node carries `overlay: true` (declares no base objects). */
|
|
285
|
-
static _rootIsOverlayOnly(parsed) {
|
|
286
|
-
if (typeof parsed !== "object" || parsed === null)
|
|
287
|
-
return false;
|
|
288
|
-
const rootKey = `${TYPE_METADATA}.${SUBTYPE_ROOT}`; // "metadata.root"
|
|
289
|
-
const rootBody = parsed[rootKey];
|
|
290
|
-
if (typeof rootBody !== "object" || rootBody === null)
|
|
291
|
-
return false;
|
|
292
|
-
const children = rootBody[RESERVED_KEY_CHILDREN];
|
|
293
|
-
if (!Array.isArray(children) || children.length === 0)
|
|
294
|
-
return false;
|
|
295
|
-
return children.every((child) => {
|
|
296
|
-
if (typeof child !== "object" || child === null)
|
|
297
|
-
return false;
|
|
298
|
-
// Each child is a single-key wrapper: { "object.projection": { ... } }.
|
|
299
|
-
const bodies = Object.values(child);
|
|
300
|
-
return (bodies.length > 0 &&
|
|
301
|
-
bodies.every((body) => typeof body === "object" &&
|
|
302
|
-
body !== null &&
|
|
303
|
-
body[RESERVED_KEY_OVERLAY] === true));
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
// ---------------------------------------------------------------------------
|
|
307
315
|
// load — async pipeline over MetaDataSource[]
|
|
308
316
|
// ---------------------------------------------------------------------------
|
|
309
317
|
/**
|
|
@@ -338,19 +346,17 @@ export class MetaDataLoader {
|
|
|
338
346
|
if (sources.some((s) => s.format === "yaml")) {
|
|
339
347
|
await MetaDataLoader._ensureYamlParser();
|
|
340
348
|
}
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
// Directory discovery order is not guaranteed to present base files first
|
|
349
|
-
// (basename sort can put an overlay-only file first), so stable-partition
|
|
350
|
-
// overlay-only sources to the END here, making the merge order-independent.
|
|
351
|
-
// Stable within each group preserves last-writer-wins overlay semantics.
|
|
352
|
-
sources = await MetaDataLoader._partitionOverlayLast(sources);
|
|
349
|
+
// ADR-0055 — sources are parsed in the order given. Overlays are not applied
|
|
350
|
+
// during the walk: each parse QUEUES its `overlay: true` declarations and the
|
|
351
|
+
// loader applies them below, once every base exists. That retired the #160
|
|
352
|
+
// overlay-only source partition, whose file-level predicate could not rescue a
|
|
353
|
+
// MIXED file (plain + overlay declarations together) and therefore left the
|
|
354
|
+
// load order-dependent — and divergent across ports, since every port's
|
|
355
|
+
// directory walk orders files differently.
|
|
353
356
|
let root;
|
|
357
|
+
// ADR-0055 — every source's queue, concatenated in parse order, which is
|
|
358
|
+
// already "source order, then declaration order within a source" (G2).
|
|
359
|
+
const pendingOverlays = [];
|
|
354
360
|
// Parse all sources with super resolution DEFERRED so cross-file super
|
|
355
361
|
// refs work — one source may declare a super target that's defined in a
|
|
356
362
|
// source parsed later. A second pass (resolveDeferredSupers) resolves
|
|
@@ -372,6 +378,10 @@ export class MetaDataLoader {
|
|
|
372
378
|
registry: this._registry,
|
|
373
379
|
strict: this._strict,
|
|
374
380
|
deferSuperResolution: true,
|
|
381
|
+
// ADR-0055 — queue overlays; this loader drains them after every source
|
|
382
|
+
// is parsed. Without it each document would drain its own queue and an
|
|
383
|
+
// overlay could still not reach a base declared in a later source.
|
|
384
|
+
deferOverlays: true,
|
|
375
385
|
sourceName: source.id,
|
|
376
386
|
};
|
|
377
387
|
if (root !== undefined)
|
|
@@ -384,6 +394,7 @@ export class MetaDataLoader {
|
|
|
384
394
|
// source). The legacy `warnings` channel still flows into the
|
|
385
395
|
// WARN_LEGACY-wrapping path below for unchanged behavior.
|
|
386
396
|
envelopeWarnings.push(...parseResult.envelopeWarnings);
|
|
397
|
+
pendingOverlays.push(...parseResult.pendingOverlays);
|
|
387
398
|
root = parseResult.root;
|
|
388
399
|
}
|
|
389
400
|
catch (err) {
|
|
@@ -392,6 +403,20 @@ export class MetaDataLoader {
|
|
|
392
403
|
: new Error(`Parse error in "${source.id}": ${String(err)}`));
|
|
393
404
|
}
|
|
394
405
|
}
|
|
406
|
+
// ADR-0055 — apply every queued overlay now: all plain declarations from all
|
|
407
|
+
// sources are in the tree (G1), so a base/overlay relation no longer depends
|
|
408
|
+
// on file order (G4). This runs BEFORE super resolution so a node an overlay
|
|
409
|
+
// contributes is visible to every `extends` (G5) — the ordering #160's
|
|
410
|
+
// partition was reaching for, now guaranteed rather than approximated.
|
|
411
|
+
if (root !== undefined && pendingOverlays.length > 0) {
|
|
412
|
+
const applied = applyPendingOverlays(pendingOverlays, {
|
|
413
|
+
registry: this._registry,
|
|
414
|
+
strict: this._strict,
|
|
415
|
+
});
|
|
416
|
+
warnings.push(...applied.warnings);
|
|
417
|
+
errors.push(...applied.errors);
|
|
418
|
+
envelopeWarnings.push(...applied.envelopeWarnings);
|
|
419
|
+
}
|
|
395
420
|
// Second pass: resolve every deferred super ref against the full tree.
|
|
396
421
|
// Unresolved refs are always errors (matches the original eager-throw
|
|
397
422
|
// behavior — broken metadata is broken regardless of strict mode).
|
|
@@ -458,6 +483,11 @@ export class MetaDataLoader {
|
|
|
458
483
|
// @symmetric is self-join-only + mutually exclusive with @sourceRefField; M:N attrs
|
|
459
484
|
// are invalid on a 1:N relationship.
|
|
460
485
|
errors.push(...validateRelationships(root));
|
|
486
|
+
// Rule (e) — #368: a `@cardinality: one` relationship must resolve to
|
|
487
|
+
// exactly one identity.reference when its declaring entity holds more
|
|
488
|
+
// than one onto the same target; an unresolvable case is a load error
|
|
489
|
+
// naming the candidates (ADR-0029 §5).
|
|
490
|
+
errors.push(...validateOneSideReferenceResolution(root));
|
|
461
491
|
// index.lookup @fields resolution — each index.lookup must name ≥1 field,
|
|
462
492
|
// and every field must exist in the entity's effective (resolved) field set
|
|
463
493
|
// (ADR-0039: resolving accessor, so inherited fields via extends are visible).
|