@ontologie/schema 0.1.0-preview.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.
Files changed (70) hide show
  1. package/README.md +149 -0
  2. package/dist/compiler/compile.d.ts +36 -0
  3. package/dist/compiler/compile.d.ts.map +1 -0
  4. package/dist/compiler/compile.js +295 -0
  5. package/dist/compiler/compile.js.map +1 -0
  6. package/dist/compiler/resolve.d.ts +14 -0
  7. package/dist/compiler/resolve.d.ts.map +1 -0
  8. package/dist/compiler/resolve.js +132 -0
  9. package/dist/compiler/resolve.js.map +1 -0
  10. package/dist/compiler/validate.d.ts +15 -0
  11. package/dist/compiler/validate.d.ts.map +1 -0
  12. package/dist/compiler/validate.js +188 -0
  13. package/dist/compiler/validate.js.map +1 -0
  14. package/dist/diff/diff.d.ts +20 -0
  15. package/dist/diff/diff.d.ts.map +1 -0
  16. package/dist/diff/diff.js +393 -0
  17. package/dist/diff/diff.js.map +1 -0
  18. package/dist/diff/format.d.ts +21 -0
  19. package/dist/diff/format.d.ts.map +1 -0
  20. package/dist/diff/format.js +88 -0
  21. package/dist/diff/format.js.map +1 -0
  22. package/dist/dsl/action.d.ts +148 -0
  23. package/dist/dsl/action.d.ts.map +1 -0
  24. package/dist/dsl/action.js +330 -0
  25. package/dist/dsl/action.js.map +1 -0
  26. package/dist/dsl/interface.d.ts +32 -0
  27. package/dist/dsl/interface.d.ts.map +1 -0
  28. package/dist/dsl/interface.js +77 -0
  29. package/dist/dsl/interface.js.map +1 -0
  30. package/dist/dsl/link.d.ts +34 -0
  31. package/dist/dsl/link.d.ts.map +1 -0
  32. package/dist/dsl/link.js +95 -0
  33. package/dist/dsl/link.js.map +1 -0
  34. package/dist/dsl/object-type.d.ts +35 -0
  35. package/dist/dsl/object-type.d.ts.map +1 -0
  36. package/dist/dsl/object-type.js +102 -0
  37. package/dist/dsl/object-type.js.map +1 -0
  38. package/dist/dsl/property.d.ts +43 -0
  39. package/dist/dsl/property.d.ts.map +1 -0
  40. package/dist/dsl/property.js +125 -0
  41. package/dist/dsl/property.js.map +1 -0
  42. package/dist/index.d.ts +47 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +63 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/lock/lockfile.d.ts +31 -0
  47. package/dist/lock/lockfile.d.ts.map +1 -0
  48. package/dist/lock/lockfile.js +86 -0
  49. package/dist/lock/lockfile.js.map +1 -0
  50. package/dist/model-assurance.d.ts +27 -0
  51. package/dist/model-assurance.d.ts.map +1 -0
  52. package/dist/model-assurance.js +194 -0
  53. package/dist/model-assurance.js.map +1 -0
  54. package/dist/pull/emit-schema.d.ts +24 -0
  55. package/dist/pull/emit-schema.d.ts.map +1 -0
  56. package/dist/pull/emit-schema.js +476 -0
  57. package/dist/pull/emit-schema.js.map +1 -0
  58. package/dist/push/execute.d.ts +34 -0
  59. package/dist/push/execute.d.ts.map +1 -0
  60. package/dist/push/execute.js +119 -0
  61. package/dist/push/execute.js.map +1 -0
  62. package/dist/push/plan.d.ts +37 -0
  63. package/dist/push/plan.d.ts.map +1 -0
  64. package/dist/push/plan.js +484 -0
  65. package/dist/push/plan.js.map +1 -0
  66. package/dist/types.d.ts +330 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +5 -0
  69. package/dist/types.js.map +1 -0
  70. package/package.json +45 -0
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Validate a resolved schema for correctness.
3
+ */
4
+ import type { ResolvedSchema, ValidationResult } from '../types.js';
5
+ /**
6
+ * Validate a resolved schema:
7
+ * - No duplicate apiNames across object types
8
+ * - All apiNames are valid identifiers (alphanumeric + underscore, starts with letter)
9
+ * - No duplicate property names within a type
10
+ * - All link targets resolve to known types (already enforced by resolve, but double-check)
11
+ * - Action targets resolve to known types
12
+ * - No duplicate action apiNames per object type
13
+ */
14
+ export declare function validate(schema: ResolvedSchema): ValidationResult;
15
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/compiler/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAmB,MAAM,aAAa,CAAC;AAIrF;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,gBAAgB,CA6LjE"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Validate a resolved schema for correctness.
3
+ */
4
+ const API_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
5
+ /**
6
+ * Validate a resolved schema:
7
+ * - No duplicate apiNames across object types
8
+ * - All apiNames are valid identifiers (alphanumeric + underscore, starts with letter)
9
+ * - No duplicate property names within a type
10
+ * - All link targets resolve to known types (already enforced by resolve, but double-check)
11
+ * - Action targets resolve to known types
12
+ * - No duplicate action apiNames per object type
13
+ */
14
+ export function validate(schema) {
15
+ const errors = [];
16
+ const seenApiNames = new Map();
17
+ for (const ot of schema.objectTypes) {
18
+ // Check apiName format
19
+ if (!API_NAME_RE.test(ot.apiName)) {
20
+ errors.push({
21
+ path: ot.apiName,
22
+ message: `Invalid apiName "${ot.apiName}": must start with a letter and contain only alphanumeric characters and underscores`,
23
+ });
24
+ }
25
+ // Check duplicate apiNames
26
+ const count = (seenApiNames.get(ot.apiName) ?? 0) + 1;
27
+ seenApiNames.set(ot.apiName, count);
28
+ if (count > 1) {
29
+ errors.push({
30
+ path: ot.apiName,
31
+ message: `Duplicate objectType apiName "${ot.apiName}"`,
32
+ });
33
+ }
34
+ // Check duplicate property/link names within the type
35
+ const fieldNames = new Set();
36
+ for (const propName of Object.keys(ot.properties)) {
37
+ if (fieldNames.has(propName)) {
38
+ errors.push({
39
+ path: `${ot.apiName}.${propName}`,
40
+ message: `Duplicate field name "${propName}" in type "${ot.apiName}"`,
41
+ });
42
+ }
43
+ fieldNames.add(propName);
44
+ }
45
+ for (const linkName of Object.keys(ot.links)) {
46
+ if (fieldNames.has(linkName)) {
47
+ errors.push({
48
+ path: `${ot.apiName}.${linkName}`,
49
+ message: `Duplicate field name "${linkName}" in type "${ot.apiName}" (link collides with property)`,
50
+ });
51
+ }
52
+ fieldNames.add(linkName);
53
+ }
54
+ // Verify primaryKey field exists in properties (if declared)
55
+ if (ot.primaryKey && !ot.properties[ot.primaryKey]) {
56
+ errors.push({
57
+ path: `${ot.apiName}.primaryKey`,
58
+ message: `primaryKey "${ot.primaryKey}" is not a declared property of "${ot.apiName}"`,
59
+ });
60
+ }
61
+ // Verify link targets exist
62
+ const allApiNames = new Set(schema.objectTypes.map(o => o.apiName));
63
+ for (const [linkName, linkDef] of Object.entries(ot.links)) {
64
+ if (!allApiNames.has(linkDef.targetApiName)) {
65
+ errors.push({
66
+ path: `${ot.apiName}.${linkName}`,
67
+ message: `Link "${linkName}" targets unknown type "${linkDef.targetApiName}"`,
68
+ });
69
+ }
70
+ }
71
+ }
72
+ // Validate interfaces
73
+ const seenIfaceNames = new Set();
74
+ for (const iface of schema.interfaces) {
75
+ if (!API_NAME_RE.test(iface.apiName)) {
76
+ errors.push({
77
+ path: `interface:${iface.apiName}`,
78
+ message: `Invalid interface apiName "${iface.apiName}": must start with a letter and contain only alphanumeric characters and underscores`,
79
+ });
80
+ }
81
+ if (seenIfaceNames.has(iface.apiName)) {
82
+ errors.push({
83
+ path: `interface:${iface.apiName}`,
84
+ message: `Duplicate interface apiName "${iface.apiName}"`,
85
+ });
86
+ }
87
+ seenIfaceNames.add(iface.apiName);
88
+ }
89
+ // Validate interface references on types
90
+ const knownIfaceNames = new Set(schema.interfaces.map(i => i.apiName));
91
+ for (const ot of schema.objectTypes) {
92
+ for (const ifaceName of ot.interfaces) {
93
+ if (!knownIfaceNames.has(ifaceName)) {
94
+ errors.push({
95
+ path: `${ot.apiName}.implements`,
96
+ message: `Type "${ot.apiName}" implements unknown interface "${ifaceName}"`,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ // Validate actions
102
+ const allTypeApiNames = new Set(schema.objectTypes.map(o => o.apiName));
103
+ const seenActionKeys = new Set();
104
+ for (const action of schema.actions) {
105
+ // Action apiName format
106
+ if (!API_NAME_RE.test(action.apiName)) {
107
+ errors.push({
108
+ path: `action:${action.apiName}`,
109
+ message: `Invalid action apiName "${action.apiName}": must start with a letter and contain only alphanumeric characters and underscores`,
110
+ });
111
+ }
112
+ // Action must target a known type
113
+ if (!allTypeApiNames.has(action.objectTypeApiName)) {
114
+ errors.push({
115
+ path: `action:${action.apiName}`,
116
+ message: `Action "${action.apiName}" targets unknown type "${action.objectTypeApiName}"`,
117
+ });
118
+ }
119
+ // No duplicate action apiName per type
120
+ const key = `${action.objectTypeApiName}.${action.apiName}`;
121
+ if (seenActionKeys.has(key)) {
122
+ errors.push({
123
+ path: `action:${key}`,
124
+ message: `Duplicate action "${action.apiName}" on type "${action.objectTypeApiName}"`,
125
+ });
126
+ }
127
+ seenActionKeys.add(key);
128
+ // Validate condition operators and field existence
129
+ const validOps = new Set(['equals', 'not_equals', 'greater_than', 'less_than', 'contains', 'in', 'is_null']);
130
+ const targetType = schema.objectTypes.find(ot => ot.apiName === action.objectTypeApiName);
131
+ for (const cond of action.conditions) {
132
+ if (!validOps.has(cond.operator)) {
133
+ errors.push({
134
+ path: `action:${action.apiName}.when`,
135
+ message: `Invalid condition operator "${cond.operator}" — valid: ${[...validOps].join(', ')}`,
136
+ });
137
+ }
138
+ // Verify .when() field exists on target type
139
+ if (targetType && cond.field && !targetType.properties[cond.field]) {
140
+ errors.push({
141
+ path: `action:${action.apiName}.when(${cond.field})`,
142
+ message: `Condition references unknown field "${cond.field}" on type "${action.objectTypeApiName}"`,
143
+ });
144
+ }
145
+ }
146
+ // Validate .set() effect field existence
147
+ if (targetType) {
148
+ for (const effect of action.effects) {
149
+ const eRecord = effect;
150
+ const kind = eRecord.kind ?? 'set';
151
+ if (kind === 'set' && eRecord.field) {
152
+ const fieldName = eRecord.field;
153
+ if (!targetType.properties[fieldName]) {
154
+ errors.push({
155
+ path: `action:${action.apiName}.set(${fieldName})`,
156
+ message: `Effect sets unknown field "${fieldName}" on type "${action.objectTypeApiName}"`,
157
+ });
158
+ }
159
+ }
160
+ }
161
+ }
162
+ // V1 DSL: Validate mutableBy — action must be in the property's mutableBy list
163
+ if (allTypeApiNames.has(action.objectTypeApiName)) {
164
+ const targetType = schema.objectTypes.find(ot => ot.apiName === action.objectTypeApiName);
165
+ if (targetType) {
166
+ const actionKey = `${action.objectTypeApiName}.${action.apiName}`;
167
+ for (const effect of action.effects) {
168
+ const eRecord = effect;
169
+ const kind = eRecord.kind ?? 'set';
170
+ if (kind === 'set' && eRecord.field) {
171
+ const fieldName = eRecord.field;
172
+ const prop = targetType.properties[fieldName];
173
+ if (prop?.mutableBy && prop.mutableBy.length > 0) {
174
+ if (!prop.mutableBy.includes(action.apiName) && !prop.mutableBy.includes(actionKey)) {
175
+ errors.push({
176
+ path: `action:${action.apiName}.set(${fieldName})`,
177
+ message: `Action "${action.apiName}" sets field "${fieldName}" but is not in its mutableBy list [${prop.mutableBy.join(', ')}]`,
178
+ });
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ return { valid: errors.length === 0, errors };
187
+ }
188
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/compiler/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Diff engine — compare local OntologyManifest vs remote
3
+ *
4
+ * Fixes over V2:
5
+ * - Canonical enum normalization: constraints.enum and enumValues unified into single representation
6
+ * - constraints.enum removed from comparison (only enumValues compared)
7
+ * - dataType 'enum' normalized to 'string' (backend normalizes the same way)
8
+ * - name excluded from property comparison (apiName is the identity)
9
+ * - displayName defaults normalized (generated displayName == no displayName)
10
+ * - Default values for actionType ('CUSTOM'), trigger ('MANUAL'), status ('active') normalized
11
+ * - Link relationshipType default derived from apiName (strip prefix)
12
+ * - indexed forced true when unique is true (unique implies indexed)
13
+ */
14
+ import type { OntologyManifest } from '@dataforge/sdk-types';
15
+ import type { DiffResult } from '../types.js';
16
+ /**
17
+ * Compare a local manifest against a remote manifest and produce a diff.
18
+ */
19
+ export declare function diff(local: OntologyManifest, remote: OntologyManifest): DiffResult;
20
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/diff/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAiF,MAAM,sBAAsB,CAAC;AAC5I,OAAO,KAAK,EAAE,UAAU,EAAc,MAAM,aAAa,CAAC;AAuZ1D;;GAEG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAqDlF"}
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Diff engine — compare local OntologyManifest vs remote
3
+ *
4
+ * Fixes over V2:
5
+ * - Canonical enum normalization: constraints.enum and enumValues unified into single representation
6
+ * - constraints.enum removed from comparison (only enumValues compared)
7
+ * - dataType 'enum' normalized to 'string' (backend normalizes the same way)
8
+ * - name excluded from property comparison (apiName is the identity)
9
+ * - displayName defaults normalized (generated displayName == no displayName)
10
+ * - Default values for actionType ('CUSTOM'), trigger ('MANUAL'), status ('active') normalized
11
+ * - Link relationshipType default derived from apiName (strip prefix)
12
+ * - indexed forced true when unique is true (unique implies indexed)
13
+ */
14
+ // ---------------------------------------------------------------------------
15
+ // Canonical comparison helpers
16
+ // ---------------------------------------------------------------------------
17
+ function isRecord(value) {
18
+ return !!value && typeof value === 'object' && !Array.isArray(value);
19
+ }
20
+ function canonicalize(value) {
21
+ if (Array.isArray(value)) {
22
+ return value.map(canonicalize);
23
+ }
24
+ if (isRecord(value)) {
25
+ const output = {};
26
+ for (const key of Object.keys(value).sort()) {
27
+ const v = value[key];
28
+ if (v !== undefined) {
29
+ output[key] = canonicalize(v);
30
+ }
31
+ }
32
+ return output;
33
+ }
34
+ return value;
35
+ }
36
+ function deepEqualCanonical(a, b) {
37
+ return JSON.stringify(canonicalize(a)) === JSON.stringify(canonicalize(b));
38
+ }
39
+ function uniqueSortedStrings(value) {
40
+ if (!Array.isArray(value))
41
+ return [];
42
+ return [...new Set(value.filter((item) => typeof item === 'string'))].sort((a, b) => a.localeCompare(b));
43
+ }
44
+ function normalizeOptionalString(value) {
45
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
46
+ }
47
+ function defaultDisplayName(apiName) {
48
+ return apiName
49
+ .replace(/_/g, ' ')
50
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
51
+ .replace(/\b\w/g, c => c.toUpperCase());
52
+ }
53
+ /** Normalize displayName: if it matches the auto-generated default, treat as absent */
54
+ function normalizeDisplayName(apiName, displayName) {
55
+ const value = normalizeOptionalString(displayName);
56
+ if (!value)
57
+ return undefined;
58
+ if (value === defaultDisplayName(apiName))
59
+ return undefined;
60
+ return value;
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Enum normalization — unify constraints.enum and enumValues
64
+ // ---------------------------------------------------------------------------
65
+ function normalizeEnumValues(prop) {
66
+ const fromEnumValues = uniqueSortedStrings(prop.enumValues);
67
+ if (fromEnumValues.length > 0)
68
+ return fromEnumValues;
69
+ const constraints = isRecord(prop.constraints) ? prop.constraints : undefined;
70
+ const fromConstraints = uniqueSortedStrings(constraints?.enum);
71
+ return fromConstraints.length > 0 ? fromConstraints : undefined;
72
+ }
73
+ function normalizeConstraintsWithoutEnum(prop) {
74
+ if (!isRecord(prop.constraints))
75
+ return undefined;
76
+ const output = {};
77
+ for (const [key, value] of Object.entries(prop.constraints)) {
78
+ if (key !== 'enum' && value !== undefined) {
79
+ output[key] = value;
80
+ }
81
+ }
82
+ return Object.keys(output).length > 0
83
+ ? canonicalize(output)
84
+ : undefined;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Property normalization & diff
88
+ // ---------------------------------------------------------------------------
89
+ function normalizeProperty(prop) {
90
+ const enumValues = normalizeEnumValues(prop);
91
+ const constraints = normalizeConstraintsWithoutEnum(prop);
92
+ const result = {
93
+ apiName: prop.apiName,
94
+ // Normalize 'enum' to 'string' — backend does the same
95
+ dataType: prop.dataType === 'enum' ? 'string' : prop.dataType,
96
+ required: prop.required === true,
97
+ indexed: prop.indexed === true || prop.unique === true,
98
+ unique: prop.unique === true,
99
+ };
100
+ if (normalizeOptionalString(prop.description) !== undefined) {
101
+ result.description = prop.description;
102
+ }
103
+ if (normalizeOptionalString(prop.semanticType) !== undefined) {
104
+ result.semanticType = prop.semanticType;
105
+ }
106
+ if (prop.defaultValue !== undefined) {
107
+ result.defaultValue = prop.defaultValue;
108
+ }
109
+ if (constraints !== undefined) {
110
+ result.constraints = constraints;
111
+ }
112
+ if (enumValues !== undefined) {
113
+ result.enumValues = enumValues;
114
+ }
115
+ const mutableBy = uniqueSortedStrings(prop.mutableBy);
116
+ if (mutableBy.length > 0)
117
+ result.mutableBy = mutableBy;
118
+ return canonicalize(result);
119
+ }
120
+ function diffProperties(localProps, remoteProps, parentName) {
121
+ const changes = [];
122
+ const remoteMap = new Map(remoteProps.map(p => [p.apiName, p]));
123
+ const localMap = new Map(localProps.map(p => [p.apiName, p]));
124
+ // Added properties
125
+ for (const [name, prop] of localMap) {
126
+ if (!remoteMap.has(name)) {
127
+ changes.push({ kind: 'added', entity: 'property', name, parent: parentName, after: prop });
128
+ }
129
+ }
130
+ // Removed properties
131
+ for (const [name, prop] of remoteMap) {
132
+ if (!localMap.has(name)) {
133
+ changes.push({ kind: 'removed', entity: 'property', name, parent: parentName, before: prop });
134
+ }
135
+ }
136
+ // Modified properties — canonical comparison
137
+ for (const [name, localProp] of localMap) {
138
+ const remoteProp = remoteMap.get(name);
139
+ if (!remoteProp)
140
+ continue;
141
+ if (!deepEqualCanonical(normalizeProperty(localProp), normalizeProperty(remoteProp))) {
142
+ changes.push({
143
+ kind: 'modified',
144
+ entity: 'property',
145
+ name,
146
+ parent: parentName,
147
+ before: remoteProp,
148
+ after: localProp,
149
+ });
150
+ }
151
+ }
152
+ return changes;
153
+ }
154
+ // ---------------------------------------------------------------------------
155
+ // ObjectType metadata normalization
156
+ // ---------------------------------------------------------------------------
157
+ function normalizeObjectTypeMeta(type) {
158
+ const result = {};
159
+ const status = type.status ?? 'active';
160
+ if (status !== 'active')
161
+ result.status = status;
162
+ const displayName = normalizeDisplayName(type.apiName, type.displayName);
163
+ if (displayName !== undefined)
164
+ result.displayName = displayName;
165
+ if (normalizeOptionalString(type.description) !== undefined) {
166
+ result.description = type.description;
167
+ }
168
+ if (normalizeOptionalString(type.primaryKey) !== undefined) {
169
+ result.primaryKey = type.primaryKey;
170
+ }
171
+ const interfaces = uniqueSortedStrings(type.interfaces);
172
+ if (interfaces.length > 0)
173
+ result.interfaces = interfaces;
174
+ return canonicalize(result);
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Link type diff
178
+ // ---------------------------------------------------------------------------
179
+ /** Derive default relationshipType from link apiName */
180
+ function linkFieldNameFromApiName(link) {
181
+ const prefix = `${link.sourceTypeApiName}__`;
182
+ return link.apiName.startsWith(prefix)
183
+ ? link.apiName.slice(prefix.length)
184
+ : link.apiName;
185
+ }
186
+ function normalizeLinkType(link) {
187
+ const result = {
188
+ apiName: link.apiName,
189
+ sourceTypeApiName: link.sourceTypeApiName,
190
+ targetTypeApiName: link.targetTypeApiName,
191
+ cardinality: link.cardinality ?? 'N:1',
192
+ };
193
+ const defaultRelationshipType = linkFieldNameFromApiName(link);
194
+ const relationshipType = normalizeOptionalString(link.relationshipType);
195
+ if (relationshipType && relationshipType !== defaultRelationshipType) {
196
+ result.relationshipType = relationshipType;
197
+ }
198
+ if (normalizeOptionalString(link.label) !== undefined)
199
+ result.label = link.label;
200
+ if (normalizeOptionalString(link.inverseName) !== undefined)
201
+ result.inverseName = link.inverseName;
202
+ if (Array.isArray(link.properties) && link.properties.length > 0) {
203
+ result.properties = link.properties.map(canonicalize);
204
+ }
205
+ return canonicalize(result);
206
+ }
207
+ function diffLinkTypes(localLinks, remoteLinks) {
208
+ const changes = [];
209
+ const remoteMap = new Map(remoteLinks.map(l => [l.apiName, l]));
210
+ const localMap = new Map(localLinks.map(l => [l.apiName, l]));
211
+ for (const [name, link] of localMap) {
212
+ if (!remoteMap.has(name)) {
213
+ changes.push({ kind: 'added', entity: 'linkType', name, after: link });
214
+ }
215
+ }
216
+ for (const [name, link] of remoteMap) {
217
+ if (!localMap.has(name)) {
218
+ changes.push({ kind: 'removed', entity: 'linkType', name, before: link });
219
+ }
220
+ }
221
+ for (const [name, localLink] of localMap) {
222
+ const remoteLink = remoteMap.get(name);
223
+ if (!remoteLink)
224
+ continue;
225
+ if (!deepEqualCanonical(normalizeLinkType(localLink), normalizeLinkType(remoteLink))) {
226
+ changes.push({ kind: 'modified', entity: 'linkType', name, before: remoteLink, after: localLink });
227
+ }
228
+ }
229
+ return changes;
230
+ }
231
+ // ---------------------------------------------------------------------------
232
+ // Action diff
233
+ // ---------------------------------------------------------------------------
234
+ function normalizeActionParameters(parameters) {
235
+ if (!Array.isArray(parameters))
236
+ return [];
237
+ return parameters.map(parameter => canonicalize({
238
+ name: parameter.name,
239
+ apiName: parameter.apiName ?? parameter.name,
240
+ dataType: parameter.dataType,
241
+ required: parameter.required === true,
242
+ ...(parameter.defaultValue !== undefined ? { defaultValue: parameter.defaultValue } : {}),
243
+ ...(normalizeOptionalString(parameter.description) ? { description: parameter.description } : {}),
244
+ }));
245
+ }
246
+ function normalizeAction(action) {
247
+ const result = {
248
+ apiName: action.apiName,
249
+ objectTypeApiName: action.objectTypeApiName,
250
+ parameters: normalizeActionParameters(action.parameters ?? []),
251
+ };
252
+ const displayName = normalizeDisplayName(action.apiName, action.displayName);
253
+ if (displayName !== undefined)
254
+ result.displayName = displayName;
255
+ if (normalizeOptionalString(action.description) !== undefined) {
256
+ result.description = action.description;
257
+ }
258
+ const actionType = action.actionType ?? 'CUSTOM';
259
+ if (actionType !== 'CUSTOM')
260
+ result.actionType = actionType;
261
+ const trigger = action.trigger ?? 'MANUAL';
262
+ if (trigger !== 'MANUAL')
263
+ result.trigger = trigger;
264
+ if (Array.isArray(action.conditions) && action.conditions.length > 0) {
265
+ result.conditions = action.conditions.map(canonicalize);
266
+ }
267
+ if (Array.isArray(action.effects) && action.effects.length > 0) {
268
+ result.effects = action.effects.map(canonicalize);
269
+ }
270
+ if (action.timeout !== undefined) {
271
+ result.timeout = action.timeout;
272
+ }
273
+ // requiredScopes: compare as a sorted set (order is not semantically significant)
274
+ const scopes = uniqueSortedStrings(action.requiredScopes);
275
+ if (scopes.length > 0)
276
+ result.requiredScopes = scopes;
277
+ return canonicalize(result);
278
+ }
279
+ function diffActions(localActions, remoteActions) {
280
+ const changes = [];
281
+ const key = (a) => `${a.objectTypeApiName}.${a.apiName}`;
282
+ const remoteMap = new Map(remoteActions.map(a => [key(a), a]));
283
+ const localMap = new Map(localActions.map(a => [key(a), a]));
284
+ for (const [k, act] of localMap) {
285
+ if (!remoteMap.has(k)) {
286
+ changes.push({ kind: 'added', entity: 'action', name: k, after: act });
287
+ }
288
+ }
289
+ for (const [k, act] of remoteMap) {
290
+ if (!localMap.has(k)) {
291
+ changes.push({ kind: 'removed', entity: 'action', name: k, before: act });
292
+ }
293
+ }
294
+ for (const [k, localAct] of localMap) {
295
+ const remoteAct = remoteMap.get(k);
296
+ if (!remoteAct)
297
+ continue;
298
+ if (!deepEqualCanonical(normalizeAction(localAct), normalizeAction(remoteAct))) {
299
+ changes.push({ kind: 'modified', entity: 'action', name: k, before: remoteAct, after: localAct });
300
+ }
301
+ }
302
+ return changes;
303
+ }
304
+ // ---------------------------------------------------------------------------
305
+ // Interface diff
306
+ // ---------------------------------------------------------------------------
307
+ function normalizeInterfaceMeta(iface) {
308
+ const result = {
309
+ apiName: iface.apiName,
310
+ };
311
+ const displayName = normalizeDisplayName(iface.apiName, iface.displayName);
312
+ if (displayName !== undefined)
313
+ result.displayName = displayName;
314
+ if (normalizeOptionalString(iface.description) !== undefined)
315
+ result.description = iface.description;
316
+ return canonicalize(result);
317
+ }
318
+ function diffInterfaces(localInterfaces, remoteInterfaces) {
319
+ const changes = [];
320
+ const remoteMap = new Map(remoteInterfaces.map(i => [i.apiName, i]));
321
+ const localMap = new Map(localInterfaces.map(i => [i.apiName, i]));
322
+ for (const [name, iface] of localMap) {
323
+ if (!remoteMap.has(name)) {
324
+ changes.push({ kind: 'added', entity: 'interface', name, after: iface });
325
+ }
326
+ }
327
+ for (const [name, iface] of remoteMap) {
328
+ if (!localMap.has(name)) {
329
+ changes.push({ kind: 'removed', entity: 'interface', name, before: iface });
330
+ }
331
+ }
332
+ for (const [name, localIface] of localMap) {
333
+ const remoteIface = remoteMap.get(name);
334
+ if (!remoteIface)
335
+ continue;
336
+ const metaChanged = !deepEqualCanonical(normalizeInterfaceMeta(localIface), normalizeInterfaceMeta(remoteIface));
337
+ const propChanges = diffProperties(localIface.sharedProperties ?? [], remoteIface.sharedProperties ?? [], name);
338
+ if (metaChanged || propChanges.length > 0) {
339
+ changes.push({ kind: 'modified', entity: 'interface', name, before: remoteIface, after: localIface });
340
+ changes.push(...propChanges);
341
+ }
342
+ }
343
+ return changes;
344
+ }
345
+ // ---------------------------------------------------------------------------
346
+ // Main diff entry point
347
+ // ---------------------------------------------------------------------------
348
+ /**
349
+ * Compare a local manifest against a remote manifest and produce a diff.
350
+ */
351
+ export function diff(local, remote) {
352
+ const changes = [];
353
+ const remoteTypeMap = new Map(remote.objectTypes.map(t => [t.apiName, t]));
354
+ const localTypeMap = new Map(local.objectTypes.map(t => [t.apiName, t]));
355
+ // Added types
356
+ for (const [name, type] of localTypeMap) {
357
+ if (!remoteTypeMap.has(name)) {
358
+ changes.push({ kind: 'added', entity: 'objectType', name, after: type });
359
+ }
360
+ }
361
+ // Removed types
362
+ for (const [name, type] of remoteTypeMap) {
363
+ if (!localTypeMap.has(name)) {
364
+ changes.push({ kind: 'removed', entity: 'objectType', name, before: type });
365
+ }
366
+ }
367
+ // Modified types — compare properties AND metadata
368
+ for (const [name, localType] of localTypeMap) {
369
+ const remoteType = remoteTypeMap.get(name);
370
+ if (!remoteType)
371
+ continue;
372
+ const propChanges = diffProperties(localType.properties ?? [], remoteType.properties ?? [], name);
373
+ const metaChanged = !deepEqualCanonical(normalizeObjectTypeMeta(localType), normalizeObjectTypeMeta(remoteType));
374
+ if (metaChanged || propChanges.length > 0) {
375
+ changes.push({
376
+ kind: 'modified',
377
+ entity: 'objectType',
378
+ name,
379
+ before: remoteType,
380
+ after: localType,
381
+ });
382
+ changes.push(...propChanges);
383
+ }
384
+ }
385
+ // Link type diffs
386
+ changes.push(...diffLinkTypes(local.linkTypes, remote.linkTypes));
387
+ // Action diffs
388
+ changes.push(...diffActions(local.actions ?? [], remote.actions ?? []));
389
+ // Interface diffs
390
+ changes.push(...diffInterfaces(local.interfaces ?? [], remote.interfaces ?? []));
391
+ return { hasChanges: changes.length > 0, changes };
392
+ }
393
+ //# sourceMappingURL=diff.js.map