@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.
- package/README.md +149 -0
- package/dist/compiler/compile.d.ts +36 -0
- package/dist/compiler/compile.d.ts.map +1 -0
- package/dist/compiler/compile.js +295 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/resolve.d.ts +14 -0
- package/dist/compiler/resolve.d.ts.map +1 -0
- package/dist/compiler/resolve.js +132 -0
- package/dist/compiler/resolve.js.map +1 -0
- package/dist/compiler/validate.d.ts +15 -0
- package/dist/compiler/validate.d.ts.map +1 -0
- package/dist/compiler/validate.js +188 -0
- package/dist/compiler/validate.js.map +1 -0
- package/dist/diff/diff.d.ts +20 -0
- package/dist/diff/diff.d.ts.map +1 -0
- package/dist/diff/diff.js +393 -0
- package/dist/diff/diff.js.map +1 -0
- package/dist/diff/format.d.ts +21 -0
- package/dist/diff/format.d.ts.map +1 -0
- package/dist/diff/format.js +88 -0
- package/dist/diff/format.js.map +1 -0
- package/dist/dsl/action.d.ts +148 -0
- package/dist/dsl/action.d.ts.map +1 -0
- package/dist/dsl/action.js +330 -0
- package/dist/dsl/action.js.map +1 -0
- package/dist/dsl/interface.d.ts +32 -0
- package/dist/dsl/interface.d.ts.map +1 -0
- package/dist/dsl/interface.js +77 -0
- package/dist/dsl/interface.js.map +1 -0
- package/dist/dsl/link.d.ts +34 -0
- package/dist/dsl/link.d.ts.map +1 -0
- package/dist/dsl/link.js +95 -0
- package/dist/dsl/link.js.map +1 -0
- package/dist/dsl/object-type.d.ts +35 -0
- package/dist/dsl/object-type.d.ts.map +1 -0
- package/dist/dsl/object-type.js +102 -0
- package/dist/dsl/object-type.js.map +1 -0
- package/dist/dsl/property.d.ts +43 -0
- package/dist/dsl/property.d.ts.map +1 -0
- package/dist/dsl/property.js +125 -0
- package/dist/dsl/property.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/lock/lockfile.d.ts +31 -0
- package/dist/lock/lockfile.d.ts.map +1 -0
- package/dist/lock/lockfile.js +86 -0
- package/dist/lock/lockfile.js.map +1 -0
- package/dist/model-assurance.d.ts +27 -0
- package/dist/model-assurance.d.ts.map +1 -0
- package/dist/model-assurance.js +194 -0
- package/dist/model-assurance.js.map +1 -0
- package/dist/pull/emit-schema.d.ts +24 -0
- package/dist/pull/emit-schema.d.ts.map +1 -0
- package/dist/pull/emit-schema.js +476 -0
- package/dist/pull/emit-schema.js.map +1 -0
- package/dist/push/execute.d.ts +34 -0
- package/dist/push/execute.d.ts.map +1 -0
- package/dist/push/execute.js +119 -0
- package/dist/push/execute.js.map +1 -0
- package/dist/push/plan.d.ts +37 -0
- package/dist/push/plan.d.ts.map +1 -0
- package/dist/push/plan.js +484 -0
- package/dist/push/plan.js.map +1 -0
- package/dist/types.d.ts +330 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- 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
|