@saptools/service-flow 0.1.70 → 0.1.73
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/CHANGELOG.md +21 -0
- package/README.md +23 -6
- package/TECHNICAL-NOTE.md +24 -2
- package/dist/{chunk-GSLFY6J2.js → chunk-32WOTGTS.js} +12061 -9288
- package/dist/chunk-32WOTGTS.js.map +1 -0
- package/dist/cli.js +854 -161
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +54 -14
- package/dist/index.js +2 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor-event-quality.ts +446 -0
- package/src/cli/doctor.ts +4 -6
- package/src/cli.ts +35 -13
- package/src/config/workspace-config.ts +15 -0
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -29
- package/src/db/event-fact-semantics.ts +546 -0
- package/src/db/event-site-semantics.ts +62 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/fact-lifecycle.ts +70 -12
- package/src/db/migrations.ts +15 -1
- package/src/db/package-target-invalidation.ts +80 -0
- package/src/db/repositories.ts +28 -2
- package/src/db/schema-structure.ts +41 -1
- package/src/db/schema.ts +6 -2
- package/src/indexer/repository-indexer.ts +93 -10
- package/src/indexer/workspace-indexer.ts +13 -3
- package/src/linker/cross-repo-linker.ts +27 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +204 -0
- package/src/linker/event-subscription-handler-linker.ts +220 -25
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +302 -0
- package/src/output/repository-inspection.ts +11 -0
- package/src/output/table-output.ts +13 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +404 -0
- package/src/parsers/event-call-analysis.ts +370 -0
- package/src/parsers/event-environment-reference.ts +242 -0
- package/src/parsers/event-loop-registration.ts +132 -0
- package/src/parsers/event-name-import-resolution.ts +243 -0
- package/src/parsers/event-receiver-analysis.ts +394 -0
- package/src/parsers/event-subscription-facts.ts +4 -0
- package/src/parsers/generated-constants-parser.ts +80 -14
- package/src/parsers/outbound-call-classifier.ts +27 -124
- package/src/parsers/outbound-call-parser.ts +13 -1
- package/src/parsers/outbound-expression-analysis.ts +2 -2
- package/src/parsers/stable-local-value.ts +42 -10
- package/src/parsers/string-constant-lookups.ts +408 -0
- package/src/trace/edge-target.ts +65 -0
- package/src/trace/event-runtime-resolution.ts +24 -3
- package/src/trace/event-shape-candidate-trace.ts +172 -0
- package/src/trace/event-subscriber-traversal.ts +90 -8
- package/src/trace/evidence.ts +7 -28
- package/src/trace/trace-scope-execution.ts +22 -30
- package/src/types.ts +19 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-GSLFY6J2.js.map +0 -1
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import type { RepositorySourceContext } from './ts-project.js';
|
|
2
|
+
|
|
3
|
+
export const ENVIRONMENT_DECLARATIONS_SCHEMA =
|
|
4
|
+
'service-flow/environment-declarations@1';
|
|
5
|
+
export const ENVIRONMENT_DECLARATION_RECORD_CAP = 32;
|
|
6
|
+
export const EVENT_ENVIRONMENT_KEY_CAP = 16;
|
|
7
|
+
export const DEFAULT_EVENT_ENVIRONMENT_KEYS = ['SHARD_CODE'] as const;
|
|
8
|
+
export const EVENT_ENVIRONMENT_KEY_ALLOWLIST =
|
|
9
|
+
DEFAULT_EVENT_ENVIRONMENT_KEYS;
|
|
10
|
+
|
|
11
|
+
export type EventEnvironmentKey = string;
|
|
12
|
+
export type EnvironmentDeclarationProvenance =
|
|
13
|
+
| 'env_declaration_manifest'
|
|
14
|
+
| 'env_declaration_mta'
|
|
15
|
+
| 'env_declaration_dotenv'
|
|
16
|
+
| 'env_declaration_dev';
|
|
17
|
+
|
|
18
|
+
export interface EnvironmentDeclaration {
|
|
19
|
+
key: EventEnvironmentKey;
|
|
20
|
+
value: string;
|
|
21
|
+
provenance: EnvironmentDeclarationProvenance;
|
|
22
|
+
sourceFile: string;
|
|
23
|
+
startOffset: number;
|
|
24
|
+
endOffset: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface EnvironmentDeclarationsFact {
|
|
28
|
+
schema: typeof ENVIRONMENT_DECLARATIONS_SCHEMA;
|
|
29
|
+
allowedKeys: string[];
|
|
30
|
+
status: 'complete' | 'ambiguous' | 'not_applicable' | 'incomplete';
|
|
31
|
+
reason: string | null;
|
|
32
|
+
recordCap: typeof ENVIRONMENT_DECLARATION_RECORD_CAP;
|
|
33
|
+
total: number;
|
|
34
|
+
shown: number;
|
|
35
|
+
omitted: number;
|
|
36
|
+
declarations: EnvironmentDeclaration[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const allowedProvenance = new Set<EnvironmentDeclarationProvenance>([
|
|
40
|
+
'env_declaration_manifest',
|
|
41
|
+
'env_declaration_mta',
|
|
42
|
+
'env_declaration_dotenv',
|
|
43
|
+
'env_declaration_dev',
|
|
44
|
+
]);
|
|
45
|
+
const environmentValueLimit = 512;
|
|
46
|
+
const dynamicEnvironmentValue = /\$\{|\$\(|~\{|\(\(/;
|
|
47
|
+
const environmentKeyGrammar = /^[A-Z_][A-Z0-9_]{0,63}$/;
|
|
48
|
+
|
|
49
|
+
export function validEventEnvironmentKey(value: string): boolean {
|
|
50
|
+
return environmentKeyGrammar.test(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function normalizeEventEnvironmentKeys(
|
|
54
|
+
values: readonly string[] = DEFAULT_EVENT_ENVIRONMENT_KEYS,
|
|
55
|
+
): string[] {
|
|
56
|
+
const unique = [...new Set(values)].sort((left, right) =>
|
|
57
|
+
left < right ? -1 : left > right ? 1 : 0);
|
|
58
|
+
if (unique.length === 0 || unique.length > EVENT_ENVIRONMENT_KEY_CAP
|
|
59
|
+
|| !unique.every(validEventEnvironmentKey))
|
|
60
|
+
throw new Error('invalid_event_environment_keys');
|
|
61
|
+
return unique;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function hasControlCharacter(value: string): boolean {
|
|
65
|
+
for (const character of value) {
|
|
66
|
+
const code = character.charCodeAt(0);
|
|
67
|
+
if (code <= 31 || code === 127) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
73
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
74
|
+
? value as Record<string, unknown> : undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseJson(value: unknown): unknown {
|
|
78
|
+
if (typeof value !== 'string') return value;
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(value) as unknown;
|
|
81
|
+
} catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function scalarValue(raw: string): string | undefined {
|
|
87
|
+
const value = raw.trim();
|
|
88
|
+
if (!value || /^[{[*&!>|]/.test(value)) return undefined;
|
|
89
|
+
if ((value.startsWith('"') && value.endsWith('"'))
|
|
90
|
+
|| (value.startsWith("'") && value.endsWith("'")))
|
|
91
|
+
return value.slice(1, -1);
|
|
92
|
+
return /[\r\n]/.test(value) ? undefined : value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function declaration(
|
|
96
|
+
key: string,
|
|
97
|
+
value: unknown,
|
|
98
|
+
provenance: EnvironmentDeclarationProvenance,
|
|
99
|
+
sourceFile: string,
|
|
100
|
+
startOffset: number,
|
|
101
|
+
allowedKeys: ReadonlySet<string>,
|
|
102
|
+
): EnvironmentDeclaration[] {
|
|
103
|
+
if (!allowedKeys.has(key) || typeof value !== 'string'
|
|
104
|
+
|| !value || value.length > environmentValueLimit
|
|
105
|
+
|| hasControlCharacter(value)
|
|
106
|
+
|| dynamicEnvironmentValue.test(value)) return [];
|
|
107
|
+
return [{
|
|
108
|
+
key: key as EventEnvironmentKey,
|
|
109
|
+
value,
|
|
110
|
+
provenance,
|
|
111
|
+
sourceFile,
|
|
112
|
+
startOffset,
|
|
113
|
+
endOffset: startOffset + value.length,
|
|
114
|
+
}];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function valueOffset(
|
|
118
|
+
text: string,
|
|
119
|
+
value: string,
|
|
120
|
+
after: number,
|
|
121
|
+
): number {
|
|
122
|
+
const direct = text.indexOf(value, Math.max(0, after));
|
|
123
|
+
return direct >= 0 ? direct : Math.max(0, after);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function jsonDeclarations(
|
|
127
|
+
filePath: string,
|
|
128
|
+
text: string,
|
|
129
|
+
keys: readonly string[],
|
|
130
|
+
allowedKeys: ReadonlySet<string>,
|
|
131
|
+
): EnvironmentDeclaration[] {
|
|
132
|
+
let parsed: unknown;
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(text) as unknown;
|
|
135
|
+
} catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
139
|
+
return [];
|
|
140
|
+
const env = (parsed as Record<string, unknown>).env;
|
|
141
|
+
if (!env || typeof env !== 'object' || Array.isArray(env)) return [];
|
|
142
|
+
return keys.flatMap((key) => {
|
|
143
|
+
const value = (env as Record<string, unknown>)[key];
|
|
144
|
+
const keyOffset = text.indexOf(`"${key}"`);
|
|
145
|
+
const offset = typeof value === 'string'
|
|
146
|
+
? valueOffset(text, value, keyOffset + key.length + 2) : keyOffset;
|
|
147
|
+
return declaration(
|
|
148
|
+
key, value, 'env_declaration_dev', filePath, Math.max(0, offset),
|
|
149
|
+
allowedKeys,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function dotenvDeclarations(
|
|
155
|
+
filePath: string,
|
|
156
|
+
text: string,
|
|
157
|
+
allowedKeys: ReadonlySet<string>,
|
|
158
|
+
): EnvironmentDeclaration[] {
|
|
159
|
+
const values: EnvironmentDeclaration[] = [];
|
|
160
|
+
let offset = 0;
|
|
161
|
+
for (const line of text.split(/\n/)) {
|
|
162
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/
|
|
163
|
+
.exec(line);
|
|
164
|
+
const value = match?.[2] === undefined
|
|
165
|
+
? undefined : scalarValue(match[2]);
|
|
166
|
+
if (match?.[1] && value !== undefined)
|
|
167
|
+
values.push(...declaration(
|
|
168
|
+
match[1], value, 'env_declaration_dotenv', filePath,
|
|
169
|
+
offset + valueOffset(line, value, line.indexOf('=') + 1),
|
|
170
|
+
allowedKeys,
|
|
171
|
+
));
|
|
172
|
+
offset += line.length + 1;
|
|
173
|
+
}
|
|
174
|
+
return values;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function indentation(value: string): number {
|
|
178
|
+
return /^\s*/.exec(value)?.[0].length ?? 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function yamlDeclarations(
|
|
182
|
+
filePath: string,
|
|
183
|
+
text: string,
|
|
184
|
+
provenance: EnvironmentDeclarationProvenance,
|
|
185
|
+
allowedKeys: ReadonlySet<string>,
|
|
186
|
+
): EnvironmentDeclaration[] {
|
|
187
|
+
const values: EnvironmentDeclaration[] = [];
|
|
188
|
+
let envIndent: number | undefined;
|
|
189
|
+
let offset = 0;
|
|
190
|
+
for (const line of text.split(/\n/)) {
|
|
191
|
+
const trimmed = line.trim();
|
|
192
|
+
const indent = indentation(line);
|
|
193
|
+
if (/^env\s*:\s*(?:#.*)?$/.test(trimmed)) envIndent = indent;
|
|
194
|
+
else if (envIndent !== undefined && trimmed && !trimmed.startsWith('#')) {
|
|
195
|
+
if (indent <= envIndent) envIndent = undefined;
|
|
196
|
+
else {
|
|
197
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*?)\s*(?:#.*)?$/
|
|
198
|
+
.exec(trimmed);
|
|
199
|
+
const value = match?.[2] === undefined
|
|
200
|
+
? undefined : scalarValue(match[2]);
|
|
201
|
+
if (match?.[1] && value !== undefined)
|
|
202
|
+
values.push(...declaration(
|
|
203
|
+
match[1], value, provenance, filePath,
|
|
204
|
+
offset + valueOffset(line, value, line.indexOf(':') + 1),
|
|
205
|
+
allowedKeys,
|
|
206
|
+
));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
offset += line.length + 1;
|
|
210
|
+
}
|
|
211
|
+
return values;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function snapshotDeclarations(
|
|
215
|
+
filePath: string,
|
|
216
|
+
text: string,
|
|
217
|
+
keys: readonly string[],
|
|
218
|
+
allowedKeys: ReadonlySet<string>,
|
|
219
|
+
): EnvironmentDeclaration[] {
|
|
220
|
+
const name = filePath.split('/').at(-1);
|
|
221
|
+
if (name === 'nodemon.json')
|
|
222
|
+
return jsonDeclarations(filePath, text, keys, allowedKeys);
|
|
223
|
+
if (name === '.env')
|
|
224
|
+
return dotenvDeclarations(filePath, text, allowedKeys);
|
|
225
|
+
if (name === 'manifest.yml')
|
|
226
|
+
return yamlDeclarations(
|
|
227
|
+
filePath, text, 'env_declaration_manifest', allowedKeys,
|
|
228
|
+
);
|
|
229
|
+
return name === 'mta.yaml'
|
|
230
|
+
? yamlDeclarations(filePath, text, 'env_declaration_mta', allowedKeys) : [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function compareDeclaration(
|
|
234
|
+
left: EnvironmentDeclaration,
|
|
235
|
+
right: EnvironmentDeclaration,
|
|
236
|
+
): number {
|
|
237
|
+
const leftKey = `${left.key}\0${left.sourceFile}\0${left.startOffset}`;
|
|
238
|
+
const rightKey = `${right.key}\0${right.sourceFile}\0${right.startOffset}`;
|
|
239
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function declarationIdentityValid(
|
|
243
|
+
item: Record<string, unknown>,
|
|
244
|
+
allowedKeys: ReadonlySet<string>,
|
|
245
|
+
): boolean {
|
|
246
|
+
return typeof item.key === 'string'
|
|
247
|
+
&& allowedKeys.has(item.key)
|
|
248
|
+
&& typeof item.provenance === 'string'
|
|
249
|
+
&& allowedProvenance.has(
|
|
250
|
+
item.provenance as EnvironmentDeclarationProvenance,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function declarationValueValid(item: Record<string, unknown>): boolean {
|
|
255
|
+
return typeof item.value === 'string'
|
|
256
|
+
&& item.value.length > 0
|
|
257
|
+
&& item.value.length <= environmentValueLimit
|
|
258
|
+
&& !hasControlCharacter(item.value)
|
|
259
|
+
&& !dynamicEnvironmentValue.test(item.value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function declarationLocationValid(item: Record<string, unknown>): boolean {
|
|
263
|
+
return typeof item.sourceFile === 'string'
|
|
264
|
+
&& item.sourceFile.length > 0
|
|
265
|
+
&& Number.isInteger(item.startOffset)
|
|
266
|
+
&& Number(item.startOffset) >= 0
|
|
267
|
+
&& Number.isInteger(item.endOffset)
|
|
268
|
+
&& Number(item.endOffset) > Number(item.startOffset);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parsedDeclaration(
|
|
272
|
+
value: unknown,
|
|
273
|
+
allowedKeys: ReadonlySet<string>,
|
|
274
|
+
): EnvironmentDeclaration | undefined {
|
|
275
|
+
const item = record(value);
|
|
276
|
+
if (!item || !declarationIdentityValid(item, allowedKeys)
|
|
277
|
+
|| !declarationValueValid(item)
|
|
278
|
+
|| !declarationLocationValid(item)) return undefined;
|
|
279
|
+
return item as unknown as EnvironmentDeclaration;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function countsValid(
|
|
283
|
+
item: Record<string, unknown>,
|
|
284
|
+
declarations: EnvironmentDeclaration[],
|
|
285
|
+
): boolean {
|
|
286
|
+
return item.recordCap === ENVIRONMENT_DECLARATION_RECORD_CAP
|
|
287
|
+
&& Number.isInteger(item.total) && Number(item.total) >= 0
|
|
288
|
+
&& Number.isInteger(item.shown) && Number(item.shown) >= 0
|
|
289
|
+
&& Number.isInteger(item.omitted) && Number(item.omitted) >= 0
|
|
290
|
+
&& Number(item.shown) + Number(item.omitted) === Number(item.total)
|
|
291
|
+
&& Number(item.shown) === declarations.length
|
|
292
|
+
&& declarations.length <= ENVIRONMENT_DECLARATION_RECORD_CAP;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function statusValid(
|
|
296
|
+
item: Record<string, unknown>,
|
|
297
|
+
declarations: EnvironmentDeclaration[],
|
|
298
|
+
): boolean {
|
|
299
|
+
const distinct = new Map<string, Set<string>>();
|
|
300
|
+
for (const value of declarations) {
|
|
301
|
+
const values = distinct.get(value.key) ?? new Set<string>();
|
|
302
|
+
values.add(value.value);
|
|
303
|
+
distinct.set(value.key, values);
|
|
304
|
+
}
|
|
305
|
+
const ambiguous = [...distinct.values()].some((values) => values.size > 1);
|
|
306
|
+
if (item.status === 'not_applicable')
|
|
307
|
+
return item.reason === null && item.total === 0;
|
|
308
|
+
if (item.status === 'complete')
|
|
309
|
+
return item.reason === null && item.total === item.shown
|
|
310
|
+
&& Number(item.total) > 0 && !ambiguous;
|
|
311
|
+
if (item.status === 'ambiguous')
|
|
312
|
+
return item.reason === 'environment_declaration_values_conflict'
|
|
313
|
+
&& ambiguous && item.omitted === 0;
|
|
314
|
+
return item.status === 'incomplete'
|
|
315
|
+
&& item.reason === 'environment_declaration_record_cap_exceeded'
|
|
316
|
+
&& Number(item.omitted) > 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function parsedAllowedKeys(value: unknown): string[] | undefined {
|
|
320
|
+
if (!Array.isArray(value)
|
|
321
|
+
|| !value.every((item): item is string => typeof item === 'string'))
|
|
322
|
+
return undefined;
|
|
323
|
+
try {
|
|
324
|
+
const normalized = normalizeEventEnvironmentKeys(value);
|
|
325
|
+
return normalized.length === value.length
|
|
326
|
+
&& normalized.every((item, index) => item === value[index])
|
|
327
|
+
? normalized : undefined;
|
|
328
|
+
} catch {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function parseEnvironmentDeclarationsFact(
|
|
334
|
+
value: unknown,
|
|
335
|
+
): EnvironmentDeclarationsFact | undefined {
|
|
336
|
+
const item = record(parseJson(value));
|
|
337
|
+
if (!item || item.schema !== ENVIRONMENT_DECLARATIONS_SCHEMA
|
|
338
|
+
|| !Array.isArray(item.declarations)) return undefined;
|
|
339
|
+
const keys = parsedAllowedKeys(item.allowedKeys);
|
|
340
|
+
if (!keys) return undefined;
|
|
341
|
+
const allowedKeys = new Set(keys);
|
|
342
|
+
const declarations = item.declarations.flatMap((entry) => {
|
|
343
|
+
const parsed = parsedDeclaration(entry, allowedKeys);
|
|
344
|
+
return parsed ? [parsed] : [];
|
|
345
|
+
});
|
|
346
|
+
if (declarations.length !== item.declarations.length
|
|
347
|
+
|| !countsValid(item, declarations)
|
|
348
|
+
|| !statusValid(item, declarations)) return undefined;
|
|
349
|
+
const identities = declarations.map((entry) =>
|
|
350
|
+
`${entry.key}\0${entry.sourceFile}\0${entry.startOffset}\0${entry.endOffset}`);
|
|
351
|
+
if (new Set(identities).size !== identities.length) return undefined;
|
|
352
|
+
return { ...item, declarations } as unknown as EnvironmentDeclarationsFact;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function collectEnvironmentDeclarations(
|
|
356
|
+
sources: RepositorySourceContext,
|
|
357
|
+
configuredKeys: readonly string[] = DEFAULT_EVENT_ENVIRONMENT_KEYS,
|
|
358
|
+
): EnvironmentDeclarationsFact {
|
|
359
|
+
const keys = normalizeEventEnvironmentKeys(configuredKeys);
|
|
360
|
+
const allowedKeys = new Set(keys);
|
|
361
|
+
const all = sources.entries().flatMap((snapshot) =>
|
|
362
|
+
snapshotDeclarations(
|
|
363
|
+
snapshot.filePath, snapshot.text, keys, allowedKeys,
|
|
364
|
+
))
|
|
365
|
+
.sort(compareDeclaration);
|
|
366
|
+
const values = new Set(all.map((item) => `${item.key}\0${item.value}`));
|
|
367
|
+
const ambiguous = keys.some((key) =>
|
|
368
|
+
[...values].filter((value) => value.startsWith(`${key}\0`)).length > 1);
|
|
369
|
+
const declarations = all.slice(0, ENVIRONMENT_DECLARATION_RECORD_CAP);
|
|
370
|
+
return {
|
|
371
|
+
schema: ENVIRONMENT_DECLARATIONS_SCHEMA,
|
|
372
|
+
allowedKeys: keys,
|
|
373
|
+
status: ambiguous
|
|
374
|
+
? 'ambiguous'
|
|
375
|
+
: all.length > ENVIRONMENT_DECLARATION_RECORD_CAP
|
|
376
|
+
? 'incomplete'
|
|
377
|
+
: all.length > 0 ? 'complete' : 'not_applicable',
|
|
378
|
+
reason: ambiguous
|
|
379
|
+
? 'environment_declaration_values_conflict'
|
|
380
|
+
: all.length > ENVIRONMENT_DECLARATION_RECORD_CAP
|
|
381
|
+
? 'environment_declaration_record_cap_exceeded' : null,
|
|
382
|
+
recordCap: ENVIRONMENT_DECLARATION_RECORD_CAP,
|
|
383
|
+
total: all.length,
|
|
384
|
+
shown: declarations.length,
|
|
385
|
+
omitted: Math.max(0, all.length - declarations.length),
|
|
386
|
+
declarations,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function emptyEnvironmentDeclarations(
|
|
391
|
+
configuredKeys: readonly string[] = DEFAULT_EVENT_ENVIRONMENT_KEYS,
|
|
392
|
+
): EnvironmentDeclarationsFact {
|
|
393
|
+
return {
|
|
394
|
+
schema: ENVIRONMENT_DECLARATIONS_SCHEMA,
|
|
395
|
+
allowedKeys: normalizeEventEnvironmentKeys(configuredKeys),
|
|
396
|
+
status: 'not_applicable',
|
|
397
|
+
reason: null,
|
|
398
|
+
recordCap: ENVIRONMENT_DECLARATION_RECORD_CAP,
|
|
399
|
+
total: 0,
|
|
400
|
+
shown: 0,
|
|
401
|
+
omitted: 0,
|
|
402
|
+
declarations: [],
|
|
403
|
+
};
|
|
404
|
+
}
|