@metaobjectsdev/metadata 0.6.0 → 0.7.0-rc.10
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 +54 -3
- package/dist/attr-schema-validate.js +7 -7
- package/dist/attr-schema-validate.js.map +1 -1
- package/dist/core/export-json.d.ts +6 -7
- package/dist/core/export-json.d.ts.map +1 -1
- package/dist/core/export-json.js +15 -17
- package/dist/core/export-json.js.map +1 -1
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/parser-yaml.d.ts.map +1 -1
- package/dist/core/parser-yaml.js +36 -11
- package/dist/core/parser-yaml.js.map +1 -1
- package/dist/core/yaml-desugar.d.ts.map +1 -1
- package/dist/core/yaml-desugar.js +54 -5
- package/dist/core/yaml-desugar.js.map +1 -1
- package/dist/core/yaml-positions-walker.d.ts +21 -0
- package/dist/core/yaml-positions-walker.d.ts.map +1 -0
- package/dist/core/yaml-positions-walker.js +75 -0
- package/dist/core/yaml-positions-walker.js.map +1 -0
- package/dist/core/yaml-positions.d.ts +19 -0
- package/dist/core/yaml-positions.d.ts.map +1 -0
- package/dist/core/yaml-positions.js +60 -0
- package/dist/core/yaml-positions.js.map +1 -0
- package/dist/core-types.d.ts.map +1 -1
- package/dist/core-types.js +7 -4
- package/dist/core-types.js.map +1 -1
- package/dist/errors.d.ts +32 -9
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -5
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/json-path.d.ts +8 -0
- package/dist/json-path.d.ts.map +1 -0
- package/dist/json-path.js +39 -0
- package/dist/json-path.js.map +1 -0
- package/dist/loader/meta-data-loader.d.ts +47 -6
- package/dist/loader/meta-data-loader.d.ts.map +1 -1
- package/dist/loader/meta-data-loader.js +126 -8
- package/dist/loader/meta-data-loader.js.map +1 -1
- package/dist/loader/meta-data-source.d.ts +6 -2
- package/dist/loader/meta-data-source.d.ts.map +1 -1
- package/dist/loader/meta-data-source.js +10 -6
- package/dist/loader/meta-data-source.js.map +1 -1
- package/dist/loader/shortcuts.d.ts +9 -0
- package/dist/loader/shortcuts.d.ts.map +1 -0
- package/dist/loader/shortcuts.js +19 -0
- package/dist/loader/shortcuts.js.map +1 -0
- package/dist/loader/sources/directory-source.d.ts +15 -0
- package/dist/loader/sources/directory-source.d.ts.map +1 -0
- package/dist/loader/sources/directory-source.js +80 -0
- package/dist/loader/sources/directory-source.js.map +1 -0
- package/dist/loader/sources/file-source.d.ts +12 -0
- package/dist/loader/sources/file-source.d.ts.map +1 -0
- package/dist/loader/sources/file-source.js +46 -0
- package/dist/loader/sources/file-source.js.map +1 -0
- package/dist/loader/sources/index.d.ts +5 -0
- package/dist/loader/sources/index.d.ts.map +1 -0
- package/dist/loader/sources/index.js +5 -0
- package/dist/loader/sources/index.js.map +1 -0
- package/dist/loader/sources/uri-source.d.ts +9 -0
- package/dist/loader/sources/uri-source.d.ts.map +1 -0
- package/dist/loader/sources/uri-source.js +42 -0
- package/dist/loader/sources/uri-source.js.map +1 -0
- package/dist/loader/validation-passes.d.ts.map +1 -1
- package/dist/loader/validation-passes.js +92 -28
- package/dist/loader/validation-passes.js.map +1 -1
- package/dist/naming.d.ts +15 -2
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +20 -6
- package/dist/naming.js.map +1 -1
- package/dist/parser-core.d.ts +17 -4
- package/dist/parser-core.d.ts.map +1 -1
- package/dist/parser-core.js +371 -44
- package/dist/parser-core.js.map +1 -1
- package/dist/parser-json.d.ts.map +1 -1
- package/dist/parser-json.js +10 -2
- package/dist/parser-json.js.map +1 -1
- package/dist/persistence/source/validate-source-roles.js +2 -2
- package/dist/persistence/source/validate-source-roles.js.map +1 -1
- package/dist/semantic-diff.d.ts +5 -0
- package/dist/semantic-diff.d.ts.map +1 -0
- package/dist/semantic-diff.js +49 -0
- package/dist/semantic-diff.js.map +1 -0
- package/dist/shared/meta-data.d.ts +10 -0
- package/dist/shared/meta-data.d.ts.map +1 -1
- package/dist/shared/meta-data.js +23 -0
- package/dist/shared/meta-data.js.map +1 -1
- package/dist/source.d.ts +96 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +38 -0
- package/dist/source.js.map +1 -0
- package/dist/subtype-rules.js +1 -1
- package/dist/subtype-rules.js.map +1 -1
- package/dist/super-resolve.d.ts +2 -0
- package/dist/super-resolve.d.ts.map +1 -1
- package/dist/super-resolve.js +1 -1
- package/dist/super-resolve.js.map +1 -1
- package/dist/template/template-constants.d.ts +3 -1
- package/dist/template/template-constants.d.ts.map +1 -1
- package/dist/template/template-constants.js +22 -6
- package/dist/template/template-constants.js.map +1 -1
- package/dist/template/template-schema.d.ts.map +1 -1
- package/dist/template/template-schema.js +41 -1
- package/dist/template/template-schema.js.map +1 -1
- package/package.json +1 -1
- package/src/attr-schema-validate.ts +7 -7
- package/src/core/export-json.ts +15 -18
- package/src/core/index.ts +8 -2
- package/src/core/parser-yaml.ts +38 -11
- package/src/core/yaml-desugar.ts +58 -4
- package/src/core/yaml-positions-walker.ts +101 -0
- package/src/core/yaml-positions.ts +80 -0
- package/src/core-types.ts +7 -4
- package/src/errors.ts +57 -8
- package/src/index.ts +28 -3
- package/src/json-path.ts +46 -0
- package/src/loader/meta-data-loader.ts +168 -10
- package/src/loader/meta-data-source.ts +10 -6
- package/src/loader/shortcuts.ts +31 -0
- package/src/loader/sources/directory-source.ts +90 -0
- package/src/{core → loader/sources}/file-source.ts +3 -3
- package/src/loader/sources/index.ts +6 -0
- package/src/loader/sources/uri-source.ts +44 -0
- package/src/loader/validation-passes.ts +96 -29
- package/src/naming.ts +39 -7
- package/src/parser-core.ts +412 -46
- package/src/parser-json.ts +11 -2
- package/src/persistence/source/validate-source-roles.ts +2 -2
- package/src/semantic-diff.ts +48 -0
- package/src/shared/meta-data.ts +28 -0
- package/src/source.ts +99 -0
- package/src/subtype-rules.ts +1 -1
- package/src/super-resolve.ts +3 -1
- package/src/template/template-constants.ts +23 -6
- package/src/template/template-schema.ts +43 -0
- package/src/core/file-meta-data-loader.ts +0 -89
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// FR5b — YAML authoring source-position carrier (per ADR-0009).
|
|
2
|
+
//
|
|
3
|
+
// This module is split into two layers so the browser bundle (which
|
|
4
|
+
// imports from src/index.ts) stays free of the Node-only `yaml` package:
|
|
5
|
+
//
|
|
6
|
+
// - yaml-positions.ts (this file) — pure types + Symbol + accessors. NO
|
|
7
|
+
// `yaml` import. Imported by parser-core.ts so the source-on-node
|
|
8
|
+
// stamper can read positions when stamping `format: "yaml"` envelopes.
|
|
9
|
+
// - yaml-positions-walker.ts — depends on `yaml`. Imported only by
|
|
10
|
+
// parser-yaml.ts (and parser-yaml itself only ships server-side).
|
|
11
|
+
//
|
|
12
|
+
// Source-map carrier (per the FR5b spec's "open question" §2): a
|
|
13
|
+
// Symbol-keyed, non-enumerable property on the wrapper-mapping object. The
|
|
14
|
+
// symbol is the well-known cross-port key
|
|
15
|
+
// `Symbol.for("@metaobjectsdev/yamlPositionByKey")`, so any plugin that
|
|
16
|
+
// touches the canonical JS can read positions if it knows to look. The
|
|
17
|
+
// map's keys are the wrapper's own keys (e.g. "object.entity" for a
|
|
18
|
+
// wrapper `{ "object.entity": { ... } }` or "name" / "package" /
|
|
19
|
+
// "children" for the body keys of a node).
|
|
20
|
+
//
|
|
21
|
+
// Rationale for "symbol-keyed property" over a parallel sourcemap / wrapper
|
|
22
|
+
// type:
|
|
23
|
+
// - Invisible to JSON.stringify and Object.keys (non-enumerable).
|
|
24
|
+
// - No parallel data structure to keep in sync — the position rides with
|
|
25
|
+
// the node it describes.
|
|
26
|
+
// - No wrapper type — desugar still operates on plain JS objects, so the
|
|
27
|
+
// existing Rule 1–5 logic does not need a rewrite.
|
|
28
|
+
//
|
|
29
|
+
// On desugar-synthesized nodes (Rule 2's scalar-body lift): the synthesized
|
|
30
|
+
// body `{ name: rawScalar }` inherits the wrapper key's position from the
|
|
31
|
+
// parent's position map. On any other synthesis (Rule 4's isArray stamping,
|
|
32
|
+
// for example), the position survives because we shallow-copy via the
|
|
33
|
+
// existing desugar path.
|
|
34
|
+
|
|
35
|
+
/** Cross-port well-known symbol key for the position-by-key map. */
|
|
36
|
+
export const YAML_POSITION_BY_KEY = Symbol.for(
|
|
37
|
+
"@metaobjectsdev/yamlPositionByKey",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/** A YAML source position — 1-indexed line and column. */
|
|
41
|
+
export interface YamlPosition {
|
|
42
|
+
readonly line: number;
|
|
43
|
+
readonly col: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The position-by-key map attached to a mapping object. */
|
|
47
|
+
export type PositionMap = Record<string, YamlPosition>;
|
|
48
|
+
|
|
49
|
+
/** Read the position-by-key map from a JS object, if present.
|
|
50
|
+
* Returns undefined for primitives, arrays, null, and untagged objects. */
|
|
51
|
+
export function getPositionMap(obj: unknown): PositionMap | undefined {
|
|
52
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return (obj as { [YAML_POSITION_BY_KEY]?: PositionMap })[YAML_POSITION_BY_KEY];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Read the position for a specific key on a mapping object. */
|
|
59
|
+
export function getYamlPosition(
|
|
60
|
+
obj: unknown,
|
|
61
|
+
key: string,
|
|
62
|
+
): YamlPosition | undefined {
|
|
63
|
+
const map = getPositionMap(obj);
|
|
64
|
+
return map?.[key];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Attach (or replace) the position-by-key map on a mapping object. The map
|
|
68
|
+
* property is non-enumerable so JSON.stringify and `for (const k in obj)`
|
|
69
|
+
* loops do not see it. */
|
|
70
|
+
export function setPositionMap(
|
|
71
|
+
obj: Record<string, unknown>,
|
|
72
|
+
positions: PositionMap,
|
|
73
|
+
): void {
|
|
74
|
+
Object.defineProperty(obj, YAML_POSITION_BY_KEY, {
|
|
75
|
+
value: positions,
|
|
76
|
+
enumerable: false,
|
|
77
|
+
writable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
package/src/core-types.ts
CHANGED
|
@@ -279,10 +279,13 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
|
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
// template — renderable text artifacts (FR-004)
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
282
|
+
// template — renderable text artifacts (FR-004) + tool-call envelopes
|
|
283
|
+
// (ADR-0011). Three subtypes: prompt + output + toolcall; attr-only children.
|
|
284
|
+
// A single MetaTemplate class backs all three subtypes (mirrors source);
|
|
285
|
+
// per-subtype attr schemas drive validation (prompt + output require
|
|
286
|
+
// @payloadRef + @textRef + @format closed enum; prompt adds the LLM overlay;
|
|
287
|
+
// toolcall has its own set — @toolName + @payloadRef + @description, no
|
|
288
|
+
// @textRef requirement since toolcalls have no renderable body).
|
|
286
289
|
for (const subType of TEMPLATE_SUBTYPES) {
|
|
287
290
|
const templateAttrs = TEMPLATE_ATTRS_MAP.get(subType) ?? [];
|
|
288
291
|
registry.register(
|
package/src/errors.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Typed error classes for the metadata parser.
|
|
2
2
|
|
|
3
|
+
import type { ErrorSource, LoaderError, NodeContext } from "./source.js";
|
|
4
|
+
|
|
3
5
|
/** Stable, language-neutral error codes — mirrors fixtures/conformance/ERROR-CODES.json. */
|
|
4
6
|
// NOTE: The following codes are forward-declared (no emitting site in the current
|
|
5
7
|
// TS parser/loader — the condition is not yet detected):
|
|
@@ -50,25 +52,72 @@ export const ERROR_CODES = [
|
|
|
50
52
|
// ADR-0006 D2 — YAML type-coercion guard. Emitted by every port's YAML
|
|
51
53
|
// loader when a coerced scalar mismatches the schema-declared type.
|
|
52
54
|
"ERR_YAML_COERCION",
|
|
55
|
+
// FR5c — multi-file overlay merge produced a conflicting attribute value:
|
|
56
|
+
// two contributors set the same @attr to different non-empty values.
|
|
57
|
+
"ERR_MERGE_CONFLICT",
|
|
53
58
|
"ERR_UNKNOWN",
|
|
54
59
|
] as const;
|
|
55
60
|
|
|
61
|
+
/** Warning codes — same envelope shape as errors but advisory. */
|
|
62
|
+
export const WARNING_CODES = [
|
|
63
|
+
// FR5c — two contributors declared the same node identically (no semantic
|
|
64
|
+
// change). Emitted at the overlay-merge boundary.
|
|
65
|
+
"WARN_DUPLICATE_DECLARATION",
|
|
66
|
+
// Pre-FR5c legacy: parser/validator messages still surface as plain
|
|
67
|
+
// strings; wrapped at the loader boundary into the envelope shape with
|
|
68
|
+
// this code. Retired as those sites are migrated to envelopes.
|
|
69
|
+
"WARN_LEGACY",
|
|
70
|
+
] as const;
|
|
71
|
+
export type WarningCode = (typeof WARNING_CODES)[number];
|
|
72
|
+
|
|
56
73
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Loader error carrying the ADR-0009 LoaderError envelope.
|
|
77
|
+
*
|
|
78
|
+
* Public shape (FR5a):
|
|
79
|
+
* new ParseError(message, { code, source, suggestions?, fixture?, node? })
|
|
80
|
+
*
|
|
81
|
+
* - `code` and `source` are required.
|
|
82
|
+
* - `source` is the ErrorSource discriminated union (json/yaml/merged/resolved/
|
|
83
|
+
* database/code) — the same envelope every cross-language port emits.
|
|
84
|
+
* - `suggestions[]`, `fixture`, `node` are optional per ADR-0009 §RECOMMENDED;
|
|
85
|
+
* FR5a does not populate them, FR5b–FR5e may.
|
|
86
|
+
*
|
|
87
|
+
* Legacy fields (`path?: string`, `source?: string`) were superseded by the
|
|
88
|
+
* envelope's `jsonPath` and `files` and have been dropped — see CHANGELOG.
|
|
89
|
+
*/
|
|
90
|
+
export class ParseError extends Error implements LoaderError {
|
|
91
|
+
readonly code: ErrorCode;
|
|
92
|
+
readonly source: ErrorSource;
|
|
93
|
+
readonly suggestions?: string[];
|
|
94
|
+
readonly fixture?: string;
|
|
95
|
+
readonly node?: NodeContext;
|
|
62
96
|
|
|
63
97
|
constructor(
|
|
64
98
|
message: string,
|
|
65
|
-
opts
|
|
99
|
+
opts: {
|
|
100
|
+
code: ErrorCode;
|
|
101
|
+
source: ErrorSource;
|
|
102
|
+
suggestions?: string[];
|
|
103
|
+
fixture?: string;
|
|
104
|
+
node?: NodeContext;
|
|
105
|
+
},
|
|
66
106
|
) {
|
|
67
107
|
super(message);
|
|
68
108
|
this.name = "ParseError";
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
71
|
-
|
|
109
|
+
this.code = opts.code;
|
|
110
|
+
this.source = opts.source;
|
|
111
|
+
// exactOptionalPropertyTypes: only assign when defined.
|
|
112
|
+
if (opts.suggestions !== undefined) {
|
|
113
|
+
(this as { suggestions?: string[] }).suggestions = opts.suggestions;
|
|
114
|
+
}
|
|
115
|
+
if (opts.fixture !== undefined) {
|
|
116
|
+
(this as { fixture?: string }).fixture = opts.fixture;
|
|
117
|
+
}
|
|
118
|
+
if (opts.node !== undefined) {
|
|
119
|
+
(this as { node?: NodeContext }).node = opts.node;
|
|
120
|
+
}
|
|
72
121
|
}
|
|
73
122
|
}
|
|
74
123
|
|
package/src/index.ts
CHANGED
|
@@ -150,13 +150,37 @@ export { resolveSuperRef } from "./super-resolve.js";
|
|
|
150
150
|
// Loader hierarchy
|
|
151
151
|
export { MetaDataLoader } from "./loader/meta-data-loader.js";
|
|
152
152
|
export type { LoadOptions, LoadResult, LoadingState } from "./loader/meta-data-loader.js";
|
|
153
|
-
export {
|
|
153
|
+
export { InMemoryStringSource } from "./loader/meta-data-source.js";
|
|
154
154
|
export type { MetaDataSource, MetaDataFormat } from "./loader/meta-data-source.js";
|
|
155
155
|
|
|
156
|
+
// Module-level loader shortcuts — delegate to MetaDataLoader.from* static
|
|
157
|
+
// factories. The shortcuts honor the ergonomic per-port pattern (TS and
|
|
158
|
+
// Python expose both class statics AND module-level functions); Java/C# stay
|
|
159
|
+
// class-only. Browser safety is preserved: the underlying sources are
|
|
160
|
+
// loaded via dynamic import inside MetaDataLoader.from*.
|
|
161
|
+
export {
|
|
162
|
+
loadDirectory,
|
|
163
|
+
loadUris,
|
|
164
|
+
loadString,
|
|
165
|
+
} from "./loader/shortcuts.js";
|
|
166
|
+
|
|
156
167
|
// Errors
|
|
157
168
|
export { ParseError, MetaModelError, ERROR_CODES } from "./errors.js";
|
|
158
169
|
export type { ErrorCode } from "./errors.js";
|
|
159
170
|
|
|
171
|
+
// FR5a — loader error envelope + source-on-node (ADR-0009).
|
|
172
|
+
// Re-exported from the package root so consumers that catch + repackage
|
|
173
|
+
// ParseErrors (or narrow on `err.source.format === "json"`) can import the
|
|
174
|
+
// envelope types alongside the runtime discriminator.
|
|
175
|
+
export type {
|
|
176
|
+
ErrorSource,
|
|
177
|
+
LoaderError,
|
|
178
|
+
LoaderWarning,
|
|
179
|
+
NodeContext,
|
|
180
|
+
Contributor,
|
|
181
|
+
} from "./source.js";
|
|
182
|
+
export { codeSource } from "./source.js";
|
|
183
|
+
|
|
160
184
|
// Attribute-schema validation pass (Phase A3)
|
|
161
185
|
export { validateAttrSchema } from "./attr-schema-validate.js";
|
|
162
186
|
export type { AttrSchemaValidationResult } from "./attr-schema-validate.js";
|
|
@@ -164,9 +188,10 @@ export type { AttrSchemaValidationResult } from "./attr-schema-validate.js";
|
|
|
164
188
|
// Naming — hoisted from runtime-ts in v0.2.3 so multiple consumers (runtime-ts, migrate-ts, codegen-ts)
|
|
165
189
|
// share identical name resolution. See spec §4.1.
|
|
166
190
|
export {
|
|
167
|
-
toSnakeCase, pluralize,
|
|
191
|
+
toSnakeCase, toKebabCase, pluralize,
|
|
192
|
+
applyColumnNamingStrategy, DEFAULT_COLUMN_NAMING_STRATEGY,
|
|
168
193
|
resolveTableName, resolveColumnName, resolveTableSchema,
|
|
169
194
|
buildNameMap,
|
|
170
195
|
stripPackage,
|
|
171
196
|
} from "./naming.js";
|
|
172
|
-
export type { EntityNameMap } from "./naming.js";
|
|
197
|
+
export type { EntityNameMap, ColumnNamingStrategy } from "./naming.js";
|
package/src/json-path.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// server/typescript/packages/metadata/src/json-path.ts
|
|
2
|
+
//
|
|
3
|
+
// FR5a / ADR-0009 — Canonical JSONPath builder.
|
|
4
|
+
//
|
|
5
|
+
// Construction rules (cross-port-aligned):
|
|
6
|
+
// - Root is `$`.
|
|
7
|
+
// - Object keys matching /^[A-Za-z_][A-Za-z0-9_]*$/ use dot notation: `.foo`.
|
|
8
|
+
// - All other keys use single-quoted bracket form: `['my-key']`, `['@attr']`.
|
|
9
|
+
// - Array indices use bracket form: `[N]`.
|
|
10
|
+
// - No trailing dots, no whitespace.
|
|
11
|
+
|
|
12
|
+
const IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
13
|
+
|
|
14
|
+
type Segment =
|
|
15
|
+
| { kind: "key"; value: string }
|
|
16
|
+
| { kind: "index"; value: number };
|
|
17
|
+
|
|
18
|
+
export class JsonPathBuilder {
|
|
19
|
+
private readonly segments: Segment[] = [];
|
|
20
|
+
|
|
21
|
+
pushKey(key: string): void {
|
|
22
|
+
this.segments.push({ kind: "key", value: key });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pushIndex(idx: number): void {
|
|
26
|
+
this.segments.push({ kind: "index", value: idx });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pop(): void {
|
|
30
|
+
this.segments.pop();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
toString(): string {
|
|
34
|
+
let out = "$";
|
|
35
|
+
for (const seg of this.segments) {
|
|
36
|
+
if (seg.kind === "index") {
|
|
37
|
+
out += `[${seg.value}]`;
|
|
38
|
+
} else if (IDENT_RE.test(seg.value)) {
|
|
39
|
+
out += `.${seg.value}`;
|
|
40
|
+
} else {
|
|
41
|
+
out += `['${seg.value.replace(/'/g, "\\'")}']`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -14,15 +14,34 @@ import { coreProviders } from "../core-types.js";
|
|
|
14
14
|
import { composeRegistry } from "../provider.js";
|
|
15
15
|
import { TYPE_METADATA, SUBTYPE_ROOT } from "../shared/base-types.js";
|
|
16
16
|
import { ParseError } from "../errors.js";
|
|
17
|
+
import type { LoaderWarning } from "../source.js";
|
|
18
|
+
import { codeSource, resolvedSource } from "../source.js";
|
|
17
19
|
import { parseJson } from "../parser-json.js";
|
|
18
20
|
import { validateDataGridSortFields, validateFilterableHasIndex, validateOriginPaths, validateDataGridFilterValues, validateFieldObjectStorage, validateTemplatePayloadRefs } from "./validation-passes.js";
|
|
19
21
|
import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
|
|
20
22
|
import { resolveDeferredSupers } from "../super-resolve.js";
|
|
21
23
|
import { validateSubtypeRules } from "../subtype-rules.js";
|
|
22
24
|
import { validateAttrSchema } from "../attr-schema-validate.js";
|
|
23
|
-
import type { MetaDataSource } from "./meta-data-source.js";
|
|
25
|
+
import type { MetaDataFormat, MetaDataSource } from "./meta-data-source.js";
|
|
26
|
+
import { InMemoryStringSource } from "./meta-data-source.js";
|
|
24
27
|
import type { ParseOptions, ParseResult } from "../parser-core.js";
|
|
25
28
|
|
|
29
|
+
// Local mirror of DirectorySource's options shape. Deliberately inlined here
|
|
30
|
+
// (instead of `import type`'d from ./sources/directory-source.js) so the
|
|
31
|
+
// browser-safety crawler — which walks every `import|export from` it sees,
|
|
32
|
+
// type-only or not — never follows a path into a node:fs-using file.
|
|
33
|
+
// Keep field-for-field in sync with `DirectoryOptions` in `./sources/directory-source.ts`.
|
|
34
|
+
type DirectoryFactoryOptions = {
|
|
35
|
+
exclude?: string[];
|
|
36
|
+
recurse?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// YAML parser and node:fs-backed Source impls are loaded lazily (dynamic
|
|
40
|
+
// import) inside the methods that need them. Reason: the package root
|
|
41
|
+
// (src/index.ts) re-exports MetaDataLoader and must stay browser-safe —
|
|
42
|
+
// the browser-safety test asserts that no file reachable from index.ts
|
|
43
|
+
// statically imports `yaml` or `node:fs(/promises)`.
|
|
44
|
+
|
|
26
45
|
// ---------------------------------------------------------------------------
|
|
27
46
|
// Public API types
|
|
28
47
|
// ---------------------------------------------------------------------------
|
|
@@ -41,7 +60,13 @@ export interface LoadOptions {
|
|
|
41
60
|
|
|
42
61
|
export interface LoadResult {
|
|
43
62
|
root: MetaRoot;
|
|
44
|
-
|
|
63
|
+
/** Cross-port-aligned warning envelopes per ADR-0009.
|
|
64
|
+
* FR5a creates the channel; FR5c (overlay-merge duplicate detection)
|
|
65
|
+
* will be the first feature to populate it. Legacy string warnings
|
|
66
|
+
* collected during parse/validation are wrapped at the loader boundary
|
|
67
|
+
* with `code: "WARN_LEGACY"` and `source: { format: "code" }` so the
|
|
68
|
+
* channel always presents the envelope shape to consumers. */
|
|
69
|
+
warnings: LoaderWarning[];
|
|
45
70
|
errors: Error[];
|
|
46
71
|
}
|
|
47
72
|
|
|
@@ -75,6 +100,71 @@ export class MetaDataLoader {
|
|
|
75
100
|
return composeRegistry(coreProviders);
|
|
76
101
|
}
|
|
77
102
|
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Static factories — the 99% case (cross-language consistent)
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load every supported file (`.json` / `.yaml` / `.yml`) under `dir` in
|
|
109
|
+
* deterministic ordinal-basename order. Recurses by default.
|
|
110
|
+
*
|
|
111
|
+
* Convenience for the typical "load a directory of metadata" path. The
|
|
112
|
+
* `DirectorySource` impl is loaded lazily to keep the package root
|
|
113
|
+
* browser-safe (the underlying source uses node:fs).
|
|
114
|
+
*
|
|
115
|
+
* A missing/unreadable directory is surfaced as a collected entry in
|
|
116
|
+
* `result.errors`; the loader returns a synthetic empty root rather than
|
|
117
|
+
* throwing — preserves the `meta export` CLI exit-code contract.
|
|
118
|
+
*/
|
|
119
|
+
static async fromDirectory(
|
|
120
|
+
dir: string,
|
|
121
|
+
opts?: DirectoryFactoryOptions & LoadOptions,
|
|
122
|
+
): Promise<LoadResult> {
|
|
123
|
+
const { exclude, recurse, ...loaderOpts } = opts ?? {};
|
|
124
|
+
// Conditional spreads honor exactOptionalPropertyTypes — only forward keys
|
|
125
|
+
// when the caller supplied a value, so DirectorySource's own defaults apply.
|
|
126
|
+
const dirOpts: DirectoryFactoryOptions = {
|
|
127
|
+
...(exclude !== undefined && { exclude }),
|
|
128
|
+
...(recurse !== undefined && { recurse }),
|
|
129
|
+
};
|
|
130
|
+
const { DirectorySource } = await import("./sources/directory-source.js");
|
|
131
|
+
const loader = new MetaDataLoader(loaderOpts);
|
|
132
|
+
try {
|
|
133
|
+
const sources = await new DirectorySource(dir, dirOpts).expand();
|
|
134
|
+
return loader.load(sources);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
// Match the pre-unification contract: a missing/unreadable directory is
|
|
137
|
+
// surfaced as a collected error on the LoadResult, not a throw. The
|
|
138
|
+
// pipeline still completes with a synthetic empty root.
|
|
139
|
+
const emptyResult = await loader.load([]);
|
|
140
|
+
const expandErr =
|
|
141
|
+
err instanceof Error
|
|
142
|
+
? err
|
|
143
|
+
: new Error(`MetaDataLoader.fromDirectory: ${String(err)}`);
|
|
144
|
+
return { ...emptyResult, errors: [expandErr, ...emptyResult.errors] };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Load each URI as a {@link UriSource}. Supports `file://`, `http://`,
|
|
150
|
+
* `https://` schemes. The source impl is loaded lazily to keep the package
|
|
151
|
+
* root browser-safe.
|
|
152
|
+
*/
|
|
153
|
+
static async fromUris(uris: string[], opts?: LoadOptions): Promise<LoadResult> {
|
|
154
|
+
const { UriSource } = await import("./sources/uri-source.js");
|
|
155
|
+
const sources = uris.map((u) => new UriSource(u));
|
|
156
|
+
return new MetaDataLoader(opts).load(sources);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Load a single in-memory string of the given format. */
|
|
160
|
+
static async fromString(
|
|
161
|
+
content: string,
|
|
162
|
+
format: MetaDataFormat,
|
|
163
|
+
opts?: LoadOptions,
|
|
164
|
+
): Promise<LoadResult> {
|
|
165
|
+
return new MetaDataLoader(opts).load([new InMemoryStringSource(content, { format })]);
|
|
166
|
+
}
|
|
167
|
+
|
|
78
168
|
// ---------------------------------------------------------------------------
|
|
79
169
|
// Lifecycle
|
|
80
170
|
// ---------------------------------------------------------------------------
|
|
@@ -156,10 +246,16 @@ export class MetaDataLoader {
|
|
|
156
246
|
// ---------------------------------------------------------------------------
|
|
157
247
|
|
|
158
248
|
/**
|
|
159
|
-
* Parse one source's raw content into a ParseResult.
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
249
|
+
* Parse one source's raw content into a ParseResult. Dispatches on the
|
|
250
|
+
* source's declared `format` — `"json"` runs the canonical JSON parser,
|
|
251
|
+
* `"yaml"` desugars the authoring YAML into canonical JSON via parseYaml.
|
|
252
|
+
* Cross-language consistent: the same format vocabulary is honored by the
|
|
253
|
+
* Java / C# / Python MetaDataLoaders.
|
|
254
|
+
*
|
|
255
|
+
* The YAML parser is loaded lazily so the browser-safe root entry never
|
|
256
|
+
* statically pulls in the `yaml` dependency — see the module-header comment.
|
|
257
|
+
* `parseYaml` is preloaded inside `load()` if any source declares YAML
|
|
258
|
+
* format so the call here can stay synchronous.
|
|
163
259
|
*/
|
|
164
260
|
protected parseSource(
|
|
165
261
|
content: string,
|
|
@@ -169,12 +265,35 @@ export class MetaDataLoader {
|
|
|
169
265
|
if (source.format === "json") {
|
|
170
266
|
return parseJson(content, parseOpts);
|
|
171
267
|
}
|
|
268
|
+
if (source.format === "yaml") {
|
|
269
|
+
const fn = MetaDataLoader._yamlParser;
|
|
270
|
+
if (fn === undefined) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`MetaDataLoader: YAML parser was not preloaded — this is an internal bug. ` +
|
|
273
|
+
`Source "${source.id}" declares format "yaml".`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return fn(content, parseOpts);
|
|
277
|
+
}
|
|
172
278
|
throw new Error(
|
|
173
|
-
`MetaDataLoader
|
|
174
|
-
`"${source.id}"
|
|
279
|
+
`MetaDataLoader: unsupported source format "${source.format}" ` +
|
|
280
|
+
`on source "${source.id}"`,
|
|
175
281
|
);
|
|
176
282
|
}
|
|
177
283
|
|
|
284
|
+
// Cached lazy YAML parser — populated by _ensureYamlParser() before
|
|
285
|
+
// parseSource is invoked on a YAML source. Module-level cache (one import
|
|
286
|
+
// per process) keeps the per-load cost negligible.
|
|
287
|
+
private static _yamlParser:
|
|
288
|
+
| ((content: string, opts: ParseOptions) => ParseResult)
|
|
289
|
+
| undefined;
|
|
290
|
+
|
|
291
|
+
private static async _ensureYamlParser(): Promise<void> {
|
|
292
|
+
if (MetaDataLoader._yamlParser !== undefined) return;
|
|
293
|
+
const mod = await import("../core/parser-yaml.js");
|
|
294
|
+
MetaDataLoader._yamlParser = mod.parseYaml;
|
|
295
|
+
}
|
|
296
|
+
|
|
178
297
|
// ---------------------------------------------------------------------------
|
|
179
298
|
// load — async pipeline over MetaDataSource[]
|
|
180
299
|
// ---------------------------------------------------------------------------
|
|
@@ -202,6 +321,19 @@ export class MetaDataLoader {
|
|
|
202
321
|
this._state = "loading";
|
|
203
322
|
const warnings: string[] = [];
|
|
204
323
|
const errors: Error[] = [];
|
|
324
|
+
// FR5c — envelope-shaped warnings (WARN_DUPLICATE_DECLARATION et al.)
|
|
325
|
+
// surface here untouched. Distinct from the legacy `warnings: string[]`
|
|
326
|
+
// channel: those are wrapped in a WARN_LEGACY envelope at the boundary,
|
|
327
|
+
// while these already carry their own code + source.
|
|
328
|
+
const envelopeWarnings: LoaderWarning[] = [];
|
|
329
|
+
|
|
330
|
+
// Pre-load the YAML parser via dynamic import if any source declares
|
|
331
|
+
// YAML format. This keeps `parseSource` synchronous and the package root
|
|
332
|
+
// (src/index.ts) browser-safe — yaml is never statically imported from a
|
|
333
|
+
// file reachable from the package entry. See `_ensureYamlParser`.
|
|
334
|
+
if (sources.some((s) => s.format === "yaml")) {
|
|
335
|
+
await MetaDataLoader._ensureYamlParser();
|
|
336
|
+
}
|
|
205
337
|
|
|
206
338
|
let root: MetaRoot | undefined;
|
|
207
339
|
|
|
@@ -236,6 +368,10 @@ export class MetaDataLoader {
|
|
|
236
368
|
const parseResult = this.parseSource(content, source, parseOpts);
|
|
237
369
|
warnings.push(...parseResult.warnings);
|
|
238
370
|
errors.push(...parseResult.errors);
|
|
371
|
+
// FR5c — collect envelope-shaped warnings (already carry code +
|
|
372
|
+
// source). The legacy `warnings` channel still flows into the
|
|
373
|
+
// WARN_LEGACY-wrapping path below for unchanged behavior.
|
|
374
|
+
envelopeWarnings.push(...parseResult.envelopeWarnings);
|
|
239
375
|
root = parseResult.root;
|
|
240
376
|
} catch (err) {
|
|
241
377
|
errors.push(
|
|
@@ -252,10 +388,17 @@ export class MetaDataLoader {
|
|
|
252
388
|
if (root !== undefined) {
|
|
253
389
|
const failures = resolveDeferredSupers(root);
|
|
254
390
|
for (const failure of failures) {
|
|
391
|
+
// FR5d — emit format=resolved with referrer + target. The referrer's
|
|
392
|
+
// parse-time source supplies files + jsonPath (the location of the
|
|
393
|
+
// broken `extends:` on disk); referrer = the declaring node's FQN;
|
|
394
|
+
// target = the unresolved supertype ref.
|
|
255
395
|
errors.push(
|
|
256
396
|
new ParseError(
|
|
257
397
|
`the SuperClass '${failure.ref}' does not exist (referenced by ${failure.nodeFqn})`,
|
|
258
|
-
{
|
|
398
|
+
{
|
|
399
|
+
code: "ERR_UNRESOLVED_SUPER",
|
|
400
|
+
source: resolvedSource(failure.source, failure.nodeFqn, failure.ref),
|
|
401
|
+
},
|
|
259
402
|
),
|
|
260
403
|
);
|
|
261
404
|
}
|
|
@@ -316,6 +459,21 @@ export class MetaDataLoader {
|
|
|
316
459
|
}
|
|
317
460
|
|
|
318
461
|
this._root = root;
|
|
319
|
-
|
|
462
|
+
// Wrap legacy string warnings collected from parser-core / validators in
|
|
463
|
+
// LoaderWarning envelopes at the loader boundary. The parser/validator
|
|
464
|
+
// surface keeps its `string[]` shape internally (parser-core is shared
|
|
465
|
+
// with parseJson() / parseYaml() callers who consume string warnings
|
|
466
|
+
// directly). FR5c-onward sites emit proper envelopes via parseResult.
|
|
467
|
+
// envelopeWarnings (collected above) — those surface unchanged.
|
|
468
|
+
const wrappedLegacy: LoaderWarning[] = warnings.map((msg) => ({
|
|
469
|
+
code: "WARN_LEGACY",
|
|
470
|
+
message: msg,
|
|
471
|
+
source: codeSource("MetaDataLoader"),
|
|
472
|
+
}));
|
|
473
|
+
return {
|
|
474
|
+
root,
|
|
475
|
+
warnings: [...envelopeWarnings, ...wrappedLegacy],
|
|
476
|
+
errors,
|
|
477
|
+
};
|
|
320
478
|
}
|
|
321
479
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// MetaDataSource — the raw-document unit consumed by the loader pipeline.
|
|
2
2
|
//
|
|
3
|
-
// A loader (
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// A loader (MetaDataLoader, fed by FileSource/DirectorySource/UriSource/
|
|
4
|
+
// InMemoryStringSource) parses each source's content. read() is async so file
|
|
5
|
+
// and URI sources can do I/O; InMemoryStringSource resolves immediately.
|
|
6
6
|
|
|
7
7
|
/** Format of a source's content. Selects the parser. */
|
|
8
8
|
export type MetaDataFormat = "json" | "yaml";
|
|
@@ -17,15 +17,19 @@ export interface MetaDataSource {
|
|
|
17
17
|
read(): Promise<string>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* A metadata source backed by an in-memory string. The default identity is
|
|
22
|
+
* `"<inline>"` — matches the cross-language convention shared by the Java /
|
|
23
|
+
* C# / Python ports.
|
|
24
|
+
*/
|
|
25
|
+
export class InMemoryStringSource implements MetaDataSource {
|
|
22
26
|
readonly id: string;
|
|
23
27
|
readonly format: MetaDataFormat;
|
|
24
28
|
private readonly _content: string;
|
|
25
29
|
|
|
26
30
|
constructor(content: string, opts?: { id?: string; format?: MetaDataFormat }) {
|
|
27
31
|
this._content = content;
|
|
28
|
-
this.id = opts?.id ?? "<
|
|
32
|
+
this.id = opts?.id ?? "<inline>";
|
|
29
33
|
this.format = opts?.format ?? "json";
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Module-level loader shortcuts — one-liners that delegate to the
|
|
2
|
+
// MetaDataLoader.from* static factories. Per the cross-language convention:
|
|
3
|
+
// TS and Python expose both class statics AND module-level shortcuts; Java
|
|
4
|
+
// and C# stay class-only.
|
|
5
|
+
//
|
|
6
|
+
// These functions do not statically import any node:fs- or yaml-using file;
|
|
7
|
+
// MetaDataLoader.from* loads the underlying source impls via dynamic import,
|
|
8
|
+
// preserving the package root's browser-safety contract.
|
|
9
|
+
|
|
10
|
+
import { MetaDataLoader } from "./meta-data-loader.js";
|
|
11
|
+
import type { LoadOptions, LoadResult } from "./meta-data-loader.js";
|
|
12
|
+
import type { MetaDataFormat } from "./meta-data-source.js";
|
|
13
|
+
|
|
14
|
+
export function loadDirectory(
|
|
15
|
+
dir: string,
|
|
16
|
+
opts?: { exclude?: string[]; recurse?: boolean } & LoadOptions,
|
|
17
|
+
): Promise<LoadResult> {
|
|
18
|
+
return MetaDataLoader.fromDirectory(dir, opts);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadUris(uris: string[], opts?: LoadOptions): Promise<LoadResult> {
|
|
22
|
+
return MetaDataLoader.fromUris(uris, opts);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function loadString(
|
|
26
|
+
content: string,
|
|
27
|
+
format: MetaDataFormat,
|
|
28
|
+
opts?: LoadOptions,
|
|
29
|
+
): Promise<LoadResult> {
|
|
30
|
+
return MetaDataLoader.fromString(content, format, opts);
|
|
31
|
+
}
|