@rytass/bpm-core-nestjs-module 0.10.0 โ†’ 0.12.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 (33) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/package.json +3 -3
  3. package/src/lib/condition/condition.service.js +4 -1
  4. package/src/lib/condition/condition.service.js.map +1 -1
  5. package/src/lib/form/form-schema.validator.js +277 -56
  6. package/src/lib/form/form-schema.validator.js.map +1 -1
  7. package/src/lib/form/form-table-reference.d.ts +28 -0
  8. package/src/lib/form/form-table-reference.js +53 -0
  9. package/src/lib/form/form-table-reference.js.map +1 -0
  10. package/src/lib/form-data-source/form-data-source-value-resolver.service.js +81 -13
  11. package/src/lib/form-data-source/form-data-source-value-resolver.service.js.map +1 -1
  12. package/src/lib/form-data-source/form-data-source.queries.d.ts +4 -0
  13. package/src/lib/form-data-source/form-data-source.queries.js +31 -0
  14. package/src/lib/form-data-source/form-data-source.queries.js.map +1 -1
  15. package/src/lib/form-data-source/form-data-source.service.d.ts +21 -0
  16. package/src/lib/form-data-source/form-data-source.service.js +70 -17
  17. package/src/lib/form-data-source/form-data-source.service.js.map +1 -1
  18. package/src/lib/form-data-source/form-data-source.types.d.ts +6 -0
  19. package/src/lib/form-data-source/form-data-source.types.js.map +1 -1
  20. package/src/lib/form-data-source/form-data-source.validation.d.ts +12 -1
  21. package/src/lib/form-data-source/form-data-source.validation.js +34 -5
  22. package/src/lib/form-data-source/form-data-source.validation.js.map +1 -1
  23. package/src/lib/notification/notification-delivery.service.js +70 -8
  24. package/src/lib/notification/notification-delivery.service.js.map +1 -1
  25. package/src/lib/notification/notification-options.d.ts +13 -0
  26. package/src/lib/notification/notification-options.js +13 -0
  27. package/src/lib/notification/notification-options.js.map +1 -1
  28. package/src/lib/template/template.service.js +34 -1
  29. package/src/lib/template/template.service.js.map +1 -1
  30. package/src/lib/workflow-engine/workflow-condition-evaluator.js +18 -0
  31. package/src/lib/workflow-engine/workflow-condition-evaluator.js.map +1 -1
  32. package/src/lib/workflow-engine/workflow-engine.service.js +119 -6
  33. package/src/lib/workflow-engine/workflow-engine.service.js.map +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,49 @@
1
+ ## 0.12.0 (2026-08-27)
2
+
3
+ ### ๐Ÿš€ Features
4
+
5
+ - **form:** wire cell-level DataSources through the client and renderer ([c7561ad](https://github.com/Rytass/BPMCore/commit/c7561ad))
6
+ - **form:** resolve table column DataSources per cell ([ca40a4c](https://github.com/Rytass/BPMCore/commit/ca40a4c))
7
+ - **form:** add table column factories and row-scoped binding rename ([0c8c07d](https://github.com/Rytass/BPMCore/commit/0c8c07d))
8
+ - **form:** validate and seed table values in the renderer helpers ([5c7edab](https://github.com/Rytass/BPMCore/commit/5c7edab))
9
+ - **template:** reject published conditions that address table internals ([04d74e3](https://github.com/Rytass/BPMCore/commit/04d74e3))
10
+ - **form:** validate submitted table form data ([69036d1](https://github.com/Rytass/BPMCore/commit/69036d1))
11
+ - **form:** lint table field structure before publishing ([d9e95ee](https://github.com/Rytass/BPMCore/commit/d9e95ee))
12
+ - **condition:** compile table emptiness from the row count ([624c96d](https://github.com/Rytass/BPMCore/commit/624c96d))
13
+ - **form:** add table field definitions to the shared contract ([5f97165](https://github.com/Rytass/BPMCore/commit/5f97165))
14
+
15
+ ### ๐Ÿฉน Fixes
16
+
17
+ - **form:** keep a table's row indexes stable past a malformed row ([21d5a57](https://github.com/Rytass/BPMCore/commit/21d5a57))
18
+ - **form:** read required DataSource parameters as own properties ([a5895a7](https://github.com/Rytass/BPMCore/commit/a5895a7))
19
+ - **form:** report a table column's binding lint as an actionable binding error ([ed4fed2](https://github.com/Rytass/BPMCore/commit/ed4fed2))
20
+ - **condition:** never match an edge value comparison against row records ([58b4194](https://github.com/Rytass/BPMCore/commit/58b4194))
21
+ - **workflow:** no-op indexed set-form-field paths and read cells as own properties ([952b7bf](https://github.com/Rytass/BPMCore/commit/952b7bf))
22
+ - **template:** close the whitespace gap in the table-internals condition lint ([6d15d07](https://github.com/Rytass/BPMCore/commit/6d15d07))
23
+ - **condition:** keep table values from deciding form conditions ([894ef00](https://github.com/Rytass/BPMCore/commit/894ef00))
24
+ - **workflow:** stop nested form writes from replacing a non-record value ([336951c](https://github.com/Rytass/BPMCore/commit/336951c))
25
+
26
+ ### โค๏ธ Thank You
27
+
28
+ - Chia Yu Pai @fantasywind
29
+ - Claude Opus 5 (1M context)
30
+
31
+ ## 0.11.0 (2026-08-18)
32
+
33
+ ### ๐Ÿš€ Features
34
+
35
+ - **notification:** bound delivery dispatch with a timeout ([d8115f0](https://github.com/Rytass/BPMCore/commit/d8115f0))
36
+
37
+ ### ๐Ÿฉน Fixes
38
+
39
+ - **notification:** read claimed ids from the UPDATE result pair ([2fe6e44](https://github.com/Rytass/BPMCore/commit/2fe6e44))
40
+ - **notification:** move nodemailer to ^9.0.1 ([e094738](https://github.com/Rytass/BPMCore/commit/e094738))
41
+
42
+ ### โค๏ธ Thank You
43
+
44
+ - Chia Yu Pai @fantasywind
45
+ - Claude Opus 5
46
+
1
47
  ## 0.10.0 (2026-08-18)
2
48
 
3
49
  ### ๐Ÿš€ Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/bpm-core-nestjs-module",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Embeddable NestJS BPM approval workflow module: workflow engine, approval templates, forms, organization/member contracts, attachments, signatures, notifications, delegation, and TypeORM migrations.",
5
5
  "keywords": [
6
6
  "bpm",
@@ -155,7 +155,7 @@
155
155
  "@nestjs/core": ">=10.0.0",
156
156
  "@nestjs/graphql": ">=12.0.0",
157
157
  "@nestjs/typeorm": ">=10.0.0",
158
- "@rytass/bpm-core-shared": "^0.10.0",
158
+ "@rytass/bpm-core-shared": "^0.12.0",
159
159
  "@rytass/secret-adapter-vault-nestjs": ">=0.4.5",
160
160
  "express": ">=4.0.0",
161
161
  "graphql": ">=16.0.0",
@@ -177,7 +177,7 @@
177
177
  "class-transformer": "^0.5.1",
178
178
  "class-validator": "^0.15.1",
179
179
  "handlebars": "^4.7.9",
180
- "nodemailer": "^8.0.7",
180
+ "nodemailer": "^9.0.1",
181
181
  "tslib": "^2.3.0"
182
182
  }
183
183
  }
@@ -5,7 +5,10 @@ const tslib_1 = require("tslib");
5
5
  const common_1 = require("@nestjs/common");
6
6
  const cel_js_1 = require("cel-js");
7
7
  const DEFAULT_MAX_EXPRESSION_LENGTH = 2000;
8
- const CEL_KEYWORDS = new Set(['false', 'has', 'in', 'null', 'true']);
8
+ // `has` and `size` are CEL macros, not context identifiers; the root-identifier
9
+ // lint must not report them as unknown. `size` is required by the table field
10
+ // IS_FILLED / IS_EMPTY expressions (ADR 16 ยง3.8).
11
+ const CEL_KEYWORDS = new Set(['false', 'has', 'in', 'null', 'size', 'true']);
9
12
  let ConditionService = class ConditionService {
10
13
  lintExpressions(expressions, options = {}) {
11
14
  return expressions.flatMap(({ expression, label }) => this.lintExpression(expression, label, options));
@@ -1 +1 @@
1
- {"version":3,"file":"condition.service.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/condition/condition.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"condition.service.js","sourceRoot":"","sources":["../../../../../../libs/bpm-core/src/lib/condition/condition.service.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ exports.EMPTY_FORM_UI_SCHEMA = exports.EMPTY_FORM_SCHEMA = void 0;
4
4
  exports.parseAndValidateFormSchemas = parseAndValidateFormSchemas;
5
5
  exports.lintFormSchemaJson = lintFormSchemaJson;
6
6
  const form_1 = require("@rytass/bpm-core-shared/form");
7
+ const form_table_reference_1 = require("./form-table-reference");
7
8
  const SUPPORTED_FIELD_TYPES = [
8
9
  'boolean',
9
10
  'checkbox',
@@ -15,6 +16,7 @@ const SUPPORTED_FIELD_TYPES = [
15
16
  'autocomplete',
16
17
  'radio',
17
18
  'select',
19
+ 'table',
18
20
  'text',
19
21
  'textarea',
20
22
  ];
@@ -37,6 +39,11 @@ const DATE_FIELD_TYPES = new Set([
37
39
  'datetime',
38
40
  ]);
39
41
  const UI_WIDTHS = new Set(['FULL', 'HALF', 'THIRD']);
42
+ const CONDITION_FIELD_KEYS = [
43
+ 'visibleWhen',
44
+ 'requiredWhen',
45
+ 'readonlyWhen',
46
+ ];
40
47
  exports.EMPTY_FORM_SCHEMA = {
41
48
  fields: [],
42
49
  schemaVersion: 1,
@@ -105,7 +112,7 @@ function parseJsonValue(value, label) {
105
112
  }
106
113
  function lintFormSchemaValues(schema, uiSchema) {
107
114
  const schemaErrors = lintDefinitionSchema(schema);
108
- const uiSchemaErrors = lintUiSchema(uiSchema, readFieldKeySet(schema));
115
+ const uiSchemaErrors = lintUiSchema(uiSchema, readFieldKeySet(schema), readTableFieldKeySet(schema));
109
116
  const errors = [...schemaErrors, ...uiSchemaErrors];
110
117
  return {
111
118
  errors,
@@ -121,8 +128,13 @@ function lintDefinitionSchema(schema) {
121
128
  if (!Array.isArray(fields)) {
122
129
  return [...versionErrors, 'schema.fields must be an array'];
123
130
  }
124
- const fieldKeys = readFieldKeySet(schema);
125
- const fieldErrors = fields.flatMap((field, index) => lintFieldDefinition(field, index, fieldKeys));
131
+ const tableFieldKeys = readTableFieldKeySet(schema);
132
+ const scope = {
133
+ columnKeys: null,
134
+ scalarFieldKeys: readScalarFieldKeySet(schema),
135
+ tableFieldKeys,
136
+ };
137
+ const fieldErrors = fields.flatMap((field, index) => lintFieldDefinition(field, index, scope));
126
138
  const duplicateErrors = readDuplicateFieldKeyErrors(fields);
127
139
  const dependencyErrors = lintFieldDependencyGraph(fields);
128
140
  return [
@@ -132,46 +144,80 @@ function lintDefinitionSchema(schema) {
132
144
  ...dependencyErrors,
133
145
  ];
134
146
  }
135
- function lintFieldDefinition(field, index, fieldKeys) {
147
+ function lintFieldDefinition(field, index, scope) {
136
148
  if (!isRecord(field)) {
137
149
  return [`schema.fields[${index}] must be an object`];
138
150
  }
151
+ const path = `schema.fields[${index}]`;
139
152
  const type = field.type;
140
153
  const basicErrors = [
141
- ...lintRequiredString(field.fieldKey, `schema.fields[${index}].fieldKey`),
142
- ...lintRequiredString(field.label, `schema.fields[${index}].label`),
143
- ...lintOptionalString(field.description, `schema.fields[${index}].description`),
144
- ...lintOptionalString(field.placeholder, `schema.fields[${index}].placeholder`),
145
- ...lintOptionalString(field.visibleWhen, `schema.fields[${index}].visibleWhen`),
146
- ...lintOptionalString(field.requiredWhen, `schema.fields[${index}].requiredWhen`),
147
- ...lintOptionalString(field.readonlyWhen, `schema.fields[${index}].readonlyWhen`),
148
- ...(typeof field.required === 'boolean'
149
- ? []
150
- : [`schema.fields[${index}].required must be a boolean`]),
151
- ...(isSupportedFieldType(type)
152
- ? []
153
- : [`schema.fields[${index}].type is not supported`]),
154
+ ...lintFieldIdentity(field, path),
155
+ ...lintOptionalString(field.visibleWhen, `${path}.visibleWhen`),
156
+ ...lintOptionalString(field.requiredWhen, `${path}.requiredWhen`),
157
+ ...lintOptionalString(field.readonlyWhen, `${path}.readonlyWhen`),
158
+ ...lintFieldRequiredFlag(field, path),
159
+ ...(isSupportedFieldType(type) ? [] : [`${path}.type is not supported`]),
160
+ ...lintConditionTableReferences(field, path, scope.tableFieldKeys),
154
161
  ];
155
162
  if (!isSupportedFieldType(type)) {
156
163
  return basicErrors;
157
164
  }
158
- const path = `schema.fields[${index}]`;
165
+ return [...basicErrors, ...lintFieldTypeSpecifics(field, type, path, scope)];
166
+ }
167
+ function lintFieldTypeSpecifics(field, type, path, scope) {
159
168
  if (TEXT_FIELD_TYPES.has(type)) {
160
- return [...basicErrors, ...lintTextField(field, path)];
169
+ return lintTextField(field, path);
161
170
  }
162
171
  if (NUMBER_FIELD_TYPES.has(type)) {
163
- return [...basicErrors, ...lintNumberField(field, path)];
172
+ return lintNumberField(field, path);
164
173
  }
165
174
  if (DATE_FIELD_TYPES.has(type)) {
166
- return [...basicErrors, ...lintDateField(field, path)];
175
+ return lintDateField(field, path);
167
176
  }
168
177
  if (SELECT_FIELD_TYPES.has(type)) {
169
- return [...basicErrors, ...lintSelectField(field, type, path, fieldKeys)];
178
+ return lintSelectField(field, type, path, scope);
170
179
  }
171
180
  if (type === 'boolean') {
172
- return [...basicErrors, ...lintBooleanField(field, path)];
181
+ return lintBooleanField(field, path);
182
+ }
183
+ if (type === 'table') {
184
+ return lintTableField(field, path, scope);
185
+ }
186
+ return lintFileUploadField(field, path);
187
+ }
188
+ function lintFieldIdentity(field, path) {
189
+ return [
190
+ ...lintRequiredString(field.fieldKey, `${path}.fieldKey`),
191
+ ...lintRequiredString(field.label, `${path}.label`),
192
+ ...lintOptionalString(field.description, `${path}.description`),
193
+ ...lintOptionalString(field.placeholder, `${path}.placeholder`),
194
+ ];
195
+ }
196
+ function lintFieldRequiredFlag(field, path) {
197
+ return typeof field.required === 'boolean'
198
+ ? []
199
+ : [`${path}.required must be a boolean`];
200
+ }
201
+ /**
202
+ * Form-level conditions cannot address a cell: neither the frontend nor the
203
+ * backend condition parser understands bracket paths, so referencing table
204
+ * internals is rejected outright instead of relying on a parse failure
205
+ * (ADR 16 ยง3.8). Referencing the table itself stays legal โ€” that is what the
206
+ * IS_FILLED / IS_EMPTY operators compile to.
207
+ */
208
+ function lintConditionTableReferences(field, path, tableFieldKeys) {
209
+ if (!tableFieldKeys.size) {
210
+ return [];
173
211
  }
174
- return [...basicErrors, ...lintFileUploadField(field, path)];
212
+ return CONDITION_FIELD_KEYS.flatMap((conditionKey) => {
213
+ const expression = field[conditionKey];
214
+ if (typeof expression !== 'string') {
215
+ return [];
216
+ }
217
+ return [...tableFieldKeys]
218
+ .filter((tableKey) => (0, form_table_reference_1.referencesTableInternals)(expression, tableKey))
219
+ .map((tableKey) => `${path}.${conditionKey} must not reference table field internals: ${tableKey}`);
220
+ });
175
221
  }
176
222
  function lintTextField(field, path) {
177
223
  const minLengthErrors = lintOptionalInteger(field.minLength, `${path}.minLength`, 0);
@@ -206,8 +252,8 @@ function lintNumberField(field, path) {
206
252
  function lintDateField(field, path) {
207
253
  return lintOptionalDefaultValue(field.defaultValue, path, (value) => typeof value === 'string' || value === null);
208
254
  }
209
- function lintSelectField(field, type, path, fieldKeys) {
210
- const sourceErrors = lintOptionSource(field, type, path, fieldKeys);
255
+ function lintSelectField(field, type, path, scope) {
256
+ const sourceErrors = lintOptionSource(field, type, path, scope);
211
257
  const hasDataSource = Object.prototype.hasOwnProperty.call(field, 'dataSource');
212
258
  return [
213
259
  ...sourceErrors,
@@ -216,7 +262,7 @@ function lintSelectField(field, type, path, fieldKeys) {
216
262
  : lintSelectDefaultValue(field, type, path)),
217
263
  ];
218
264
  }
219
- function lintOptionSource(field, type, path, fieldKeys) {
265
+ function lintOptionSource(field, type, path, scope) {
220
266
  const hasOptions = Object.prototype.hasOwnProperty.call(field, 'options');
221
267
  const hasDataSource = Object.prototype.hasOwnProperty.call(field, 'dataSource');
222
268
  const sourceCount = Number(hasOptions) + Number(hasDataSource);
@@ -239,7 +285,7 @@ function lintOptionSource(field, type, path, fieldKeys) {
239
285
  return [
240
286
  ...sourceShapeErrors,
241
287
  ...modeErrors,
242
- ...lintDataSourceReference(field.dataSource, `${path}.dataSource`, fieldKeys),
288
+ ...lintDataSourceReference(field.dataSource, `${path}.dataSource`, scope),
243
289
  ];
244
290
  }
245
291
  function lintOptionMode(field, type, path) {
@@ -256,7 +302,7 @@ function lintOptionMode(field, type, path) {
256
302
  ? []
257
303
  : [`${path}.mode must be single or multiple`];
258
304
  }
259
- function lintDataSourceReference(value, path, fieldKeys) {
305
+ function lintDataSourceReference(value, path, scope) {
260
306
  if (!isRecord(value)) {
261
307
  return [`${path} must be an object`];
262
308
  }
@@ -268,7 +314,7 @@ function lintDataSourceReference(value, path, fieldKeys) {
268
314
  if (!Array.isArray(bindings)) {
269
315
  return [...referenceErrors, `${path}.bindings must be an array`];
270
316
  }
271
- const bindingErrors = bindings.flatMap((binding, index) => lintDataSourceBinding(binding, `${path}.bindings[${index}]`, fieldKeys));
317
+ const bindingErrors = bindings.flatMap((binding, index) => lintDataSourceBinding(binding, `${path}.bindings[${index}]`, scope));
272
318
  const parameters = bindings
273
319
  .filter(isRecord)
274
320
  .map((binding) => binding.parameter)
@@ -282,7 +328,7 @@ function lintDataSourceReference(value, path, fieldKeys) {
282
328
  ...duplicateParameters.map((parameter) => `${path}.bindings.parameter is duplicated: ${parameter}`),
283
329
  ];
284
330
  }
285
- function lintDataSourceBinding(value, path, fieldKeys) {
331
+ function lintDataSourceBinding(value, path, scope) {
286
332
  if (!isRecord(value)) {
287
333
  return [`${path} must be an object`];
288
334
  }
@@ -295,9 +341,7 @@ function lintDataSourceBinding(value, path, fieldKeys) {
295
341
  return [
296
342
  ...errors,
297
343
  ...lintRequiredString(from.fieldKey, `${path}.from.fieldKey`),
298
- ...(typeof from.fieldKey === 'string' && !fieldKeys.has(from.fieldKey)
299
- ? [`${path}.from.fieldKey does not match a schema field`]
300
- : []),
344
+ ...lintFieldBindingTarget(from.fieldKey, `${path}.from.fieldKey`, scope),
301
345
  ];
302
346
  }
303
347
  if (from.kind === 'CONSTANT') {
@@ -306,7 +350,37 @@ function lintDataSourceBinding(value, path, fieldKeys) {
306
350
  ...lintConstantValue(from.value, `${path}.from.value`),
307
351
  ];
308
352
  }
309
- return [...errors, `${path}.from.kind must be FIELD or CONSTANT`];
353
+ if (from.kind === 'ROW_FIELD') {
354
+ const columnKeys = scope.columnKeys;
355
+ if (!columnKeys) {
356
+ return [
357
+ ...errors,
358
+ `${path}.from.kind ROW_FIELD is only supported inside a table column`,
359
+ ];
360
+ }
361
+ return [
362
+ ...errors,
363
+ ...lintRequiredString(from.columnKey, `${path}.from.columnKey`),
364
+ ...(typeof from.columnKey === 'string' && !columnKeys.has(from.columnKey)
365
+ ? [`${path}.from.columnKey does not match a table column`]
366
+ : []),
367
+ ];
368
+ }
369
+ return [...errors, `${path}.from.kind must be FIELD, CONSTANT, or ROW_FIELD`];
370
+ }
371
+ function lintFieldBindingTarget(fieldKey, path, scope) {
372
+ if (typeof fieldKey !== 'string') {
373
+ return [];
374
+ }
375
+ // A table value is a list of rows, so it can never stand in for a scalar
376
+ // data source parameter โ€” and cross-row references have no single-value
377
+ // semantics in V1 (ADR 16 ยง3.4).
378
+ if (scope.tableFieldKeys.has(fieldKey)) {
379
+ return [`${path} must not reference a table field`];
380
+ }
381
+ return scope.scalarFieldKeys.has(fieldKey)
382
+ ? []
383
+ : [`${path} does not match a schema field`];
310
384
  }
311
385
  function lintConstantValue(value, path) {
312
386
  return value === null ||
@@ -324,6 +398,100 @@ function lintDynamicDefaultValue(field, path) {
324
398
  function lintBooleanField(field, path) {
325
399
  return lintOptionalDefaultValue(field.defaultValue, path, (value) => typeof value === 'boolean' || value === null);
326
400
  }
401
+ function lintTableField(field, path, scope) {
402
+ const structureErrors = [
403
+ ...lintIdentifierKey(field.fieldKey, `${path}.fieldKey`),
404
+ ...lintOptionalString(field.addRowLabel, `${path}.addRowLabel`),
405
+ ...lintTableRowBounds(field, path),
406
+ ...(Object.prototype.hasOwnProperty.call(field, 'defaultValue')
407
+ ? [`${path}.defaultValue is not supported for table fields`]
408
+ : []),
409
+ ];
410
+ const columns = field.columns;
411
+ if (!Array.isArray(columns) || columns.length === 0) {
412
+ return [...structureErrors, `${path}.columns must contain at least one column`];
413
+ }
414
+ const columnKeys = columns
415
+ .filter(isRecord)
416
+ .map((column) => column.fieldKey)
417
+ .filter((columnKey) => typeof columnKey === 'string');
418
+ const columnScope = {
419
+ ...scope,
420
+ columnKeys: new Set(columnKeys),
421
+ };
422
+ const duplicatedColumnKeys = [
423
+ ...new Set(columnKeys.filter((columnKey, index) => columnKeys.indexOf(columnKey) !== index)),
424
+ ];
425
+ return [
426
+ ...structureErrors,
427
+ ...columns.flatMap((column, index) => lintTableColumn(column, `${path}.columns[${index}]`, columnScope)),
428
+ ...duplicatedColumnKeys.map((columnKey) => `${path}.columns fieldKey is duplicated: ${columnKey}`),
429
+ ];
430
+ }
431
+ function lintTableRowBounds(field, path) {
432
+ const minRowsErrors = lintOptionalInteger(field.minRows, `${path}.minRows`, 0);
433
+ const maxRowsErrors = lintOptionalInteger(field.maxRows, `${path}.maxRows`, 1);
434
+ const ceilingErrors = typeof field.maxRows === 'number' && field.maxRows > form_1.FORM_TABLE_MAX_ROWS
435
+ ? [
436
+ `${path}.maxRows must be less than or equal to ${form_1.FORM_TABLE_MAX_ROWS}`,
437
+ ]
438
+ : [];
439
+ const rangeErrors = typeof field.minRows === 'number' &&
440
+ typeof field.maxRows === 'number' &&
441
+ field.minRows > field.maxRows
442
+ ? [`${path}.minRows must be less than or equal to ${path}.maxRows`]
443
+ : [];
444
+ const implicitCeilingErrors = typeof field.minRows === 'number' &&
445
+ typeof field.maxRows === 'undefined' &&
446
+ field.minRows > form_1.FORM_TABLE_MAX_ROWS
447
+ ? [
448
+ `${path}.minRows must be less than or equal to ${form_1.FORM_TABLE_MAX_ROWS}`,
449
+ ]
450
+ : [];
451
+ return [
452
+ ...minRowsErrors,
453
+ ...maxRowsErrors,
454
+ ...ceilingErrors,
455
+ ...rangeErrors,
456
+ ...implicitCeilingErrors,
457
+ ];
458
+ }
459
+ function lintTableColumn(column, path, scope) {
460
+ if (!isRecord(column)) {
461
+ return [`${path} must be an object`];
462
+ }
463
+ const type = column.type;
464
+ const basicErrors = [
465
+ ...lintFieldIdentity(column, path),
466
+ ...lintIdentifierKey(column.fieldKey, `${path}.fieldKey`),
467
+ ...CONDITION_FIELD_KEYS.filter((conditionKey) => Object.prototype.hasOwnProperty.call(column, conditionKey)).map((conditionKey) => `${path}.${conditionKey} is not supported for table columns`),
468
+ ...lintFieldRequiredFlag(column, path),
469
+ ...lintTableColumnType(type, path),
470
+ ];
471
+ if (!isSupportedFieldType(type) || !(0, form_1.isTableColumnFieldType)(type)) {
472
+ return basicErrors;
473
+ }
474
+ return [...basicErrors, ...lintFieldTypeSpecifics(column, type, path, scope)];
475
+ }
476
+ function lintTableColumnType(type, path) {
477
+ if (type === 'table') {
478
+ return [`${path}.type must not be a nested table`];
479
+ }
480
+ if (!isSupportedFieldType(type)) {
481
+ return [`${path}.type is not supported`];
482
+ }
483
+ return (0, form_1.isTableColumnFieldType)(type)
484
+ ? []
485
+ : [`${path}.type is not supported for a table column`];
486
+ }
487
+ function lintIdentifierKey(value, path) {
488
+ if (typeof value !== 'string' || !value.trim()) {
489
+ return [];
490
+ }
491
+ return (0, form_1.isFormIdentifierKey)(value)
492
+ ? []
493
+ : [`${path} must match /^[A-Za-z_][A-Za-z0-9_]*$/`];
494
+ }
327
495
  function lintFileUploadField(field, path) {
328
496
  return [
329
497
  ...lintOptionalInteger(field.maxFiles, `${path}.maxFiles`, 1),
@@ -378,7 +546,7 @@ function lintOptionalDefaultValue(value, path, predicate) {
378
546
  }
379
547
  return [`${path}.defaultValue has invalid type for field type`];
380
548
  }
381
- function lintUiSchema(uiSchema, fieldKeys) {
549
+ function lintUiSchema(uiSchema, fieldKeys, tableFieldKeys) {
382
550
  if (!isRecord(uiSchema)) {
383
551
  return ['uiSchema must be an object'];
384
552
  }
@@ -389,10 +557,10 @@ function lintUiSchema(uiSchema, fieldKeys) {
389
557
  }
390
558
  return [
391
559
  ...versionErrors,
392
- ...layout.flatMap((item, index) => lintLayoutItem(item, index, fieldKeys)),
560
+ ...layout.flatMap((item, index) => lintLayoutItem(item, index, fieldKeys, tableFieldKeys)),
393
561
  ];
394
562
  }
395
- function lintLayoutItem(item, index, fieldKeys) {
563
+ function lintLayoutItem(item, index, fieldKeys, tableFieldKeys) {
396
564
  if (!isRecord(item)) {
397
565
  return [`uiSchema.layout[${index}] must be an object`];
398
566
  }
@@ -406,6 +574,11 @@ function lintLayoutItem(item, index, fieldKeys) {
406
574
  ...(isUiWidth(width)
407
575
  ? []
408
576
  : [`uiSchema.layout[${index}].width must be FULL, HALF, or THIRD`]),
577
+ ...(typeof fieldKey === 'string' &&
578
+ tableFieldKeys.has(fieldKey) &&
579
+ width !== 'FULL'
580
+ ? [`uiSchema.layout[${index}].width must be FULL for table fields`]
581
+ : []),
409
582
  ];
410
583
  }
411
584
  function readDuplicateFieldKeyErrors(fields) {
@@ -417,30 +590,68 @@ function readDuplicateFieldKeyErrors(fields) {
417
590
  const uniqueDuplicated = [...new Set(duplicated)];
418
591
  return uniqueDuplicated.map((key) => `schema.fields fieldKey is duplicated: ${key}`);
419
592
  }
593
+ /**
594
+ * Two layers in one graph (ADR 16 ยง3.4): top-level fields joined by `FIELD`
595
+ * bindings, and each table's columns joined by `ROW_FIELD` bindings plus their
596
+ * `FIELD` edges up to the top level. Node ids are prefixed so a top-level key
597
+ * that happens to contain a dot cannot collide with a column node.
598
+ */
420
599
  function lintFieldDependencyGraph(fields) {
421
- const dependencies = fields.reduce((graph, field) => {
422
- if (!isRecord(field) || typeof field.fieldKey !== 'string') {
423
- return graph;
424
- }
425
- const nextDependencies = isRecord(field.dataSource) &&
426
- Array.isArray(field.dataSource.bindings)
427
- ? field.dataSource.bindings
428
- .filter(isRecord)
429
- .map((binding) => binding.from)
430
- .filter(isRecord)
431
- .filter((from) => from.kind === 'FIELD')
432
- .map((from) => from.fieldKey)
433
- .filter((fieldKey) => typeof fieldKey === 'string')
434
- : [];
435
- return new Map(graph).set(field.fieldKey, nextDependencies);
436
- }, new Map());
600
+ const dependencies = new Map(fields.filter(isRecord).flatMap(readFieldDependencyEntries));
437
601
  const cycles = [...dependencies.keys()]
438
- .map((fieldKey) => findDependencyCycle(fieldKey, dependencies, []))
602
+ .map((node) => findDependencyCycle(node, dependencies, []))
439
603
  .filter((cycle) => Boolean(cycle))
440
- .map((cycle) => cycle.join(' -> '));
604
+ .map((cycle) => cycle.map(readDependencyNodeLabel).join(' -> '));
441
605
  const uniqueCycles = [...new Set(cycles)];
442
606
  return uniqueCycles.map((cycle) => `schema.fields dependency cycle: ${cycle}`);
443
607
  }
608
+ function readFieldDependencyEntries(field) {
609
+ if (typeof field.fieldKey !== 'string') {
610
+ return [];
611
+ }
612
+ const fieldKey = field.fieldKey;
613
+ if (field.type === 'table') {
614
+ return Array.isArray(field.columns)
615
+ ? field.columns
616
+ .filter(isRecord)
617
+ .filter((column) => typeof column.fieldKey === 'string')
618
+ .map((column) => [
619
+ `col:${fieldKey}.${String(column.fieldKey)}`,
620
+ readBindingSources(column).flatMap((from) => readColumnDependencyNode(from, fieldKey)),
621
+ ])
622
+ : [];
623
+ }
624
+ return [
625
+ [
626
+ `top:${fieldKey}`,
627
+ readBindingSources(field)
628
+ .filter((from) => from.kind === 'FIELD')
629
+ .map((from) => from.fieldKey)
630
+ .filter((dependencyKey) => typeof dependencyKey === 'string')
631
+ .map((dependencyKey) => `top:${dependencyKey}`),
632
+ ],
633
+ ];
634
+ }
635
+ function readColumnDependencyNode(from, tableKey) {
636
+ if (from.kind === 'FIELD' && typeof from.fieldKey === 'string') {
637
+ return [`top:${from.fieldKey}`];
638
+ }
639
+ if (from.kind === 'ROW_FIELD' && typeof from.columnKey === 'string') {
640
+ return [`col:${tableKey}.${from.columnKey}`];
641
+ }
642
+ return [];
643
+ }
644
+ function readBindingSources(field) {
645
+ return isRecord(field.dataSource) && Array.isArray(field.dataSource.bindings)
646
+ ? field.dataSource.bindings
647
+ .filter(isRecord)
648
+ .map((binding) => binding.from)
649
+ .filter(isRecord)
650
+ : [];
651
+ }
652
+ function readDependencyNodeLabel(node) {
653
+ return node.replace(/^(?:top|col):/u, '');
654
+ }
444
655
  function findDependencyCycle(fieldKey, dependencies, path) {
445
656
  const cycleStart = path.indexOf(fieldKey);
446
657
  if (cycleStart >= 0) {
@@ -457,11 +668,21 @@ function findDependencyCycle(fieldKey, dependencies, path) {
457
668
  return null;
458
669
  }
459
670
  function readFieldKeySet(schema) {
671
+ return readFieldKeySetBy(schema, () => true);
672
+ }
673
+ function readTableFieldKeySet(schema) {
674
+ return readFieldKeySetBy(schema, (field) => field.type === 'table');
675
+ }
676
+ function readScalarFieldKeySet(schema) {
677
+ return readFieldKeySetBy(schema, (field) => field.type !== 'table');
678
+ }
679
+ function readFieldKeySetBy(schema, predicate) {
460
680
  if (!isRecord(schema) || !Array.isArray(schema.fields)) {
461
681
  return new Set();
462
682
  }
463
683
  return new Set(schema.fields
464
684
  .filter(isRecord)
685
+ .filter(predicate)
465
686
  .map((field) => field.fieldKey)
466
687
  .filter((fieldKey) => typeof fieldKey === 'string'));
467
688
  }