@shipfox/api-definitions 12.3.0 → 14.0.0
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +45 -0
- package/dist/core/entities/index.d.ts +1 -1
- package/dist/core/entities/index.d.ts.map +1 -1
- package/dist/core/entities/index.js +1 -1
- package/dist/core/entities/index.js.map +1 -1
- package/dist/core/entities/integration-context.d.ts +4 -0
- package/dist/core/entities/integration-context.d.ts.map +1 -1
- package/dist/core/entities/integration-context.js.map +1 -1
- package/dist/core/entities/sync-state.d.ts +10 -4
- package/dist/core/entities/sync-state.d.ts.map +1 -1
- package/dist/core/entities/sync-state.js +26 -7
- package/dist/core/entities/sync-state.js.map +1 -1
- package/dist/core/entities/validation-diagnostic.d.ts +8 -0
- package/dist/core/entities/validation-diagnostic.d.ts.map +1 -0
- package/dist/core/entities/validation-diagnostic.js +3 -0
- package/dist/core/entities/validation-diagnostic.js.map +1 -0
- package/dist/core/entities/workflow-definition.d.ts +2 -0
- package/dist/core/entities/workflow-definition.d.ts.map +1 -1
- package/dist/core/entities/workflow-definition.js.map +1 -1
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +9 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/integrations.d.ts +3 -1
- package/dist/core/integrations.d.ts.map +1 -1
- package/dist/core/integrations.js +10 -2
- package/dist/core/integrations.js.map +1 -1
- package/dist/core/parse-definition.d.ts +4 -4
- package/dist/core/parse-definition.d.ts.map +1 -1
- package/dist/core/parse-definition.js +5 -5
- package/dist/core/parse-definition.js.map +1 -1
- package/dist/core/resolve-definition-at-ref.d.ts +66 -0
- package/dist/core/resolve-definition-at-ref.d.ts.map +1 -0
- package/dist/core/resolve-definition-at-ref.js +374 -0
- package/dist/core/resolve-definition-at-ref.js.map +1 -0
- package/dist/core/sync-definitions.d.ts +2 -2
- package/dist/core/sync-definitions.d.ts.map +1 -1
- package/dist/core/sync-definitions.js +5 -5
- package/dist/core/sync-definitions.js.map +1 -1
- package/dist/core/validate-definition.d.ts +3 -3
- package/dist/core/validate-definition.d.ts.map +1 -1
- package/dist/core/validate-definition.js +17 -12
- package/dist/core/validate-definition.js.map +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts +1 -1
- package/dist/core/workflow-model/cron-trigger.d.ts.map +1 -1
- package/dist/core/workflow-model/cron-trigger.js +9 -5
- package/dist/core/workflow-model/cron-trigger.js.map +1 -1
- package/dist/core/workflow-model/index.d.ts +1 -1
- package/dist/core/workflow-model/index.d.ts.map +1 -1
- package/dist/core/workflow-model/index.js.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +4 -2
- package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
- package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-job-listening.js +51 -30
- package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts +1 -1
- package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-triggers.js +48 -18
- package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
- package/dist/core/workflow-model/normalize-workflow-document.js +3 -3
- package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
- package/dist/core/workflow-model/parse-interpolation-field.js +1 -15
- package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts +3 -1
- package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
- package/dist/core/workflow-model/validate-predicate-expression.js +3 -0
- package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
- package/dist/core/workflow-model/validation-issue.d.ts +2 -1
- package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
- package/dist/core/workflow-model/validation-issue.js +12 -15
- package/dist/core/workflow-model/validation-issue.js.map +1 -1
- package/dist/db/db.d.ts +196 -4
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +3 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/definition-triggers.js +3 -1
- package/dist/db/definition-triggers.js.map +1 -1
- package/dist/db/definitions.d.ts +17 -0
- package/dist/db/definitions.d.ts.map +1 -1
- package/dist/db/definitions.js +190 -23
- package/dist/db/definitions.js.map +1 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/definitions.d.ts +17 -0
- package/dist/db/schema/definitions.d.ts.map +1 -1
- package/dist/db/schema/definitions.js +8 -0
- package/dist/db/schema/definitions.js.map +1 -1
- package/dist/db/schema/sync-states.d.ts +7 -3
- package/dist/db/schema/sync-states.d.ts.map +1 -1
- package/dist/db/schema/sync-states.js +5 -1
- package/dist/db/schema/sync-states.js.map +1 -1
- package/dist/db/schema/workflows.d.ts +78 -0
- package/dist/db/schema/workflows.d.ts.map +1 -0
- package/dist/db/schema/workflows.js +17 -0
- package/dist/db/schema/workflows.js.map +1 -0
- package/dist/db/sync-states.d.ts +2 -2
- package/dist/db/sync-states.d.ts.map +1 -1
- package/dist/db/sync-states.js +4 -4
- package/dist/db/sync-states.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +4 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +19 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/presentation/dto/definition.d.ts.map +1 -1
- package/dist/presentation/dto/definition.js +11 -4
- package/dist/presentation/dto/definition.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +9 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +66 -2
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/create-definition.d.ts.map +1 -1
- package/dist/presentation/routes/create-definition.js +16 -3
- package/dist/presentation/routes/create-definition.js.map +1 -1
- package/dist/presentation/routes/get-definition.d.ts.map +1 -1
- package/dist/presentation/routes/get-definition.js +18 -4
- package/dist/presentation/routes/get-definition.js.map +1 -1
- package/dist/presentation/routes/project-access.d.ts +1 -0
- package/dist/presentation/routes/project-access.d.ts.map +1 -1
- package/dist/presentation/routes/validate-definition.js +3 -3
- package/dist/presentation/routes/validate-definition.js.map +1 -1
- package/dist/temporal/activities/sync-activities.d.ts +3 -3
- package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
- package/dist/temporal/activities/sync-activities.js +7 -4
- package/dist/temporal/activities/sync-activities.js.map +1 -1
- package/dist/temporal/workflows/definition-sync.js +1 -1
- package/dist/temporal/workflows/definition-sync.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +50 -22
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0003_nebulous_nemesis.sql +11 -0
- package/drizzle/0004_workflow_ref_lookup.sql +1 -0
- package/drizzle/meta/0003_snapshot.json +627 -0
- package/drizzle/meta/0004_snapshot.json +649 -0
- package/drizzle/meta/_journal.json +14 -0
- package/drizzle.config.ts +1 -0
- package/package.json +7 -7
- package/src/core/entities/index.ts +1 -1
- package/src/core/entities/integration-context.ts +4 -0
- package/src/core/entities/sync-state.test.ts +49 -0
- package/src/core/entities/sync-state.ts +35 -12
- package/src/core/entities/validation-diagnostic.ts +8 -0
- package/src/core/entities/workflow-definition.ts +2 -0
- package/src/core/errors.ts +26 -0
- package/src/core/index.ts +12 -0
- package/src/core/integrations.test.ts +38 -0
- package/src/core/integrations.ts +10 -1
- package/src/core/parse-definition-diagnostics.test.ts +47 -0
- package/src/core/parse-definition.test.ts +29 -5
- package/src/core/parse-definition.ts +7 -7
- package/src/core/resolve-definition-at-ref.test.ts +792 -0
- package/src/core/resolve-definition-at-ref.ts +551 -0
- package/src/core/sync-definitions.test.ts +61 -2
- package/src/core/sync-definitions.ts +7 -7
- package/src/core/validate-definition.test.ts +31 -22
- package/src/core/validate-definition.ts +21 -13
- package/src/core/workflow-model/cron-trigger.ts +6 -2
- package/src/core/workflow-model/index.ts +1 -0
- package/src/core/workflow-model/invalid-workflow-model-error.ts +5 -1
- package/src/core/workflow-model/normalize-job-listening.ts +55 -23
- package/src/core/workflow-model/normalize-triggers.ts +44 -15
- package/src/core/workflow-model/normalize-workflow-document.test.ts +494 -36
- package/src/core/workflow-model/normalize-workflow-document.ts +8 -4
- package/src/core/workflow-model/parse-interpolation-field.ts +1 -23
- package/src/core/workflow-model/validate-predicate-expression.ts +6 -0
- package/src/core/workflow-model/validation-issue.ts +8 -13
- package/src/db/db.ts +7 -1
- package/src/db/definition-triggers.test.ts +44 -0
- package/src/db/definition-triggers.ts +1 -1
- package/src/db/definitions.test.ts +300 -1
- package/src/db/definitions.ts +282 -28
- package/src/db/index.ts +2 -0
- package/src/db/schema/definitions.test.ts +1 -0
- package/src/db/schema/definitions.ts +11 -0
- package/src/db/schema/sync-states.ts +12 -3
- package/src/db/schema/workflows.ts +23 -0
- package/src/db/sync-states.test.ts +87 -18
- package/src/db/sync-states.ts +6 -6
- package/src/db/workflow-lineage-migration.test.ts +158 -0
- package/src/index.ts +3 -1
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.test.ts +50 -0
- package/src/metrics/instance.ts +23 -0
- package/src/presentation/dto/definition.test.ts +89 -1
- package/src/presentation/dto/definition.ts +8 -5
- package/src/presentation/inter-module.test.ts +177 -0
- package/src/presentation/inter-module.ts +77 -4
- package/src/presentation/routes/create-definition.test.ts +38 -0
- package/src/presentation/routes/create-definition.ts +20 -3
- package/src/presentation/routes/get-definition.test.ts +125 -11
- package/src/presentation/routes/get-definition.ts +21 -4
- package/src/presentation/routes/list-definitions.test.ts +1 -1
- package/src/presentation/routes/validate-definition.test.ts +1 -1
- package/src/presentation/routes/validate-definition.ts +3 -3
- package/src/presentation/subscribers/on-integration-connection-available.test.ts +3 -0
- package/src/temporal/activities/sync-activities.test.ts +46 -2
- package/src/temporal/activities/sync-activities.ts +9 -7
- package/src/temporal/workflows/definition-sync.ts +1 -1
- package/test/factories/definition.ts +1 -0
- package/test/globalSetup.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/entities/validation-warning.d.ts +0 -6
- package/dist/core/entities/validation-warning.d.ts.map +0 -1
- package/dist/core/entities/validation-warning.js +0 -3
- package/dist/core/entities/validation-warning.js.map +0 -1
- package/src/core/entities/validation-warning.ts +0 -5
- package/src/core/parse-definition-warnings.test.ts +0 -33
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
|
|
25
25
|
expect(result.valid).toBe(true);
|
|
26
26
|
if (result.valid) {
|
|
27
|
-
expect(result.
|
|
27
|
+
expect(result.diagnostics).toEqual([]);
|
|
28
28
|
expect(result.definition.document.name).toBe('Test');
|
|
29
29
|
expect(result.definition.document.jobs.build?.steps).toHaveLength(1);
|
|
30
30
|
expect(result.definition.model.jobs[0]?.id).toBe('build');
|
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
|
|
37
37
|
expect(result.valid).toBe(false);
|
|
38
38
|
if (!result.valid) {
|
|
39
|
-
expect(result).not.toHaveProperty('
|
|
39
|
+
expect(result).not.toHaveProperty('diagnostics');
|
|
40
40
|
expect(result.errors).toHaveLength(1);
|
|
41
41
|
expect(result.errors[0]?.message).toContain('Invalid workflow YAML syntax');
|
|
42
42
|
}
|
|
@@ -159,14 +159,15 @@ jobs:
|
|
|
159
159
|
|
|
160
160
|
expect(result.valid).toBe(true);
|
|
161
161
|
if (result.valid) {
|
|
162
|
-
expect(result.
|
|
163
|
-
expect(result.
|
|
162
|
+
expect(result.diagnostics).toHaveLength(1);
|
|
163
|
+
expect(result.diagnostics[0]).toMatchObject({
|
|
164
164
|
code: 're-evaluating-command',
|
|
165
165
|
path: 'jobs.build.steps.0.run',
|
|
166
|
+
severity: 'warning',
|
|
166
167
|
});
|
|
167
|
-
expect(result.
|
|
168
|
-
expect(result.
|
|
169
|
-
expect(result.
|
|
168
|
+
expect(result.diagnostics[0]?.message).toContain(`$${valueName}`);
|
|
169
|
+
expect(result.diagnostics[0]?.message).toContain('eval');
|
|
170
|
+
expect(result.diagnostics[0]?.message).toContain('re-executed as code');
|
|
170
171
|
expect(result.definition.model.jobs[0]?.steps).toHaveLength(1);
|
|
171
172
|
}
|
|
172
173
|
});
|
|
@@ -189,7 +190,7 @@ jobs:
|
|
|
189
190
|
${run}
|
|
190
191
|
`);
|
|
191
192
|
|
|
192
|
-
expect(result).toMatchObject({valid: true,
|
|
193
|
+
expect(result).toMatchObject({valid: true, diagnostics: []});
|
|
193
194
|
});
|
|
194
195
|
|
|
195
196
|
test.each([
|
|
@@ -209,13 +210,14 @@ jobs:
|
|
|
209
210
|
|
|
210
211
|
expect(result.valid).toBe(true);
|
|
211
212
|
if (result.valid) {
|
|
212
|
-
expect(result.
|
|
213
|
-
expect(result.
|
|
213
|
+
expect(result.diagnostics).toHaveLength(1);
|
|
214
|
+
expect(result.diagnostics[0]).toMatchObject({
|
|
214
215
|
code: 're-evaluating-command',
|
|
215
216
|
path: 'jobs.build.steps.0.run',
|
|
217
|
+
severity: 'warning',
|
|
216
218
|
});
|
|
217
|
-
expect(result.
|
|
218
|
-
expect(result.
|
|
219
|
+
expect(result.diagnostics[0]?.message).toContain('event.ref');
|
|
220
|
+
expect(result.diagnostics[0]?.message).toContain(construct);
|
|
219
221
|
}
|
|
220
222
|
});
|
|
221
223
|
|
|
@@ -234,18 +236,25 @@ jobs:
|
|
|
234
236
|
|
|
235
237
|
expect(result.valid).toBe(true);
|
|
236
238
|
if (result.valid) {
|
|
237
|
-
expect(result.
|
|
238
|
-
expect(
|
|
239
|
-
|
|
240
|
-
);
|
|
241
|
-
expect(
|
|
242
|
-
|
|
243
|
-
);
|
|
244
|
-
expect(result.
|
|
245
|
-
expect(result.warnings.some((warning) => warning.message.includes('event.first'))).toBe(true);
|
|
246
|
-
expect(result.warnings.some((warning) => warning.message.includes('event.second'))).toBe(
|
|
239
|
+
expect(result.diagnostics).toHaveLength(5);
|
|
240
|
+
expect(
|
|
241
|
+
result.diagnostics.every((diagnostic) => diagnostic.code === 're-evaluating-command'),
|
|
242
|
+
).toBe(true);
|
|
243
|
+
expect(
|
|
244
|
+
result.diagnostics.every((diagnostic) => diagnostic.path === 'jobs.build.steps.0.run'),
|
|
245
|
+
).toBe(true);
|
|
246
|
+
expect(result.diagnostics.every((diagnostic) => diagnostic.severity === 'warning')).toBe(
|
|
247
247
|
true,
|
|
248
248
|
);
|
|
249
|
+
expect(
|
|
250
|
+
result.diagnostics.filter((diagnostic) => diagnostic.message.includes('eval')),
|
|
251
|
+
).toHaveLength(3);
|
|
252
|
+
expect(
|
|
253
|
+
result.diagnostics.some((diagnostic) => diagnostic.message.includes('event.first')),
|
|
254
|
+
).toBe(true);
|
|
255
|
+
expect(
|
|
256
|
+
result.diagnostics.some((diagnostic) => diagnostic.message.includes('event.second')),
|
|
257
|
+
).toBe(true);
|
|
249
258
|
}
|
|
250
259
|
});
|
|
251
260
|
|
|
@@ -2,7 +2,7 @@ import type {AgentValidationCatalog} from '@shipfox/api-agent-dto/inter-module';
|
|
|
2
2
|
import {InvalidWorkflowDocumentError} from '@shipfox/workflow-document';
|
|
3
3
|
import {definitionDefaultRunnerLabels} from '../config.js';
|
|
4
4
|
import type {IntegrationValidationContext} from './entities/integration-context.js';
|
|
5
|
-
import type {
|
|
5
|
+
import type {ValidationDiagnostic} from './entities/validation-diagnostic.js';
|
|
6
6
|
import type {WorkflowDefinitionPayload} from './entities/workflow-definition.js';
|
|
7
7
|
import {
|
|
8
8
|
InvalidWorkflowModelError,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import {InvalidWorkflowYamlError, parseWorkflowYamlWithLocations} from './workflow-yaml/index.js';
|
|
13
13
|
|
|
14
14
|
export type ValidationError = {message: string; path?: string | undefined};
|
|
15
|
-
export type {
|
|
15
|
+
export type {ValidationDiagnostic} from './entities/validation-diagnostic.js';
|
|
16
16
|
|
|
17
17
|
export interface DefinitionValidationOptions {
|
|
18
18
|
defaultRunnerLabels?: readonly string[];
|
|
@@ -21,7 +21,7 @@ export interface DefinitionValidationOptions {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export type ValidationResult =
|
|
24
|
-
| {valid: true; definition: WorkflowDefinitionPayload;
|
|
24
|
+
| {valid: true; definition: WorkflowDefinitionPayload; diagnostics: ValidationDiagnostic[]}
|
|
25
25
|
| {valid: false; errors: ValidationError[]};
|
|
26
26
|
|
|
27
27
|
export function validateDefinition(
|
|
@@ -30,28 +30,33 @@ export function validateDefinition(
|
|
|
30
30
|
): ValidationResult {
|
|
31
31
|
try {
|
|
32
32
|
const {document, stepSourceLocations} = parseWorkflowYamlWithLocations(yamlContent);
|
|
33
|
-
const
|
|
33
|
+
const diagnostics: WorkflowModelValidationIssue[] = [];
|
|
34
34
|
const model = normalizeWorkflowDocument(document, {
|
|
35
35
|
defaultRunnerLabels: options.defaultRunnerLabels ?? definitionDefaultRunnerLabels,
|
|
36
36
|
agentValidationCatalog: options.agentValidationCatalog,
|
|
37
37
|
integrationValidationContext: options.integrationValidationContext,
|
|
38
38
|
stepSourceLocations,
|
|
39
|
-
|
|
39
|
+
diagnostics,
|
|
40
40
|
});
|
|
41
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
valid: true,
|
|
43
|
+
definition: {document, model},
|
|
44
|
+
diagnostics: validationDiagnosticsFor(diagnostics),
|
|
45
|
+
};
|
|
42
46
|
} catch (error) {
|
|
43
47
|
return {valid: false, errors: validationErrorsFor(error)};
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
function
|
|
51
|
+
function validationDiagnosticsFor(
|
|
48
52
|
issues: readonly WorkflowModelValidationIssue[],
|
|
49
|
-
):
|
|
53
|
+
): ValidationDiagnostic[] {
|
|
50
54
|
return issues.map((issue) =>
|
|
51
|
-
|
|
55
|
+
validationDiagnostic({
|
|
52
56
|
code: issue.code,
|
|
53
57
|
message: issue.message,
|
|
54
58
|
path: issue.path.join('.') || undefined,
|
|
59
|
+
severity: issue.severity,
|
|
55
60
|
}),
|
|
56
61
|
);
|
|
57
62
|
}
|
|
@@ -92,11 +97,14 @@ function validationError(params: {message: string; path?: string | undefined}):
|
|
|
92
97
|
return {message: params.message, path: params.path};
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
function
|
|
100
|
+
function validationDiagnostic(params: {
|
|
96
101
|
code: string;
|
|
97
102
|
message: string;
|
|
98
103
|
path?: string | undefined;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
severity: 'error' | 'warning';
|
|
105
|
+
}): ValidationDiagnostic {
|
|
106
|
+
if (params.path === undefined) {
|
|
107
|
+
return {code: params.code, message: params.message, severity: params.severity};
|
|
108
|
+
}
|
|
109
|
+
return {code: params.code, message: params.message, path: params.path, severity: params.severity};
|
|
102
110
|
}
|
|
@@ -8,7 +8,7 @@ const cronFieldSeparatorRegex = /\s+/;
|
|
|
8
8
|
export function validateCronTrigger(params: {
|
|
9
9
|
readonly sourceKey: string;
|
|
10
10
|
readonly trigger: {
|
|
11
|
-
readonly event
|
|
11
|
+
readonly event?: string | undefined;
|
|
12
12
|
};
|
|
13
13
|
readonly config: {
|
|
14
14
|
readonly schedule?: string | undefined;
|
|
@@ -18,13 +18,14 @@ export function validateCronTrigger(params: {
|
|
|
18
18
|
}): void {
|
|
19
19
|
const {sourceKey, trigger, config, issues} = params;
|
|
20
20
|
|
|
21
|
-
if (trigger.event !== 'tick') {
|
|
21
|
+
if (trigger.event !== undefined && trigger.event !== 'tick') {
|
|
22
22
|
issues.push(
|
|
23
23
|
issue({
|
|
24
24
|
code: 'invalid-cron-event',
|
|
25
25
|
message: `A cron trigger must use event "tick"; found "${trigger.event}".`,
|
|
26
26
|
path: ['triggers', sourceKey, 'event'],
|
|
27
27
|
details: {event: trigger.event},
|
|
28
|
+
scope: 'trigger',
|
|
28
29
|
}),
|
|
29
30
|
);
|
|
30
31
|
}
|
|
@@ -35,6 +36,7 @@ export function validateCronTrigger(params: {
|
|
|
35
36
|
code: 'missing-cron-schedule',
|
|
36
37
|
message: 'A cron trigger requires a schedule.',
|
|
37
38
|
path: ['triggers', sourceKey, 'config', 'schedule'],
|
|
39
|
+
scope: 'trigger',
|
|
38
40
|
}),
|
|
39
41
|
);
|
|
40
42
|
} else if (!isValidCronExpression(config.schedule)) {
|
|
@@ -44,6 +46,7 @@ export function validateCronTrigger(params: {
|
|
|
44
46
|
message: 'Cron trigger schedule must be a valid 5-field cron expression.',
|
|
45
47
|
path: ['triggers', sourceKey, 'config', 'schedule'],
|
|
46
48
|
details: {schedule: config.schedule},
|
|
49
|
+
scope: 'trigger',
|
|
47
50
|
}),
|
|
48
51
|
);
|
|
49
52
|
}
|
|
@@ -55,6 +58,7 @@ export function validateCronTrigger(params: {
|
|
|
55
58
|
message: 'Cron trigger timezone must be a valid IANA time zone.',
|
|
56
59
|
path: ['triggers', sourceKey, 'config', 'timezone'],
|
|
57
60
|
details: {timezone: config.timezone},
|
|
61
|
+
scope: 'trigger',
|
|
58
62
|
}),
|
|
59
63
|
);
|
|
60
64
|
}
|
|
@@ -5,6 +5,7 @@ export {
|
|
|
5
5
|
type WorkflowModelValidationIssue,
|
|
6
6
|
type WorkflowModelValidationIssueCode,
|
|
7
7
|
type WorkflowModelValidationIssuePathSegment,
|
|
8
|
+
type WorkflowModelValidationIssueScope,
|
|
8
9
|
type WorkflowModelValidationIssueSeverity,
|
|
9
10
|
} from './invalid-workflow-model-error.js';
|
|
10
11
|
export {normalizeWorkflowDocument} from './normalize-workflow-document.js';
|
|
@@ -34,6 +34,7 @@ export type WorkflowModelValidationIssueCode =
|
|
|
34
34
|
| 'invalid-step-if'
|
|
35
35
|
| 'job-dependency-cycle'
|
|
36
36
|
| 'listening-job-missing-resolution-source'
|
|
37
|
+
| 'listening-job-no-active-matcher'
|
|
37
38
|
| 'listening-timeout-exceeds-run-timeout'
|
|
38
39
|
| 'missing-harness-for-tools'
|
|
39
40
|
| 'missing-connection-for-integration'
|
|
@@ -58,12 +59,15 @@ export type WorkflowModelValidationIssuePathSegment = string | number;
|
|
|
58
59
|
|
|
59
60
|
export type WorkflowModelValidationIssueSeverity = 'error' | 'warning';
|
|
60
61
|
|
|
62
|
+
export type WorkflowModelValidationIssueScope = 'definition' | 'trigger';
|
|
63
|
+
|
|
61
64
|
export interface WorkflowModelValidationIssue {
|
|
62
65
|
readonly code: WorkflowModelValidationIssueCode;
|
|
63
66
|
readonly message: string;
|
|
64
67
|
readonly path: readonly WorkflowModelValidationIssuePathSegment[];
|
|
65
68
|
readonly details?: Readonly<Record<string, unknown>>;
|
|
66
|
-
readonly severity
|
|
69
|
+
readonly severity: WorkflowModelValidationIssueSeverity;
|
|
70
|
+
readonly scope: WorkflowModelValidationIssueScope;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
export class InvalidWorkflowModelError extends Error {
|
|
@@ -39,8 +39,49 @@ export function normalizeJobListening(params: {
|
|
|
39
39
|
issues: params.issues,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
const on = listening.on
|
|
43
|
+
.map((trigger, index) =>
|
|
44
|
+
normalizeListeningTrigger({
|
|
45
|
+
trigger,
|
|
46
|
+
field: 'listener.on',
|
|
47
|
+
path: [...path, 'listening', 'on', index, 'filter'],
|
|
48
|
+
issues: params.issues,
|
|
49
|
+
allowedJobReferences: params.allowedJobReferences,
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
.filter((matcher): matcher is WorkflowModelJobListening['on'][number] => matcher !== undefined);
|
|
53
|
+
|
|
54
|
+
if (on.length === 0) {
|
|
55
|
+
params.issues.push(
|
|
56
|
+
issue({
|
|
57
|
+
code: 'listening-job-no-active-matcher',
|
|
58
|
+
message: `Listening job "${params.sourceName}" has no active "on" matcher; it can never execute.`,
|
|
59
|
+
path: [...path, 'listening', 'on'],
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const until =
|
|
65
|
+
listening.until === undefined
|
|
66
|
+
? undefined
|
|
67
|
+
: listening.until
|
|
68
|
+
.map((trigger, index) =>
|
|
69
|
+
normalizeListeningTrigger({
|
|
70
|
+
trigger,
|
|
71
|
+
field: 'listener.until',
|
|
72
|
+
path: [...path, 'listening', 'until', index, 'filter'],
|
|
73
|
+
issues: params.issues,
|
|
74
|
+
allowedJobReferences: params.allowedJobReferences,
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
77
|
+
.filter(
|
|
78
|
+
(matcher): matcher is WorkflowModelJobListening['on'][number] => matcher !== undefined,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Inert `until` matchers do not resolve the listening job; the check runs on
|
|
82
|
+
// the matchers that stay active.
|
|
42
83
|
if (
|
|
43
|
-
|
|
84
|
+
(until === undefined || until.length === 0) &&
|
|
44
85
|
listening.timeout === undefined &&
|
|
45
86
|
listening.max_executions === undefined
|
|
46
87
|
) {
|
|
@@ -63,27 +104,11 @@ export function normalizeJobListening(params: {
|
|
|
63
104
|
};
|
|
64
105
|
|
|
65
106
|
return {
|
|
66
|
-
on
|
|
67
|
-
|
|
68
|
-
trigger,
|
|
69
|
-
field: 'listener.on',
|
|
70
|
-
path: [...path, 'listening', 'on', index, 'filter'],
|
|
71
|
-
issues: params.issues,
|
|
72
|
-
allowedJobReferences: params.allowedJobReferences,
|
|
73
|
-
}),
|
|
74
|
-
),
|
|
75
|
-
...(listening.until === undefined
|
|
107
|
+
on,
|
|
108
|
+
...(until === undefined || until.length === 0
|
|
76
109
|
? {}
|
|
77
110
|
: {
|
|
78
|
-
until
|
|
79
|
-
normalizeListeningTrigger({
|
|
80
|
-
trigger,
|
|
81
|
-
field: 'listener.until',
|
|
82
|
-
path: [...path, 'listening', 'until', index, 'filter'],
|
|
83
|
-
issues: params.issues,
|
|
84
|
-
allowedJobReferences: params.allowedJobReferences,
|
|
85
|
-
}),
|
|
86
|
-
),
|
|
111
|
+
until,
|
|
87
112
|
}),
|
|
88
113
|
...(timeoutMs === undefined ? {} : {timeoutMs}),
|
|
89
114
|
...(listening.max_executions === undefined ? {} : {maxExecutions: listening.max_executions}),
|
|
@@ -95,7 +120,7 @@ export function normalizeJobListening(params: {
|
|
|
95
120
|
function normalizeListeningTrigger(params: {
|
|
96
121
|
trigger: {
|
|
97
122
|
readonly source: string;
|
|
98
|
-
readonly event
|
|
123
|
+
readonly event?: string | undefined;
|
|
99
124
|
readonly with?: Readonly<Record<string, unknown>> | undefined;
|
|
100
125
|
readonly filter?: string | undefined;
|
|
101
126
|
};
|
|
@@ -103,7 +128,8 @@ function normalizeListeningTrigger(params: {
|
|
|
103
128
|
path: readonly (string | number)[];
|
|
104
129
|
issues: WorkflowModelValidationIssue[];
|
|
105
130
|
allowedJobReferences: ReadonlySet<string>;
|
|
106
|
-
}): WorkflowModelJobListening['on'][number] {
|
|
131
|
+
}): WorkflowModelJobListening['on'][number] | undefined {
|
|
132
|
+
const matcherIssues: WorkflowModelValidationIssue[] = [];
|
|
107
133
|
if (params.trigger.filter !== undefined) {
|
|
108
134
|
validatePredicateExpression({
|
|
109
135
|
field: params.field,
|
|
@@ -111,10 +137,16 @@ function normalizeListeningTrigger(params: {
|
|
|
111
137
|
path: params.path,
|
|
112
138
|
invalidCode: 'invalid-listener-filter',
|
|
113
139
|
invalidMessage: `${params.field === 'listener.on' ? 'Listener on' : 'Listener until'} filter must be a valid CEL boolean expression.`,
|
|
114
|
-
issues:
|
|
140
|
+
issues: matcherIssues,
|
|
115
141
|
allowedJobReferences: params.allowedJobReferences,
|
|
142
|
+
scope: 'trigger',
|
|
116
143
|
});
|
|
117
144
|
}
|
|
145
|
+
params.issues.push(...matcherIssues);
|
|
146
|
+
|
|
147
|
+
// A matcher with a trigger-scoped issue is inert: excluded from the matcher
|
|
148
|
+
// list while the authored document entry stays untouched.
|
|
149
|
+
if (matcherIssues.some((candidate) => candidate.scope === 'trigger')) return undefined;
|
|
118
150
|
|
|
119
151
|
return normalizeTriggerEntry(params.trigger);
|
|
120
152
|
}
|
|
@@ -22,17 +22,7 @@ export function normalizeTriggers(
|
|
|
22
22
|
.filter(([, trigger]) => trigger.source === manualTriggerSource)
|
|
23
23
|
.map(([sourceKey]) => sourceKey);
|
|
24
24
|
const usedTriggerIds = new Map<string, string>();
|
|
25
|
-
|
|
26
|
-
if (manualTriggerKeys.length > 1) {
|
|
27
|
-
issues.push(
|
|
28
|
-
issue({
|
|
29
|
-
code: 'multiple-manual-triggers',
|
|
30
|
-
message: `A workflow may declare at most one manual trigger; found ${manualTriggerKeys.length}: ${manualTriggerKeys.join(', ')}.`,
|
|
31
|
-
path: ['triggers'],
|
|
32
|
-
details: {manualTriggerKeys},
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
35
|
-
}
|
|
25
|
+
let activeManualTriggerSeen = false;
|
|
36
26
|
|
|
37
27
|
return Object.entries(triggers).flatMap(([sourceKey, trigger]) => {
|
|
38
28
|
const id = stableId(sourceKey);
|
|
@@ -44,16 +34,37 @@ export function normalizeTriggers(
|
|
|
44
34
|
message: `Trigger keys "${existingSourceKey}" and "${sourceKey}" resolve to the same stable id "${id}".`,
|
|
45
35
|
path: ['triggers', sourceKey],
|
|
46
36
|
details: {id, sourceKeys: [existingSourceKey, sourceKey]},
|
|
37
|
+
scope: 'trigger',
|
|
47
38
|
}),
|
|
48
39
|
);
|
|
49
40
|
return [];
|
|
50
41
|
}
|
|
51
42
|
usedTriggerIds.set(id, sourceKey);
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
if (trigger.source === manualTriggerSource) {
|
|
45
|
+
if (activeManualTriggerSeen) {
|
|
46
|
+
issues.push(
|
|
47
|
+
issue({
|
|
48
|
+
code: 'multiple-manual-triggers',
|
|
49
|
+
message: `A workflow may declare at most one manual trigger; found ${manualTriggerKeys.length}: ${manualTriggerKeys.join(', ')}. This trigger is inert because it is not the first manual trigger in document order.`,
|
|
50
|
+
path: ['triggers', sourceKey],
|
|
51
|
+
details: {manualTriggerKeys},
|
|
52
|
+
scope: 'trigger',
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const triggerIssues: WorkflowModelValidationIssue[] = [];
|
|
60
|
+
validateTriggerFilter({sourceKey, trigger, issues: triggerIssues});
|
|
61
|
+
issues.push(...triggerIssues);
|
|
54
62
|
|
|
55
63
|
const normalizedTrigger = normalizeTriggerEntry(trigger);
|
|
64
|
+
const triggerIsInert = triggerIssues.some((candidate) => candidate.scope === 'trigger');
|
|
56
65
|
if (trigger.source !== cronTriggerSource) {
|
|
66
|
+
if (triggerIsInert) return [];
|
|
67
|
+
if (trigger.source === manualTriggerSource) activeManualTriggerSeen = true;
|
|
57
68
|
return [
|
|
58
69
|
{
|
|
59
70
|
id,
|
|
@@ -70,7 +81,12 @@ export function normalizeTriggers(
|
|
|
70
81
|
timezone: cronConfig.timezone ?? cronTriggerDefaultTimezone,
|
|
71
82
|
};
|
|
72
83
|
|
|
73
|
-
|
|
84
|
+
const cronIssues: WorkflowModelValidationIssue[] = [];
|
|
85
|
+
validateCronTrigger({trigger, config: cronConfig, sourceKey, issues: cronIssues});
|
|
86
|
+
issues.push(...cronIssues);
|
|
87
|
+
|
|
88
|
+
const cronIsInert = cronIssues.some((candidate) => candidate.scope === 'trigger');
|
|
89
|
+
if (triggerIsInert || cronIsInert) return [];
|
|
74
90
|
|
|
75
91
|
return [
|
|
76
92
|
{
|
|
@@ -99,6 +115,7 @@ function validateTriggerFilter(params: {
|
|
|
99
115
|
message: `A ${trigger.source} trigger cannot define a filter because it does not receive an event payload.`,
|
|
100
116
|
path,
|
|
101
117
|
details: {source: trigger.filter, triggerSource: trigger.source},
|
|
118
|
+
scope: 'trigger',
|
|
102
119
|
}),
|
|
103
120
|
);
|
|
104
121
|
return;
|
|
@@ -111,19 +128,31 @@ function validateTriggerFilter(params: {
|
|
|
111
128
|
invalidCode: 'invalid-trigger-filter',
|
|
112
129
|
invalidMessage: 'Trigger filter must be a valid boolean predicate.',
|
|
113
130
|
issues,
|
|
131
|
+
scope: 'trigger',
|
|
114
132
|
});
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
export function normalizeTriggerEntry(trigger: {
|
|
118
136
|
readonly source: string;
|
|
119
|
-
readonly event
|
|
137
|
+
readonly event?: string | undefined;
|
|
120
138
|
readonly with?: Readonly<Record<string, unknown>> | undefined;
|
|
121
139
|
readonly filter?: string | undefined;
|
|
122
140
|
}): WorkflowModelListeningTrigger {
|
|
141
|
+
// `manual` and `cron` have no delivered event to resolve against at fire
|
|
142
|
+
// time, so materialize their built-in name when the author omitted it.
|
|
143
|
+
// Integration sources keep the event absent, which becomes a source
|
|
144
|
+
// subscription (NULL row) on the write path.
|
|
145
|
+
const event = builtinEventForSource(trigger.source, trigger.event);
|
|
123
146
|
return {
|
|
124
147
|
source: trigger.source,
|
|
125
|
-
event:
|
|
148
|
+
...(event === undefined ? {} : {event}),
|
|
126
149
|
...(trigger.with === undefined ? {} : {inputs: trigger.with}),
|
|
127
150
|
...(trigger.filter === undefined ? {} : {filter: trigger.filter}),
|
|
128
151
|
};
|
|
129
152
|
}
|
|
153
|
+
|
|
154
|
+
function builtinEventForSource(source: string, event: string | undefined): string | undefined {
|
|
155
|
+
if (source === manualTriggerSource) return event ?? 'fire';
|
|
156
|
+
if (source === cronTriggerSource) return event ?? 'tick';
|
|
157
|
+
return event;
|
|
158
|
+
}
|