@nyrra/prismantix-cli 0.0.4-next.23 → 0.0.4-next.25

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.
@@ -4,6 +4,8 @@ import { isRecord } from "./utils.js";
4
4
  export function emitFoundryImportFiles(result, options = {}) {
5
5
  const ontologyName = options.ontologyName ?? "ImportedFoundryOntology";
6
6
  return [
7
+ emitPackageJson(ontologyName),
8
+ emitTsconfig(),
7
9
  emitIndex(ontologyName),
8
10
  emitOntologyEntrypoint(ontologyName),
9
11
  { path: "objects.mts", content: emitObjects(result, ontologyName) },
@@ -22,6 +24,45 @@ export function emitFoundryImportFiles(result, options = {}) {
22
24
  { path: "diagnostics.md", content: emitDiagnosticsMarkdown(result) },
23
25
  ];
24
26
  }
27
+ function emitPackageJson(ontologyName) {
28
+ return {
29
+ path: "package.json",
30
+ content: `${JSON.stringify({
31
+ dependencies: {
32
+ "@osdk/maker": "0.44.0",
33
+ },
34
+ devDependencies: {
35
+ typescript: "^5.9.3",
36
+ },
37
+ name: `@imported-ontology/${packageSlug(ontologyName)}`,
38
+ private: true,
39
+ scripts: {
40
+ typecheck: "tsc -p tsconfig.json --noEmit",
41
+ },
42
+ type: "module",
43
+ version: "0.0.0",
44
+ }, null, 2)}\n`,
45
+ };
46
+ }
47
+ function emitTsconfig() {
48
+ return {
49
+ path: "tsconfig.json",
50
+ content: `${JSON.stringify({
51
+ compilerOptions: {
52
+ declaration: true,
53
+ module: "NodeNext",
54
+ moduleResolution: "NodeNext",
55
+ noEmitOnError: true,
56
+ outDir: "dist",
57
+ rootDir: ".",
58
+ skipLibCheck: true,
59
+ strict: true,
60
+ target: "ES2022",
61
+ },
62
+ include: ["*.mts"],
63
+ }, null, 2)}\n`,
64
+ };
65
+ }
25
66
  function emitIndex(ontologyName) {
26
67
  return {
27
68
  path: "index.mts",
@@ -55,6 +96,7 @@ function emitObjects(result, ontologyName) {
55
96
  return [
56
97
  header(ontologyName),
57
98
  `import { defineObject } from "@osdk/maker";`,
99
+ ...objectInterfaceImports(result),
58
100
  "",
59
101
  body,
60
102
  "",
@@ -62,6 +104,12 @@ function emitObjects(result, ontologyName) {
62
104
  "",
63
105
  ].join("\n");
64
106
  }
107
+ function objectInterfaceImports(result) {
108
+ const interfaceNames = Array.from(new Set(result.objects.flatMap((object) => object.interfaceImplementations.map((implementation) => implementation.interfaceVariableName))));
109
+ return interfaceNames.length === 0
110
+ ? []
111
+ : [`import { ${interfaceNames.join(", ")} } from "./interfaces.mjs";`];
112
+ }
65
113
  function emitObject(object) {
66
114
  const properties = object.properties.map(emitObjectProperty).join(",\n");
67
115
  return [
@@ -70,6 +118,9 @@ function emitObject(object) {
70
118
  apiName: object.apiName.sanitized,
71
119
  description: object.description,
72
120
  displayName: object.displayName,
121
+ implementsInterfaces: object.interfaceImplementations.length > 0
122
+ ? raw(`[\n${object.interfaceImplementations.map(emitObjectInterfaceImplementation).join(",\n")}\n\t]`)
123
+ : undefined,
73
124
  pluralDisplayName: object.pluralDisplayName,
74
125
  primaryKeyPropertyApiName: object.primaryKeyPropertyApiName,
75
126
  properties: raw(`{\n${properties}\n\t}`),
@@ -78,6 +129,12 @@ function emitObject(object) {
78
129
  }, 0)});`,
79
130
  ].join("\n");
80
131
  }
132
+ function emitObjectInterfaceImplementation(implementation) {
133
+ return `\t\t{\n\t\t\timplements: ${implementation.interfaceVariableName},\n\t\t\tpropertyMapping: ${objectLiteral(implementation.propertyMappings.map((mapping) => ({
134
+ interfaceProperty: mapping.interfacePropertyApiName,
135
+ mapsTo: mapping.objectPropertyApiName,
136
+ })), 3)}\n\t\t}`;
137
+ }
81
138
  function emitObjectProperty(property) {
82
139
  return `\t\t${safePropertyKey(property.apiName.sanitized)}: ${objectLiteral({
83
140
  array: property.array || undefined,
@@ -142,17 +199,56 @@ function emitSharedProperty(property) {
142
199
  ].join("\n");
143
200
  }
144
201
  function emitInterfaces(result, ontologyName) {
145
- const body = result.interfaces.map(emitInterface).join("\n\n");
202
+ const interfaces = orderedInterfacesForEmit(result.interfaces);
203
+ const body = interfaces.map(emitInterface).join("\n\n");
204
+ const links = interfaces.flatMap(emitInterfaceLinks).join("\n\n");
146
205
  return [
147
206
  header(ontologyName),
148
- `import { defineInterface } from "@osdk/maker";`,
207
+ `import { defineInterface, defineInterfaceLinkConstraint } from "@osdk/maker";`,
208
+ ...interfaceSharedPropertyImports(result),
149
209
  "",
150
210
  body,
211
+ links ? `\n${links}` : "",
151
212
  "",
152
- `export const INTERFACES = [${result.interfaces.map((interfaceType) => interfaceType.variableName).join(", ")}] as const;`,
213
+ `export const INTERFACES = [${interfaces.map((interfaceType) => interfaceType.variableName).join(", ")}] as const;`,
153
214
  "",
154
215
  ].join("\n");
155
216
  }
217
+ function interfaceSharedPropertyImports(result) {
218
+ const sharedPropertyNames = Array.from(new Set(result.interfaces.flatMap((interfaceType) => interfaceType.properties.flatMap((property) => property.sharedPropertyVariableName
219
+ ? [property.sharedPropertyVariableName]
220
+ : []))));
221
+ return sharedPropertyNames.length === 0
222
+ ? []
223
+ : [
224
+ `import { ${sharedPropertyNames.join(", ")} } from "./sharedProperties.mjs";`,
225
+ ];
226
+ }
227
+ function orderedInterfacesForEmit(interfaces) {
228
+ const byApiName = new Map(interfaces.map((interfaceType) => [
229
+ interfaceType.apiName.sanitized,
230
+ interfaceType,
231
+ ]));
232
+ const emitted = new Set();
233
+ const ordered = [];
234
+ const visit = (interfaceType) => {
235
+ if (emitted.has(interfaceType.apiName.sanitized)) {
236
+ return;
237
+ }
238
+ emitted.add(interfaceType.apiName.sanitized);
239
+ for (const parent of interfaceType.extendsInterfaces) {
240
+ const parentInterface = byApiName.get(parent.apiName.sanitized);
241
+ if (parentInterface) {
242
+ visit(parentInterface);
243
+ }
244
+ }
245
+ ordered.push(interfaceType);
246
+ };
247
+ for (const interfaceType of interfaces) {
248
+ visit(interfaceType);
249
+ }
250
+ return ordered;
251
+ }
156
252
  function emitInterface(interfaceType) {
157
253
  return [
158
254
  entityComment(interfaceType.apiName, interfaceType.rid),
@@ -160,6 +256,10 @@ function emitInterface(interfaceType) {
160
256
  apiName: interfaceType.apiName.sanitized,
161
257
  description: interfaceType.description,
162
258
  displayName: interfaceType.displayName,
259
+ extends: emitInterfaceExtends(interfaceType),
260
+ properties: interfaceType.properties.length > 0
261
+ ? raw(`{\n${interfaceType.properties.map(emitInterfaceProperty).join(",\n")}\n\t}`)
262
+ : undefined,
163
263
  searchable: interfaceType.searchable,
164
264
  status: interfaceType.status
165
265
  ? { type: interfaceType.status }
@@ -167,14 +267,74 @@ function emitInterface(interfaceType) {
167
267
  }, 0)});`,
168
268
  ].join("\n");
169
269
  }
270
+ function emitInterfaceExtends(interfaceType) {
271
+ if (interfaceType.extendsInterfaces.length === 0) {
272
+ return undefined;
273
+ }
274
+ const [firstReference] = interfaceType.extendsInterfaces;
275
+ return raw(interfaceType.extendsInterfaces.length === 1 && firstReference
276
+ ? firstReference.variableName
277
+ : `[${interfaceType.extendsInterfaces.map((reference) => reference.variableName).join(", ")}]`);
278
+ }
279
+ function emitInterfaceProperty(property) {
280
+ const value = property.sharedPropertyVariableName
281
+ ? objectLiteral(raw(`{ sharedPropertyType: ${property.sharedPropertyVariableName}, required: ${property.required ? "true" : "false"} }`), 2)
282
+ : objectLiteral({
283
+ displayName: property.displayName,
284
+ required: property.required || undefined,
285
+ type: property.type,
286
+ }, 2);
287
+ return `\t\t${safePropertyKey(property.apiName.sanitized)}: ${value}`;
288
+ }
289
+ function emitInterfaceLinks(interfaceType) {
290
+ return interfaceType.links.map((link) => `defineInterfaceLinkConstraint(${objectLiteral({
291
+ apiName: link.apiName.sanitized,
292
+ description: link.description,
293
+ displayName: link.displayName,
294
+ from: raw(interfaceType.variableName),
295
+ required: link.required,
296
+ ...(link.cardinality === "MANY"
297
+ ? { toMany: raw(link.targetVariableName) }
298
+ : { toOne: raw(link.targetVariableName) }),
299
+ }, 0)});`);
300
+ }
170
301
  function emitActions(result, ontologyName) {
302
+ const body = result.actions.map(emitAction).join("\n\n");
171
303
  return [
172
304
  header(ontologyName),
173
- "// Action forms and logic are summarized here. Full action-rule reconstruction is intentionally deferred.",
174
- `export const ACTIONS = ${objectLiteral(result.actions, 0)} as const;`,
305
+ `import { defineAction } from "@osdk/maker";`,
306
+ "",
307
+ body,
308
+ "",
309
+ `export const ACTIONS = [${result.actions.map((action) => action.variableName).join(", ")}] as const;`,
175
310
  "",
176
311
  ].join("\n");
177
312
  }
313
+ function emitAction(action) {
314
+ return [
315
+ entityComment(action.apiName, undefined),
316
+ `export const ${action.variableName} = defineAction(${objectLiteral({
317
+ apiName: action.apiName.sanitized,
318
+ displayName: action.displayName,
319
+ parameters: action.parameters.map(emitActionParameter),
320
+ parameterOrdering: action.parameters.map((parameter) => parameter.id.sanitized),
321
+ rules: action.rules,
322
+ status: action.status ?? "active",
323
+ }, 0)});`,
324
+ ].join("\n");
325
+ }
326
+ function emitActionParameter(parameter) {
327
+ return {
328
+ description: parameter.description,
329
+ displayName: parameter.displayName,
330
+ id: parameter.id.sanitized,
331
+ type: parameter.type,
332
+ validation: {
333
+ allowedValues: parameter.validationAllowedValues,
334
+ required: parameter.required,
335
+ },
336
+ };
337
+ }
178
338
  function emitTypeGroups(result, ontologyName) {
179
339
  return [
180
340
  header(ontologyName),
@@ -261,3 +421,10 @@ function header(ontologyName) {
261
421
  `// Source ontology: ${ontologyName}. Review diagnostics before treating this as hand-authored OAC.`,
262
422
  ].join("\n");
263
423
  }
424
+ function packageSlug(value) {
425
+ return (value
426
+ .toLowerCase()
427
+ .match(/[a-z0-9]+/g)
428
+ ?.join("-")
429
+ .slice(0, 100) || "imported-foundry-ontology");
430
+ }
@@ -1,3 +1,12 @@
1
- import type { ConversionDiagnostic, FoundryInterface, NormalizedInterface } from "./types.js";
2
- export declare function normalizeInterface(interfaceType: FoundryInterface, interfaceIndex: number, nameAllocator: (name: string) => string, variableNameAllocator: (name: string) => string, diagnostics: ConversionDiagnostic[]): NormalizedInterface;
1
+ import type { ConversionDiagnostic, FoundryInterface, NormalizedInterface, NormalizedSharedProperty } from "./types.js";
2
+ interface InterfaceNormalizationContext {
3
+ readonly diagnostics: ConversionDiagnostic[];
4
+ readonly interfaceNameAllocator: (name: string) => string;
5
+ readonly sharedPropertiesByOriginalApiName: ReadonlyMap<string, NormalizedSharedProperty>;
6
+ readonly sharedPropertiesByRid: ReadonlyMap<string, NormalizedSharedProperty>;
7
+ readonly variableNameAllocator: (name: string) => string;
8
+ }
9
+ export declare function normalizeInterface(interfaceType: FoundryInterface, interfaceIndex: number, context: InterfaceNormalizationContext): NormalizedInterface;
10
+ export declare function addInterfaceReferences(interfaceType: FoundryInterface, normalized: NormalizedInterface, interfaceIndex: number, interfacesByApiName: ReadonlyMap<string, NormalizedInterface>, diagnostics: ConversionDiagnostic[]): NormalizedInterface;
11
+ export {};
3
12
  //# sourceMappingURL=interfaces.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/foundry-import/interfaces.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,MAAM,YAAY,CAAC;AAQpB,wBAAgB,kBAAkB,CACjC,aAAa,EAAE,gBAAgB,EAC/B,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACvC,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAC/C,WAAW,EAAE,oBAAoB,EAAE,GACjC,mBAAmB,CA0CrB"}
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/foundry-import/interfaces.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACX,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,wBAAwB,EACxB,MAAM,YAAY,CAAC;AAQpB,UAAU,6BAA6B;IACtC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,EAAE,CAAC;IAC7C,QAAQ,CAAC,sBAAsB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D,QAAQ,CAAC,iCAAiC,EAAE,WAAW,CACtD,MAAM,EACN,wBAAwB,CACxB,CAAC;IACF,QAAQ,CAAC,qBAAqB,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAC9E,QAAQ,CAAC,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAOD,wBAAgB,kBAAkB,CACjC,aAAa,EAAE,gBAAgB,EAC/B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,6BAA6B,GACpC,mBAAmB,CA2CrB;AAED,wBAAgB,sBAAsB,CACrC,aAAa,EAAE,gBAAgB,EAC/B,UAAU,EAAE,mBAAmB,EAC/B,cAAc,EAAE,MAAM,EACtB,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAC7D,WAAW,EAAE,oBAAoB,EAAE,GACjC,mBAAmB,CAkBrB"}
@@ -1,43 +1,151 @@
1
- import { addSanitizationDiagnostic, sanitizeApiName, toPascalCase, } from "./names.js";
1
+ import { addSanitizationDiagnostic, createNameAllocator, sanitizeApiName, toPascalCase, } from "./names.js";
2
+ import { mapPropertyType } from "./property-types.js";
2
3
  import { displayNameFor, normalizeDescription, normalizeStatus, requiredString, } from "./utils.js";
3
- export function normalizeInterface(interfaceType, interfaceIndex, nameAllocator, variableNameAllocator, diagnostics) {
4
- const originalApiName = requiredString(interfaceType.apiName, `$.interfaces[${interfaceIndex}].apiName`, diagnostics, `Interface${interfaceIndex + 1}`);
5
- const sanitizedApiName = nameAllocator(sanitizeApiName(originalApiName, "api"));
6
- addSanitizationDiagnostic(diagnostics, `$.interfaces[${interfaceIndex}].apiName`, originalApiName, sanitizedApiName);
7
- addInterfaceDiagnostics(interfaceType, interfaceIndex, originalApiName, diagnostics);
4
+ export function normalizeInterface(interfaceType, interfaceIndex, context) {
5
+ const originalApiName = requiredString(interfaceType.apiName, `$.interfaces[${interfaceIndex}].apiName`, context.diagnostics, `Interface${interfaceIndex + 1}`);
6
+ const sanitizedApiName = context.interfaceNameAllocator(sanitizeApiName(originalApiName, "api"));
7
+ addSanitizationDiagnostic(context.diagnostics, `$.interfaces[${interfaceIndex}].apiName`, originalApiName, sanitizedApiName);
8
8
  return {
9
9
  apiName: { original: originalApiName, sanitized: sanitizedApiName },
10
10
  description: normalizeDescription(interfaceType.displayMetadata?.description),
11
11
  displayName: displayNameFor(interfaceType.displayMetadata, sanitizedApiName),
12
+ extendsInterfaces: [],
13
+ links: [],
14
+ properties: normalizeInterfaceProperties(interfaceType, interfaceIndex, context),
12
15
  rid: interfaceType.rid,
13
16
  searchable: interfaceType.searchable ?? false,
14
- status: normalizeStatus(interfaceType.status, `$.interfaces[${interfaceIndex}].status`, diagnostics),
15
- variableName: variableNameAllocator(`${toPascalCase(sanitizedApiName)}Interface`),
17
+ status: normalizeStatus(interfaceType.status, `$.interfaces[${interfaceIndex}].status`, context.diagnostics),
18
+ variableName: context.variableNameAllocator(`${toPascalCase(sanitizedApiName)}Interface`),
16
19
  };
17
20
  }
18
- function addInterfaceDiagnostics(interfaceType, interfaceIndex, apiName, diagnostics) {
19
- addInterfaceLinksDiagnostic(interfaceType, interfaceIndex, apiName, diagnostics);
20
- addInterfacePropertiesDiagnostic(interfaceType, interfaceIndex, apiName, diagnostics);
21
+ export function addInterfaceReferences(interfaceType, normalized, interfaceIndex, interfacesByApiName, diagnostics) {
22
+ return {
23
+ ...normalized,
24
+ extendsInterfaces: normalizeInterfaceExtends(interfaceType, normalized, interfaceIndex, interfacesByApiName, diagnostics),
25
+ links: normalizeInterfaceLinks(interfaceType, normalized, interfaceIndex, interfacesByApiName, diagnostics),
26
+ };
27
+ }
28
+ function normalizeInterfaceProperties(interfaceType, interfaceIndex, context) {
29
+ const propertyNameAllocator = createNameAllocator();
30
+ return (interfaceType.properties ?? []).map((property, propertyIndex) => normalizeInterfaceProperty(property, propertyIndex, interfaceIndex, propertyNameAllocator, context));
31
+ }
32
+ function normalizeInterfaceProperty(property, propertyIndex, interfaceIndex, propertyNameAllocator, context) {
33
+ const path = `$.interfaces[${interfaceIndex}].properties[${propertyIndex}]`;
34
+ const original = requiredString(property.apiName, `${path}.apiName`, context.diagnostics, `property${propertyIndex + 1}`);
35
+ const sharedProperty = interfaceSharedProperty(property, original, context);
36
+ return sharedProperty
37
+ ? sharedInterfaceProperty(property, sharedProperty)
38
+ : inlineInterfaceProperty(property, original, path, propertyNameAllocator, context);
39
+ }
40
+ function interfaceSharedProperty(property, original, context) {
41
+ return ((property.sharedPropertyRid
42
+ ? context.sharedPropertiesByRid.get(property.sharedPropertyRid)
43
+ : undefined) ??
44
+ context.sharedPropertiesByOriginalApiName.get(original) ??
45
+ context.sharedPropertiesByOriginalApiName.get(sanitizeApiName(original, "api")));
46
+ }
47
+ function sharedInterfaceProperty(property, sharedProperty) {
48
+ return {
49
+ apiName: sharedProperty.apiName,
50
+ displayName: property.displayName ?? sharedProperty.apiName.sanitized,
51
+ required: property.required ?? false,
52
+ sharedPropertyVariableName: sharedProperty.variableName,
53
+ type: undefined,
54
+ };
55
+ }
56
+ function inlineInterfaceProperty(property, original, path, propertyNameAllocator, context) {
57
+ const apiName = normalizeInterfacePropertyApiName(original, path, propertyNameAllocator, context.diagnostics);
58
+ return {
59
+ apiName,
60
+ displayName: property.displayName ?? apiName.sanitized,
61
+ required: property.required ?? false,
62
+ sharedPropertyVariableName: undefined,
63
+ type: mapPropertyType(property.type, `${path}.type`, context.diagnostics)
64
+ .type,
65
+ };
21
66
  }
22
- function addInterfaceLinksDiagnostic(interfaceType, interfaceIndex, apiName, diagnostics) {
23
- if ((interfaceType.links?.length ?? 0) === 0) {
24
- return;
67
+ function normalizeInterfacePropertyApiName(original, path, nameAllocator, diagnostics) {
68
+ const sanitized = nameAllocator(sanitizeApiName(original, "api"));
69
+ addSanitizationDiagnostic(diagnostics, `${path}.apiName`, original, sanitized);
70
+ return { original, sanitized };
71
+ }
72
+ function normalizeInterfaceExtends(interfaceType, normalized, interfaceIndex, interfacesByApiName, diagnostics) {
73
+ return (interfaceType.extendsInterfaces ?? []).flatMap((apiName, index) => {
74
+ const target = interfacesByApiName.get(apiName);
75
+ if (target) {
76
+ return [
77
+ {
78
+ apiName: target.apiName,
79
+ variableName: target.variableName,
80
+ },
81
+ ];
82
+ }
83
+ diagnostics.push({
84
+ code: "skipped.interfaceExtends",
85
+ message: `Interface ${normalized.apiName.original} extends missing interface ${apiName}; extension was not emitted.`,
86
+ path: `$.interfaces[${interfaceIndex}].extendsInterfaces[${index}]`,
87
+ severity: "warning",
88
+ });
89
+ return [];
90
+ });
91
+ }
92
+ function normalizeInterfaceLinks(interfaceType, normalized, interfaceIndex, interfacesByApiName, diagnostics) {
93
+ const linkNameAllocator = createNameAllocator();
94
+ return (interfaceType.links ?? []).flatMap((link, linkIndex) => normalizeInterfaceLink(link, linkIndex, normalized, interfaceIndex, linkNameAllocator, interfacesByApiName, diagnostics));
95
+ }
96
+ function normalizeInterfaceLink(link, linkIndex, normalized, interfaceIndex, linkNameAllocator, interfacesByApiName, diagnostics) {
97
+ const path = `$.interfaces[${interfaceIndex}].links[${linkIndex}]`;
98
+ const target = resolveInterfaceLinkTarget(link, linkIndex, normalized, path, interfacesByApiName, diagnostics);
99
+ if (!target) {
100
+ return [];
101
+ }
102
+ const original = requiredString(link.apiName, `${path}.apiName`, diagnostics, `link${linkIndex + 1}`);
103
+ const sanitized = linkNameAllocator(sanitizeApiName(original, "api"));
104
+ addSanitizationDiagnostic(diagnostics, `${path}.apiName`, original, sanitized);
105
+ return [
106
+ {
107
+ apiName: { original, sanitized },
108
+ cardinality: interfaceLinkCardinality(link),
109
+ description: normalizeDescription(link.description),
110
+ displayName: link.displayName ?? sanitized,
111
+ required: link.required,
112
+ targetApiName: target.apiName,
113
+ targetVariableName: target.variableName,
114
+ },
115
+ ];
116
+ }
117
+ function resolveInterfaceLinkTarget(link, linkIndex, normalized, path, interfacesByApiName, diagnostics) {
118
+ if (unsupportedInterfaceLinkTarget(link)) {
119
+ addSkippedInterfaceLinkDiagnostic(link, linkIndex, normalized, path, diagnostics);
120
+ return undefined;
25
121
  }
122
+ const target = link.linkedEntityApiName
123
+ ? interfacesByApiName.get(link.linkedEntityApiName)
124
+ : undefined;
125
+ if (!target) {
126
+ addMissingInterfaceLinkTargetDiagnostic(link, linkIndex, normalized, path, diagnostics);
127
+ }
128
+ return target;
129
+ }
130
+ function unsupportedInterfaceLinkTarget(link) {
131
+ return Boolean(link.linkedEntityType && link.linkedEntityType !== "interfaceTypeApiName");
132
+ }
133
+ function addSkippedInterfaceLinkDiagnostic(link, linkIndex, normalized, path, diagnostics) {
26
134
  diagnostics.push({
27
- code: "unsupported.interfaceLinks",
28
- message: `Interface ${apiName} has interface links; emitted interface shell only.`,
29
- path: `$.interfaces[${interfaceIndex}].links`,
135
+ code: "skipped.interfaceLinkTarget",
136
+ message: `Interface ${normalized.apiName.original} link ${link.apiName ?? linkIndex} targets ${link.linkedEntityType}; only interface targets are emitted.`,
137
+ path,
30
138
  severity: "warning",
31
139
  });
32
140
  }
33
- function addInterfacePropertiesDiagnostic(interfaceType, interfaceIndex, apiName, diagnostics) {
34
- if ((interfaceType.properties?.length ?? 0) === 0) {
35
- return;
36
- }
141
+ function addMissingInterfaceLinkTargetDiagnostic(link, linkIndex, normalized, path, diagnostics) {
37
142
  diagnostics.push({
38
- code: "partial.interfaceProperties",
39
- message: `Interface ${apiName} has properties; emitted interface shell only in this first pass.`,
40
- path: `$.interfaces[${interfaceIndex}].properties`,
143
+ code: "skipped.interfaceLinkTarget",
144
+ message: `Interface ${normalized.apiName.original} link ${link.apiName ?? linkIndex} targets missing interface ${link.linkedEntityApiName ?? "unknown"}; link was not emitted.`,
145
+ path,
41
146
  severity: "warning",
42
147
  });
43
148
  }
149
+ function interfaceLinkCardinality(link) {
150
+ return link.cardinality === "MANY" ? "MANY" : "SINGLE";
151
+ }
@@ -2,6 +2,7 @@ import type { ConversionDiagnostic, NamePair } from "./types.js";
2
2
  type ApiNameMode = "api" | "object";
3
3
  export declare function addSanitizationDiagnostic(diagnostics: ConversionDiagnostic[], path: string, original: string, sanitized: string): void;
4
4
  export declare function sanitizeApiName(name: string, mode: ApiNameMode): string;
5
+ export declare function sanitizeActionApiName(name: string): string;
5
6
  export declare function createNameAllocator(): (name: string) => string;
6
7
  export declare function entityComment(name: NamePair, rid: string | undefined): string;
7
8
  export declare function safePropertyKey(key: string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/foundry-import/names.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgBjE,KAAK,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEpC,wBAAgB,yBAAyB,CACxC,WAAW,EAAE,oBAAoB,EAAE,EACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GACf,IAAI,CAUN;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAMvE;AAED,wBAAgB,mBAAmB,IAAI,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAO9D;AAQD,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI7E;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlD"}
1
+ {"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/foundry-import/names.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAiBjE,KAAK,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEpC,wBAAgB,yBAAyB,CACxC,WAAW,EAAE,oBAAoB,EAAE,EACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GACf,IAAI,CAUN;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAMvE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmB1D;AAED,wBAAgB,mBAAmB,IAAI,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAO9D;AAQD,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI7E;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlD"}
@@ -1,5 +1,6 @@
1
1
  const objectApiNamePattern = /^[a-zA-Z][a-zA-Z0-9]{0,99}$/;
2
2
  const apiNamePattern = /^[a-zA-Z][a-zA-Z0-9_]{0,99}$/;
3
+ const actionApiNamePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
3
4
  const reservedApiNames = new Set([
4
5
  "ontology",
5
6
  "object",
@@ -29,6 +30,23 @@ export function sanitizeApiName(name, mode) {
29
30
  }
30
31
  return prefixedApiName(candidate, mode);
31
32
  }
33
+ export function sanitizeActionApiName(name) {
34
+ const words = name
35
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
36
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1 $2")
37
+ .match(/[a-zA-Z0-9]+/g)
38
+ ?.map((word) => word.toLowerCase())
39
+ .filter((word) => word.length > 0) ?? ["imported"];
40
+ const candidate = words
41
+ .join("-")
42
+ .replace(/^-+|-+$/g, "")
43
+ .slice(0, 100);
44
+ if (actionApiNamePattern.test(candidate)) {
45
+ return candidate;
46
+ }
47
+ const fallback = `imported-${candidate.replace(/^[^a-z0-9]+/, "")}`.slice(0, 100);
48
+ return actionApiNamePattern.test(fallback) ? fallback : "imported-action";
49
+ }
32
50
  export function createNameAllocator() {
33
51
  const seen = new Map();
34
52
  return (name) => {
@@ -1,6 +1,7 @@
1
- import type { ConversionDiagnostic, FoundryObjectType, NormalizedObject } from "./types.js";
1
+ import type { ConversionDiagnostic, FoundryObjectType, NormalizedInterface, NormalizedObject } from "./types.js";
2
2
  interface ObjectNormalizationContext {
3
3
  readonly diagnostics: ConversionDiagnostic[];
4
+ readonly interfacesByApiName: ReadonlyMap<string, NormalizedInterface>;
4
5
  readonly objectNameAllocator: (name: string) => string;
5
6
  readonly variableNameAllocator: (name: string) => string;
6
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../../src/foundry-import/objects.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACX,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,MAAM,YAAY,CAAC;AAQpB,UAAU,0BAA0B;IACnC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,EAAE,CAAC;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD,QAAQ,CAAC,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAED,wBAAgB,eAAe,CAC9B,UAAU,EAAE,iBAAiB,EAC7B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,0BAA0B,GACjC,gBAAgB,CAqDlB"}
1
+ {"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../../src/foundry-import/objects.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACX,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,EAChB,MAAM,YAAY,CAAC;AAQpB,UAAU,0BAA0B;IACnC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,EAAE,CAAC;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACvE,QAAQ,CAAC,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD,QAAQ,CAAC,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACzD;AAED,wBAAgB,eAAe,CAC9B,UAAU,EAAE,iBAAiB,EAC7B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,0BAA0B,GACjC,gBAAgB,CA6DlB"}
@@ -13,6 +13,8 @@ export function normalizeObject(objectType, objectIndex, context) {
13
13
  apiName,
14
14
  description: objectDescription(objectType),
15
15
  displayName: objectDisplayName(objectType, apiName.sanitized),
16
+ id: objectType.id,
17
+ interfaceImplementations: normalizeInterfaceImplementations(objectType, objectIndex, apiName.original, properties, context),
16
18
  pluralDisplayName: pluralDisplayNameFor(objectType, apiName.sanitized),
17
19
  primaryKeyPropertyApiName: keyPropertyApiName(keys.primaryKeyProperty, fallbackProperty),
18
20
  properties,
@@ -57,20 +59,100 @@ function normalizeObjectProperties(objectType, objectIndex, context) {
57
59
  return (objectType.properties ?? []).map((property, propertyIndex) => normalizeProperty(property, `$.objectTypes[${objectIndex}].properties[${propertyIndex}]`, propertyNameAllocator, context.diagnostics));
58
60
  }
59
61
  function addObjectMetadataDiagnostics(objectType, objectIndex, apiName, diagnostics) {
60
- addObjectInterfacesDiagnostic(objectType, objectIndex, apiName, diagnostics);
61
62
  addObjectTypeGroupsDiagnostic(objectType, objectIndex, apiName, diagnostics);
62
63
  }
63
- function addObjectInterfacesDiagnostic(objectType, objectIndex, apiName, diagnostics) {
64
- if ((objectType.interfaces?.length ?? 0) === 0) {
65
- return;
66
- }
67
- diagnostics.push({
68
- code: "partial.objectInterfaces",
69
- message: `Object ${apiName} implements interfaces; interface implementation bindings are not emitted in this first pass.`,
70
- path: `$.objectTypes[${objectIndex}].interfaces`,
71
- severity: "warning",
64
+ function normalizeInterfaceImplementations(objectType, objectIndex, apiName, properties, context) {
65
+ const implementations = objectInterfaceImplementations(objectType);
66
+ const propertiesByApiName = indexPropertiesByApiName(properties);
67
+ return implementations.flatMap((implementation, implementationIndex) => {
68
+ const path = `$.objectTypes[${objectIndex}].interfaces[${implementationIndex}]`;
69
+ const interfaceType = context.interfacesByApiName.get(implementation.interfaceApiName);
70
+ if (!interfaceType) {
71
+ context.diagnostics.push({
72
+ code: "skipped.objectInterfaceImplementation",
73
+ message: `Object ${apiName} implements missing interface ${implementation.interfaceApiName}; implementation was not emitted.`,
74
+ path,
75
+ severity: "warning",
76
+ });
77
+ return [];
78
+ }
79
+ const mappedProperties = implementation.propertyMappings.flatMap((mapping, mappingIndex) => normalizeInterfacePropertyMapping(mapping, mappingIndex, path, apiName, interfaceType, propertiesByApiName, context.diagnostics));
80
+ const missingProperties = flattenedInterfaceProperties(interfaceType, context.interfacesByApiName).filter((property) => !mappedProperties.some((mapping) => mapping.interfacePropertyApiName === property.apiName.sanitized));
81
+ if (missingProperties.length > 0) {
82
+ context.diagnostics.push({
83
+ code: "skipped.objectInterfaceImplementation",
84
+ message: `Object ${apiName} implementation of ${interfaceType.apiName.original} is missing mappings for ${missingProperties.map((property) => property.apiName.original).join(", ")}; implementation was not emitted.`,
85
+ path,
86
+ severity: "warning",
87
+ });
88
+ return [];
89
+ }
90
+ return [
91
+ {
92
+ interfaceApiName: interfaceType.apiName,
93
+ interfaceVariableName: interfaceType.variableName,
94
+ propertyMappings: mappedProperties,
95
+ },
96
+ ];
72
97
  });
73
98
  }
99
+ function objectInterfaceImplementations(objectType) {
100
+ const explicit = objectType.interfaceImplementations ?? [];
101
+ if (explicit.length > 0) {
102
+ return explicit;
103
+ }
104
+ return (objectType.interfaces ?? []).map((interfaceApiName) => ({
105
+ interfaceApiName,
106
+ propertyMappings: [],
107
+ }));
108
+ }
109
+ function indexPropertiesByApiName(properties) {
110
+ const index = new Map();
111
+ for (const property of properties) {
112
+ index.set(property.originalId, property);
113
+ index.set(property.apiName.original, property);
114
+ index.set(property.apiName.sanitized, property);
115
+ }
116
+ return index;
117
+ }
118
+ function normalizeInterfacePropertyMapping(mapping, mappingIndex, path, objectApiName, interfaceType, propertiesByApiName, diagnostics) {
119
+ const interfaceProperty = interfacePropertyByApiName(interfaceType, mapping.interfacePropertyApiName);
120
+ const objectProperty = propertiesByApiName.get(mapping.objectPropertyApiName);
121
+ if (!interfaceProperty || !objectProperty) {
122
+ diagnostics.push({
123
+ code: "skipped.objectInterfacePropertyMapping",
124
+ message: `Object ${objectApiName} has an unresolved interface mapping ${mapping.interfacePropertyApiName} -> ${mapping.objectPropertyApiName}.`,
125
+ path: `${path}.propertyMappings[${mappingIndex}]`,
126
+ severity: "warning",
127
+ });
128
+ return [];
129
+ }
130
+ return [
131
+ {
132
+ interfacePropertyApiName: interfaceProperty.apiName.sanitized,
133
+ objectPropertyApiName: objectProperty.apiName.sanitized,
134
+ },
135
+ ];
136
+ }
137
+ function interfacePropertyByApiName(interfaceType, apiName) {
138
+ return interfaceType.properties.find((property) => property.apiName.original === apiName ||
139
+ property.apiName.sanitized === apiName);
140
+ }
141
+ function flattenedInterfaceProperties(interfaceType, interfacesByApiName, seen = new Set()) {
142
+ if (seen.has(interfaceType.apiName.sanitized)) {
143
+ return interfaceType.properties;
144
+ }
145
+ seen.add(interfaceType.apiName.sanitized);
146
+ return [
147
+ ...interfaceType.extendsInterfaces.flatMap((reference) => {
148
+ const extended = interfacesByApiName.get(reference.apiName.original);
149
+ return extended
150
+ ? flattenedInterfaceProperties(extended, interfacesByApiName, seen)
151
+ : [];
152
+ }),
153
+ ...interfaceType.properties,
154
+ ];
155
+ }
74
156
  function addObjectTypeGroupsDiagnostic(objectType, objectIndex, apiName, diagnostics) {
75
157
  if ((objectType.typeGroups?.length ?? 0) === 0) {
76
158
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/foundry-import/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGxE,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,oBAAoB,EAAE,GACjC,eAAe,CAejB"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/foundry-import/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EAYpB,eAAe,EACf,MAAM,YAAY,CAAC;AAOpB,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,oBAAoB,EAAE,GACjC,eAAe,CAkBjB"}