@bleedingdev/modern-js-backend-federation-contracts 0.0.0 → 3.9.0-ultramodern.11
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/LICENSE +21 -0
- package/dist/cjs/backend-federation-contract/build-artifact.js +185 -0
- package/dist/cjs/backend-federation-contract/constants.js +92 -0
- package/dist/cjs/backend-federation-contract/delivery-unit.js +130 -0
- package/dist/cjs/backend-federation-contract/index.js +137 -0
- package/dist/cjs/backend-federation-contract/metadata.js +133 -0
- package/dist/cjs/backend-federation-contract/types.js +18 -0
- package/dist/cjs/backend-federation-contract/validation-core.js +85 -0
- package/dist/esm/backend-federation-contract/build-artifact.mjs +135 -0
- package/dist/esm/backend-federation-contract/constants.mjs +18 -0
- package/dist/esm/backend-federation-contract/delivery-unit.mjs +74 -0
- package/dist/esm/backend-federation-contract/index.mjs +5 -0
- package/dist/esm/backend-federation-contract/metadata.mjs +80 -0
- package/dist/esm/backend-federation-contract/types.mjs +0 -0
- package/dist/esm/backend-federation-contract/validation-core.mjs +29 -0
- package/dist/esm-node/backend-federation-contract/build-artifact.mjs +136 -0
- package/dist/esm-node/backend-federation-contract/constants.mjs +19 -0
- package/dist/esm-node/backend-federation-contract/delivery-unit.mjs +75 -0
- package/dist/esm-node/backend-federation-contract/index.mjs +6 -0
- package/dist/esm-node/backend-federation-contract/metadata.mjs +81 -0
- package/dist/esm-node/backend-federation-contract/types.mjs +1 -0
- package/dist/esm-node/backend-federation-contract/validation-core.mjs +30 -0
- package/dist/types/backend-federation-contract/build-artifact.d.ts +9 -0
- package/dist/types/backend-federation-contract/constants.d.ts +14 -0
- package/dist/types/backend-federation-contract/delivery-unit.d.ts +9 -0
- package/dist/types/backend-federation-contract/index.d.ts +7 -0
- package/dist/types/backend-federation-contract/metadata.d.ts +7 -0
- package/dist/types/backend-federation-contract/types.d.ts +60 -0
- package/dist/types/backend-federation-contract/validation-core.d.ts +8 -0
- package/package.json +38 -5
- package/src/backend-federation-contract/build-artifact.ts +224 -0
- package/src/backend-federation-contract/constants.ts +33 -0
- package/src/backend-federation-contract/delivery-unit.ts +170 -0
- package/src/backend-federation-contract/index.ts +56 -0
- package/src/backend-federation-contract/metadata.ts +225 -0
- package/src/backend-federation-contract/types.ts +73 -0
- package/src/backend-federation-contract/validation-core.ts +67 -0
- package/README.md +0 -3
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DELIVERY_UNIT_IDENTITY_FIELDS,
|
|
3
|
+
DELIVERY_UNIT_SCHEMA_VERSION,
|
|
4
|
+
} from './constants';
|
|
5
|
+
import {
|
|
6
|
+
deliveryUnitIdentityFieldValue,
|
|
7
|
+
validateDeliveryUnitRecord,
|
|
8
|
+
} from './delivery-unit';
|
|
9
|
+
import type {
|
|
10
|
+
BackendFederationContractValidationError,
|
|
11
|
+
BackendFederationContractValidationResult,
|
|
12
|
+
DeliveryUnitRecord,
|
|
13
|
+
UltramodernBuildArtifact,
|
|
14
|
+
} from './types';
|
|
15
|
+
import {
|
|
16
|
+
addError,
|
|
17
|
+
isRecord,
|
|
18
|
+
nonEmptyString,
|
|
19
|
+
recordField,
|
|
20
|
+
validationResult,
|
|
21
|
+
} from './validation-core';
|
|
22
|
+
|
|
23
|
+
export const createUltramodernBuildArtifact = (
|
|
24
|
+
record: DeliveryUnitRecord,
|
|
25
|
+
): UltramodernBuildArtifact => {
|
|
26
|
+
const deliveryUnit = {
|
|
27
|
+
appId: record.appId,
|
|
28
|
+
build: record.buildMarker,
|
|
29
|
+
buildMarker: record.buildMarker,
|
|
30
|
+
deployProfile: record.deployProfile,
|
|
31
|
+
kind: record.kind,
|
|
32
|
+
packageName: record.packageName,
|
|
33
|
+
schemaVersion: record.schemaVersion,
|
|
34
|
+
sourceRevision: record.sourceRevision,
|
|
35
|
+
unitId: record.unitId,
|
|
36
|
+
version: record.version,
|
|
37
|
+
};
|
|
38
|
+
const api = {
|
|
39
|
+
appId: deliveryUnit.appId,
|
|
40
|
+
build: deliveryUnit.build,
|
|
41
|
+
buildMarker: deliveryUnit.buildMarker,
|
|
42
|
+
deployProfile: deliveryUnit.deployProfile,
|
|
43
|
+
kind: deliveryUnit.kind,
|
|
44
|
+
packageName: deliveryUnit.packageName,
|
|
45
|
+
schemaVersion: deliveryUnit.schemaVersion,
|
|
46
|
+
sourceRevision: deliveryUnit.sourceRevision,
|
|
47
|
+
surface: 'api' as const,
|
|
48
|
+
unitId: deliveryUnit.unitId,
|
|
49
|
+
version: deliveryUnit.version,
|
|
50
|
+
};
|
|
51
|
+
const ui = {
|
|
52
|
+
appId: deliveryUnit.appId,
|
|
53
|
+
build: deliveryUnit.build,
|
|
54
|
+
buildMarker: deliveryUnit.buildMarker,
|
|
55
|
+
deployProfile: deliveryUnit.deployProfile,
|
|
56
|
+
kind: deliveryUnit.kind,
|
|
57
|
+
packageName: deliveryUnit.packageName,
|
|
58
|
+
schemaVersion: deliveryUnit.schemaVersion,
|
|
59
|
+
sourceRevision: deliveryUnit.sourceRevision,
|
|
60
|
+
surface: 'ui' as const,
|
|
61
|
+
unitId: deliveryUnit.unitId,
|
|
62
|
+
version: deliveryUnit.version,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
deliveryUnit,
|
|
67
|
+
kind: 'ultramodern-build-artifact',
|
|
68
|
+
schemaVersion: DELIVERY_UNIT_SCHEMA_VERSION,
|
|
69
|
+
surfaces: {
|
|
70
|
+
api,
|
|
71
|
+
ui,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const validateUltramodernBuildArtifact = (
|
|
77
|
+
value: unknown,
|
|
78
|
+
path = 'artifact',
|
|
79
|
+
): BackendFederationContractValidationResult => {
|
|
80
|
+
const errors: BackendFederationContractValidationError[] = [];
|
|
81
|
+
|
|
82
|
+
if (!isRecord(value)) {
|
|
83
|
+
addError(errors, path, 'must be an object.');
|
|
84
|
+
return validationResult(errors);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (value.schemaVersion !== DELIVERY_UNIT_SCHEMA_VERSION) {
|
|
88
|
+
addError(
|
|
89
|
+
errors,
|
|
90
|
+
`${path}.schemaVersion`,
|
|
91
|
+
`must be ${DELIVERY_UNIT_SCHEMA_VERSION}.`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (value.kind !== 'ultramodern-build-artifact') {
|
|
95
|
+
addError(errors, `${path}.kind`, 'must be "ultramodern-build-artifact".');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const deliveryUnit = value.deliveryUnit;
|
|
99
|
+
errors.push(
|
|
100
|
+
...validateDeliveryUnitRecord(deliveryUnit, {
|
|
101
|
+
path: `${path}.deliveryUnit`,
|
|
102
|
+
allowBuildAlias: true,
|
|
103
|
+
}).errors,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (isRecord(deliveryUnit)) {
|
|
107
|
+
const build = nonEmptyString(deliveryUnit.build);
|
|
108
|
+
const buildMarker = nonEmptyString(deliveryUnit.buildMarker);
|
|
109
|
+
if (!build) {
|
|
110
|
+
addError(
|
|
111
|
+
errors,
|
|
112
|
+
`${path}.deliveryUnit.build`,
|
|
113
|
+
'must be a non-empty string.',
|
|
114
|
+
);
|
|
115
|
+
} else if (buildMarker && build !== buildMarker) {
|
|
116
|
+
addError(
|
|
117
|
+
errors,
|
|
118
|
+
`${path}.deliveryUnit.build`,
|
|
119
|
+
'must match deliveryUnit.buildMarker.',
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const surfaces = recordField(value, 'surfaces');
|
|
125
|
+
if (!surfaces) {
|
|
126
|
+
addError(errors, `${path}.surfaces`, 'must be an object.');
|
|
127
|
+
return validationResult(errors);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const surface of ['ui', 'api'] as const) {
|
|
131
|
+
const marker = surfaces[surface];
|
|
132
|
+
const markerPath = `${path}.surfaces.${surface}`;
|
|
133
|
+
errors.push(
|
|
134
|
+
...validateDeliveryUnitRecord(marker, {
|
|
135
|
+
path: markerPath,
|
|
136
|
+
allowBuildAlias: true,
|
|
137
|
+
}).errors,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (!isRecord(marker)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const markerBuild = nonEmptyString(marker.build);
|
|
145
|
+
const markerBuildMarker = nonEmptyString(marker.buildMarker);
|
|
146
|
+
if (!markerBuild) {
|
|
147
|
+
addError(errors, `${markerPath}.build`, 'must be non-empty string.');
|
|
148
|
+
} else if (markerBuildMarker && markerBuild !== markerBuildMarker) {
|
|
149
|
+
addError(errors, `${markerPath}.build`, 'must match buildMarker.');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (marker.surface !== surface) {
|
|
153
|
+
addError(errors, `${markerPath}.surface`, `must be "${surface}".`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
|
|
157
|
+
const deliveryUnitValue = deliveryUnitIdentityFieldValue(
|
|
158
|
+
deliveryUnit,
|
|
159
|
+
field,
|
|
160
|
+
{ allowBuildAlias: true },
|
|
161
|
+
);
|
|
162
|
+
const markerValue = deliveryUnitIdentityFieldValue(marker, field, {
|
|
163
|
+
allowBuildAlias: true,
|
|
164
|
+
});
|
|
165
|
+
if (
|
|
166
|
+
deliveryUnitValue !== undefined &&
|
|
167
|
+
markerValue !== undefined &&
|
|
168
|
+
deliveryUnitValue !== markerValue
|
|
169
|
+
) {
|
|
170
|
+
addError(
|
|
171
|
+
errors,
|
|
172
|
+
`${markerPath}.${field}`,
|
|
173
|
+
`must match ${path}.deliveryUnit.${field}.`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return validationResult(errors);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const isUltramodernBuildArtifact = (
|
|
183
|
+
value: unknown,
|
|
184
|
+
): value is UltramodernBuildArtifact =>
|
|
185
|
+
validateUltramodernBuildArtifact(value).ok;
|
|
186
|
+
|
|
187
|
+
export const stampUltramodernBuildArtifactIdentity = (
|
|
188
|
+
artifact: UltramodernBuildArtifact,
|
|
189
|
+
identity: {
|
|
190
|
+
buildMarker: string;
|
|
191
|
+
sourceRevision: string;
|
|
192
|
+
},
|
|
193
|
+
): UltramodernBuildArtifact => ({
|
|
194
|
+
...artifact,
|
|
195
|
+
deliveryUnit: {
|
|
196
|
+
...artifact.deliveryUnit,
|
|
197
|
+
build: identity.buildMarker,
|
|
198
|
+
buildMarker: identity.buildMarker,
|
|
199
|
+
sourceRevision: identity.sourceRevision,
|
|
200
|
+
},
|
|
201
|
+
surfaces: {
|
|
202
|
+
ui: {
|
|
203
|
+
...artifact.surfaces.ui,
|
|
204
|
+
build: identity.buildMarker,
|
|
205
|
+
buildMarker: identity.buildMarker,
|
|
206
|
+
sourceRevision: identity.sourceRevision,
|
|
207
|
+
},
|
|
208
|
+
api: {
|
|
209
|
+
...artifact.surfaces.api,
|
|
210
|
+
build: identity.buildMarker,
|
|
211
|
+
buildMarker: identity.buildMarker,
|
|
212
|
+
sourceRevision: identity.sourceRevision,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export const stampUltramodernBuildArtifactSourceRevision = (
|
|
218
|
+
artifact: UltramodernBuildArtifact,
|
|
219
|
+
sourceRevision: string,
|
|
220
|
+
): UltramodernBuildArtifact =>
|
|
221
|
+
stampUltramodernBuildArtifactIdentity(artifact, {
|
|
222
|
+
buildMarker: artifact.deliveryUnit.buildMarker,
|
|
223
|
+
sourceRevision,
|
|
224
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const BACKEND_FEDERATION_EFFECT_EXPOSE = './effect-api';
|
|
2
|
+
|
|
3
|
+
export const BACKEND_FEDERATION_MANIFEST_FILE = 'backend-mf-manifest.json';
|
|
4
|
+
|
|
5
|
+
export const BACKEND_FEDERATION_REMOTE_ENTRY_FILE = 'backendRemoteEntry.cjs';
|
|
6
|
+
|
|
7
|
+
export const BACKEND_FEDERATION_CONTRACT_VERSION =
|
|
8
|
+
'microvertical-server-effect-v1';
|
|
9
|
+
|
|
10
|
+
export const BACKEND_FEDERATION_NODE_ADAPTER_VERSION = 'backend-mf-effect-v1';
|
|
11
|
+
|
|
12
|
+
export const DELIVERY_UNIT_SCHEMA_VERSION = 1;
|
|
13
|
+
|
|
14
|
+
export const DELIVERY_UNIT_KIND = 'microvertical-delivery-unit';
|
|
15
|
+
|
|
16
|
+
export const DELIVERY_UNIT_DEPLOY_PROFILE = 'cloudflare-ssr-mf-effect-v1';
|
|
17
|
+
|
|
18
|
+
export const DELIVERY_UNIT_IDENTITY_FIELDS = [
|
|
19
|
+
'unitId',
|
|
20
|
+
'buildMarker',
|
|
21
|
+
'sourceRevision',
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
24
|
+
export type DeliveryUnitIdentityField =
|
|
25
|
+
(typeof DELIVERY_UNIT_IDENTITY_FIELDS)[number];
|
|
26
|
+
|
|
27
|
+
export const ULTRAMODERN_BUILD_ARTIFACT_FILE = 'ultramodern-build.json';
|
|
28
|
+
|
|
29
|
+
export const ULTRAMODERN_BUILD_ARTIFACT_PATH = `shared/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
|
|
30
|
+
|
|
31
|
+
export const ULTRAMODERN_BUILD_MODULE_FILE = 'ultramodern-build.ts';
|
|
32
|
+
|
|
33
|
+
export const ULTRAMODERN_BUILD_MODULE_PATH = `shared/${ULTRAMODERN_BUILD_MODULE_FILE}`;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { DeliveryUnitIdentityField } from './constants';
|
|
2
|
+
import {
|
|
3
|
+
DELIVERY_UNIT_DEPLOY_PROFILE,
|
|
4
|
+
DELIVERY_UNIT_IDENTITY_FIELDS,
|
|
5
|
+
DELIVERY_UNIT_KIND,
|
|
6
|
+
DELIVERY_UNIT_SCHEMA_VERSION,
|
|
7
|
+
} from './constants';
|
|
8
|
+
import type {
|
|
9
|
+
BackendFederationContractValidationError,
|
|
10
|
+
BackendFederationContractValidationResult,
|
|
11
|
+
DeliveryUnitContractBlock,
|
|
12
|
+
DeliveryUnitIdentity,
|
|
13
|
+
DeliveryUnitRecord,
|
|
14
|
+
ValidateDeliveryUnitIdentityOptions,
|
|
15
|
+
} from './types';
|
|
16
|
+
import {
|
|
17
|
+
addError,
|
|
18
|
+
isRecord,
|
|
19
|
+
nonEmptyString,
|
|
20
|
+
validationResult,
|
|
21
|
+
} from './validation-core';
|
|
22
|
+
|
|
23
|
+
export const deliveryUnitIdentityFieldValue = (
|
|
24
|
+
value: unknown,
|
|
25
|
+
field: DeliveryUnitIdentityField,
|
|
26
|
+
options: Pick<ValidateDeliveryUnitIdentityOptions, 'allowBuildAlias'> = {},
|
|
27
|
+
): string | undefined => {
|
|
28
|
+
if (!isRecord(value)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (field === 'buildMarker' && options.allowBuildAlias) {
|
|
33
|
+
return nonEmptyString(value.buildMarker) ?? nonEmptyString(value.build);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return nonEmptyString(value[field]);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const validateDeliveryUnitIdentity = (
|
|
40
|
+
value: unknown,
|
|
41
|
+
options: ValidateDeliveryUnitIdentityOptions = {},
|
|
42
|
+
): BackendFederationContractValidationResult => {
|
|
43
|
+
const path = options.path ?? 'deliveryUnit';
|
|
44
|
+
const errors: BackendFederationContractValidationError[] = [];
|
|
45
|
+
|
|
46
|
+
if (!isRecord(value)) {
|
|
47
|
+
addError(errors, path, 'must be an object.');
|
|
48
|
+
return validationResult(errors);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
|
|
52
|
+
if (!deliveryUnitIdentityFieldValue(value, field, options)) {
|
|
53
|
+
addError(errors, `${path}.${field}`, 'must be a non-empty string.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return validationResult(errors);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const validateDeliveryUnitContractBlock = (
|
|
61
|
+
value: unknown,
|
|
62
|
+
options: ValidateDeliveryUnitIdentityOptions = {},
|
|
63
|
+
): BackendFederationContractValidationResult => {
|
|
64
|
+
const path = options.path ?? 'deliveryUnit';
|
|
65
|
+
const errors = [...validateDeliveryUnitIdentity(value, options).errors];
|
|
66
|
+
|
|
67
|
+
if (!isRecord(value)) {
|
|
68
|
+
return validationResult(errors);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (value.schemaVersion !== DELIVERY_UNIT_SCHEMA_VERSION) {
|
|
72
|
+
addError(
|
|
73
|
+
errors,
|
|
74
|
+
`${path}.schemaVersion`,
|
|
75
|
+
`must be ${DELIVERY_UNIT_SCHEMA_VERSION}.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (value.kind !== DELIVERY_UNIT_KIND) {
|
|
79
|
+
addError(errors, `${path}.kind`, `must be "${DELIVERY_UNIT_KIND}".`);
|
|
80
|
+
}
|
|
81
|
+
if (!nonEmptyString(value.packageName)) {
|
|
82
|
+
addError(errors, `${path}.packageName`, 'must be a non-empty string.');
|
|
83
|
+
}
|
|
84
|
+
if (!nonEmptyString(value.version)) {
|
|
85
|
+
addError(errors, `${path}.version`, 'must be a non-empty string.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return validationResult(errors);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const validateDeliveryUnitRecord = (
|
|
92
|
+
value: unknown,
|
|
93
|
+
options: ValidateDeliveryUnitIdentityOptions = {},
|
|
94
|
+
): BackendFederationContractValidationResult => {
|
|
95
|
+
const path = options.path ?? 'deliveryUnit';
|
|
96
|
+
const errors = [
|
|
97
|
+
...validateDeliveryUnitContractBlock(value, {
|
|
98
|
+
...options,
|
|
99
|
+
path,
|
|
100
|
+
}).errors,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
if (!isRecord(value)) {
|
|
104
|
+
return validationResult(errors);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!nonEmptyString(value.appId)) {
|
|
108
|
+
addError(errors, `${path}.appId`, 'must be a non-empty string.');
|
|
109
|
+
}
|
|
110
|
+
if (value.deployProfile !== DELIVERY_UNIT_DEPLOY_PROFILE) {
|
|
111
|
+
addError(
|
|
112
|
+
errors,
|
|
113
|
+
`${path}.deployProfile`,
|
|
114
|
+
`must be "${DELIVERY_UNIT_DEPLOY_PROFILE}".`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return validationResult(errors);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const validateDeliveryUnitBoundaryIdentity = (
|
|
122
|
+
value: unknown,
|
|
123
|
+
path: string,
|
|
124
|
+
): BackendFederationContractValidationError[] => {
|
|
125
|
+
const errors: BackendFederationContractValidationError[] = [];
|
|
126
|
+
|
|
127
|
+
if (!isRecord(value)) {
|
|
128
|
+
addError(errors, path, 'must be an object.');
|
|
129
|
+
return errors;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
|
|
133
|
+
if (!deliveryUnitIdentityFieldValue(value, field)) {
|
|
134
|
+
addError(errors, `${path}.${field}`, 'must be a non-empty string.');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return errors;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const deliveryUnitContractBlock = (
|
|
142
|
+
record: DeliveryUnitRecord,
|
|
143
|
+
): DeliveryUnitContractBlock => ({
|
|
144
|
+
schemaVersion: record.schemaVersion,
|
|
145
|
+
kind: record.kind,
|
|
146
|
+
unitId: record.unitId,
|
|
147
|
+
packageName: record.packageName,
|
|
148
|
+
version: record.version,
|
|
149
|
+
buildMarker: record.buildMarker,
|
|
150
|
+
sourceRevision: record.sourceRevision,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export const toDeliveryUnitIdentity = (
|
|
154
|
+
value: unknown,
|
|
155
|
+
): DeliveryUnitIdentity | undefined => {
|
|
156
|
+
const unitId = deliveryUnitIdentityFieldValue(value, 'unitId');
|
|
157
|
+
const buildMarker = deliveryUnitIdentityFieldValue(value, 'buildMarker', {
|
|
158
|
+
allowBuildAlias: true,
|
|
159
|
+
});
|
|
160
|
+
const sourceRevision = deliveryUnitIdentityFieldValue(
|
|
161
|
+
value,
|
|
162
|
+
'sourceRevision',
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (!unitId || !buildMarker || !sourceRevision) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { unitId, buildMarker, sourceRevision };
|
|
170
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createUltramodernBuildArtifact,
|
|
3
|
+
isUltramodernBuildArtifact,
|
|
4
|
+
stampUltramodernBuildArtifactIdentity,
|
|
5
|
+
stampUltramodernBuildArtifactSourceRevision,
|
|
6
|
+
validateUltramodernBuildArtifact,
|
|
7
|
+
} from './build-artifact';
|
|
8
|
+
export type { DeliveryUnitIdentityField } from './constants';
|
|
9
|
+
export {
|
|
10
|
+
BACKEND_FEDERATION_CONTRACT_VERSION,
|
|
11
|
+
BACKEND_FEDERATION_EFFECT_EXPOSE,
|
|
12
|
+
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
13
|
+
BACKEND_FEDERATION_NODE_ADAPTER_VERSION,
|
|
14
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
15
|
+
DELIVERY_UNIT_DEPLOY_PROFILE,
|
|
16
|
+
DELIVERY_UNIT_IDENTITY_FIELDS,
|
|
17
|
+
DELIVERY_UNIT_KIND,
|
|
18
|
+
DELIVERY_UNIT_SCHEMA_VERSION,
|
|
19
|
+
ULTRAMODERN_BUILD_ARTIFACT_FILE,
|
|
20
|
+
ULTRAMODERN_BUILD_ARTIFACT_PATH,
|
|
21
|
+
ULTRAMODERN_BUILD_MODULE_FILE,
|
|
22
|
+
ULTRAMODERN_BUILD_MODULE_PATH,
|
|
23
|
+
} from './constants';
|
|
24
|
+
export {
|
|
25
|
+
deliveryUnitContractBlock,
|
|
26
|
+
deliveryUnitIdentityFieldValue,
|
|
27
|
+
toDeliveryUnitIdentity,
|
|
28
|
+
validateDeliveryUnitContractBlock,
|
|
29
|
+
validateDeliveryUnitIdentity,
|
|
30
|
+
validateDeliveryUnitRecord,
|
|
31
|
+
} from './delivery-unit';
|
|
32
|
+
export {
|
|
33
|
+
backendFederationExposeNames,
|
|
34
|
+
backendFederationMetadata,
|
|
35
|
+
backendFederationVersionBoundary,
|
|
36
|
+
toBackendFederationExposeNames,
|
|
37
|
+
validateBackendFederationManifest,
|
|
38
|
+
validateBackendFederationMetadata,
|
|
39
|
+
} from './metadata';
|
|
40
|
+
export type {
|
|
41
|
+
BackendFederationContractValidationError,
|
|
42
|
+
BackendFederationContractValidationResult,
|
|
43
|
+
DeliveryUnitContractBlock,
|
|
44
|
+
DeliveryUnitIdentity,
|
|
45
|
+
DeliveryUnitRecord,
|
|
46
|
+
UltramodernBuildArtifact,
|
|
47
|
+
UltramodernBuildDeliveryUnit,
|
|
48
|
+
UltramodernBuildSurface,
|
|
49
|
+
ValidateBackendFederationManifestOptions,
|
|
50
|
+
ValidateBackendFederationMetadataOptions,
|
|
51
|
+
ValidateDeliveryUnitIdentityOptions,
|
|
52
|
+
} from './types';
|
|
53
|
+
export {
|
|
54
|
+
formatBackendFederationValidationErrors,
|
|
55
|
+
nonEmptyString,
|
|
56
|
+
} from './validation-core';
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACKEND_FEDERATION_CONTRACT_VERSION,
|
|
3
|
+
BACKEND_FEDERATION_EFFECT_EXPOSE,
|
|
4
|
+
BACKEND_FEDERATION_NODE_ADAPTER_VERSION,
|
|
5
|
+
DELIVERY_UNIT_IDENTITY_FIELDS,
|
|
6
|
+
DELIVERY_UNIT_SCHEMA_VERSION,
|
|
7
|
+
} from './constants';
|
|
8
|
+
import {
|
|
9
|
+
deliveryUnitIdentityFieldValue,
|
|
10
|
+
validateDeliveryUnitBoundaryIdentity,
|
|
11
|
+
validateDeliveryUnitContractBlock,
|
|
12
|
+
} from './delivery-unit';
|
|
13
|
+
import type {
|
|
14
|
+
BackendFederationContractValidationError,
|
|
15
|
+
BackendFederationContractValidationResult,
|
|
16
|
+
ValidateBackendFederationManifestOptions,
|
|
17
|
+
ValidateBackendFederationMetadataOptions,
|
|
18
|
+
} from './types';
|
|
19
|
+
import {
|
|
20
|
+
addError,
|
|
21
|
+
isRecord,
|
|
22
|
+
nonEmptyString,
|
|
23
|
+
recordField,
|
|
24
|
+
validateVersionField,
|
|
25
|
+
validationResult,
|
|
26
|
+
} from './validation-core';
|
|
27
|
+
|
|
28
|
+
export const toBackendFederationExposeNames = (value: unknown): string[] => {
|
|
29
|
+
const names: string[] = [];
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
for (const expose of value) {
|
|
33
|
+
const name =
|
|
34
|
+
nonEmptyString(expose) ??
|
|
35
|
+
(isRecord(expose) ? nonEmptyString(expose.name) : undefined);
|
|
36
|
+
if (name) {
|
|
37
|
+
names.push(name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else if (isRecord(value)) {
|
|
41
|
+
names.push(...Object.keys(value).filter(name => name.length > 0));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return Array.from(new Set(names));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const backendFederationExposeNames = (value: unknown): string[] => {
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const exposes = toBackendFederationExposeNames(value.exposes);
|
|
53
|
+
const expose = nonEmptyString(value.expose);
|
|
54
|
+
return Array.from(new Set([...exposes, ...(expose ? [expose] : [])]));
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const backendFederationMetadata = (
|
|
58
|
+
value: unknown,
|
|
59
|
+
): Record<string, unknown> | undefined =>
|
|
60
|
+
isRecord(value) ? recordField(value, 'backendFederation') : undefined;
|
|
61
|
+
|
|
62
|
+
export const backendFederationVersionBoundary = (
|
|
63
|
+
value: unknown,
|
|
64
|
+
): Record<string, unknown> | undefined =>
|
|
65
|
+
recordField(backendFederationMetadata(value), 'versionBoundary');
|
|
66
|
+
|
|
67
|
+
export const validateBackendFederationMetadata = (
|
|
68
|
+
value: unknown,
|
|
69
|
+
options: ValidateBackendFederationMetadataOptions = {},
|
|
70
|
+
): BackendFederationContractValidationResult => {
|
|
71
|
+
const path = options.path ?? 'backendFederation';
|
|
72
|
+
const errors: BackendFederationContractValidationError[] = [];
|
|
73
|
+
|
|
74
|
+
if (!isRecord(value)) {
|
|
75
|
+
addError(errors, path, 'must be an object.');
|
|
76
|
+
return validationResult(errors);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (options.requireEffectRuntime && value.runtimeFramework !== 'effect') {
|
|
80
|
+
addError(errors, `${path}.runtimeFramework`, 'must be "effect".');
|
|
81
|
+
}
|
|
82
|
+
if (options.requireEffectRuntime && value.strictEffectApproach !== true) {
|
|
83
|
+
addError(errors, `${path}.strictEffectApproach`, 'must be true.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
validateVersionField(
|
|
87
|
+
errors,
|
|
88
|
+
value,
|
|
89
|
+
'contractVersion',
|
|
90
|
+
options.expectedContractVersion ?? BACKEND_FEDERATION_CONTRACT_VERSION,
|
|
91
|
+
path,
|
|
92
|
+
options.requireVersionFields ?? false,
|
|
93
|
+
);
|
|
94
|
+
validateVersionField(
|
|
95
|
+
errors,
|
|
96
|
+
value,
|
|
97
|
+
'nodeAdapterVersion',
|
|
98
|
+
options.expectedNodeAdapterVersion ??
|
|
99
|
+
BACKEND_FEDERATION_NODE_ADAPTER_VERSION,
|
|
100
|
+
path,
|
|
101
|
+
options.requireVersionFields ?? false,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
options.requireEffectExpose &&
|
|
106
|
+
!backendFederationExposeNames(value).includes(
|
|
107
|
+
BACKEND_FEDERATION_EFFECT_EXPOSE,
|
|
108
|
+
)
|
|
109
|
+
) {
|
|
110
|
+
addError(
|
|
111
|
+
errors,
|
|
112
|
+
`${path}.expose`,
|
|
113
|
+
`must include "${BACKEND_FEDERATION_EFFECT_EXPOSE}".`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (options.validateDeliveryUnit ?? true) {
|
|
118
|
+
const deliveryUnit = recordField(value, 'deliveryUnit');
|
|
119
|
+
if (deliveryUnit) {
|
|
120
|
+
errors.push(
|
|
121
|
+
...validateDeliveryUnitContractBlock(deliveryUnit, {
|
|
122
|
+
path: `${path}.deliveryUnit`,
|
|
123
|
+
}).errors,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const versionBoundary = recordField(value, 'versionBoundary');
|
|
128
|
+
const versionBoundaryDeliveryUnit = recordField(
|
|
129
|
+
versionBoundary,
|
|
130
|
+
'deliveryUnit',
|
|
131
|
+
);
|
|
132
|
+
if (versionBoundaryDeliveryUnit) {
|
|
133
|
+
errors.push(
|
|
134
|
+
...validateDeliveryUnitBoundaryIdentity(
|
|
135
|
+
versionBoundaryDeliveryUnit,
|
|
136
|
+
`${path}.versionBoundary.deliveryUnit`,
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (deliveryUnit && versionBoundaryDeliveryUnit) {
|
|
142
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
|
|
143
|
+
const deliveryUnitValue = deliveryUnitIdentityFieldValue(
|
|
144
|
+
deliveryUnit,
|
|
145
|
+
field,
|
|
146
|
+
);
|
|
147
|
+
const versionBoundaryDeliveryUnitValue = deliveryUnitIdentityFieldValue(
|
|
148
|
+
versionBoundaryDeliveryUnit,
|
|
149
|
+
field,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
deliveryUnitValue &&
|
|
154
|
+
versionBoundaryDeliveryUnitValue &&
|
|
155
|
+
deliveryUnitValue !== versionBoundaryDeliveryUnitValue
|
|
156
|
+
) {
|
|
157
|
+
addError(
|
|
158
|
+
errors,
|
|
159
|
+
`${path}.versionBoundary.deliveryUnit.${field}`,
|
|
160
|
+
`must match ${path}.deliveryUnit.${field}.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return validationResult(errors);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const validateBackendFederationManifest = (
|
|
171
|
+
value: unknown,
|
|
172
|
+
options: ValidateBackendFederationManifestOptions = {},
|
|
173
|
+
): BackendFederationContractValidationResult => {
|
|
174
|
+
const path = options.path ?? 'manifest';
|
|
175
|
+
const errors: BackendFederationContractValidationError[] = [];
|
|
176
|
+
|
|
177
|
+
if (!isRecord(value)) {
|
|
178
|
+
addError(errors, path, 'must be an object.');
|
|
179
|
+
return validationResult(errors);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
value.schemaVersion !== undefined &&
|
|
184
|
+
value.schemaVersion !== DELIVERY_UNIT_SCHEMA_VERSION
|
|
185
|
+
) {
|
|
186
|
+
addError(
|
|
187
|
+
errors,
|
|
188
|
+
`${path}.schemaVersion`,
|
|
189
|
+
`must be ${DELIVERY_UNIT_SCHEMA_VERSION}.`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const metadata = backendFederationMetadata(value);
|
|
194
|
+
if (!metadata) {
|
|
195
|
+
if (options.requireBackendFederation ?? true) {
|
|
196
|
+
addError(errors, `${path}.backendFederation`, 'must be an object.');
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
errors.push(
|
|
200
|
+
...validateBackendFederationMetadata(metadata, {
|
|
201
|
+
...options,
|
|
202
|
+
path: `${path}.backendFederation`,
|
|
203
|
+
requireEffectExpose: false,
|
|
204
|
+
}).errors,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (options.requireEffectExpose) {
|
|
209
|
+
const topLevelExposeNames = toBackendFederationExposeNames(value.exposes);
|
|
210
|
+
const metadataExposeNames = backendFederationExposeNames(metadata);
|
|
211
|
+
const exposeNames = new Set([
|
|
212
|
+
...topLevelExposeNames,
|
|
213
|
+
...metadataExposeNames,
|
|
214
|
+
]);
|
|
215
|
+
if (!exposeNames.has(BACKEND_FEDERATION_EFFECT_EXPOSE)) {
|
|
216
|
+
addError(
|
|
217
|
+
errors,
|
|
218
|
+
`${path}.exposes`,
|
|
219
|
+
`must include "${BACKEND_FEDERATION_EFFECT_EXPOSE}".`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return validationResult(errors);
|
|
225
|
+
};
|