@builder-builder/builder 0.0.12 → 0.0.14

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 (66) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +53 -0
  3. package/dist/codegen/index.d.ts +7 -0
  4. package/dist/codegen/index.js +212 -0
  5. package/dist/codegen/template.d.ts +5 -0
  6. package/dist/codegen/template.js +17 -0
  7. package/dist/entities/index.d.ts +3 -3
  8. package/dist/entities/index.js +1 -1
  9. package/dist/entities/kind.d.ts +1 -1
  10. package/dist/entities/serialise.d.ts +554 -8
  11. package/dist/entities/serialise.js +5 -3
  12. package/dist/entities/ui/describe.d.ts +222 -8
  13. package/dist/entities/ui/describe.js +5 -5
  14. package/dist/entities/ui/index.d.ts +2 -0
  15. package/dist/entities/ui/index.js +1 -0
  16. package/dist/entities/ui/input.d.ts +334 -0
  17. package/dist/entities/ui/input.js +39 -0
  18. package/dist/entities/ui/page.d.ts +222 -8
  19. package/dist/entities/ui/page.js +5 -5
  20. package/dist/entities/ui/ui.d.ts +3 -3
  21. package/dist/entities/ui/ui.js +7 -4
  22. package/dist/entities/validated.d.ts +23 -21
  23. package/dist/entities/when.d.ts +2 -2
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +1 -1
  26. package/dist/mappers/index.d.ts +3 -2
  27. package/dist/mappers/index.js +1 -1
  28. package/dist/mappers/instance.js +10 -6
  29. package/dist/mappers/order.js +5 -2
  30. package/dist/mappers/render/index.d.ts +1 -1
  31. package/dist/mappers/render/pages.d.ts +8 -0
  32. package/dist/mappers/render/pages.js +2 -1
  33. package/dist/mappers/render/render.js +31 -54
  34. package/dist/mappers/resolve.d.ts +13 -4
  35. package/dist/mappers/resolve.js +73 -16
  36. package/dist/mappers/variants/index.d.ts +2 -1
  37. package/dist/mappers/variants/index.js +2 -1
  38. package/dist/mappers/variants/option-graph.d.ts +2 -2
  39. package/dist/mappers/variants/option-graph.js +6 -3
  40. package/dist/mappers/variants/variants.d.ts +5 -2
  41. package/dist/mappers/variants/variants.js +11 -7
  42. package/dist/paths.d.ts +0 -1
  43. package/dist/paths.js +0 -8
  44. package/dist/validate/brand.d.ts +0 -7
  45. package/dist/validate/brand.js +12 -6
  46. package/dist/validate/builder.d.ts +2 -1
  47. package/dist/validate/builder.js +14 -12
  48. package/dist/validate/errors.d.ts +130 -0
  49. package/dist/validate/errors.js +141 -0
  50. package/dist/validate/expectations.d.ts +3 -10
  51. package/dist/validate/expectations.js +8 -10
  52. package/dist/validate/index.d.ts +8 -14
  53. package/dist/validate/index.js +4 -7
  54. package/dist/validate/instance.d.ts +2 -15
  55. package/dist/validate/instance.js +42 -40
  56. package/dist/validate/model.d.ts +4 -31
  57. package/dist/validate/model.js +157 -173
  58. package/dist/validate/resolve.d.ts +3 -15
  59. package/dist/validate/resolve.js +66 -71
  60. package/dist/validate/result.d.ts +1 -7
  61. package/dist/validate/result.js +1 -4
  62. package/dist/validate/ui.d.ts +4 -4
  63. package/dist/validate/ui.js +80 -62
  64. package/dist/validate/variants.d.ts +2 -53
  65. package/dist/validate/variants.js +83 -86
  66. package/package.json +14 -3
@@ -1,220 +1,204 @@
1
1
  import { check } from '../check.js';
2
2
  import { BuilderCollectionConfigSerialisedSchema, BuilderModelSerialisedSchema, BuilderWhenSerialisedSchema, model, modelsMerge, serialise } from '../entities/index.js';
3
- import { builderError } from '../exception.js';
4
3
  import { validate } from './brand.js';
4
+ import { BuilderValidateErrors } from './errors.js';
5
5
  import { checkExpectations } from './expectations.js';
6
6
  import { resolver } from './resolve.js';
7
- import { builderErrorInvalidInput } from './result.js';
8
- export function builderErrorDuplicateName(name, location = []) {
9
- return { ...builderError('duplicate-name', location), name };
10
- }
11
- export function builderErrorInvalidCollectionBounds(name, min, max, location = []) {
12
- return { ...builderError('invalid-collection-bounds', location), name, min, max };
13
- }
14
- export function builderErrorInvalidPath(reason, location = []) {
15
- return { ...builderError('invalid-path', location), reason };
16
- }
17
- export function builderErrorInvalidSelectMapKey(key, location = []) {
18
- return { ...builderError('invalid-select-map-key', location), key };
19
- }
20
7
  const EMPTY_MODEL = validate(serialise.model(model()));
21
- export function validateModel(input, references = []) {
8
+ export function validateModel(input, references = [], errors = new BuilderValidateErrors()) {
22
9
  if (!check.is(BuilderModelSerialisedSchema, input)) {
23
- return [EMPTY_MODEL, [builderErrorInvalidInput('model')]];
10
+ errors.invalidInput('model');
11
+ return [EMPTY_MODEL, errors.errors];
24
12
  }
25
- const resolve = resolver(references);
26
- const [structure, structureErrors] = validateModelStructure(input, resolve, []);
13
+ const resolve = resolver(errors, references);
14
+ const structure = validateModelStructure(input, resolve, errors);
27
15
  const data = validate(modelsMerge(structure));
28
- const expectationErrors = checkModelExpectations(data, structure, []);
29
- return [data, [...expectationErrors, ...structureErrors]];
16
+ checkModelExpectations(data, structure, errors);
17
+ return [data, errors.errors];
30
18
  }
31
- export function checkModelExpectations(mergedModel, model, location) {
32
- return [
33
- ...checkExpectations(mergedModel, model.expectations, [...location, 'expectations']),
34
- ...model.models.flatMap((nested, nestedIndex) => checkModelExpectations(mergedModel, nested, [
35
- ...location,
36
- 'models',
37
- nestedIndex
38
- ]))
39
- ];
19
+ export function checkModelExpectations(mergedModel, rootModel, errors) {
20
+ recurse(rootModel);
21
+ function recurse(node) {
22
+ errors.scope('expectations', () => {
23
+ checkExpectations(mergedModel, node.expectations, errors);
24
+ });
25
+ errors.scope('models', () => {
26
+ node.models.forEach((nested, nestedIndex) => {
27
+ errors.scope(nestedIndex, () => {
28
+ recurse(nested);
29
+ });
30
+ });
31
+ });
32
+ }
40
33
  }
41
- export function validateModelStructure(input, resolve, location) {
42
- const [resolved, resolveErrors] = resolve(input, location);
34
+ export function validateModelStructure(input, resolve, errors) {
35
+ const resolved = resolve(input);
43
36
  if (!check.is(BuilderModelSerialisedSchema, resolved)) {
44
- return [validate(serialise.model(model())), resolveErrors];
37
+ return validate(serialise.model(model()));
45
38
  }
46
- const errors = [...resolveErrors];
47
39
  const modelInput = resolved;
48
- const childModels = modelInput.models.flatMap((part, partIndex) => {
49
- const partLocation = [...location, 'models', partIndex];
50
- const [child, childErrors] = validateModelStructure(part, resolve, partLocation);
51
- errors.push(...childErrors);
52
- return [child];
53
- });
54
- const [options, optionErrors] = walkEntries(modelInput.options, 'option', resolve, location);
55
- const [components, componentErrors] = walkEntries(modelInput.components, 'component', resolve, location);
56
- const [collections, collectionErrors] = walkEntries(modelInput.collections, 'collection', resolve, location);
57
- errors.push(...optionErrors, ...componentErrors, ...collectionErrors);
58
40
  const skipPathValidation = modelInput.expectations.length > 0;
59
- const checkPaths = (entry, entryLocation) => (skipPathValidation ? [] : checkEntryPaths(modelInput, entry, entryLocation));
60
- options.forEach((entry, entryIndex) => errors.push(...checkPaths(entry, [...location, 'options', entryIndex])));
61
- components.forEach((entry, entryIndex) => errors.push(...checkPaths(entry, [...location, 'components', entryIndex])));
62
- const resolvedCollections = collections.map((entry, entryIndex) => {
63
- const collectionLocation = [...location, 'collections', entryIndex];
64
- errors.push(...checkPaths(entry, collectionLocation), ...checkCollectionBounds(entry, collectionLocation));
65
- const [resolvedEntry, entryErrors] = resolveCollectionInnerModels(entry, resolve, [...collectionLocation, 'payload']);
66
- errors.push(...entryErrors);
67
- return resolvedEntry;
68
- });
69
- const data = {
41
+ const childModels = errors.scope('models', () => modelInput.models.flatMap((part, partIndex) => errors.scope(partIndex, () => [validateModelStructure(part, resolve, errors)])));
42
+ const options = walkEntries(modelInput.options, 'option');
43
+ const components = walkEntries(modelInput.components, 'component');
44
+ const collections = walkCollections();
45
+ return {
70
46
  ...modelInput,
71
47
  models: childModels,
72
48
  options,
73
49
  components,
74
- collections: resolvedCollections
50
+ collections
75
51
  };
76
- return [data, errors];
77
- }
78
- function resolveCollectionInnerModels(entry, resolve, payloadLocation) {
79
- const errors = [];
80
- const newPayload = mapCollectionConfigs(entry.payload, payloadLocation, (config, configLocation) => {
81
- if (!check.is(BuilderModelSerialisedSchema, config.model)) {
82
- return config;
83
- }
84
- const [validated, modelErrors] = validateModelStructure(config.model, resolve, [...configLocation, 'model']);
85
- errors.push(...modelErrors);
86
- return { ...config, model: validated };
87
- });
88
- return [{ ...entry, payload: newPayload }, errors];
89
- }
90
- function mapCollectionConfigs(payload, location, visit) {
91
- if (check.is(BuilderCollectionConfigSerialisedSchema, payload)) {
92
- return visit(payload, location);
52
+ function walkEntries(entries, entryKind) {
53
+ const seen = new Set();
54
+ const items = [];
55
+ errors.scope(`${entryKind}s`, () => {
56
+ entries.forEach((entry, entryIndex) => {
57
+ errors.scope(entryIndex, () => {
58
+ const resolved = resolveEntry(entry, seen);
59
+ if (resolved != null) {
60
+ items.push(resolved);
61
+ }
62
+ });
63
+ });
64
+ });
65
+ return items;
93
66
  }
94
- if (!check.is(BuilderWhenSerialisedSchema, payload)) {
95
- return payload;
96
- }
97
- const when = payload;
98
- if (when.type === 'match') {
99
- const selectMap = when.selectMap;
100
- const newSelectMap = Object.fromEntries(Object.entries(selectMap).map(([key, value]) => [
101
- key,
102
- mapCollectionConfigs(value, [...location, 'selectMap', key], visit)
103
- ]));
104
- return { ...when, selectMap: newSelectMap };
67
+ function walkCollections() {
68
+ const seen = new Set();
69
+ const items = [];
70
+ errors.scope('collections', () => {
71
+ modelInput.collections.forEach((entry, entryIndex) => {
72
+ errors.scope(entryIndex, () => {
73
+ const resolved = resolveEntry(entry, seen);
74
+ if (resolved == null) {
75
+ return;
76
+ }
77
+ checkCollectionBounds(resolved);
78
+ items.push(resolved);
79
+ });
80
+ });
81
+ });
82
+ return items;
105
83
  }
106
- return { ...when, payload: mapCollectionConfigs(when.payload, [...location, 'payload'], visit) };
107
- }
108
- function walkEntries(entries, entryKind, resolve, location) {
109
- const seen = new Set();
110
- const items = [];
111
- const errors = [];
112
- const arrayKey = `${entryKind}s`;
113
- entries.forEach((entry, entryIndex) => {
114
- const entryLocation = [...location, arrayKey, entryIndex];
84
+ function resolveEntry(entry, seen) {
115
85
  let paths = entry.paths;
116
86
  if (entry.paths != null) {
117
- const [resolvedPaths, pathErrors] = resolve(entry.paths, [...entryLocation, 'paths']);
87
+ const resolvedPaths = errors.scope('paths', () => resolve(entry.paths));
118
88
  paths = resolvedPaths ?? entry.paths;
119
- errors.push(...pathErrors);
120
89
  }
121
- const [payload, payloadErrors] = resolve(entry.payload, [...entryLocation, 'payload']);
122
- errors.push(...payloadErrors);
123
- const resolved = { ...entry, paths, payload: payload ?? entry.payload };
124
- if (seen.has(resolved.name)) {
125
- errors.push(builderErrorDuplicateName(resolved.name, entryLocation));
90
+ const payload = errors.scope('payload', () => resolve(entry.payload));
91
+ const resolvedEntry = { ...entry, paths, payload: payload ?? entry.payload };
92
+ if (seen.has(resolvedEntry.name)) {
93
+ errors.duplicateName(resolvedEntry.name);
94
+ return null;
95
+ }
96
+ seen.add(resolvedEntry.name);
97
+ checkPaths(resolvedEntry);
98
+ return resolvedEntry;
99
+ }
100
+ function checkPaths(entry) {
101
+ if (skipPathValidation) {
126
102
  return;
127
103
  }
128
- seen.add(resolved.name);
129
- items.push(resolved);
130
- });
131
- return [items, errors];
132
- }
133
- function checkEntryPaths(modelInput, entry, location) {
134
- const { name, paths, payload } = entry;
135
- const directPaths = Array.isArray(paths)
136
- ? paths.map((path, pathIndex) => [path, [...location, 'paths', pathIndex]])
137
- : [];
138
- const extractWhenPathsList = extractWhenPaths(payload, location);
139
- return [...directPaths, ...extractWhenPathsList].flatMap(([path, pathLocation]) => {
140
- const [reached, walkErrors] = walkPath([modelInput], path.slice(0, -1), name, path, pathLocation);
141
- if (walkErrors.length > 0) {
142
- return walkErrors;
104
+ const { paths, payload } = entry;
105
+ if (Array.isArray(paths)) {
106
+ errors.scope('paths', () => {
107
+ paths.forEach((path, pathIndex) => {
108
+ errors.scope(pathIndex, () => {
109
+ checkPath(path);
110
+ });
111
+ });
112
+ });
113
+ }
114
+ checkWhenPaths(payload);
115
+ }
116
+ function checkPath(path) {
117
+ const reached = walkPath([modelInput], path.slice(0, -1));
118
+ if (reached == null) {
119
+ return;
143
120
  }
144
121
  const optionName = path.at(-1);
145
122
  if (!reached.some((candidate) => candidate.options.some((option) => option.name === optionName))) {
146
- return [builderErrorInvalidPath('option-not-found', pathLocation)];
123
+ errors.invalidPath('option-not-found');
147
124
  }
148
- return [];
149
- });
150
- }
151
- function checkCollectionBounds(collection, location) {
152
- return collectionConfigs(collection, location).flatMap(([{ min, max }]) => {
153
- if (typeof min !== 'number' || typeof max !== 'number') {
154
- return [];
125
+ }
126
+ function checkWhenPaths(payload) {
127
+ if (!check.is(BuilderWhenSerialisedSchema, payload)) {
128
+ return;
155
129
  }
156
- if (min < 0 || max <= 0 || max < min) {
157
- return [builderErrorInvalidCollectionBounds(collection.name, min, max, location)];
130
+ if (payload.type === 'match' && Array.isArray(payload.matchPath)) {
131
+ errors.scope('payload', () => {
132
+ errors.scope('matchPath', () => {
133
+ checkPath(payload.matchPath);
134
+ });
135
+ });
136
+ return;
158
137
  }
159
- return [];
160
- });
138
+ if (payload.type === 'unless' && Array.isArray(payload.unlessPath)) {
139
+ errors.scope('payload', () => {
140
+ errors.scope('unlessPath', () => {
141
+ checkPath(payload.unlessPath);
142
+ });
143
+ });
144
+ }
145
+ }
146
+ function walkPath(candidates, remaining) {
147
+ if (remaining.length === 0) {
148
+ return candidates;
149
+ }
150
+ const [collectionName, index, ...rest] = remaining;
151
+ if (typeof collectionName !== 'string' || typeof index !== 'number') {
152
+ errors.invalidPath('shape');
153
+ return null;
154
+ }
155
+ const matchedConfigs = candidates.flatMap((current) => {
156
+ const collection = current.collections.find((entry) => entry.name === collectionName);
157
+ return collection == null ? [] : [collectionConfigs(collection)];
158
+ });
159
+ if (matchedConfigs.length === 0) {
160
+ errors.invalidPath('missing-collection');
161
+ return null;
162
+ }
163
+ const anyOutOfBounds = matchedConfigs.some((configs) => configs.length > 0 && configs.every(({ max }) => typeof max === 'number' && index >= max));
164
+ if (anyOutOfBounds) {
165
+ errors.invalidPath('out-of-bounds');
166
+ return null;
167
+ }
168
+ const next = matchedConfigs.flatMap((configs) => configs
169
+ .filter(({ max }) => typeof max !== 'number' || index < max)
170
+ .flatMap(({ model: configModel }) => check.is(BuilderModelSerialisedSchema, configModel) ? [configModel] : []));
171
+ if (next.length === 0) {
172
+ errors.invalidPath('missing-collection');
173
+ return null;
174
+ }
175
+ return walkPath(next, rest);
176
+ }
177
+ function checkCollectionBounds(collection) {
178
+ collectionConfigs(collection).forEach(({ min, max }) => {
179
+ if (typeof min !== 'number' || typeof max !== 'number') {
180
+ return;
181
+ }
182
+ if (min < 0 || max <= 0 || max < min) {
183
+ errors.invalidCollectionBounds(collection.name, min, max);
184
+ }
185
+ });
186
+ }
161
187
  }
162
- function collectionConfigs(collection, location) {
188
+ function collectionConfigs(collection) {
163
189
  const { payload } = collection;
164
- const payloadLocation = [...location, 'payload'];
165
190
  if (check.is(BuilderCollectionConfigSerialisedSchema, payload)) {
166
- return [[payload, payloadLocation]];
191
+ return [payload];
167
192
  }
168
193
  if (!check.is(BuilderWhenSerialisedSchema, payload)) {
169
194
  return [];
170
195
  }
171
196
  if (payload.type === 'match') {
172
- return Object.entries(payload.selectMap).flatMap(([key, value]) => check.is(BuilderCollectionConfigSerialisedSchema, value)
173
- ? [[value, [...payloadLocation, 'selectMap', key]]]
197
+ return Object.entries(payload.selectMap).flatMap(([, value]) => check.is(BuilderCollectionConfigSerialisedSchema, value)
198
+ ? [value]
174
199
  : []);
175
200
  }
176
201
  return check.is(BuilderCollectionConfigSerialisedSchema, payload.payload)
177
- ? [[payload.payload, [...payloadLocation, 'payload']]]
202
+ ? [payload.payload]
178
203
  : [];
179
204
  }
180
- function extractWhenPaths(payload, location) {
181
- if (!check.is(BuilderWhenSerialisedSchema, payload)) {
182
- return [];
183
- }
184
- if (payload.type === 'match' && Array.isArray(payload.matchPath)) {
185
- return [[payload.matchPath, [...location, 'payload', 'matchPath']]];
186
- }
187
- if (payload.type === 'unless' && Array.isArray(payload.unlessPath)) {
188
- return [[payload.unlessPath, [...location, 'payload', 'unlessPath']]];
189
- }
190
- return [];
191
- }
192
- function walkPath(candidates, remaining, entryName, fullPath, location) {
193
- if (remaining.length === 0) {
194
- return [candidates, []];
195
- }
196
- const [collectionName, indexValue, ...rest] = remaining;
197
- if (typeof collectionName !== 'string' || typeof indexValue !== 'number') {
198
- return [[], [builderErrorInvalidPath('shape', location)]];
199
- }
200
- const [next, anyOutOfBounds] = candidates.reduce(([accumulator, anyOutOfBoundsAccumulator], current) => {
201
- const collection = current.collections.find(({ name }) => name === collectionName);
202
- if (collection == null) {
203
- return [accumulator, anyOutOfBoundsAccumulator];
204
- }
205
- const configs = collectionConfigs(collection, []);
206
- const withinBounds = configs.filter(([{ max }]) => typeof max !== 'number' || indexValue < max);
207
- if (withinBounds.length === 0 && configs.length > 0) {
208
- return [accumulator, true];
209
- }
210
- const reached = withinBounds.flatMap(([{ model: configModel }]) => check.is(BuilderModelSerialisedSchema, configModel) ? [configModel] : []);
211
- return [[...accumulator, ...reached], anyOutOfBoundsAccumulator];
212
- }, [[], false]);
213
- if (anyOutOfBounds) {
214
- return [[], [builderErrorInvalidPath('out-of-bounds', location)]];
215
- }
216
- if (next.length === 0) {
217
- return [[], [builderErrorInvalidPath('missing-collection', location)]];
218
- }
219
- return walkPath(next, rest, entryName, fullPath, location);
220
- }
@@ -1,16 +1,4 @@
1
1
  import type { BuilderBindings, BuilderRefEntities } from '../entities/index';
2
- import type { BuilderErrorLocation, BuilderErrors } from '../exception';
3
- export declare function builderErrorUnboundParameter(name: string, location?: BuilderErrorLocation): {
4
- name: string;
5
- kind: "unbound-parameter";
6
- location: BuilderErrorLocation;
7
- };
8
- export type BuilderErrorUnboundParameter = ReturnType<typeof builderErrorUnboundParameter>;
9
- export declare function builderErrorMissingReference(id: string, location?: BuilderErrorLocation): {
10
- id: string;
11
- kind: "missing-reference";
12
- location: BuilderErrorLocation;
13
- };
14
- export type BuilderErrorMissingReference = ReturnType<typeof builderErrorMissingReference>;
15
- export type BuilderResolve = (value: unknown, location: BuilderErrorLocation) => readonly [unknown, BuilderErrors];
16
- export declare function resolver(references?: BuilderRefEntities, bindings?: BuilderBindings): BuilderResolve;
2
+ import type { BuilderValidateErrors } from './errors';
3
+ export type BuilderResolve = (value: unknown) => unknown;
4
+ export declare function resolver(errors: BuilderValidateErrors, references?: BuilderRefEntities, bindings?: BuilderBindings): BuilderResolve;
@@ -1,91 +1,86 @@
1
1
  import { check } from '../check.js';
2
- import { BuilderCollectionConfigSerialisedSchema, BuilderWhenSerialisedSchema } from '../entities/index.js';
3
- import { builderError } from '../exception.js';
2
+ import { BuilderCollectionConfigSerialisedSchema, BuilderModelSerialisedSchema, BuilderWhenSerialisedSchema } from '../entities/index.js';
4
3
  import { BuilderParameterSerialisedSchema, BuilderRefSerialisedSchema } from '../references.js';
5
- export function builderErrorUnboundParameter(name, location = []) {
6
- return { ...builderError('unbound-parameter', location), name };
7
- }
8
- export function builderErrorMissingReference(id, location = []) {
9
- return { ...builderError('missing-reference', location), id };
10
- }
11
- export function resolver(references = [], bindings = {}) {
12
- function resolve(value, location) {
4
+ import { validateModelStructure } from './model.js';
5
+ export function resolver(errors, references = [], bindings = {}) {
6
+ function resolve(value) {
13
7
  if (check.is(BuilderParameterSerialisedSchema, value)) {
14
- return resolveParameter(value, location);
8
+ return resolveParameter(value);
15
9
  }
16
10
  if (check.is(BuilderRefSerialisedSchema, value)) {
17
- return resolveReference(value, location);
11
+ return resolveReference(value);
18
12
  }
19
13
  if (check.is(BuilderWhenSerialisedSchema, value)) {
20
- return resolveWhen(value, location);
14
+ return resolveWhen(value);
21
15
  }
22
16
  if (check.is(BuilderCollectionConfigSerialisedSchema, value)) {
23
- return resolveCollectionConfig(value, location);
24
- }
25
- return [value, []];
26
- }
27
- function resolveReference(reference, location) {
28
- const found = references.find((entry) => entry.id === reference.id);
29
- if (found == null) {
30
- return [reference, [builderErrorMissingReference(reference.id, location)]];
31
- }
32
- return [reference, []];
33
- }
34
- function resolveParameter(parameter, location) {
35
- if (!(parameter.name in bindings)) {
36
- return [null, [builderErrorUnboundParameter(parameter.name, location)]];
37
- }
38
- const binding = bindings[parameter.name];
39
- if (!check.is(BuilderRefSerialisedSchema, binding)) {
40
- return [binding, []];
41
- }
42
- const refTarget = references.find((entry) => entry.id === binding.id);
43
- const errors = refTarget == null ? [builderErrorMissingReference(binding.id, location)] : [];
44
- return [binding, errors];
45
- }
46
- function resolveWhen(when, location) {
47
- if (when.type === 'enable') {
48
- const [payload, errors] = resolve(when.payload, [...location, 'payload']);
49
- return [{ ...when, payload: payload ?? when.payload }, errors];
17
+ return resolveCollectionConfig(value);
50
18
  }
51
- if (when.type === 'unless') {
52
- const [unlessPath, unlessErrors] = resolve(when.unlessPath, [...location, 'unlessPath']);
53
- const [payload, payloadErrors] = resolve(when.payload, [...location, 'payload']);
54
- return [
55
- { ...when, unlessPath: unlessPath ?? when.unlessPath, payload: payload ?? when.payload },
56
- [...unlessErrors, ...payloadErrors]
57
- ];
19
+ return value;
20
+ function resolveReference(reference) {
21
+ const found = references.find((entry) => entry.id === reference.id);
22
+ if (found == null) {
23
+ errors.missingReference(reference.id);
24
+ return reference;
25
+ }
26
+ return found.serialised;
58
27
  }
59
- const [matchPath, matchErrors] = resolve(when.matchPath, [...location, 'matchPath']);
60
- const [map, mapErrors] = resolve(when.selectMap, [...location, 'selectMap']);
61
- if (map == null || typeof map !== 'object') {
62
- return [{ ...when, matchPath: matchPath ?? when.matchPath }, [...matchErrors, ...mapErrors]];
28
+ function resolveParameter(parameter) {
29
+ if (!(parameter.name in bindings)) {
30
+ errors.unboundParameter(parameter.name);
31
+ return null;
32
+ }
33
+ const binding = bindings[parameter.name];
34
+ if (!check.is(BuilderRefSerialisedSchema, binding)) {
35
+ return binding;
36
+ }
37
+ return resolveReference(binding);
63
38
  }
64
- const [selectMap, entryErrors] = Object.entries(map).reduce(([values, errors], [key, entryValue]) => {
65
- if (entryValue == null) {
66
- return [{ ...values, [key]: null }, errors];
39
+ function resolveWhen(when) {
40
+ if (when.type === 'enable') {
41
+ const payload = errors.scope('payload', () => resolve(when.payload));
42
+ return { ...when, payload: payload ?? when.payload };
67
43
  }
68
- const [resolved, resolvedErrors] = resolve(entryValue, [...location, 'selectMap', key]);
69
- return [{ ...values, [key]: resolved ?? entryValue }, [...errors, ...resolvedErrors]];
70
- }, [{}, []]);
71
- return [
72
- { ...when, matchPath: matchPath ?? when.matchPath, selectMap },
73
- [...matchErrors, ...mapErrors, ...entryErrors]
74
- ];
75
- }
76
- function resolveCollectionConfig(config, location) {
77
- const [model, modelErrors] = resolve(config.model, [...location, 'model']);
78
- const [min, minErrors] = resolve(config.min, [...location, 'min']);
79
- const [max, maxErrors] = resolve(config.max, [...location, 'max']);
80
- return [
81
- {
44
+ if (when.type === 'unless') {
45
+ const unlessPath = errors.scope('unlessPath', () => resolve(when.unlessPath));
46
+ const payload = errors.scope('payload', () => resolve(when.payload));
47
+ return {
48
+ ...when,
49
+ unlessPath: unlessPath ?? when.unlessPath,
50
+ payload: payload ?? when.payload
51
+ };
52
+ }
53
+ const matchPath = errors.scope('matchPath', () => resolve(when.matchPath));
54
+ const map = errors.scope('selectMap', () => resolve(when.selectMap));
55
+ if (map == null || typeof map !== 'object') {
56
+ return { ...when, matchPath: matchPath ?? when.matchPath };
57
+ }
58
+ const selectMap = errors.scope('selectMap', () => Object.entries(map).reduce((values, [key, entryValue]) => {
59
+ if (entryValue == null) {
60
+ return { ...values, [key]: null };
61
+ }
62
+ const resolved = errors.scope(key, () => resolve(entryValue));
63
+ return { ...values, [key]: resolved ?? entryValue };
64
+ }, {}));
65
+ return { ...when, matchPath: matchPath ?? when.matchPath, selectMap };
66
+ }
67
+ function resolveCollectionConfig(config) {
68
+ const model = errors.scope('model', () => {
69
+ const resolved = resolve(config.model);
70
+ if (check.is(BuilderModelSerialisedSchema, resolved)) {
71
+ return validateModelStructure(resolved, resolve, errors);
72
+ }
73
+ return resolved;
74
+ });
75
+ const min = errors.scope('min', () => resolve(config.min));
76
+ const max = errors.scope('max', () => resolve(config.max));
77
+ return {
82
78
  ...config,
83
79
  model: model ?? config.model,
84
80
  min: min ?? config.min,
85
81
  max: max ?? config.max
86
- },
87
- [...modelErrors, ...minErrors, ...maxErrors]
88
- ];
82
+ };
83
+ }
89
84
  }
90
85
  return resolve;
91
86
  }
@@ -1,8 +1,2 @@
1
- import type { BuilderErrorLocation, BuilderErrors } from '../exception';
1
+ import type { BuilderErrors } from '../exception';
2
2
  export type ValidationResult<Entity> = readonly [Entity, BuilderErrors];
3
- export declare function builderErrorInvalidInput(target: 'builder' | 'model' | 'ui' | 'variants', location?: BuilderErrorLocation): {
4
- target: "builder" | "model" | "ui" | "variants";
5
- kind: "invalid-input";
6
- location: BuilderErrorLocation;
7
- };
8
- export type BuilderErrorInvalidInput = ReturnType<typeof builderErrorInvalidInput>;
@@ -1,4 +1 @@
1
- import { builderError } from '../exception.js';
2
- export function builderErrorInvalidInput(target, location = []) {
3
- return { ...builderError('invalid-input', location), target };
4
- }
1
+ export {};
@@ -1,8 +1,8 @@
1
1
  import type { BuilderModelSerialised, BuilderRefEntities, BuilderUISerialised, BuilderUIValidated } from '../entities/index';
2
- import type { BuilderErrorLocation, BuilderErrors } from '../exception';
3
2
  import type { BuilderResolve } from './resolve';
4
3
  import type { ValidationResult } from './result';
4
+ import { BuilderValidateErrors } from './errors.js';
5
5
  export type BuilderUIValidationResult = ValidationResult<BuilderUIValidated>;
6
- export declare function validateUI(input: unknown, references?: BuilderRefEntities): BuilderUIValidationResult;
7
- export declare function validateUIStructure(ui: BuilderUISerialised, resolve: BuilderResolve, location: BuilderErrorLocation): readonly [BuilderUIValidated, BuilderErrors];
8
- export declare function checkUIExpectations(mergedModel: BuilderModelSerialised, ui: BuilderUIValidated, location: BuilderErrorLocation): BuilderErrors;
6
+ export declare function validateUI(input: unknown, references?: BuilderRefEntities, errors?: BuilderValidateErrors): BuilderUIValidationResult;
7
+ export declare function validateUIStructure(ui: BuilderUISerialised, resolve: BuilderResolve, errors: BuilderValidateErrors): BuilderUIValidated;
8
+ export declare function checkUIExpectations(mergedModel: BuilderModelSerialised, ui: BuilderUIValidated, errors: BuilderValidateErrors): void;