@rytass/bpm-core-nestjs-module 0.9.0 → 0.10.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/CHANGELOG.md +57 -0
- package/README.md +17 -5
- package/package.json +11 -11
- package/src/lib/attachment/attachment-options.js +7 -1
- package/src/lib/attachment/attachment-options.js.map +1 -1
- package/src/lib/attachment/attachment.service.js +7 -0
- package/src/lib/attachment/attachment.service.js.map +1 -1
- package/src/lib/bpm/bpm-root.module.d.ts +11 -0
- package/src/lib/bpm/bpm-root.module.js.map +1 -1
- package/src/lib/calendar/calendar.module.d.ts +12 -0
- package/src/lib/calendar/calendar.module.js.map +1 -1
- package/src/lib/common/filters/all-exceptions.filter.d.ts +11 -0
- package/src/lib/common/filters/all-exceptions.filter.js +35 -1
- package/src/lib/common/filters/all-exceptions.filter.js.map +1 -1
- package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +6 -7
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js +91 -291
- package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.queries.d.ts +24 -0
- package/src/lib/form-data-source/form-data-source.queries.js +179 -7
- package/src/lib/form-data-source/form-data-source.queries.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.service.d.ts +36 -21
- package/src/lib/form-data-source/form-data-source.service.js +143 -359
- package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.types.d.ts +17 -0
- package/src/lib/form-data-source/form-data-source.types.js.map +1 -1
- package/src/lib/form-data-source/form-data-source.validation.d.ts +78 -0
- package/src/lib/form-data-source/form-data-source.validation.js +409 -0
- package/src/lib/form-data-source/form-data-source.validation.js.map +1 -0
- package/src/lib/migrations/0000000021000-notification-silenced.d.ts +15 -0
- package/src/lib/migrations/0000000021000-notification-silenced.js +31 -0
- package/src/lib/migrations/0000000021000-notification-silenced.js.map +1 -0
- package/src/lib/migrations/index.d.ts +1 -0
- package/src/lib/migrations/index.js +3 -0
- package/src/lib/migrations/index.js.map +1 -1
- package/src/lib/notification/index.d.ts +1 -0
- package/src/lib/notification/index.js +1 -0
- package/src/lib/notification/index.js.map +1 -1
- package/src/lib/notification/notification-delivery.service.d.ts +26 -1
- package/src/lib/notification/notification-delivery.service.js +136 -13
- package/src/lib/notification/notification-delivery.service.js.map +1 -1
- package/src/lib/notification/notification-dispatcher.token.d.ts +13 -0
- package/src/lib/notification/notification-dispatcher.token.js.map +1 -1
- package/src/lib/notification/notification-observer.token.d.ts +6 -0
- package/src/lib/notification/notification-observer.token.js.map +1 -1
- package/src/lib/notification/notification-options.d.ts +30 -0
- package/src/lib/notification/notification-options.js +32 -4
- package/src/lib/notification/notification-options.js.map +1 -1
- package/src/lib/notification/notification-schedule.d.ts +41 -0
- package/src/lib/notification/notification-schedule.js +158 -0
- package/src/lib/notification/notification-schedule.js.map +1 -0
- package/src/lib/notification/notification.entity.d.ts +12 -0
- package/src/lib/notification/notification.entity.js +5 -0
- package/src/lib/notification/notification.entity.js.map +1 -1
- package/src/lib/notification/notification.service.d.ts +10 -0
- package/src/lib/notification/notification.service.js +100 -21
- package/src/lib/notification/notification.service.js.map +1 -1
- package/src/lib/template/index.d.ts +1 -0
- package/src/lib/template/index.js +1 -0
- package/src/lib/template/index.js.map +1 -1
- package/src/lib/template/template-observer.token.d.ts +67 -0
- package/src/lib/template/template-observer.token.js +16 -0
- package/src/lib/template/template-observer.token.js.map +1 -0
- package/src/lib/template/template.mutations.d.ts +1 -1
- package/src/lib/template/template.mutations.js +4 -3
- package/src/lib/template/template.mutations.js.map +1 -1
- package/src/lib/template/template.service.d.ts +18 -2
- package/src/lib/template/template.service.js +117 -17
- package/src/lib/template/template.service.js.map +1 -1
|
@@ -16,20 +16,7 @@ const workflow_engine_enums_1 = require("../workflow-engine/workflow-engine.enum
|
|
|
16
16
|
const workflow_engine_service_1 = require("../workflow-engine/workflow-engine.service");
|
|
17
17
|
const form_data_source_errors_1 = require("./form-data-source.errors");
|
|
18
18
|
const form_data_source_types_1 = require("./form-data-source.types");
|
|
19
|
-
const
|
|
20
|
-
const PROVIDER_TIMEOUT_MS = 5_000;
|
|
21
|
-
const SOURCE_CONTROLS = [
|
|
22
|
-
'autocomplete',
|
|
23
|
-
'checkbox',
|
|
24
|
-
'radio',
|
|
25
|
-
'select',
|
|
26
|
-
];
|
|
27
|
-
const PARAMETER_TYPES = [
|
|
28
|
-
'BOOLEAN',
|
|
29
|
-
'NUMBER',
|
|
30
|
-
'STRING',
|
|
31
|
-
'STRING_ARRAY',
|
|
32
|
-
];
|
|
19
|
+
const form_data_source_validation_1 = require("./form-data-source.validation");
|
|
33
20
|
let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceService {
|
|
34
21
|
constructor(registry, approvalTemplateVersionRepository, formDefinitionVersionRepository, workflowEngineService) {
|
|
35
22
|
this.registry = registry;
|
|
@@ -39,7 +26,9 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
39
26
|
this.logger = new common_1.Logger(FormDataSourceService_1.name);
|
|
40
27
|
}
|
|
41
28
|
listDescriptors() {
|
|
42
|
-
return this.registry
|
|
29
|
+
return this.registry
|
|
30
|
+
.list()
|
|
31
|
+
.flatMap((source) => (0, form_data_source_validation_1.readDescriptorErrors)(source.descriptor).length === 0
|
|
43
32
|
? [source.descriptor]
|
|
44
33
|
: []);
|
|
45
34
|
}
|
|
@@ -51,9 +40,11 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
51
40
|
const path = `schema.fields[${index}].dataSource`;
|
|
52
41
|
const source = this.registry.get(field.dataSource.key, field.dataSource.version);
|
|
53
42
|
if (!source) {
|
|
54
|
-
return [
|
|
43
|
+
return [
|
|
44
|
+
`${path} ${(0, form_data_source_validation_1.readMissingSourceCode)(this.registry, field.dataSource.key)}`,
|
|
45
|
+
];
|
|
55
46
|
}
|
|
56
|
-
const descriptorErrors =
|
|
47
|
+
const descriptorErrors = (0, form_data_source_validation_1.readDescriptorErrors)(source.descriptor);
|
|
57
48
|
const controlErrors = this.readControlErrors(field, source.descriptor, path);
|
|
58
49
|
const bindingErrors = this.readBindingEnvironmentErrors(field.dataSource, source.descriptor, path);
|
|
59
50
|
return [...descriptorErrors, ...controlErrors, ...bindingErrors];
|
|
@@ -84,54 +75,141 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
84
75
|
searchText: input.searchText ?? '',
|
|
85
76
|
});
|
|
86
77
|
}
|
|
78
|
+
async previewResolveFormFieldOptionValues(input, authContext) {
|
|
79
|
+
const { schema } = this.parseSchema(input.schemaJson, input.uiSchemaJson);
|
|
80
|
+
this.assertEnvironmentSchema(schema);
|
|
81
|
+
const field = this.readOptionField(schema, input.fieldKey);
|
|
82
|
+
return this.reportFormFieldOptionValues({
|
|
83
|
+
authContext,
|
|
84
|
+
field,
|
|
85
|
+
formData: this.parseFormData(input.formDataJson),
|
|
86
|
+
values: this.parseValues(input.valuesJson),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async resolveFormFieldOptionValues(input, authContext) {
|
|
90
|
+
const context = await this.readRuntimeSchema(input, authContext);
|
|
91
|
+
const field = this.readOptionField(context.schema, input.fieldKey);
|
|
92
|
+
return this.reportFormFieldOptionValues({
|
|
93
|
+
authContext,
|
|
94
|
+
field,
|
|
95
|
+
formData: this.parseFormData(input.formDataJson),
|
|
96
|
+
values: this.parseValues(input.valuesJson),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
87
99
|
async searchFormFieldOptions(input) {
|
|
88
|
-
const field =
|
|
89
|
-
const source =
|
|
90
|
-
|
|
91
|
-
const bindings =
|
|
100
|
+
const field = (0, form_data_source_validation_1.readDynamicOptionField)(input.field);
|
|
101
|
+
const source = (0, form_data_source_validation_1.readSourceOrThrow)(this.registry, field.dataSource);
|
|
102
|
+
(0, form_data_source_validation_1.assertControlSupported)(field, source.descriptor);
|
|
103
|
+
const bindings = (0, form_data_source_validation_1.readBindingValues)(field, source.descriptor, input.formData);
|
|
104
|
+
// Waiting on a dependency is a state, not a failure: the caller needs the
|
|
105
|
+
// field names to prompt for, and an error carries none of them.
|
|
92
106
|
if (bindings.missingParameters.length > 0) {
|
|
93
|
-
|
|
107
|
+
return {
|
|
108
|
+
dataSourceKey: source.descriptor.key,
|
|
109
|
+
dataSourceVersion: source.descriptor.version,
|
|
110
|
+
nextCursor: null,
|
|
111
|
+
options: [],
|
|
112
|
+
waitingForFieldKeys: (0, form_data_source_validation_1.readWaitingForFieldKeys)(field, bindings.missingParameters),
|
|
113
|
+
};
|
|
94
114
|
}
|
|
95
115
|
const searchText = input.searchText.trim();
|
|
96
116
|
this.assertSearchRequest(source.descriptor, searchText, input.cursor);
|
|
97
|
-
const result = await
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
const result = await (0, form_data_source_validation_1.callProvider)({
|
|
118
|
+
call: (signal) => source.search({
|
|
119
|
+
authContext: input.authContext,
|
|
120
|
+
bindings: bindings.values,
|
|
121
|
+
cursor: input.cursor,
|
|
122
|
+
searchText,
|
|
123
|
+
signal,
|
|
124
|
+
}),
|
|
125
|
+
logger: this.logger,
|
|
126
|
+
operation: 'search',
|
|
127
|
+
source,
|
|
128
|
+
});
|
|
104
129
|
const options = this.validateSearchResult(result, source.descriptor);
|
|
105
130
|
return {
|
|
106
131
|
dataSourceKey: source.descriptor.key,
|
|
107
132
|
dataSourceVersion: source.descriptor.version,
|
|
108
133
|
nextCursor: this.readNextCursor(result, source.descriptor),
|
|
109
134
|
options,
|
|
135
|
+
waitingForFieldKeys: [],
|
|
110
136
|
};
|
|
111
137
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Read-only value check for the renderer. A value the provider no longer
|
|
140
|
+
* offers is reported in `unresolvedValues` rather than thrown, because the
|
|
141
|
+
* field has to show which of several selected options went dead. Provider
|
|
142
|
+
* contract breaches and transport failures still throw.
|
|
143
|
+
*/
|
|
144
|
+
async reportFormFieldOptionValues(input) {
|
|
145
|
+
const field = (0, form_data_source_validation_1.readDynamicOptionField)(input.field);
|
|
146
|
+
const source = (0, form_data_source_validation_1.readSourceOrThrow)(this.registry, field.dataSource);
|
|
147
|
+
(0, form_data_source_validation_1.assertControlSupported)(field, source.descriptor);
|
|
148
|
+
// Nothing selected means nothing to check; asking the provider anyway would
|
|
149
|
+
// be a request for the empty set.
|
|
150
|
+
if (input.values.length === 0) {
|
|
151
|
+
return {
|
|
152
|
+
dataSourceKey: source.descriptor.key,
|
|
153
|
+
dataSourceVersion: source.descriptor.version,
|
|
154
|
+
options: [],
|
|
155
|
+
unresolvedValues: [],
|
|
156
|
+
waitingForFieldKeys: [],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const values = (0, form_data_source_validation_1.validateRequestedValues)(input.values, source.descriptor.maximumResultCount);
|
|
160
|
+
const bindings = (0, form_data_source_validation_1.readBindingValues)(field, source.descriptor, input.formData);
|
|
118
161
|
if (bindings.missingParameters.length > 0) {
|
|
119
|
-
|
|
162
|
+
return {
|
|
163
|
+
dataSourceKey: source.descriptor.key,
|
|
164
|
+
dataSourceVersion: source.descriptor.version,
|
|
165
|
+
options: [],
|
|
166
|
+
unresolvedValues: [],
|
|
167
|
+
waitingForFieldKeys: (0, form_data_source_validation_1.readWaitingForFieldKeys)(field, bindings.missingParameters),
|
|
168
|
+
};
|
|
120
169
|
}
|
|
121
|
-
const result = await this.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return
|
|
170
|
+
const result = await this.callResolve(source, input.authContext, bindings.values, values);
|
|
171
|
+
const options = (0, form_data_source_validation_1.readProviderResolvedOptions)(result, values, source.descriptor.maximumResultCount);
|
|
172
|
+
return {
|
|
173
|
+
dataSourceKey: source.descriptor.key,
|
|
174
|
+
dataSourceVersion: source.descriptor.version,
|
|
175
|
+
options: (0, form_data_source_validation_1.readOrderedResolvedOptions)(options, values),
|
|
176
|
+
unresolvedValues: (0, form_data_source_validation_1.readUnresolvedValues)(options, values),
|
|
177
|
+
waitingForFieldKeys: [],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
callResolve(source, authContext, bindings, values) {
|
|
181
|
+
return (0, form_data_source_validation_1.callProvider)({
|
|
182
|
+
call: (signal) => source.resolve({ authContext, bindings, signal, values }),
|
|
183
|
+
logger: this.logger,
|
|
184
|
+
operation: 'resolve',
|
|
185
|
+
source,
|
|
186
|
+
});
|
|
133
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Wraps the runtime context lookup so a failure from the storage layer never
|
|
190
|
+
* reaches the caller verbatim. An id that is not a UUID, for instance, comes
|
|
191
|
+
* back from Postgres as `invalid input syntax for type uuid: ...`, which
|
|
192
|
+
* hands out the database engine and column type; ADR §3.12 requires a stable
|
|
193
|
+
* code instead. Deliberate `HttpException`s — the not-found and forbidden
|
|
194
|
+
* answers this method is built to give — pass straight through.
|
|
195
|
+
*/
|
|
134
196
|
async readRuntimeSchema(input, authContext) {
|
|
197
|
+
try {
|
|
198
|
+
return await this.readRuntimeSchemaContext(input, authContext);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error instanceof common_1.HttpException) {
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
this.logger.warn(JSON.stringify({
|
|
205
|
+
errorCode: form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.RUNTIME_CONTEXT_FORBIDDEN,
|
|
206
|
+
operation: 'runtimeContext',
|
|
207
|
+
status: 'error',
|
|
208
|
+
}));
|
|
209
|
+
throw new form_data_source_errors_1.BPMFormDataSourceForbiddenException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.RUNTIME_CONTEXT_FORBIDDEN);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async readRuntimeSchemaContext(input, authContext) {
|
|
135
213
|
const templateId = readOptionalText(input.templateId);
|
|
136
214
|
const instanceId = readOptionalText(input.instanceId);
|
|
137
215
|
if (Boolean(templateId) === Boolean(instanceId)) {
|
|
@@ -160,8 +238,8 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
160
238
|
throw new form_data_source_errors_1.BPMFormDataSourceForbiddenException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.RUNTIME_CONTEXT_FORBIDDEN);
|
|
161
239
|
}
|
|
162
240
|
const snapshot = instance.formDefinitionSnapshot;
|
|
163
|
-
const schema = isRecord(snapshot.schema) ? snapshot.schema : null;
|
|
164
|
-
const uiSchema = isRecord(snapshot.uiSchema) ? snapshot.uiSchema : null;
|
|
241
|
+
const schema = (0, form_data_source_validation_1.isRecord)(snapshot.schema) ? snapshot.schema : null;
|
|
242
|
+
const uiSchema = (0, form_data_source_validation_1.isRecord)(snapshot.uiSchema) ? snapshot.uiSchema : null;
|
|
165
243
|
if (!schema || !uiSchema) {
|
|
166
244
|
throw new form_data_source_errors_1.BPMFormDataSourceForbiddenException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.RUNTIME_CONTEXT_FORBIDDEN);
|
|
167
245
|
}
|
|
@@ -215,7 +293,19 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
215
293
|
}
|
|
216
294
|
try {
|
|
217
295
|
const value = JSON.parse(formDataJson);
|
|
218
|
-
if (isRecord(value)) {
|
|
296
|
+
if ((0, form_data_source_validation_1.isRecord)(value)) {
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// The stable error below deliberately hides parser details.
|
|
302
|
+
}
|
|
303
|
+
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_BINDING);
|
|
304
|
+
}
|
|
305
|
+
parseValues(valuesJson) {
|
|
306
|
+
try {
|
|
307
|
+
const value = JSON.parse(valuesJson);
|
|
308
|
+
if ((0, form_data_source_validation_1.isStringArray)(value)) {
|
|
219
309
|
return value;
|
|
220
310
|
}
|
|
221
311
|
}
|
|
@@ -231,47 +321,6 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
231
321
|
}
|
|
232
322
|
return field;
|
|
233
323
|
}
|
|
234
|
-
readDynamicOptionField(field) {
|
|
235
|
-
if (!(0, form_1.isFormDataSourceFieldDefinition)(field)) {
|
|
236
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.FIELD_NOT_DYNAMIC);
|
|
237
|
-
}
|
|
238
|
-
return field;
|
|
239
|
-
}
|
|
240
|
-
readSourceOrThrow(reference) {
|
|
241
|
-
const source = this.registry.get(reference.key, reference.version);
|
|
242
|
-
if (!source) {
|
|
243
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(this.readMissingSourceCode(reference.key));
|
|
244
|
-
}
|
|
245
|
-
const descriptorErrors = this.readDescriptorErrors(source.descriptor);
|
|
246
|
-
if (descriptorErrors.length > 0) {
|
|
247
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_DESCRIPTOR);
|
|
248
|
-
}
|
|
249
|
-
return source;
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* A key the host never registered and a key whose requested version was
|
|
253
|
-
* dropped are different operational problems, so they keep distinct codes:
|
|
254
|
-
* the first tells the designer the source is gone, the second that only this
|
|
255
|
-
* version is.
|
|
256
|
-
*/
|
|
257
|
-
readMissingSourceCode(key) {
|
|
258
|
-
const hasKey = this.registry
|
|
259
|
-
.list()
|
|
260
|
-
.some((candidate) => candidate.descriptor?.key === key);
|
|
261
|
-
return hasKey
|
|
262
|
-
? form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.DATA_SOURCE_VERSION_MISSING
|
|
263
|
-
: form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.DATA_SOURCE_MISSING;
|
|
264
|
-
}
|
|
265
|
-
assertControlSupported(field, descriptor) {
|
|
266
|
-
const control = field.type;
|
|
267
|
-
if (!descriptor.supportedControls.includes(control)) {
|
|
268
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.UNSUPPORTED_CONTROL);
|
|
269
|
-
}
|
|
270
|
-
if ((control === 'radio' || control === 'checkbox') &&
|
|
271
|
-
(!descriptor.returnsCompleteList || descriptor.maximumResultCount > 50)) {
|
|
272
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.UNSUPPORTED_CONTROL);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
324
|
assertSearchRequest(descriptor, searchText, cursor) {
|
|
276
325
|
if (!descriptor.supportsSearch && searchText) {
|
|
277
326
|
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.SEARCH_NOT_SUPPORTED);
|
|
@@ -285,93 +334,15 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
285
334
|
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_BINDING);
|
|
286
335
|
}
|
|
287
336
|
}
|
|
288
|
-
readBindingValues(field, descriptor, formData) {
|
|
289
|
-
const parameterByKey = new Map(descriptor.parameters.map((parameter) => [
|
|
290
|
-
parameter.key,
|
|
291
|
-
parameter,
|
|
292
|
-
]));
|
|
293
|
-
const bindingValues = field.dataSource.bindings.reduce((values, binding) => {
|
|
294
|
-
const parameter = parameterByKey.get(binding.parameter);
|
|
295
|
-
if (!parameter) {
|
|
296
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_BINDING);
|
|
297
|
-
}
|
|
298
|
-
const nextValue = binding.from.kind === 'FIELD'
|
|
299
|
-
? readFormDataValue(formData[binding.from.fieldKey])
|
|
300
|
-
: binding.from.value;
|
|
301
|
-
if (typeof nextValue === 'undefined') {
|
|
302
|
-
return values;
|
|
303
|
-
}
|
|
304
|
-
this.assertParameterValue(parameter, nextValue);
|
|
305
|
-
return { ...values, [parameter.key]: nextValue };
|
|
306
|
-
}, {});
|
|
307
|
-
const missingParameters = descriptor.parameters
|
|
308
|
-
.filter((parameter) => parameter.required)
|
|
309
|
-
.filter((parameter) => !isPresentFormDataValue(bindingValues[parameter.key]))
|
|
310
|
-
.map((parameter) => parameter.key);
|
|
311
|
-
if (JSON.stringify(bindingValues).length > MAX_PARAMETER_BYTES) {
|
|
312
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.PARAMETER_LIMIT_EXCEEDED);
|
|
313
|
-
}
|
|
314
|
-
return { missingParameters, values: bindingValues };
|
|
315
|
-
}
|
|
316
|
-
assertParameterValue(parameter, value) {
|
|
317
|
-
const valid = (parameter.type === 'BOOLEAN' && typeof value === 'boolean') ||
|
|
318
|
-
(parameter.type === 'NUMBER' && typeof value === 'number') ||
|
|
319
|
-
(parameter.type === 'STRING' && typeof value === 'string') ||
|
|
320
|
-
(parameter.type === 'STRING_ARRAY' && isStringArray(value)) ||
|
|
321
|
-
value === null;
|
|
322
|
-
if (!valid) {
|
|
323
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_BINDING);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
validateRequestedValues(values, maximumResultCount) {
|
|
327
|
-
if (values.length === 0 ||
|
|
328
|
-
values.some((value) => !value.trim()) ||
|
|
329
|
-
new Set(values).size !== values.length) {
|
|
330
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_BINDING);
|
|
331
|
-
}
|
|
332
|
-
if (values.length > maximumResultCount) {
|
|
333
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.RESULT_LIMIT_EXCEEDED);
|
|
334
|
-
}
|
|
335
|
-
return [...values];
|
|
336
|
-
}
|
|
337
337
|
validateSearchResult(result, descriptor) {
|
|
338
|
-
if (!isRecord(result) || !Array.isArray(result.options)) {
|
|
338
|
+
if (!(0, form_data_source_validation_1.isRecord)(result) || !Array.isArray(result.options)) {
|
|
339
339
|
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_PROVIDER_RESULT);
|
|
340
340
|
}
|
|
341
341
|
if (result.options.length > descriptor.pageSize ||
|
|
342
342
|
result.options.length > descriptor.maximumResultCount) {
|
|
343
343
|
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.RESULT_LIMIT_EXCEEDED);
|
|
344
344
|
}
|
|
345
|
-
return
|
|
346
|
-
}
|
|
347
|
-
validateResolveResult(result, values, maximumResultCount) {
|
|
348
|
-
if (!Array.isArray(result) || result.length > maximumResultCount) {
|
|
349
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_PROVIDER_RESULT);
|
|
350
|
-
}
|
|
351
|
-
const options = this.validateOptions(result);
|
|
352
|
-
const requestedValues = new Set(values);
|
|
353
|
-
// A value the caller never asked for is a provider contract breach.
|
|
354
|
-
if (options.some((option) => !requestedValues.has(option.value))) {
|
|
355
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_PROVIDER_RESULT);
|
|
356
|
-
}
|
|
357
|
-
// Every requested value resolved to exactly one option, or the submitted
|
|
358
|
-
// value is simply no longer selectable — that is a user-facing failure.
|
|
359
|
-
if (options.length !== values.length) {
|
|
360
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.VALUE_NOT_RESOLVED);
|
|
361
|
-
}
|
|
362
|
-
return options;
|
|
363
|
-
}
|
|
364
|
-
validateOptions(value) {
|
|
365
|
-
if (!Array.isArray(value)) {
|
|
366
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_PROVIDER_RESULT);
|
|
367
|
-
}
|
|
368
|
-
const options = value.filter(isFormFieldOption);
|
|
369
|
-
if (options.length !== value.length ||
|
|
370
|
-
options.some((option) => !option.label.trim() || !option.value.trim()) ||
|
|
371
|
-
new Set(options.map((option) => option.value)).size !== options.length) {
|
|
372
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_PROVIDER_RESULT);
|
|
373
|
-
}
|
|
374
|
-
return options;
|
|
345
|
+
return (0, form_data_source_validation_1.validateOptions)(result.options);
|
|
375
346
|
}
|
|
376
347
|
readNextCursor(result, descriptor) {
|
|
377
348
|
const nextCursor = result.nextCursor ?? null;
|
|
@@ -381,143 +352,6 @@ let FormDataSourceService = FormDataSourceService_1 = class FormDataSourceServic
|
|
|
381
352
|
}
|
|
382
353
|
return nextCursor;
|
|
383
354
|
}
|
|
384
|
-
async callProvider(source, operation, call) {
|
|
385
|
-
const controller = new AbortController();
|
|
386
|
-
let timeoutId;
|
|
387
|
-
const startedAt = Date.now();
|
|
388
|
-
try {
|
|
389
|
-
const result = await Promise.race([
|
|
390
|
-
call(controller.signal),
|
|
391
|
-
new Promise((_resolve, reject) => {
|
|
392
|
-
timeoutId = setTimeout(() => {
|
|
393
|
-
controller.abort();
|
|
394
|
-
const timeout = new Error('provider timeout');
|
|
395
|
-
Object.assign(timeout, { kind: 'FORM_DATA_SOURCE_TIMEOUT' });
|
|
396
|
-
reject(timeout);
|
|
397
|
-
}, PROVIDER_TIMEOUT_MS);
|
|
398
|
-
}),
|
|
399
|
-
]);
|
|
400
|
-
this.logger.log(JSON.stringify({
|
|
401
|
-
dataSourceKey: source.descriptor.key,
|
|
402
|
-
dataSourceVersion: source.descriptor.version,
|
|
403
|
-
durationMs: Date.now() - startedAt,
|
|
404
|
-
operation,
|
|
405
|
-
status: 'success',
|
|
406
|
-
}));
|
|
407
|
-
return result;
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
const code = isTimeoutMarker(error)
|
|
411
|
-
? form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.TIMEOUT
|
|
412
|
-
: form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.PROVIDER_FAILURE;
|
|
413
|
-
this.logger.warn(JSON.stringify({
|
|
414
|
-
dataSourceKey: source.descriptor.key,
|
|
415
|
-
dataSourceVersion: source.descriptor.version,
|
|
416
|
-
durationMs: Date.now() - startedAt,
|
|
417
|
-
errorCode: code,
|
|
418
|
-
operation,
|
|
419
|
-
status: 'error',
|
|
420
|
-
}));
|
|
421
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(code);
|
|
422
|
-
}
|
|
423
|
-
finally {
|
|
424
|
-
if (timeoutId) {
|
|
425
|
-
clearTimeout(timeoutId);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
readDescriptorErrors(descriptor) {
|
|
430
|
-
const descriptorValue = isRecord(descriptor)
|
|
431
|
-
? descriptor
|
|
432
|
-
: {};
|
|
433
|
-
const parameters = Array.isArray(descriptorValue.parameters)
|
|
434
|
-
? descriptorValue.parameters
|
|
435
|
-
: [];
|
|
436
|
-
const supportedControls = Array.isArray(descriptorValue.supportedControls)
|
|
437
|
-
? descriptorValue.supportedControls
|
|
438
|
-
: [];
|
|
439
|
-
const version = descriptorValue.version;
|
|
440
|
-
const maximumResultCount = descriptorValue.maximumResultCount;
|
|
441
|
-
const pageSize = descriptorValue.pageSize;
|
|
442
|
-
const minimumSearchLength = descriptorValue.minimumSearchLength;
|
|
443
|
-
const errors = [
|
|
444
|
-
...(typeof descriptorValue.key === 'string' && descriptorValue.key.trim()
|
|
445
|
-
? []
|
|
446
|
-
: ['descriptor.key is required']),
|
|
447
|
-
...(typeof descriptorValue.label === 'string' && descriptorValue.label.trim()
|
|
448
|
-
? []
|
|
449
|
-
: ['descriptor.label is required']),
|
|
450
|
-
...(isPositiveInteger(version)
|
|
451
|
-
? []
|
|
452
|
-
: ['descriptor.version must be a positive integer']),
|
|
453
|
-
...(isPositiveInteger(maximumResultCount)
|
|
454
|
-
? []
|
|
455
|
-
: ['descriptor.maximumResultCount must be positive']),
|
|
456
|
-
...(isPositiveInteger(pageSize)
|
|
457
|
-
? []
|
|
458
|
-
: ['descriptor.pageSize must be positive']),
|
|
459
|
-
...(isNonNegativeInteger(minimumSearchLength)
|
|
460
|
-
? []
|
|
461
|
-
: ['descriptor.minimumSearchLength must be non-negative']),
|
|
462
|
-
...(typeof pageSize === 'number' &&
|
|
463
|
-
typeof maximumResultCount === 'number' &&
|
|
464
|
-
pageSize <= maximumResultCount
|
|
465
|
-
? []
|
|
466
|
-
: ['descriptor.pageSize cannot exceed descriptor.maximumResultCount']),
|
|
467
|
-
...(descriptorValue.paginationMode === 'CURSOR' ||
|
|
468
|
-
descriptorValue.paginationMode === 'NONE'
|
|
469
|
-
? []
|
|
470
|
-
: ['descriptor.paginationMode is invalid']),
|
|
471
|
-
...(descriptorValue.revalidationPolicy === 'ALWAYS' ||
|
|
472
|
-
descriptorValue.revalidationPolicy === 'WHEN_VALUE_OR_BINDINGS_CHANGE'
|
|
473
|
-
? []
|
|
474
|
-
: ['descriptor.revalidationPolicy is invalid']),
|
|
475
|
-
...(Array.isArray(descriptorValue.parameters)
|
|
476
|
-
? []
|
|
477
|
-
: ['descriptor.parameters must be an array']),
|
|
478
|
-
...(typeof descriptorValue.returnsCompleteList === 'boolean'
|
|
479
|
-
? []
|
|
480
|
-
: ['descriptor.returnsCompleteList must be boolean']),
|
|
481
|
-
...(typeof descriptorValue.supportsSearch === 'boolean'
|
|
482
|
-
? []
|
|
483
|
-
: ['descriptor.supportsSearch must be boolean']),
|
|
484
|
-
...(Array.isArray(descriptorValue.supportedControls) &&
|
|
485
|
-
supportedControls.every((control) => SOURCE_CONTROLS.includes(control))
|
|
486
|
-
? []
|
|
487
|
-
: ['descriptor.supportedControls contains an invalid control']),
|
|
488
|
-
...(new Set(supportedControls).size === supportedControls.length
|
|
489
|
-
? []
|
|
490
|
-
: ['descriptor.supportedControls contains duplicates']),
|
|
491
|
-
];
|
|
492
|
-
const parameterErrors = Array.isArray(descriptorValue.parameters)
|
|
493
|
-
? parameters.flatMap((parameter, index) => this.readParameterErrors(parameter, index))
|
|
494
|
-
: [];
|
|
495
|
-
const parameterKeys = parameters.flatMap((parameter) => isRecord(parameter) && typeof parameter.key === 'string'
|
|
496
|
-
? [parameter.key]
|
|
497
|
-
: []);
|
|
498
|
-
const duplicateParameterKeys = Array.isArray(descriptorValue.parameters)
|
|
499
|
-
? [
|
|
500
|
-
...new Set(parameterKeys
|
|
501
|
-
.filter((key, index, keys) => keys.indexOf(key) !== index)),
|
|
502
|
-
]
|
|
503
|
-
: [];
|
|
504
|
-
const duplicateParameterErrors = duplicateParameterKeys.map((key) => `descriptor.parameters.key is duplicated: ${key}`);
|
|
505
|
-
return [...errors, ...parameterErrors, ...duplicateParameterErrors];
|
|
506
|
-
}
|
|
507
|
-
readParameterErrors(parameter, index) {
|
|
508
|
-
const parameterValue = isRecord(parameter) ? parameter : {};
|
|
509
|
-
return [
|
|
510
|
-
...(typeof parameterValue.key === 'string' && parameterValue.key.trim()
|
|
511
|
-
? []
|
|
512
|
-
: [`descriptor.parameters[${index}].key is required`]),
|
|
513
|
-
...(typeof parameterValue.required === 'boolean'
|
|
514
|
-
? []
|
|
515
|
-
: [`descriptor.parameters[${index}].required must be boolean`]),
|
|
516
|
-
...(PARAMETER_TYPES.includes(parameterValue.type)
|
|
517
|
-
? []
|
|
518
|
-
: [`descriptor.parameters[${index}].type is invalid`]),
|
|
519
|
-
];
|
|
520
|
-
}
|
|
521
355
|
readControlErrors(field, descriptor, path) {
|
|
522
356
|
const control = field.type;
|
|
523
357
|
const declaresControl = descriptor.supportedControls.includes(control);
|
|
@@ -558,54 +392,10 @@ function isFormOptionField(field) {
|
|
|
558
392
|
field.type === 'radio' ||
|
|
559
393
|
field.type === 'checkbox');
|
|
560
394
|
}
|
|
561
|
-
function isFormFieldOption(value) {
|
|
562
|
-
return (isRecord(value) &&
|
|
563
|
-
typeof value.label === 'string' &&
|
|
564
|
-
typeof value.value === 'string');
|
|
565
|
-
}
|
|
566
|
-
function isRecord(value) {
|
|
567
|
-
return typeof value === 'object' && value !== null;
|
|
568
|
-
}
|
|
569
|
-
function isStringArray(value) {
|
|
570
|
-
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
571
|
-
}
|
|
572
|
-
function readFormDataValue(value) {
|
|
573
|
-
if (value === null ||
|
|
574
|
-
typeof value === 'boolean' ||
|
|
575
|
-
typeof value === 'number' ||
|
|
576
|
-
typeof value === 'string' ||
|
|
577
|
-
isStringArrayValue(value)) {
|
|
578
|
-
return value;
|
|
579
|
-
}
|
|
580
|
-
if (typeof value === 'undefined') {
|
|
581
|
-
return undefined;
|
|
582
|
-
}
|
|
583
|
-
throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.INVALID_BINDING);
|
|
584
|
-
}
|
|
585
|
-
function isStringArrayValue(value) {
|
|
586
|
-
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
587
|
-
}
|
|
588
|
-
function isPresentFormDataValue(value) {
|
|
589
|
-
if (typeof value === 'undefined' || value === null) {
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
if (typeof value === 'string') {
|
|
593
|
-
return value.trim().length > 0;
|
|
594
|
-
}
|
|
595
|
-
if (Array.isArray(value)) {
|
|
596
|
-
return value.length > 0;
|
|
597
|
-
}
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
395
|
function readOptionalText(value) {
|
|
601
396
|
const text = value?.trim() ?? '';
|
|
602
397
|
return text || null;
|
|
603
398
|
}
|
|
604
|
-
function isTimeoutMarker(value) {
|
|
605
|
-
return (value instanceof Error &&
|
|
606
|
-
value.kind ===
|
|
607
|
-
'FORM_DATA_SOURCE_TIMEOUT');
|
|
608
|
-
}
|
|
609
399
|
/**
|
|
610
400
|
* Matches only the code-bearing lint shape `<path>.dataSource <CODE>`. Reading
|
|
611
401
|
* the trailing token alone would misreport prose such as
|
|
@@ -637,10 +427,4 @@ function readLintLineErrorCode(error) {
|
|
|
637
427
|
function isFormDataSourceErrorCode(value) {
|
|
638
428
|
return Object.values(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES).includes(value);
|
|
639
429
|
}
|
|
640
|
-
function isPositiveInteger(value) {
|
|
641
|
-
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
642
|
-
}
|
|
643
|
-
function isNonNegativeInteger(value) {
|
|
644
|
-
return typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
|
645
|
-
}
|
|
646
430
|
//# sourceMappingURL=form-data-source.service.js.map
|