halfcode-compiler.xnl 0.1.1 → 0.2.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.
@@ -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-4m42imop.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-4m42imop.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-D8xlMdXx.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-DKkIFGWH.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-D8xlMdXx.js";
2
2
  export { compileResourceSkillCapsule, compileSkillCapsule, compilerSkillPackage };
@@ -1,4 +1,4 @@
1
- import { n as loadResourceTree } from "./src-COEMEqZw.js";
1
+ import { n as loadResourceTree } from "./src-DKkIFGWH.js";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import { parse } from "yaml";
@@ -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-4m42imop.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";
@@ -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) {
@@ -118,7 +133,20 @@ async function catalogFiles(catalog, manifestDir, context) {
118
133
  });
119
134
  return [];
120
135
  }
121
- if (catalog.shape === "single-file") return entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".xnl")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join(root, entry.name));
136
+ if (catalog.shape === "single-file") {
137
+ if (catalog.entry) {
138
+ if (!isPlainEntry(catalog.entry) || !catalog.entry.toLowerCase().endsWith(".xnl")) {
139
+ context.diagnostics.push({
140
+ code: "RESOURCE_CATALOG_ENTRY_INVALID",
141
+ location: catalog.location,
142
+ message: "A single-file catalog entry must be a plain XNL filename."
143
+ });
144
+ return [];
145
+ }
146
+ return [join(root, catalog.entry)];
147
+ }
148
+ return entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".xnl")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join(root, entry.name));
149
+ }
122
150
  if (!catalog.entry || !isPlainEntry(catalog.entry)) {
123
151
  context.diagnostics.push({
124
152
  code: "RESOURCE_CATALOG_ENTRY_INVALID",
@@ -130,6 +158,9 @@ async function catalogFiles(catalog, manifestDir, context) {
130
158
  return entries.filter((entry) => entry.isDirectory()).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => join(root, entry.name, catalog.entry));
131
159
  }
132
160
  async function loadXnlRecord(filePath, sourceShape, context) {
161
+ return (await loadXnlRecords(filePath, sourceShape, context, "one"))[0];
162
+ }
163
+ async function loadXnlRecords(filePath, sourceShape, context, documentCardinality) {
133
164
  const documentUri = documentUriFor(context.rootDir, filePath);
134
165
  const source = await readFile(filePath, "utf8").catch(() => void 0);
135
166
  if (source === void 0) {
@@ -138,9 +169,9 @@ async function loadXnlRecord(filePath, sourceShape, context) {
138
169
  location: documentUri,
139
170
  message: "Resource file does not exist."
140
171
  });
141
- return;
172
+ return [];
142
173
  }
143
- let root;
174
+ let roots;
144
175
  try {
145
176
  const parsed = parseXnl(source, { textBlockStyle: true });
146
177
  if (parsed.warnings?.length) {
@@ -149,25 +180,32 @@ async function loadXnlRecord(filePath, sourceShape, context) {
149
180
  location: documentUri,
150
181
  message: parsed.warnings.map((warning) => warning.message).join("; ")
151
182
  });
152
- return;
183
+ return [];
153
184
  }
154
- if (parsed.nodes.length !== 1 || !isDataElement(parsed.nodes[0])) {
185
+ if (parsed.nodes.length === 0 || parsed.nodes.some((node) => !isDataElement(node)) || documentCardinality === "one" && parsed.nodes.length !== 1) {
155
186
  context.diagnostics.push({
156
187
  code: "RESOURCE_XNL_ROOT_INVALID",
157
188
  location: documentUri,
158
- message: "XNL resource documents must contain exactly one data-element root."
189
+ 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
190
  });
160
- return;
191
+ return [];
161
192
  }
162
- root = parsed.nodes[0];
193
+ roots = parsed.nodes;
163
194
  } catch (error) {
164
195
  context.diagnostics.push({
165
196
  code: "RESOURCE_XNL_SYNTAX",
166
197
  location: documentUri,
167
198
  message: `Invalid XNL resource document: ${error instanceof Error ? error.message : String(error)}`
168
199
  });
169
- return;
200
+ return [];
170
201
  }
202
+ return roots.flatMap((root) => {
203
+ const record = resourceRecordFromRoot(root, filePath, sourceShape, context);
204
+ return record ? [record] : [];
205
+ });
206
+ }
207
+ function resourceRecordFromRoot(root, filePath, sourceShape, context) {
208
+ const documentUri = documentUriFor(context.rootDir, filePath);
171
209
  const node = normalizeNode(root);
172
210
  const resourceId = node.resourceId;
173
211
  const apiVersion = asString(node.metadata.apiVersion);
@@ -265,20 +303,55 @@ function normalizeKindDefinition(record, diagnostics) {
265
303
  const resourceKind = asString(record.node.properties.resourceKind) ?? record.resourceId.split(".").at(-1);
266
304
  const shapes = record.node.properties.sourceShapes;
267
305
  const sourceShapes = Array.isArray(shapes) ? shapes.filter(isXnlShape) : [];
268
- if (!resourceKind || sourceShapes.length === 0) {
306
+ const currentApiVersion = asString(record.node.properties.currentApiVersion);
307
+ const versions = record.node.properties.supportedApiVersions;
308
+ const supportedApiVersions = Array.isArray(versions) ? versions.filter((value) => typeof value === "string" && Boolean(value.trim())) : [];
309
+ const cardinalityValue = asString(record.node.properties.documentCardinality) ?? "one";
310
+ if (!resourceKind || sourceShapes.length === 0 || !currentApiVersion || supportedApiVersions.length === 0 || !isDocumentCardinality(cardinalityValue)) {
269
311
  diagnostics.push({
270
312
  code: "KIND_DEFINITION_INVALID",
271
313
  location: record.documentUri,
272
- message: "KindDefinition requires resourceKind (or an id suffix) and sourceShapes."
314
+ message: "KindDefinition requires resourceKind (or an id suffix), sourceShapes, currentApiVersion, and supportedApiVersions."
273
315
  });
274
316
  return;
275
317
  }
318
+ if (!supportedApiVersions.includes(currentApiVersion)) {
319
+ diagnostics.push({
320
+ code: "KIND_DEFINITION_VERSION_INVALID",
321
+ location: record.documentUri,
322
+ message: `Kind '${resourceKind}' currentApiVersion '${currentApiVersion}' must be included in supportedApiVersions.`
323
+ });
324
+ return;
325
+ }
326
+ if (cardinalityValue === "many" && sourceShapes.some((shape) => shape !== "single-file")) {
327
+ diagnostics.push({
328
+ code: "KIND_DEFINITION_CARDINALITY_INVALID",
329
+ location: record.documentUri,
330
+ message: `Kind '${resourceKind}' documentCardinality 'many' requires only the single-file source shape.`
331
+ });
332
+ return;
333
+ }
334
+ 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
335
  return {
336
+ resourceId: record.resourceId,
277
337
  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))
338
+ sourceShapes: Object.freeze([...sourceShapes]),
339
+ requiredFiles: Object.freeze([...requiredFiles]),
340
+ currentApiVersion,
341
+ supportedApiVersions: Object.freeze([...new Set(supportedApiVersions)]),
342
+ documentCardinality: cardinalityValue,
343
+ documentUri: record.documentUri
280
344
  };
281
345
  }
346
+ function validateResourceApiVersion(record, definition, diagnostics) {
347
+ if (definition.supportedApiVersions.includes(record.metadata.apiVersion)) return;
348
+ diagnostics.push({
349
+ code: "RESOURCE_API_VERSION_UNSUPPORTED",
350
+ location: record.documentUri,
351
+ message: `Kind '${record.kind}' does not support apiVersion '${record.metadata.apiVersion}'.`,
352
+ hint: `Current apiVersion is '${definition.currentApiVersion}'.`
353
+ });
354
+ }
282
355
  async function validateRequiredFiles(resourceDir, record, definition, diagnostics) {
283
356
  for (const name of definition.requiredFiles) {
284
357
  if (!isPlainEntry(name)) {
@@ -366,6 +439,9 @@ function kindMismatch(actual, expected, location) {
366
439
  function isXnlShape(value) {
367
440
  return value === "single-file" || value === "directory" || value === "manifest";
368
441
  }
442
+ function isDocumentCardinality(value) {
443
+ return value === "one" || value === "many";
444
+ }
369
445
  function isPlainEntry(value) {
370
446
  return Boolean(value) && !value.includes("/") && !value.includes("\\") && !value.includes("..");
371
447
  }
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.1",
4
4
  "description": "Compile XNL-described applications into runtime-ready skill capsules.",
5
5
  "type": "module",
6
6
  "private": false,