@metaobjectsdev/metadata 0.7.0-rc.1 → 0.7.0-rc.3

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.
Files changed (42) hide show
  1. package/dist/core/parser-yaml.d.ts.map +1 -1
  2. package/dist/core/parser-yaml.js +24 -8
  3. package/dist/core/parser-yaml.js.map +1 -1
  4. package/dist/core/yaml-desugar.d.ts.map +1 -1
  5. package/dist/core/yaml-desugar.js +54 -5
  6. package/dist/core/yaml-desugar.js.map +1 -1
  7. package/dist/core/yaml-positions-walker.d.ts +21 -0
  8. package/dist/core/yaml-positions-walker.d.ts.map +1 -0
  9. package/dist/core/yaml-positions-walker.js +75 -0
  10. package/dist/core/yaml-positions-walker.js.map +1 -0
  11. package/dist/core/yaml-positions.d.ts +19 -0
  12. package/dist/core/yaml-positions.d.ts.map +1 -0
  13. package/dist/core/yaml-positions.js +60 -0
  14. package/dist/core/yaml-positions.js.map +1 -0
  15. package/dist/errors.d.ts +4 -1
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +13 -0
  18. package/dist/errors.js.map +1 -1
  19. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  20. package/dist/loader/meta-data-loader.js +26 -6
  21. package/dist/loader/meta-data-loader.js.map +1 -1
  22. package/dist/loader/validation-passes.d.ts.map +1 -1
  23. package/dist/loader/validation-passes.js +81 -17
  24. package/dist/loader/validation-passes.js.map +1 -1
  25. package/dist/parser-core.d.ts +16 -1
  26. package/dist/parser-core.d.ts.map +1 -1
  27. package/dist/parser-core.js +266 -8
  28. package/dist/parser-core.js.map +1 -1
  29. package/dist/source.d.ts +29 -1
  30. package/dist/source.d.ts.map +1 -1
  31. package/dist/source.js +25 -0
  32. package/dist/source.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/core/parser-yaml.ts +24 -8
  35. package/src/core/yaml-desugar.ts +58 -4
  36. package/src/core/yaml-positions-walker.ts +101 -0
  37. package/src/core/yaml-positions.ts +80 -0
  38. package/src/errors.ts +15 -0
  39. package/src/loader/meta-data-loader.ts +26 -6
  40. package/src/loader/validation-passes.ts +83 -20
  41. package/src/parser-core.ts +306 -10
  42. package/src/source.ts +40 -2
@@ -11,7 +11,7 @@
11
11
 
12
12
  import type { MetaData } from "../shared/meta-data.js";
13
13
  import { ParseError } from "../errors.js";
14
- import type { ErrorSource } from "../source.js";
14
+ import { resolvedSource, type ErrorSource } from "../source.js";
15
15
  import {
16
16
  TYPE_OBJECT,
17
17
  TYPE_FIELD,
@@ -25,6 +25,7 @@ import {
25
25
  TEMPLATE_ATTR_PAYLOAD_REF,
26
26
  TEMPLATE_ATTR_REQUIRED_SLOTS,
27
27
  } from "../template/template-constants.js";
28
+ import { OBJECT_SUBTYPE_VALUE } from "../core/object/object-constants.js";
28
29
  import {
29
30
  LAYOUT_SUBTYPE_DATA_GRID,
30
31
  LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD,
@@ -99,11 +100,16 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
99
100
  const payloadRef = tmpl.ownAttr(TEMPLATE_ATTR_PAYLOAD_REF);
100
101
  if (typeof payloadRef !== "string") continue; // absence handled by the required-attr schema check
101
102
  const payload = root.ownChildren().find((c) => c.type === TYPE_OBJECT && c.name === payloadRef);
102
- if (!payload) {
103
+ if (!payload || payload.subType !== OBJECT_SUBTYPE_VALUE) {
104
+ // FR5d — @payloadRef is a reference; emit format=resolved with
105
+ // referrer=template FQN, target=the unresolved payloadRef string.
103
106
  errors.push(
104
107
  new ParseError(
105
- `template "${tmpl.name}" @payloadRef "${payloadRef}" does not resolve to a known object in this model`,
106
- { code: "ERR_INVALID_TEMPLATE", source: tmpl.source },
108
+ `template "${tmpl.name}" @payloadRef "${payloadRef}" does not resolve to an object.value at root`,
109
+ {
110
+ code: "ERR_INVALID_TEMPLATE",
111
+ source: resolvedSource(tmpl.source, tmpl.fqn(), payloadRef),
112
+ },
107
113
  ),
108
114
  );
109
115
  continue;
@@ -115,11 +121,17 @@ export function validateTemplatePayloadRefs(root: MetaData): ParseError[] {
115
121
  const slotList = Array.isArray(slots) ? slots : typeof slots === "string" ? [slots] : [];
116
122
  for (const slot of slotList) {
117
123
  if (typeof slot === "string" && !fieldNames.has(slot)) {
124
+ // FR5d — @requiredSlots is a field-on-payload reference; emit
125
+ // format=resolved with referrer=template FQN, target=`payloadRef.slot`
126
+ // (the dotted ref that did not resolve to a payload field).
118
127
  errors.push(
119
128
  new ParseError(
120
129
  `template "${tmpl.name}" @requiredSlots "${slot}" is not a field on payload "${payloadRef}". ` +
121
130
  `Available fields: ${[...fieldNames].join(", ")}`,
122
- { code: "ERR_INVALID_TEMPLATE", source: tmpl.source },
131
+ {
132
+ code: "ERR_INVALID_TEMPLATE",
133
+ source: resolvedSource(tmpl.source, tmpl.fqn(), `${payloadRef}.${slot}`),
134
+ },
123
135
  ),
124
136
  );
125
137
  }
@@ -195,18 +207,28 @@ function _findRelationship(obj: MetaData, name: string): MetaData | undefined {
195
207
  function _validateFromPath(
196
208
  fromAttr: string,
197
209
  root: MetaData,
198
- projectionName: string,
210
+ projection: MetaData,
199
211
  fieldName: string,
200
212
  originSource: ErrorSource,
201
213
  errors: ParseError[],
202
214
  label: string = "origin.passthrough.@from",
203
215
  ): void {
216
+ const projectionName = projection.name;
217
+ // FR5d — referrer is `<projection-FQN>::<fieldName>` (the canonical
218
+ // "where the broken reference lives" identifier).
219
+ const referrer = `${projection.fqn()}::${fieldName}`;
204
220
  const dotIdx = fromAttr.indexOf(".");
205
221
  if (dotIdx < 1 || dotIdx === fromAttr.length - 1) {
222
+ // Malformed shape (not "Entity.field") — not a reference resolution
223
+ // failure per se, but emit format=resolved with target=the bad string
224
+ // so consumers see the same envelope shape across all FR5d sites.
206
225
  errors.push(
207
226
  new ParseError(
208
227
  `${label} "${fromAttr}" on ${projectionName}.${fieldName}: must be of form "Entity.field".`,
209
- { code: "ERR_INVALID_ORIGIN", source: originSource },
228
+ {
229
+ code: "ERR_INVALID_ORIGIN",
230
+ source: resolvedSource(originSource, referrer, fromAttr),
231
+ },
210
232
  ),
211
233
  );
212
234
  return;
@@ -215,20 +237,28 @@ function _validateFromPath(
215
237
  const targetFieldName = fromAttr.slice(dotIdx + 1);
216
238
  const sourceObj = _findObject(root, entityName);
217
239
  if (!sourceObj) {
240
+ // FR5d — entity half of the ref didn't resolve. target = full ref.
218
241
  errors.push(
219
242
  new ParseError(
220
243
  `${label} "${fromAttr}" on ${projectionName}.${fieldName}: no such entity "${entityName}".`,
221
- { code: "ERR_INVALID_ORIGIN", source: originSource },
244
+ {
245
+ code: "ERR_INVALID_ORIGIN",
246
+ source: resolvedSource(originSource, referrer, fromAttr),
247
+ },
222
248
  ),
223
249
  );
224
250
  return;
225
251
  }
226
252
  const sourceField = _findField(sourceObj, targetFieldName);
227
253
  if (!sourceField) {
254
+ // FR5d — entity resolved, field on it did not. target = full ref.
228
255
  errors.push(
229
256
  new ParseError(
230
257
  `${label} "${fromAttr}" on ${projectionName}.${fieldName}: no such field "${targetFieldName}" on ${entityName}.`,
231
- { code: "ERR_INVALID_ORIGIN", source: originSource },
258
+ {
259
+ code: "ERR_INVALID_ORIGIN",
260
+ source: resolvedSource(originSource, referrer, fromAttr),
261
+ },
232
262
  ),
233
263
  );
234
264
  }
@@ -237,17 +267,23 @@ function _validateFromPath(
237
267
  function _validateViaPath(
238
268
  viaAttr: string,
239
269
  root: MetaData,
240
- projectionName: string,
270
+ projection: MetaData,
241
271
  fieldName: string,
242
272
  originSource: ErrorSource,
243
273
  errors: ParseError[],
244
274
  ): void {
275
+ const projectionName = projection.name;
276
+ // FR5d — referrer is `<projection-FQN>::<fieldName>`.
277
+ const referrer = `${projection.fqn()}::${fieldName}`;
245
278
  const segments = viaAttr.split(".");
246
279
  if (segments.length < 2) {
247
280
  errors.push(
248
281
  new ParseError(
249
282
  `origin.@via "${viaAttr}" on ${projectionName}.${fieldName}: must be of form "Entity.relationship[.relationship...]".`,
250
- { code: "ERR_INVALID_ORIGIN", source: originSource },
283
+ {
284
+ code: "ERR_INVALID_ORIGIN",
285
+ source: resolvedSource(originSource, referrer, viaAttr),
286
+ },
251
287
  ),
252
288
  );
253
289
  return;
@@ -258,18 +294,32 @@ function _validateViaPath(
258
294
  errors.push(
259
295
  new ParseError(
260
296
  `origin.@via "${viaAttr}" on ${projectionName}.${fieldName}: no such entity "${entityName}".`,
261
- { code: "ERR_INVALID_ORIGIN", source: originSource },
297
+ {
298
+ code: "ERR_INVALID_ORIGIN",
299
+ source: resolvedSource(originSource, referrer, viaAttr),
300
+ },
262
301
  ),
263
302
  );
264
303
  return;
265
304
  }
305
+ // FR5d — track the deepest-valid-prefix as we walk. The prefix grows
306
+ // segment-by-segment; on a hop failure the error message names the prefix
307
+ // that DID resolve, so authors can fix multi-hop typos quickly.
308
+ // After the entity lookup above, the deepest valid prefix is just the
309
+ // entity name; each successful relationship hop appends a segment.
310
+ const validSegments: string[] = [entityName];
266
311
  for (const relName of relSegments) {
267
312
  const rel = _findRelationship(currentObj, relName);
268
313
  if (!rel) {
314
+ const prefix = validSegments.join(".");
269
315
  errors.push(
270
316
  new ParseError(
271
- `origin.@via "${viaAttr}" on ${projectionName}.${fieldName}: no such relationship "${relName}" on ${currentObj.name}.`,
272
- { code: "ERR_INVALID_ORIGIN", source: originSource },
317
+ `origin.@via "${viaAttr}" on ${projectionName}.${fieldName}: no such relationship "${relName}" on ${currentObj.name}. ` +
318
+ `Deepest valid prefix was "${prefix}".`,
319
+ {
320
+ code: "ERR_INVALID_ORIGIN",
321
+ source: resolvedSource(originSource, referrer, viaAttr),
322
+ },
273
323
  ),
274
324
  );
275
325
  return;
@@ -279,21 +329,32 @@ function _validateViaPath(
279
329
  errors.push(
280
330
  new ParseError(
281
331
  `origin.@via "${viaAttr}" on ${projectionName}.${fieldName}: relationship "${relName}" on ${currentObj.name} is missing @objectRef.`,
282
- { code: "ERR_INVALID_ORIGIN", source: originSource },
332
+ {
333
+ code: "ERR_INVALID_ORIGIN",
334
+ source: resolvedSource(originSource, referrer, viaAttr),
335
+ },
283
336
  ),
284
337
  );
285
338
  return;
286
339
  }
287
340
  const nextObj = _findObject(root, refTarget);
288
341
  if (!nextObj) {
342
+ // FR5d — relationship's @objectRef points at a missing entity. This
343
+ // is the @objectRef-resolution edge of the via-path walk (the "5th
344
+ // site" in FR5d's scope list for @objectRef references encountered
345
+ // transitively).
289
346
  errors.push(
290
347
  new ParseError(
291
348
  `origin.@via "${viaAttr}" on ${projectionName}.${fieldName}: relationship "${relName}" points to non-existent entity "${refTarget}".`,
292
- { code: "ERR_INVALID_ORIGIN", source: originSource },
349
+ {
350
+ code: "ERR_INVALID_ORIGIN",
351
+ source: resolvedSource(originSource, referrer, refTarget),
352
+ },
293
353
  ),
294
354
  );
295
355
  return;
296
356
  }
357
+ validSegments.push(relName);
297
358
  currentObj = nextObj;
298
359
  }
299
360
  }
@@ -306,6 +367,8 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
306
367
  if (origin.subType === ORIGIN_SUBTYPE_PASSTHROUGH) {
307
368
  const from = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_FROM);
308
369
  if (typeof from !== "string" || from === "") {
370
+ // Missing-attr (not a reference resolution failure) — keep the
371
+ // node's own source envelope (json/yaml/merged).
309
372
  errors.push(
310
373
  new ParseError(
311
374
  `origin.passthrough on ${obj.name}.${field.name}: missing @from.`,
@@ -314,10 +377,10 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
314
377
  );
315
378
  continue;
316
379
  }
317
- _validateFromPath(from, root, obj.name, field.name, origin.source, errors);
380
+ _validateFromPath(from, root, obj, field.name, origin.source, errors);
318
381
  const via = origin.ownAttr(ORIGIN_PASSTHROUGH_ATTR_VIA);
319
382
  if (typeof via === "string" && via !== "") {
320
- _validateViaPath(via, root, obj.name, field.name, origin.source, errors);
383
+ _validateViaPath(via, root, obj, field.name, origin.source, errors);
321
384
  }
322
385
  } else if (origin.subType === ORIGIN_SUBTYPE_AGGREGATE) {
323
386
  const of_ = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_OF);
@@ -330,7 +393,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
330
393
  );
331
394
  continue;
332
395
  }
333
- _validateFromPath(of_, root, obj.name, field.name, origin.source, errors, "origin.aggregate.@of");
396
+ _validateFromPath(of_, root, obj, field.name, origin.source, errors, "origin.aggregate.@of");
334
397
  const via = origin.ownAttr(ORIGIN_AGGREGATE_ATTR_VIA);
335
398
  if (typeof via !== "string" || via === "") {
336
399
  errors.push(
@@ -341,7 +404,7 @@ export function validateOriginPaths(root: MetaData): ParseError[] {
341
404
  );
342
405
  continue;
343
406
  }
344
- _validateViaPath(via, root, obj.name, field.name, origin.source, errors);
407
+ _validateViaPath(via, root, obj, field.name, origin.source, errors);
345
408
  }
346
409
  }
347
410
  }