@rytass/bpm-core-nestjs-module 0.9.1 → 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.
Files changed (68) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +17 -5
  3. package/package.json +2 -2
  4. package/src/lib/attachment/attachment-options.js +7 -1
  5. package/src/lib/attachment/attachment-options.js.map +1 -1
  6. package/src/lib/attachment/attachment.service.js +7 -0
  7. package/src/lib/attachment/attachment.service.js.map +1 -1
  8. package/src/lib/bpm/bpm-root.module.d.ts +11 -0
  9. package/src/lib/bpm/bpm-root.module.js.map +1 -1
  10. package/src/lib/calendar/calendar.module.d.ts +12 -0
  11. package/src/lib/calendar/calendar.module.js.map +1 -1
  12. package/src/lib/common/filters/all-exceptions.filter.d.ts +11 -0
  13. package/src/lib/common/filters/all-exceptions.filter.js +35 -1
  14. package/src/lib/common/filters/all-exceptions.filter.js.map +1 -1
  15. package/src/lib/form-data-source/form-data-source-value-resolver.service.d.ts +6 -7
  16. package/src/lib/form-data-source/form-data-source-value-resolver.service.js +91 -291
  17. package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
  18. package/src/lib/form-data-source/form-data-source.queries.d.ts +24 -0
  19. package/src/lib/form-data-source/form-data-source.queries.js +179 -7
  20. package/src/lib/form-data-source/form-data-source.queries.js.map +1 -1
  21. package/src/lib/form-data-source/form-data-source.service.d.ts +36 -21
  22. package/src/lib/form-data-source/form-data-source.service.js +143 -359
  23. package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
  24. package/src/lib/form-data-source/form-data-source.types.d.ts +17 -0
  25. package/src/lib/form-data-source/form-data-source.types.js.map +1 -1
  26. package/src/lib/form-data-source/form-data-source.validation.d.ts +78 -0
  27. package/src/lib/form-data-source/form-data-source.validation.js +409 -0
  28. package/src/lib/form-data-source/form-data-source.validation.js.map +1 -0
  29. package/src/lib/migrations/0000000021000-notification-silenced.d.ts +15 -0
  30. package/src/lib/migrations/0000000021000-notification-silenced.js +31 -0
  31. package/src/lib/migrations/0000000021000-notification-silenced.js.map +1 -0
  32. package/src/lib/migrations/index.d.ts +1 -0
  33. package/src/lib/migrations/index.js +3 -0
  34. package/src/lib/migrations/index.js.map +1 -1
  35. package/src/lib/notification/index.d.ts +1 -0
  36. package/src/lib/notification/index.js +1 -0
  37. package/src/lib/notification/index.js.map +1 -1
  38. package/src/lib/notification/notification-delivery.service.d.ts +26 -1
  39. package/src/lib/notification/notification-delivery.service.js +136 -13
  40. package/src/lib/notification/notification-delivery.service.js.map +1 -1
  41. package/src/lib/notification/notification-dispatcher.token.d.ts +13 -0
  42. package/src/lib/notification/notification-dispatcher.token.js.map +1 -1
  43. package/src/lib/notification/notification-observer.token.d.ts +6 -0
  44. package/src/lib/notification/notification-observer.token.js.map +1 -1
  45. package/src/lib/notification/notification-options.d.ts +30 -0
  46. package/src/lib/notification/notification-options.js +32 -4
  47. package/src/lib/notification/notification-options.js.map +1 -1
  48. package/src/lib/notification/notification-schedule.d.ts +41 -0
  49. package/src/lib/notification/notification-schedule.js +158 -0
  50. package/src/lib/notification/notification-schedule.js.map +1 -0
  51. package/src/lib/notification/notification.entity.d.ts +12 -0
  52. package/src/lib/notification/notification.entity.js +5 -0
  53. package/src/lib/notification/notification.entity.js.map +1 -1
  54. package/src/lib/notification/notification.service.d.ts +10 -0
  55. package/src/lib/notification/notification.service.js +100 -21
  56. package/src/lib/notification/notification.service.js.map +1 -1
  57. package/src/lib/template/index.d.ts +1 -0
  58. package/src/lib/template/index.js +1 -0
  59. package/src/lib/template/index.js.map +1 -1
  60. package/src/lib/template/template-observer.token.d.ts +67 -0
  61. package/src/lib/template/template-observer.token.js +16 -0
  62. package/src/lib/template/template-observer.token.js.map +1 -0
  63. package/src/lib/template/template.mutations.d.ts +1 -1
  64. package/src/lib/template/template.mutations.js +4 -3
  65. package/src/lib/template/template.mutations.js.map +1 -1
  66. package/src/lib/template/template.service.d.ts +18 -2
  67. package/src/lib/template/template.service.js +117 -17
  68. 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 MAX_PARAMETER_BYTES = 16_384;
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.list().flatMap((source) => this.readDescriptorErrors(source.descriptor).length === 0
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 [`${path} ${this.readMissingSourceCode(field.dataSource.key)}`];
43
+ return [
44
+ `${path} ${(0, form_data_source_validation_1.readMissingSourceCode)(this.registry, field.dataSource.key)}`,
45
+ ];
55
46
  }
56
- const descriptorErrors = this.readDescriptorErrors(source.descriptor);
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 = this.readDynamicOptionField(input.field);
89
- const source = this.readSourceOrThrow(field.dataSource);
90
- this.assertControlSupported(field, source.descriptor);
91
- const bindings = this.readBindingValues(field, source.descriptor, input.formData);
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
- throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.WAITING_FOR_DEPENDENCIES);
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 this.callProvider(source, 'search', (signal) => source.search({
98
- authContext: input.authContext,
99
- bindings: bindings.values,
100
- cursor: input.cursor,
101
- searchText,
102
- signal,
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
- async resolveFormFieldOptions(input) {
113
- const field = this.readDynamicOptionField(input.field);
114
- const source = this.readSourceOrThrow(field.dataSource);
115
- this.assertControlSupported(field, source.descriptor);
116
- const values = this.validateRequestedValues(input.values, source.descriptor.maximumResultCount);
117
- const bindings = this.readBindingValues(field, source.descriptor, input.formData);
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
- throw new form_data_source_errors_1.BPMFormDataSourceException(form_data_source_errors_1.BPM_FORM_DATA_SOURCE_ERROR_CODES.WAITING_FOR_DEPENDENCIES);
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.callProvider(source, 'resolve', (signal) => source.resolve({
122
- authContext: input.authContext,
123
- bindings: bindings.values,
124
- signal,
125
- values,
126
- }));
127
- const options = this.validateResolveResult(result, values, source.descriptor.maximumResultCount);
128
- const optionByValue = new Map(options.map((option) => [
129
- option.value,
130
- option,
131
- ]));
132
- return values.map((value) => optionByValue.get(value));
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 this.validateOptions(result.options);
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