halfcode-compiler.xnl 0.1.1 → 0.2.0

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.
@@ -1,2 +1,2 @@
1
- import { a as assertObjectOperationCatalog, c as resolveApplicationAssembly, i as assertObjectOperationCall, l as resourceRefToFqn, n as applicationAssemblyPackage, o as assertObjectOperationDefinition, r as assertExecuteObjectOperationRequest, s as loadApplicationAssembly, t as ObjectOperationContractError } from "./src-Dv8bfFUo.js";
1
+ import { a as assertObjectOperationCatalog, c as resolveApplicationAssembly, i as assertObjectOperationCall, l as resourceRefToFqn, n as applicationAssemblyPackage, o as assertObjectOperationDefinition, r as assertExecuteObjectOperationRequest, s as loadApplicationAssembly, t as ObjectOperationContractError } from "./src-DJr-I4Kc.js";
2
2
  export { ObjectOperationContractError, applicationAssemblyPackage, assertExecuteObjectOperationRequest, assertObjectOperationCall, assertObjectOperationCatalog, assertObjectOperationDefinition, loadApplicationAssembly, resolveApplicationAssembly, resourceRefToFqn };
@@ -1,4 +1,4 @@
1
- import { n as ResourceDescriptor } from "./index-Ba5Gt9iW.js";
1
+ import { r as ResourceDescriptor } from "./index-DqYap2u_.js";
2
2
  //#region ../resource-projection/src/index.d.ts
3
3
  interface ResourceRegistry {
4
4
  readonly byKind: ReadonlyMap<string, readonly ResourceDescriptor[]>;
@@ -35,8 +35,22 @@ interface ResourceRecord extends ResourceDescriptor {
35
35
  format: "xnl";
36
36
  node: ResourceNode;
37
37
  }
38
+ interface ResourceKindContract {
39
+ resourceKind: string;
40
+ sourceShapes: readonly SourceShape[];
41
+ currentApiVersion: string;
42
+ supportedApiVersions: readonly string[];
43
+ documentCardinality?: "one" | "many";
44
+ }
45
+ interface RegisteredKindDefinition extends ResourceKindContract {
46
+ resourceId: string;
47
+ requiredFiles: readonly string[];
48
+ documentCardinality: "one" | "many";
49
+ documentUri: string;
50
+ }
38
51
  interface ResourceRegistry {
39
52
  readonly byKind: ReadonlyMap<string, readonly ResourceRecord[]>;
53
+ readonly kindDefinitions: ReadonlyMap<string, RegisteredKindDefinition>;
40
54
  }
41
55
  interface ResourceTree {
42
56
  manifest: ResourceRecord;
@@ -64,4 +78,4 @@ declare class ResourceValidationError extends Error {
64
78
  declare function loadResourceTree(options: LoadResourceTreeOptions): Promise<ResourceTree>;
65
79
  declare function validateResourceTree(options: LoadResourceTreeOptions): Promise<ResourceDiagnostic[]>;
66
80
  //#endregion
67
- export { loadResourceTree as _, ResourceMetadata as a, ResourceRegistry as c, ResourceTreeBuildResult as d, ResourceValidationError as f, SourceShape as g, ResourceValueMap as h, ResourceIdentity as i, ResourceScalar as l, ResourceValueList as m, ResourceDescriptor as n, ResourceNode as o, ResourceValue as p, ResourceDiagnostic as r, ResourceRecord as s, LoadResourceTreeOptions as t, ResourceTree as u, validateResourceTree as v };
81
+ export { ResourceValueMap as _, ResourceIdentity as a, validateResourceTree as b, ResourceNode as c, ResourceScalar as d, ResourceTree as f, ResourceValueList as g, ResourceValue as h, ResourceDiagnostic as i, ResourceRecord as l, ResourceValidationError as m, RegisteredKindDefinition as n, ResourceKindContract as o, ResourceTreeBuildResult as p, ResourceDescriptor as r, ResourceMetadata as s, LoadResourceTreeOptions as t, ResourceRegistry as u, SourceShape as v, loadResourceTree as y };
@@ -1,5 +1,5 @@
1
1
  import { t as ApplicationAssembly } from "./index-FdimP_SL.js";
2
- import { t as ResourceRegistry } from "./index-DuRG4TQo.js";
2
+ import { t as ResourceRegistry } from "./index-DKwcVwK6.js";
3
3
  //#region ../compiler-skill/src/index.d.ts
4
4
  interface SkillDescriptor {
5
5
  name: string;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { A as ObjectTargets, B as assertExecuteObjectOperationRequest, C as ObjectOperationDefinition, D as ObjectOperationTargetDeclaration, E as ObjectOperationOwnerKind, F as ResourceSource, G as resolveApplicationAssembly, H as assertObjectOperationCatalog, I as SkillCapsuleResource, K as resourceRefToFqn, L as TextMaterial, M as ResolveApplicationAssemblyOptions, N as ResolvedAssemblyPortBinding, O as ObjectTargetKindDefinition, P as ResourceInclude, R as TextResource, S as ObjectOperationContractError, T as ObjectOperationHandler, U as assertObjectOperationDefinition, V as assertObjectOperationCall, W as loadApplicationAssembly, _ as ObjectInvocationMode, a as AssemblyResource, b as ObjectOperationCatalog, c as BusinessObjectSopResource, d as ExecuteObjectOperationRequest, f as JsonObject, g as ObjectInvocation, h as LoadApplicationAssemblyOptions, i as AssemblyPortBinding, j as PageObjectResource, k as ObjectTargetRef, l as CodeBinding, m as JsonValue, n as ApplicationScope, o as AuthoringModuleDescriptor, p as JsonPrimitive, r as AssemblyPort, s as BusinessObjectResource, t as ApplicationAssembly, u as ContractedCallableResource, v as ObjectOperationBehavior, w as ObjectOperationEffect, x as ObjectOperationConfig, y as ObjectOperationCall, z as applicationAssemblyPackage } from "./index-FdimP_SL.js";
2
- import { a as SkillReference, c as compilerSkillPackage, i as SkillDescriptor, n as CompileSkillCapsuleInput, o as compileResourceSkillCapsule, r as SkillCapsulePlan, s as compileSkillCapsule, t as CompileResourceSkillCapsuleInput } from "./index-D-cBhQVw.js";
2
+ import { a as SkillReference, c as compilerSkillPackage, i as SkillDescriptor, n as CompileSkillCapsuleInput, o as compileResourceSkillCapsule, r as SkillCapsulePlan, s as compileSkillCapsule, t as CompileResourceSkillCapsuleInput } from "./index-wU-iKJnW.js";
3
3
  export { type ApplicationAssembly, type ApplicationScope, type AssemblyPort, type AssemblyPortBinding, type AssemblyResource, type AuthoringModuleDescriptor, type BusinessObjectResource, type BusinessObjectSopResource, type CodeBinding, CompileResourceSkillCapsuleInput, CompileSkillCapsuleInput, type ContractedCallableResource, type ExecuteObjectOperationRequest, type JsonObject, type JsonPrimitive, type JsonValue, type LoadApplicationAssemblyOptions, type ObjectInvocation, type ObjectInvocationMode, type ObjectOperationBehavior, type ObjectOperationCall, type ObjectOperationCatalog, type ObjectOperationConfig, ObjectOperationContractError, type ObjectOperationDefinition, type ObjectOperationEffect, type ObjectOperationHandler, type ObjectOperationOwnerKind, type ObjectOperationTargetDeclaration, type ObjectTargetKindDefinition, type ObjectTargetRef, type ObjectTargets, type PageObjectResource, type ResolveApplicationAssemblyOptions, type ResolvedAssemblyPortBinding, type ResourceInclude, type ResourceSource, SkillCapsulePlan, type SkillCapsuleResource, SkillDescriptor, SkillReference, type TextMaterial, type TextResource, applicationAssemblyPackage, assertExecuteObjectOperationRequest, assertObjectOperationCall, assertObjectOperationCatalog, assertObjectOperationDefinition, compileResourceSkillCapsule, compileSkillCapsule, compilerSkillPackage, loadApplicationAssembly, resolveApplicationAssembly, resourceRefToFqn };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as assertObjectOperationCatalog, c as resolveApplicationAssembly, i as assertObjectOperationCall, l as resourceRefToFqn, n as applicationAssemblyPackage, o as assertObjectOperationDefinition, r as assertExecuteObjectOperationRequest, s as loadApplicationAssembly, t as ObjectOperationContractError } from "./src-Dv8bfFUo.js";
1
+ import { a as assertObjectOperationCatalog, c as resolveApplicationAssembly, i as assertObjectOperationCall, l as resourceRefToFqn, n as applicationAssemblyPackage, o as assertObjectOperationDefinition, r as assertExecuteObjectOperationRequest, s as loadApplicationAssembly, t as ObjectOperationContractError } from "./src-DJr-I4Kc.js";
2
2
  import "./application-assembly.js";
3
- import { n as compileSkillCapsule, r as compilerSkillPackage, t as compileResourceSkillCapsule } from "./src-DEuFHizY.js";
3
+ import { n as compileSkillCapsule, r as compilerSkillPackage, t as compileResourceSkillCapsule } from "./src-B36aAZZE.js";
4
4
  export { ObjectOperationContractError, applicationAssemblyPackage, assertExecuteObjectOperationRequest, assertObjectOperationCall, assertObjectOperationCatalog, assertObjectOperationDefinition, compileResourceSkillCapsule, compileSkillCapsule, compilerSkillPackage, loadApplicationAssembly, resolveApplicationAssembly, resourceRefToFqn };
@@ -1,9 +1,21 @@
1
- import { n as ResourceDescriptor } from "./index-Ba5Gt9iW.js";
1
+ import { o as ResourceKindContract, r as ResourceDescriptor } from "./index-DqYap2u_.js";
2
2
  //#region ../kind-definition/src/index.d.ts
3
- interface KindDefinition extends ResourceDescriptor {
3
+ interface KindDefinition extends ResourceDescriptor, ResourceKindContract {
4
4
  kind: "KindDefinition";
5
+ }
6
+ interface ResourceMigrationDefinition<T> {
7
+ id: string;
5
8
  resourceKind: string;
6
- sourceShapes: readonly ("file" | "directory" | "manifest")[];
9
+ fromApiVersion: string;
10
+ toApiVersion: string;
11
+ migrate: (resource: T) => T;
12
+ }
13
+ declare class ResourceMigrationRegistry<T> {
14
+ private readonly definitions;
15
+ private readonly ids;
16
+ register(definition: ResourceMigrationDefinition<T>): this;
17
+ plan(resourceKind: string, fromApiVersion: string, toApiVersion: string): readonly ResourceMigrationDefinition<T>[];
18
+ migrate(resourceKind: string, fromApiVersion: string, toApiVersion: string, resource: T): T;
7
19
  }
8
20
  declare const kindDefinitionPackage: {
9
21
  readonly role: "framework";
@@ -11,4 +23,4 @@ declare const kindDefinitionPackage: {
11
23
  readonly owns: "resource kind validation authority";
12
24
  };
13
25
  //#endregion
14
- export { KindDefinition, kindDefinitionPackage };
26
+ export { KindDefinition, ResourceMigrationDefinition, ResourceMigrationRegistry, kindDefinitionPackage };
@@ -1,8 +1,40 @@
1
1
  //#region ../kind-definition/src/index.ts
2
+ var ResourceMigrationRegistry = class {
3
+ definitions = [];
4
+ ids = /* @__PURE__ */ new Set();
5
+ register(definition) {
6
+ if (this.ids.has(definition.id)) throw new Error(`Duplicate migration id '${definition.id}'`);
7
+ if (!definition.id || !definition.resourceKind || !definition.fromApiVersion || !definition.toApiVersion) throw new Error("Migration definitions require id, resourceKind, fromApiVersion, and toApiVersion");
8
+ if (definition.fromApiVersion === definition.toApiVersion) throw new Error(`Migration '${definition.id}' must change apiVersion`);
9
+ this.ids.add(definition.id);
10
+ this.definitions.push(Object.freeze({ ...definition }));
11
+ return this;
12
+ }
13
+ plan(resourceKind, fromApiVersion, toApiVersion) {
14
+ if (fromApiVersion === toApiVersion) return [];
15
+ const steps = [];
16
+ const visited = /* @__PURE__ */ new Set();
17
+ let current = fromApiVersion;
18
+ while (current !== toApiVersion) {
19
+ if (visited.has(current)) throw new Error(`Migration cycle detected for Kind '${resourceKind}' at '${current}'`);
20
+ visited.add(current);
21
+ const candidates = this.definitions.filter((definition) => definition.resourceKind === resourceKind && definition.fromApiVersion === current);
22
+ if (candidates.length === 0) throw new Error(`No migration for Kind '${resourceKind}' from '${current}' to '${toApiVersion}'`);
23
+ if (candidates.length > 1) throw new Error(`Ambiguous migration for Kind '${resourceKind}' from '${current}': ${candidates.map((candidate) => candidate.id).join(", ")}`);
24
+ const step = candidates[0];
25
+ steps.push(step);
26
+ current = step.toApiVersion;
27
+ }
28
+ return Object.freeze(steps);
29
+ }
30
+ migrate(resourceKind, fromApiVersion, toApiVersion, resource) {
31
+ return this.plan(resourceKind, fromApiVersion, toApiVersion).reduce((value, step) => step.migrate(value), resource);
32
+ }
33
+ };
2
34
  const kindDefinitionPackage = {
3
35
  role: "framework",
4
36
  area: "kind-definition",
5
37
  owns: "resource kind validation authority"
6
38
  };
7
39
  //#endregion
8
- export { kindDefinitionPackage };
40
+ export { ResourceMigrationRegistry, kindDefinitionPackage };
@@ -1,2 +1,2 @@
1
- import { _ as loadResourceTree, a as ResourceMetadata, c as ResourceRegistry, d as ResourceTreeBuildResult, f as ResourceValidationError, g as SourceShape, h as ResourceValueMap, i as ResourceIdentity, l as ResourceScalar, m as ResourceValueList, n as ResourceDescriptor, o as ResourceNode, p as ResourceValue, r as ResourceDiagnostic, s as ResourceRecord, t as LoadResourceTreeOptions, u as ResourceTree, v as validateResourceTree } from "./index-Ba5Gt9iW.js";
2
- export { LoadResourceTreeOptions, ResourceDescriptor, ResourceDiagnostic, ResourceIdentity, ResourceMetadata, ResourceNode, ResourceRecord, ResourceRegistry, ResourceScalar, ResourceTree, ResourceTreeBuildResult, ResourceValidationError, ResourceValue, ResourceValueList, ResourceValueMap, SourceShape, loadResourceTree, validateResourceTree };
1
+ import { _ as ResourceValueMap, a as ResourceIdentity, b as validateResourceTree, c as ResourceNode, d as ResourceScalar, f as ResourceTree, g as ResourceValueList, h as ResourceValue, i as ResourceDiagnostic, l as ResourceRecord, m as ResourceValidationError, n as RegisteredKindDefinition, o as ResourceKindContract, p as ResourceTreeBuildResult, r as ResourceDescriptor, s as ResourceMetadata, t as LoadResourceTreeOptions, u as ResourceRegistry, v as SourceShape, y as loadResourceTree } from "./index-DqYap2u_.js";
2
+ export { LoadResourceTreeOptions, RegisteredKindDefinition, ResourceDescriptor, ResourceDiagnostic, ResourceIdentity, ResourceKindContract, ResourceMetadata, ResourceNode, ResourceRecord, ResourceRegistry, ResourceScalar, ResourceTree, ResourceTreeBuildResult, ResourceValidationError, ResourceValue, ResourceValueList, ResourceValueMap, SourceShape, loadResourceTree, validateResourceTree };
@@ -1,2 +1,2 @@
1
- import { n as loadResourceTree, r as validateResourceTree, t as ResourceValidationError } from "./src-COEMEqZw.js";
1
+ import { n as loadResourceTree, r as validateResourceTree, t as ResourceValidationError } from "./src-xibObw5e.js";
2
2
  export { ResourceValidationError, loadResourceTree, validateResourceTree };
@@ -1,2 +1,2 @@
1
- import { n as resourceProjectionPackage, t as ResourceRegistry } from "./index-DuRG4TQo.js";
1
+ import { n as resourceProjectionPackage, t as ResourceRegistry } from "./index-DKwcVwK6.js";
2
2
  export { ResourceRegistry, resourceProjectionPackage };
@@ -1,2 +1,2 @@
1
- import { a as SkillReference, c as compilerSkillPackage, i as SkillDescriptor, n as CompileSkillCapsuleInput, o as compileResourceSkillCapsule, r as SkillCapsulePlan, s as compileSkillCapsule, t as CompileResourceSkillCapsuleInput } from "./index-D-cBhQVw.js";
1
+ import { a as SkillReference, c as compilerSkillPackage, i as SkillDescriptor, n as CompileSkillCapsuleInput, o as compileResourceSkillCapsule, r as SkillCapsulePlan, s as compileSkillCapsule, t as CompileResourceSkillCapsuleInput } from "./index-wU-iKJnW.js";
2
2
  export { CompileResourceSkillCapsuleInput, CompileSkillCapsuleInput, SkillCapsulePlan, SkillDescriptor, SkillReference, compileResourceSkillCapsule, compileSkillCapsule, compilerSkillPackage };
@@ -1,2 +1,2 @@
1
- import { n as compileSkillCapsule, r as compilerSkillPackage, t as compileResourceSkillCapsule } from "./src-DEuFHizY.js";
1
+ import { n as compileSkillCapsule, r as compilerSkillPackage, t as compileResourceSkillCapsule } from "./src-B36aAZZE.js";
2
2
  export { compileResourceSkillCapsule, compileSkillCapsule, compilerSkillPackage };
@@ -1,4 +1,4 @@
1
- import { l as resourceRefToFqn, u as resolveObjectOperationCompilation } from "./src-Dv8bfFUo.js";
1
+ import { l as resourceRefToFqn, u as resolveObjectOperationCompilation } from "./src-DJr-I4Kc.js";
2
2
  import { n as parseResourceMappings, r as planResourceMappings, t as applyResourceMappingPlan } from "./src-BlKlpg0J.js";
3
3
  import { mkdir, rm, writeFile } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
@@ -1,4 +1,4 @@
1
- import { n as loadResourceTree } from "./src-COEMEqZw.js";
1
+ import { n as loadResourceTree } from "./src-xibObw5e.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { parse } from "yaml";
@@ -3,8 +3,9 @@ import { readFile, readdir, realpath, stat } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  //#region ../resource-core/src/xnl-loader.ts
5
5
  async function buildXnlResourceTree(options) {
6
+ const requestedRoot = resolve(options.rootDir);
6
7
  const context = {
7
- rootDir: resolve(options.rootDir),
8
+ rootDir: await realpath(requestedRoot).catch(() => requestedRoot),
8
9
  diagnostics: [],
9
10
  kindDefinitions: /* @__PURE__ */ new Map(),
10
11
  resources: [],
@@ -22,7 +23,10 @@ async function buildXnlResourceTree(options) {
22
23
  diagnostics: [],
23
24
  tree: {
24
25
  manifest,
25
- registry: { byKind },
26
+ registry: {
27
+ byKind,
28
+ kindDefinitions: context.kindDefinitions
29
+ },
26
30
  diagnostics: []
27
31
  }
28
32
  };
@@ -88,6 +92,7 @@ async function loadCatalog(catalog, manifestDir, context) {
88
92
  context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
89
93
  continue;
90
94
  }
95
+ validateResourceApiVersion(record, definition, context.diagnostics);
91
96
  await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
92
97
  validateIdentity(record, context);
93
98
  context.resources.push(record);
@@ -95,15 +100,25 @@ async function loadCatalog(catalog, manifestDir, context) {
95
100
  for (const nested of catalogs.filter((item) => item.kind !== "KindDefinition")) await loadCatalog(nested, dirname(file), context);
96
101
  continue;
97
102
  }
98
- const record = await loadXnlRecord(file, catalog.shape, context);
99
- if (!record) continue;
100
- if (record.kind !== catalog.kind) {
101
- context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
103
+ if (definition.documentCardinality === "many" && catalog.shape !== "single-file") {
104
+ context.diagnostics.push({
105
+ code: "RESOURCE_DOCUMENT_CARDINALITY_UNSUPPORTED",
106
+ location: catalog.location,
107
+ message: `Kind '${catalog.kind}' allows multiple document roots only in a single-file catalog.`
108
+ });
102
109
  continue;
103
110
  }
104
- await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
105
- validateIdentity(record, context);
106
- context.resources.push(record);
111
+ const records = await loadXnlRecords(file, catalog.shape, context, definition.documentCardinality);
112
+ for (const record of records) {
113
+ if (record.kind !== catalog.kind) {
114
+ context.diagnostics.push(kindMismatch(record.kind, catalog.kind, record.documentUri));
115
+ continue;
116
+ }
117
+ validateResourceApiVersion(record, definition, context.diagnostics);
118
+ await validateRequiredFiles(dirname(file), record, definition, context.diagnostics);
119
+ validateIdentity(record, context);
120
+ context.resources.push(record);
121
+ }
107
122
  }
108
123
  }
109
124
  async function catalogFiles(catalog, manifestDir, context) {
@@ -130,6 +145,9 @@ async function catalogFiles(catalog, manifestDir, context) {
130
145
  return entries.filter((entry) => entry.isDirectory()).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join(root, entry.name, catalog.entry));
131
146
  }
132
147
  async function loadXnlRecord(filePath, sourceShape, context) {
148
+ return (await loadXnlRecords(filePath, sourceShape, context, "one"))[0];
149
+ }
150
+ async function loadXnlRecords(filePath, sourceShape, context, documentCardinality) {
133
151
  const documentUri = documentUriFor(context.rootDir, filePath);
134
152
  const source = await readFile(filePath, "utf8").catch(() => void 0);
135
153
  if (source === void 0) {
@@ -138,9 +156,9 @@ async function loadXnlRecord(filePath, sourceShape, context) {
138
156
  location: documentUri,
139
157
  message: "Resource file does not exist."
140
158
  });
141
- return;
159
+ return [];
142
160
  }
143
- let root;
161
+ let roots;
144
162
  try {
145
163
  const parsed = parseXnl(source, { textBlockStyle: true });
146
164
  if (parsed.warnings?.length) {
@@ -149,25 +167,32 @@ async function loadXnlRecord(filePath, sourceShape, context) {
149
167
  location: documentUri,
150
168
  message: parsed.warnings.map((warning) => warning.message).join("; ")
151
169
  });
152
- return;
170
+ return [];
153
171
  }
154
- if (parsed.nodes.length !== 1 || !isDataElement(parsed.nodes[0])) {
172
+ if (parsed.nodes.length === 0 || parsed.nodes.some((node) => !isDataElement(node)) || documentCardinality === "one" && parsed.nodes.length !== 1) {
155
173
  context.diagnostics.push({
156
174
  code: "RESOURCE_XNL_ROOT_INVALID",
157
175
  location: documentUri,
158
- message: "XNL resource documents must contain exactly one data-element root."
176
+ message: documentCardinality === "one" ? "XNL resource documents must contain exactly one data-element root." : "XNL resource forests must contain one or more data-element roots."
159
177
  });
160
- return;
178
+ return [];
161
179
  }
162
- root = parsed.nodes[0];
180
+ roots = parsed.nodes;
163
181
  } catch (error) {
164
182
  context.diagnostics.push({
165
183
  code: "RESOURCE_XNL_SYNTAX",
166
184
  location: documentUri,
167
185
  message: `Invalid XNL resource document: ${error instanceof Error ? error.message : String(error)}`
168
186
  });
169
- return;
187
+ return [];
170
188
  }
189
+ return roots.flatMap((root) => {
190
+ const record = resourceRecordFromRoot(root, filePath, sourceShape, context);
191
+ return record ? [record] : [];
192
+ });
193
+ }
194
+ function resourceRecordFromRoot(root, filePath, sourceShape, context) {
195
+ const documentUri = documentUriFor(context.rootDir, filePath);
171
196
  const node = normalizeNode(root);
172
197
  const resourceId = node.resourceId;
173
198
  const apiVersion = asString(node.metadata.apiVersion);
@@ -265,20 +290,55 @@ function normalizeKindDefinition(record, diagnostics) {
265
290
  const resourceKind = asString(record.node.properties.resourceKind) ?? record.resourceId.split(".").at(-1);
266
291
  const shapes = record.node.properties.sourceShapes;
267
292
  const sourceShapes = Array.isArray(shapes) ? shapes.filter(isXnlShape) : [];
268
- if (!resourceKind || sourceShapes.length === 0) {
293
+ const currentApiVersion = asString(record.node.properties.currentApiVersion);
294
+ const versions = record.node.properties.supportedApiVersions;
295
+ const supportedApiVersions = Array.isArray(versions) ? versions.filter((value) => typeof value === "string" && Boolean(value.trim())) : [];
296
+ const cardinalityValue = asString(record.node.properties.documentCardinality) ?? "one";
297
+ if (!resourceKind || sourceShapes.length === 0 || !currentApiVersion || supportedApiVersions.length === 0 || !isDocumentCardinality(cardinalityValue)) {
269
298
  diagnostics.push({
270
299
  code: "KIND_DEFINITION_INVALID",
271
300
  location: record.documentUri,
272
- message: "KindDefinition requires resourceKind (or an id suffix) and sourceShapes."
301
+ message: "KindDefinition requires resourceKind (or an id suffix), sourceShapes, currentApiVersion, and supportedApiVersions."
302
+ });
303
+ return;
304
+ }
305
+ if (!supportedApiVersions.includes(currentApiVersion)) {
306
+ diagnostics.push({
307
+ code: "KIND_DEFINITION_VERSION_INVALID",
308
+ location: record.documentUri,
309
+ message: `Kind '${resourceKind}' currentApiVersion '${currentApiVersion}' must be included in supportedApiVersions.`
273
310
  });
274
311
  return;
275
312
  }
313
+ if (cardinalityValue === "many" && sourceShapes.some((shape) => shape !== "single-file")) {
314
+ diagnostics.push({
315
+ code: "KIND_DEFINITION_CARDINALITY_INVALID",
316
+ location: record.documentUri,
317
+ message: `Kind '${resourceKind}' documentCardinality 'many' requires only the single-file source shape.`
318
+ });
319
+ return;
320
+ }
321
+ const requiredFiles = ((record.node.subdomains.DescriptorContract?.subdomains.RequiredFiles)?.body ?? []).filter(isResourceNode).filter((node) => node.tag === "File").map((node) => asString(node.properties.name)).filter((value) => Boolean(value));
276
322
  return {
323
+ resourceId: record.resourceId,
277
324
  resourceKind,
278
- sourceShapes,
279
- requiredFiles: ((record.node.subdomains.DescriptorContract?.subdomains.RequiredFiles)?.body ?? []).filter(isResourceNode).filter((node) => node.tag === "File").map((node) => asString(node.properties.name)).filter((value) => Boolean(value))
325
+ sourceShapes: Object.freeze([...sourceShapes]),
326
+ requiredFiles: Object.freeze([...requiredFiles]),
327
+ currentApiVersion,
328
+ supportedApiVersions: Object.freeze([...new Set(supportedApiVersions)]),
329
+ documentCardinality: cardinalityValue,
330
+ documentUri: record.documentUri
280
331
  };
281
332
  }
333
+ function validateResourceApiVersion(record, definition, diagnostics) {
334
+ if (definition.supportedApiVersions.includes(record.metadata.apiVersion)) return;
335
+ diagnostics.push({
336
+ code: "RESOURCE_API_VERSION_UNSUPPORTED",
337
+ location: record.documentUri,
338
+ message: `Kind '${record.kind}' does not support apiVersion '${record.metadata.apiVersion}'.`,
339
+ hint: `Current apiVersion is '${definition.currentApiVersion}'.`
340
+ });
341
+ }
282
342
  async function validateRequiredFiles(resourceDir, record, definition, diagnostics) {
283
343
  for (const name of definition.requiredFiles) {
284
344
  if (!isPlainEntry(name)) {
@@ -366,6 +426,9 @@ function kindMismatch(actual, expected, location) {
366
426
  function isXnlShape(value) {
367
427
  return value === "single-file" || value === "directory" || value === "manifest";
368
428
  }
429
+ function isDocumentCardinality(value) {
430
+ return value === "one" || value === "many";
431
+ }
369
432
  function isPlainEntry(value) {
370
433
  return Boolean(value) && !value.includes("/") && !value.includes("\\") && !value.includes("..");
371
434
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcode-compiler.xnl",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Compile XNL-described applications into runtime-ready skill capsules.",
5
5
  "type": "module",
6
6
  "private": false,