@builder-builder/builder 0.0.9 → 0.0.11

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 (191) hide show
  1. package/dist/bb.d.ts +28 -0
  2. package/dist/bb.js +49 -0
  3. package/dist/check.d.ts +1 -1
  4. package/dist/check.js +4 -2
  5. package/dist/entities/builder/bind.d.ts +4 -0
  6. package/dist/entities/builder/builder.d.ts +17 -490
  7. package/dist/entities/builder/builder.js +19 -76
  8. package/dist/entities/builder/factory.d.ts +7 -0
  9. package/dist/entities/builder/factory.js +4 -0
  10. package/dist/entities/builder/index.d.ts +3 -6
  11. package/dist/entities/builder/index.js +1 -2
  12. package/dist/entities/collection/collection.d.ts +71 -20
  13. package/dist/entities/collection/collection.js +10 -27
  14. package/dist/entities/collection/config.d.ts +40 -1049
  15. package/dist/entities/collection/config.js +11 -11
  16. package/dist/entities/collection/expectation.d.ts +8 -4
  17. package/dist/entities/collection/index.d.ts +3 -3
  18. package/dist/entities/collection/index.js +1 -1
  19. package/dist/entities/collection/when.d.ts +6 -5
  20. package/dist/entities/component/component.d.ts +272 -300
  21. package/dist/entities/component/component.js +9 -8
  22. package/dist/entities/component/details.d.ts +35 -60
  23. package/dist/entities/component/details.js +9 -9
  24. package/dist/entities/component/expectation.d.ts +7 -4
  25. package/dist/entities/component/expectation.js +0 -3
  26. package/dist/entities/component/field.d.ts +50 -0
  27. package/dist/entities/component/field.js +41 -0
  28. package/dist/entities/component/index.d.ts +5 -3
  29. package/dist/entities/component/index.js +3 -2
  30. package/dist/entities/component/when.d.ts +5 -5
  31. package/dist/entities/component/when.js +2 -2
  32. package/dist/entities/entry.d.ts +4 -0
  33. package/dist/entities/expectation.d.ts +10 -22
  34. package/dist/entities/expectation.js +2 -7
  35. package/dist/entities/index.d.ts +21 -16
  36. package/dist/entities/index.js +10 -8
  37. package/dist/entities/kind.d.ts +9 -0
  38. package/dist/entities/kind.js +5 -0
  39. package/dist/entities/model/bind.d.ts +83 -0
  40. package/dist/entities/model/expectation.d.ts +16 -0
  41. package/dist/entities/model/index.d.ts +7 -0
  42. package/dist/entities/model/index.js +2 -0
  43. package/dist/entities/model/methods.d.ts +57 -0
  44. package/dist/entities/{builder → model}/methods.js +8 -8
  45. package/dist/entities/model/model.d.ts +39 -0
  46. package/dist/entities/model/model.js +73 -0
  47. package/dist/entities/model/models.d.ts +39 -0
  48. package/dist/entities/model/models.js +14 -0
  49. package/dist/entities/{builder → model}/state.d.ts +10 -10
  50. package/dist/entities/option/index.d.ts +2 -2
  51. package/dist/entities/option/index.js +2 -2
  52. package/dist/entities/option/option.d.ts +216 -214
  53. package/dist/entities/option/option.js +9 -8
  54. package/dist/entities/option/select.d.ts +2 -12
  55. package/dist/entities/option/select.js +6 -3
  56. package/dist/entities/option/toggle.d.ts +2 -9
  57. package/dist/entities/option/toggle.js +3 -3
  58. package/dist/entities/option/values.d.ts +4 -24
  59. package/dist/entities/option/values.js +10 -4
  60. package/dist/entities/option/when.d.ts +6 -5
  61. package/dist/entities/refs.d.ts +6 -0
  62. package/dist/entities/refs.js +1 -0
  63. package/dist/entities/serialise.d.ts +393 -3568
  64. package/dist/entities/serialise.js +160 -31
  65. package/dist/entities/ui/describe.d.ts +23 -57
  66. package/dist/entities/ui/describe.js +4 -5
  67. package/dist/entities/ui/index.d.ts +4 -9
  68. package/dist/entities/ui/index.js +2 -4
  69. package/dist/entities/ui/page.d.ts +23 -57
  70. package/dist/entities/ui/page.js +4 -5
  71. package/dist/entities/ui/pages.d.ts +18 -403
  72. package/dist/entities/ui/pages.js +7 -7
  73. package/dist/entities/ui/ui.d.ts +22 -1575
  74. package/dist/entities/ui/ui.js +15 -28
  75. package/dist/entities/ui/uis.d.ts +5 -9
  76. package/dist/entities/ui/uis.js +12 -19
  77. package/dist/entities/validated.d.ts +35 -0
  78. package/dist/entities/validated.js +1 -0
  79. package/dist/entities/when.d.ts +79 -70
  80. package/dist/entities/when.js +11 -7
  81. package/dist/environment.d.ts +5 -0
  82. package/dist/environment.js +2 -0
  83. package/dist/exception.d.ts +8 -3
  84. package/dist/exception.js +3 -0
  85. package/dist/index.d.ts +21 -24
  86. package/dist/index.js +5 -10
  87. package/dist/instance.d.ts +56 -0
  88. package/dist/instance.js +10 -0
  89. package/dist/mappers/index.d.ts +7 -5
  90. package/dist/mappers/index.js +5 -3
  91. package/dist/mappers/instance.d.ts +3 -0
  92. package/dist/mappers/instance.js +35 -0
  93. package/dist/mappers/order.d.ts +6 -0
  94. package/dist/mappers/order.js +22 -0
  95. package/dist/mappers/render/pages.d.ts +4 -4
  96. package/dist/mappers/render/render.d.ts +2 -3
  97. package/dist/mappers/render/render.js +83 -78
  98. package/dist/mappers/resolve.d.ts +5 -9
  99. package/dist/mappers/resolve.js +25 -33
  100. package/dist/mappers/variants/index.d.ts +1 -0
  101. package/dist/mappers/variants/index.js +1 -0
  102. package/dist/mappers/variants/option-graph.d.ts +19 -0
  103. package/dist/mappers/{models → variants}/option-graph.js +33 -22
  104. package/dist/mappers/variants/variants.d.ts +3 -0
  105. package/dist/mappers/variants/variants.js +57 -0
  106. package/dist/private.d.ts +4 -0
  107. package/dist/private.js +4 -0
  108. package/dist/references.d.ts +27 -36
  109. package/dist/references.js +19 -12
  110. package/dist/serialisable.d.ts +1 -9
  111. package/dist/serialisable.js +2 -3
  112. package/dist/validate/brand.d.ts +14 -0
  113. package/dist/validate/brand.js +17 -0
  114. package/dist/validate/builder.d.ts +4 -0
  115. package/dist/validate/builder.js +27 -0
  116. package/dist/validate/expectations.d.ts +10 -0
  117. package/dist/validate/expectations.js +12 -0
  118. package/dist/validate/index.d.ts +18 -0
  119. package/dist/validate/index.js +9 -0
  120. package/dist/validate/instance.d.ts +19 -0
  121. package/dist/validate/instance.js +46 -0
  122. package/dist/validate/model.d.ts +36 -0
  123. package/dist/validate/model.js +196 -0
  124. package/dist/validate/resolve.d.ts +16 -0
  125. package/dist/validate/resolve.js +91 -0
  126. package/dist/validate/result.d.ts +8 -0
  127. package/dist/validate/result.js +4 -0
  128. package/dist/validate/ui.d.ts +8 -0
  129. package/dist/validate/ui.js +77 -0
  130. package/dist/validate/variants.d.ts +59 -0
  131. package/dist/validate/variants.js +102 -0
  132. package/package.json +12 -9
  133. package/dist/entities/builder/builders.d.ts +0 -20
  134. package/dist/entities/builder/builders.js +0 -18
  135. package/dist/entities/builder/expectation.d.ts +0 -12
  136. package/dist/entities/builder/methods.d.ts +0 -58
  137. package/dist/entities/builder/parameter.d.ts +0 -62
  138. package/dist/entities/builder/parameter.js +0 -18
  139. package/dist/entities/builder/validate.d.ts +0 -3
  140. package/dist/entities/builder/validate.js +0 -108
  141. package/dist/entities/errors.d.ts +0 -21
  142. package/dist/entities/ui/label.d.ts +0 -18
  143. package/dist/entities/ui/label.js +0 -12
  144. package/dist/entities/ui/parameter.d.ts +0 -7
  145. package/dist/entities/ui/parameter.js +0 -29
  146. package/dist/entities/ui/validate.d.ts +0 -8
  147. package/dist/entities/ui/validate.js +0 -21
  148. package/dist/entities/validate.d.ts +0 -28
  149. package/dist/mappers/assert/builder.d.ts +0 -2
  150. package/dist/mappers/assert/builder.js +0 -46
  151. package/dist/mappers/assert/expectation.d.ts +0 -2
  152. package/dist/mappers/assert/expectation.js +0 -23
  153. package/dist/mappers/assert/index.d.ts +0 -6
  154. package/dist/mappers/assert/index.js +0 -4
  155. package/dist/mappers/assert/model.d.ts +0 -13
  156. package/dist/mappers/assert/model.js +0 -47
  157. package/dist/mappers/assert/models.d.ts +0 -33
  158. package/dist/mappers/assert/models.js +0 -74
  159. package/dist/mappers/assert/ui.d.ts +0 -2
  160. package/dist/mappers/assert/ui.js +0 -22
  161. package/dist/mappers/instance/index.d.ts +0 -1
  162. package/dist/mappers/instance/index.js +0 -1
  163. package/dist/mappers/instance/instance.d.ts +0 -4
  164. package/dist/mappers/instance/instance.js +0 -33
  165. package/dist/mappers/models/component-graph.d.ts +0 -9
  166. package/dist/mappers/models/component-graph.js +0 -51
  167. package/dist/mappers/models/graph.d.ts +0 -12
  168. package/dist/mappers/models/graph.js +0 -17
  169. package/dist/mappers/models/index.d.ts +0 -1
  170. package/dist/mappers/models/index.js +0 -1
  171. package/dist/mappers/models/models.d.ts +0 -3
  172. package/dist/mappers/models/models.js +0 -37
  173. package/dist/mappers/models/option-graph.d.ts +0 -9
  174. package/dist/mappers/order/index.d.ts +0 -2
  175. package/dist/mappers/order/index.js +0 -1
  176. package/dist/mappers/order/order.d.ts +0 -14
  177. package/dist/mappers/order/order.js +0 -31
  178. package/dist/model.d.ts +0 -14
  179. package/dist/walker/index.d.ts +0 -2
  180. package/dist/walker/index.js +0 -1
  181. package/dist/walker/walkable.d.ts +0 -4
  182. package/dist/walker/walkable.js +0 -4
  183. package/dist/walker/walker.d.ts +0 -18
  184. package/dist/walker/walker.js +0 -103
  185. package/dist/walker/walkers.d.ts +0 -8
  186. package/dist/walker/walkers.js +0 -51
  187. /package/dist/entities/builder/{expectation.js → bind.js} +0 -0
  188. /package/dist/entities/{builder/state.js → entry.js} +0 -0
  189. /package/dist/entities/{errors.js → model/bind.js} +0 -0
  190. /package/dist/entities/{validate.js → model/expectation.js} +0 -0
  191. /package/dist/{model.js → entities/model/state.js} +0 -0
@@ -0,0 +1,196 @@
1
+ import { check } from '../check.js';
2
+ import { BuilderCollectionConfigSerialisedSchema, BuilderModelSerialisedSchema, BuilderWhenSerialisedSchema, model, modelsMerge, serialise } from '../entities/index.js';
3
+ import { builderError } from '../exception.js';
4
+ import { validate } from './brand.js';
5
+ import { checkExpectations } from './expectations.js';
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
+ const EMPTY_MODEL = validate(serialise.model(model()));
21
+ export function validateModel(input, references = []) {
22
+ if (!check.is(BuilderModelSerialisedSchema, input)) {
23
+ return [EMPTY_MODEL, [builderErrorInvalidInput('model')]];
24
+ }
25
+ const resolve = resolver(references);
26
+ const [structure, structureErrors] = validateModelStructure(input, resolve, []);
27
+ const data = validate(modelsMerge(structure));
28
+ const expectationErrors = checkModelExpectations(data, structure, []);
29
+ return [data, [...expectationErrors, ...structureErrors]];
30
+ }
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
+ ];
40
+ }
41
+ export function validateModelStructure(input, resolve, location) {
42
+ const [resolved, resolveErrors] = resolve(input, location);
43
+ if (!check.is(BuilderModelSerialisedSchema, resolved)) {
44
+ return [validate(serialise.model(model())), resolveErrors];
45
+ }
46
+ const errors = [...resolveErrors];
47
+ 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
+ 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
+ collections.forEach((entry, entryIndex) => {
63
+ const collectionLocation = [...location, 'collections', entryIndex];
64
+ errors.push(...checkPaths(entry, collectionLocation), ...checkCollectionBounds(entry, collectionLocation));
65
+ collectionNestedModels(entry, collectionLocation).forEach(([nested, nestedLocation]) => {
66
+ const [, nestedErrors] = validateModelStructure(nested, resolve, nestedLocation);
67
+ errors.push(...nestedErrors);
68
+ });
69
+ });
70
+ const data = {
71
+ ...modelInput,
72
+ models: childModels,
73
+ options,
74
+ components,
75
+ collections
76
+ };
77
+ return [data, errors];
78
+ }
79
+ function walkEntries(entries, entryKind, resolve, location) {
80
+ const seen = new Set();
81
+ const items = [];
82
+ const errors = [];
83
+ const arrayKey = `${entryKind}s`;
84
+ entries.forEach((entry, entryIndex) => {
85
+ const entryLocation = [...location, arrayKey, entryIndex];
86
+ let paths = entry.paths;
87
+ if (entry.paths != null) {
88
+ const [resolvedPaths, pathErrors] = resolve(entry.paths, [...entryLocation, 'paths']);
89
+ paths = resolvedPaths ?? entry.paths;
90
+ errors.push(...pathErrors);
91
+ }
92
+ const [payload, payloadErrors] = resolve(entry.payload, [...entryLocation, 'payload']);
93
+ errors.push(...payloadErrors);
94
+ const resolved = { ...entry, paths, payload: payload ?? entry.payload };
95
+ if (seen.has(resolved.name)) {
96
+ errors.push(builderErrorDuplicateName(resolved.name, entryLocation));
97
+ return;
98
+ }
99
+ seen.add(resolved.name);
100
+ items.push(resolved);
101
+ });
102
+ return [items, errors];
103
+ }
104
+ function checkEntryPaths(modelInput, entry, location) {
105
+ const { name, paths, payload } = entry;
106
+ const directPaths = Array.isArray(paths)
107
+ ? paths.map((path, pathIndex) => [path, [...location, 'paths', pathIndex]])
108
+ : [];
109
+ const extractWhenPathsList = extractWhenPaths(payload, location);
110
+ return [...directPaths, ...extractWhenPathsList].flatMap(([path, pathLocation]) => {
111
+ const [reached, walkErrors] = walkPath([modelInput], path.slice(0, -1), name, path, pathLocation);
112
+ if (walkErrors.length > 0) {
113
+ return walkErrors;
114
+ }
115
+ const optionName = path.at(-1);
116
+ if (!reached.some((candidate) => candidate.options.some((option) => option.name === optionName))) {
117
+ return [builderErrorInvalidPath('option-not-found', pathLocation)];
118
+ }
119
+ return [];
120
+ });
121
+ }
122
+ function checkCollectionBounds(collection, location) {
123
+ return collectionConfigs(collection, location).flatMap(([{ min, max }]) => {
124
+ if (typeof min !== 'number' || typeof max !== 'number') {
125
+ return [];
126
+ }
127
+ if (min < 0 || max <= 0 || max < min) {
128
+ return [builderErrorInvalidCollectionBounds(collection.name, min, max, location)];
129
+ }
130
+ return [];
131
+ });
132
+ }
133
+ function collectionNestedModels(collection, location) {
134
+ return collectionConfigs(collection, location).flatMap(([config, configLocation]) => check.is(BuilderModelSerialisedSchema, config.model)
135
+ ? [[config.model, [...configLocation, 'model']]]
136
+ : []);
137
+ }
138
+ function collectionConfigs(collection, location) {
139
+ const { payload } = collection;
140
+ const payloadLocation = [...location, 'payload'];
141
+ if (check.is(BuilderCollectionConfigSerialisedSchema, payload)) {
142
+ return [[payload, payloadLocation]];
143
+ }
144
+ if (!check.is(BuilderWhenSerialisedSchema, payload)) {
145
+ return [];
146
+ }
147
+ if (payload.type === 'match') {
148
+ return Object.entries(payload.selectMap).flatMap(([key, value]) => check.is(BuilderCollectionConfigSerialisedSchema, value)
149
+ ? [[value, [...payloadLocation, 'selectMap', key]]]
150
+ : []);
151
+ }
152
+ return check.is(BuilderCollectionConfigSerialisedSchema, payload.payload)
153
+ ? [[payload.payload, [...payloadLocation, 'payload']]]
154
+ : [];
155
+ }
156
+ function extractWhenPaths(payload, location) {
157
+ if (!check.is(BuilderWhenSerialisedSchema, payload)) {
158
+ return [];
159
+ }
160
+ if (payload.type === 'match' && Array.isArray(payload.matchPath)) {
161
+ return [[payload.matchPath, [...location, 'payload', 'matchPath']]];
162
+ }
163
+ if (payload.type === 'unless' && Array.isArray(payload.unlessPath)) {
164
+ return [[payload.unlessPath, [...location, 'payload', 'unlessPath']]];
165
+ }
166
+ return [];
167
+ }
168
+ function walkPath(candidates, remaining, entryName, fullPath, location) {
169
+ if (remaining.length === 0) {
170
+ return [candidates, []];
171
+ }
172
+ const [collectionName, indexValue, ...rest] = remaining;
173
+ if (typeof collectionName !== 'string' || typeof indexValue !== 'number') {
174
+ return [[], [builderErrorInvalidPath('shape', location)]];
175
+ }
176
+ const [next, anyOutOfBounds] = candidates.reduce(([accumulator, anyOutOfBoundsAccumulator], current) => {
177
+ const collection = current.collections.find(({ name }) => name === collectionName);
178
+ if (collection == null) {
179
+ return [accumulator, anyOutOfBoundsAccumulator];
180
+ }
181
+ const configs = collectionConfigs(collection, []);
182
+ const withinBounds = configs.filter(([{ max }]) => typeof max !== 'number' || indexValue < max);
183
+ if (withinBounds.length === 0 && configs.length > 0) {
184
+ return [accumulator, true];
185
+ }
186
+ const reached = withinBounds.flatMap(([{ model: configModel }]) => check.is(BuilderModelSerialisedSchema, configModel) ? [configModel] : []);
187
+ return [[...accumulator, ...reached], anyOutOfBoundsAccumulator];
188
+ }, [[], false]);
189
+ if (anyOutOfBounds) {
190
+ return [[], [builderErrorInvalidPath('out-of-bounds', location)]];
191
+ }
192
+ if (next.length === 0) {
193
+ return [[], [builderErrorInvalidPath('missing-collection', location)]];
194
+ }
195
+ return walkPath(next, rest, entryName, fullPath, location);
196
+ }
@@ -0,0 +1,16 @@
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;
@@ -0,0 +1,91 @@
1
+ import { check } from '../check.js';
2
+ import { BuilderCollectionConfigSerialisedSchema, BuilderWhenSerialisedSchema } from '../entities/index.js';
3
+ import { builderError } from '../exception.js';
4
+ 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) {
13
+ if (check.is(BuilderParameterSerialisedSchema, value)) {
14
+ return resolveParameter(value, location);
15
+ }
16
+ if (check.is(BuilderRefSerialisedSchema, value)) {
17
+ return resolveReference(value, location);
18
+ }
19
+ if (check.is(BuilderWhenSerialisedSchema, value)) {
20
+ return resolveWhen(value, location);
21
+ }
22
+ 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];
50
+ }
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
+ ];
58
+ }
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]];
63
+ }
64
+ const [selectMap, entryErrors] = Object.entries(map).reduce(([values, errors], [key, entryValue]) => {
65
+ if (entryValue == null) {
66
+ return [{ ...values, [key]: null }, errors];
67
+ }
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
+ {
82
+ ...config,
83
+ model: model ?? config.model,
84
+ min: min ?? config.min,
85
+ max: max ?? config.max
86
+ },
87
+ [...modelErrors, ...minErrors, ...maxErrors]
88
+ ];
89
+ }
90
+ return resolve;
91
+ }
@@ -0,0 +1,8 @@
1
+ import type { BuilderErrorLocation, BuilderErrors } from '../exception';
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>;
@@ -0,0 +1,4 @@
1
+ import { builderError } from '../exception.js';
2
+ export function builderErrorInvalidInput(target, location = []) {
3
+ return { ...builderError('invalid-input', location), target };
4
+ }
@@ -0,0 +1,8 @@
1
+ import type { BuilderModelSerialised, BuilderRefEntities, BuilderUISerialised, BuilderUIValidated } from '../entities/index';
2
+ import type { BuilderErrorLocation, BuilderErrors } from '../exception';
3
+ import type { BuilderResolve } from './resolve';
4
+ import type { ValidationResult } from './result';
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;
@@ -0,0 +1,77 @@
1
+ import { check } from '../check.js';
2
+ import { BuilderUISerialisedSchema, serialise, uis } from '../entities/index.js';
3
+ import { validate } from './brand.js';
4
+ import { checkExpectations } from './expectations.js';
5
+ import { resolver } from './resolve.js';
6
+ import { builderErrorInvalidInput } from './result.js';
7
+ export function validateUI(input, references = []) {
8
+ if (!check.is(BuilderUISerialisedSchema, input)) {
9
+ const errors = [builderErrorInvalidInput('ui')];
10
+ return [validate(serialise.ui(uis())), errors];
11
+ }
12
+ const resolve = resolver(references);
13
+ const [structure, errors] = validateUIStructure(input, resolve, []);
14
+ return [validate(structure), errors];
15
+ }
16
+ export function validateUIStructure(ui, resolve, location) {
17
+ const errors = [];
18
+ const childUIs = ui.uis.flatMap((part, partIndex) => {
19
+ const partLocation = [...location, 'uis', partIndex];
20
+ const [resolved, partErrors] = resolve(part, partLocation);
21
+ errors.push(...partErrors);
22
+ if (resolved == null || !check.is(BuilderUISerialisedSchema, resolved)) {
23
+ return [];
24
+ }
25
+ const [child, childErrors] = validateUIStructure(resolved, resolve, partLocation);
26
+ errors.push(...childErrors);
27
+ return [child];
28
+ });
29
+ const [items, itemErrors] = walkItems(ui.items, resolve, [...location, 'items']);
30
+ errors.push(...itemErrors);
31
+ const data = {
32
+ ...ui,
33
+ uis: childUIs,
34
+ items
35
+ };
36
+ return [data, errors];
37
+ }
38
+ export function checkUIExpectations(mergedModel, ui, location) {
39
+ return [
40
+ ...checkExpectations(mergedModel, ui.expectations, [...location, 'expectations']),
41
+ ...ui.uis.flatMap((nested, nestedIndex) => checkUIExpectations(mergedModel, nested, [
42
+ ...location,
43
+ 'uis',
44
+ nestedIndex
45
+ ]))
46
+ ];
47
+ }
48
+ function walkItems(items, resolve, location) {
49
+ const errors = [];
50
+ const result = items.map((item, itemIndex) => {
51
+ const itemLocation = [...location, itemIndex];
52
+ const [resolved, resolveErrors] = resolve(item, itemLocation);
53
+ errors.push(...resolveErrors);
54
+ const [walked, walkErrors] = walkUIItem((resolved ?? item), resolve, itemLocation);
55
+ errors.push(...walkErrors);
56
+ return walked;
57
+ });
58
+ return [result, errors];
59
+ }
60
+ function walkUIItem(item, resolve, location) {
61
+ const errors = [];
62
+ const [labelValue, labelErrors] = resolve(item.label, [...location, 'label']);
63
+ errors.push(...labelErrors);
64
+ const label = labelValue ?? item.label;
65
+ if (item.type === 'pages') {
66
+ const [itemsResolved, itemsErrors] = resolve(item.items, [...location, 'items']);
67
+ errors.push(...itemsErrors);
68
+ const resolvedItems = (Array.isArray(itemsResolved) ? itemsResolved : item.items);
69
+ const [innerItems, innerErrors] = walkItems(resolvedItems, resolve, [...location, 'items']);
70
+ errors.push(...innerErrors);
71
+ return [{ ...item, label, items: innerItems }, errors];
72
+ }
73
+ const [pathsValue, pathsErrors] = resolve(item.paths, [...location, 'paths']);
74
+ errors.push(...pathsErrors);
75
+ const paths = pathsValue ?? item.paths;
76
+ return [{ ...item, label, paths }, errors];
77
+ }
@@ -0,0 +1,59 @@
1
+ import type { BuilderComponentVariantsValidated, BuilderModelValidated } from '../entities/index';
2
+ import type { BuilderErrorLocation } from '../exception';
3
+ import type { BuilderInstance } from '../instance';
4
+ import type { ValidationResult } from './result';
5
+ export declare function builderErrorMissingComponent(component: string, location?: BuilderErrorLocation): {
6
+ component: string;
7
+ kind: "missing-component";
8
+ location: BuilderErrorLocation;
9
+ };
10
+ export type BuilderErrorMissingComponent = ReturnType<typeof builderErrorMissingComponent>;
11
+ export declare function builderErrorUnexpectedComponent(component: string, location?: BuilderErrorLocation): {
12
+ component: string;
13
+ kind: "unexpected-component";
14
+ location: BuilderErrorLocation;
15
+ };
16
+ export type BuilderErrorUnexpectedComponent = ReturnType<typeof builderErrorUnexpectedComponent>;
17
+ export declare function builderErrorMissingVariant(component: string, instance: BuilderInstance, location?: BuilderErrorLocation): {
18
+ component: string;
19
+ instance: BuilderInstance;
20
+ kind: "missing-variant";
21
+ location: BuilderErrorLocation;
22
+ };
23
+ export type BuilderErrorMissingVariant = ReturnType<typeof builderErrorMissingVariant>;
24
+ export declare function builderErrorInvalidVariant(component: string, instance: BuilderInstance, location?: BuilderErrorLocation): {
25
+ component: string;
26
+ instance: BuilderInstance;
27
+ kind: "invalid-variant";
28
+ location: BuilderErrorLocation;
29
+ };
30
+ export type BuilderErrorInvalidVariant = ReturnType<typeof builderErrorInvalidVariant>;
31
+ export declare function builderErrorMissingDetail(component: string, detail: string, instance: BuilderInstance, location?: BuilderErrorLocation): {
32
+ component: string;
33
+ detail: string;
34
+ instance: BuilderInstance;
35
+ kind: "missing-detail";
36
+ location: BuilderErrorLocation;
37
+ };
38
+ export type BuilderErrorMissingDetail = ReturnType<typeof builderErrorMissingDetail>;
39
+ export declare function builderErrorUnexpectedDetail(component: string, detail: string, instance: BuilderInstance, location?: BuilderErrorLocation): {
40
+ component: string;
41
+ detail: string;
42
+ instance: BuilderInstance;
43
+ kind: "unexpected-detail";
44
+ location: BuilderErrorLocation;
45
+ };
46
+ export type BuilderErrorUnexpectedDetail = ReturnType<typeof builderErrorUnexpectedDetail>;
47
+ export declare function builderErrorInvalidDetail(component: string, detail: string, instance: BuilderInstance, location?: BuilderErrorLocation): {
48
+ component: string;
49
+ detail: string;
50
+ instance: BuilderInstance;
51
+ kind: "invalid-detail";
52
+ location: BuilderErrorLocation;
53
+ };
54
+ export type BuilderErrorInvalidDetail = ReturnType<typeof builderErrorInvalidDetail>;
55
+ export type BuilderVariantsValidationOptions = {
56
+ readonly partial?: boolean;
57
+ };
58
+ export type BuilderComponentVariantsValidationResult = ValidationResult<BuilderComponentVariantsValidated>;
59
+ export declare function validateVariants(model: BuilderModelValidated, input: unknown, options?: BuilderVariantsValidationOptions): BuilderComponentVariantsValidationResult;
@@ -0,0 +1,102 @@
1
+ import { check } from '../check.js';
2
+ import { detailValueSchema } from '../entities/index.js';
3
+ import { builderError } from '../exception.js';
4
+ import { createVariants, resolveComponent } from '../mappers/index.js';
5
+ import { BuilderComponentVariantsSchema } from '../instance.js';
6
+ import { validate } from './brand.js';
7
+ import { builderErrorInvalidInput } from './result.js';
8
+ export function builderErrorMissingComponent(component, location = []) {
9
+ return { ...builderError('missing-component', location), component };
10
+ }
11
+ export function builderErrorUnexpectedComponent(component, location = []) {
12
+ return { ...builderError('unexpected-component', location), component };
13
+ }
14
+ export function builderErrorMissingVariant(component, instance, location = []) {
15
+ return { ...builderError('missing-variant', location), component, instance };
16
+ }
17
+ export function builderErrorInvalidVariant(component, instance, location = []) {
18
+ return { ...builderError('invalid-variant', location), component, instance };
19
+ }
20
+ export function builderErrorMissingDetail(component, detail, instance, location = []) {
21
+ return { ...builderError('missing-detail', location), component, detail, instance };
22
+ }
23
+ export function builderErrorUnexpectedDetail(component, detail, instance, location = []) {
24
+ return { ...builderError('unexpected-detail', location), component, detail, instance };
25
+ }
26
+ export function builderErrorInvalidDetail(component, detail, instance, location = []) {
27
+ return { ...builderError('invalid-detail', location), component, detail, instance };
28
+ }
29
+ export function validateVariants(model, input, options = {}) {
30
+ if (!check.is(BuilderComponentVariantsSchema, input)) {
31
+ const errors = [builderErrorInvalidInput('variants')];
32
+ return [validate({}), errors];
33
+ }
34
+ const expected = createVariants(model);
35
+ const errors = [
36
+ ...checkVariants(expected, input, options.partial ?? false),
37
+ ...checkUnexpectedComponents(expected, input),
38
+ ...checkInvalidDetails(input, model.components)
39
+ ];
40
+ return [validate(input), errors];
41
+ }
42
+ function checkVariants(expected, variants, partial) {
43
+ return Object.entries(expected).flatMap(([component, expectedVariants]) => {
44
+ const componentVariants = variants[component];
45
+ if (componentVariants == null) {
46
+ return partial ? [] : [builderErrorMissingComponent(component, ['variants'])];
47
+ }
48
+ const expectedKeys = new Map(expectedVariants.map(({ instance }) => [sortedKey(instance), instance]));
49
+ const actualKeys = new Set(componentVariants.map(({ instance }) => sortedKey(instance)));
50
+ return [
51
+ ...componentVariants
52
+ .filter(({ instance }) => !expectedKeys.has(sortedKey(instance)))
53
+ .map((variant) => builderErrorInvalidVariant(component, variant.instance, [
54
+ 'variants',
55
+ component,
56
+ componentVariants.indexOf(variant)
57
+ ])),
58
+ ...[...expectedKeys]
59
+ .filter(([key]) => !actualKeys.has(key))
60
+ .map(([, instance]) => builderErrorMissingVariant(component, instance, ['variants', component]))
61
+ ];
62
+ });
63
+ }
64
+ function checkUnexpectedComponents(expected, variants) {
65
+ return Object.keys(variants)
66
+ .filter((component) => expected[component] == null)
67
+ .map((component) => builderErrorUnexpectedComponent(component, ['variants', component]));
68
+ }
69
+ function checkInvalidDetails(variants, components) {
70
+ const componentByName = new Map(components.map((entry) => [entry.name, entry]));
71
+ return Object.entries(variants).flatMap(([component, componentVariants]) => {
72
+ const entry = componentByName.get(component);
73
+ if (entry == null) {
74
+ return [];
75
+ }
76
+ return componentVariants.flatMap(({ instance, details }, index) => {
77
+ const resolved = resolveComponent(entry, instance);
78
+ const fields = (resolved?.fields ?? []);
79
+ const expectedNames = new Set(fields.map(({ name }) => name));
80
+ const actualDetails = details ?? {};
81
+ const detailsLocation = ['variants', component, index, 'details'];
82
+ const missingOrInvalid = fields.flatMap(({ name, valueType, isOptional }) => {
83
+ const detailLocation = [...detailsLocation, name];
84
+ if (!(name in actualDetails)) {
85
+ return [builderErrorMissingDetail(component, name, instance, detailLocation)];
86
+ }
87
+ if (!check.is(detailValueSchema(valueType, isOptional), actualDetails[name])) {
88
+ return [builderErrorInvalidDetail(component, name, instance, detailLocation)];
89
+ }
90
+ return [];
91
+ });
92
+ const unexpected = Object.keys(actualDetails)
93
+ .filter((detail) => !expectedNames.has(detail))
94
+ .map((detail) => builderErrorUnexpectedDetail(component, detail, instance, [...detailsLocation, detail]));
95
+ return [...missingOrInvalid, ...unexpected];
96
+ });
97
+ });
98
+ }
99
+ function sortedKey(instance) {
100
+ const entries = Object.entries(instance).filter(([, value]) => value != null);
101
+ return JSON.stringify(entries.sort(([keyA], [keyB]) => keyA.localeCompare(keyB)));
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builder-builder/builder",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -23,20 +23,23 @@
23
23
  "build": "vite build",
24
24
  "package": "svelte-kit sync && svelte-package --input src/lib/builder",
25
25
  "preview": "vite preview",
26
- "prepare": "svelte-kit sync || echo ''",
26
+ "prepare": "svelte-kit sync && npm run messages || echo ''",
27
27
  "prepublishOnly": "npm run test && npm run package",
28
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
29
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
28
+ "messages": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/i18n",
29
+ "check": "npm run messages && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
30
+ "check:watch": "npm run messages && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
30
31
  "format": "prettier --write .",
31
32
  "lint": "prettier --check . && eslint .",
32
- "test:unit": "vitest",
33
33
  "test": "npm run test:unit -- --run",
34
+ "test:unit": "npm run messages && vitest --project client --project server",
35
+ "test:integration": "npm run messages && vitest run --project integration",
34
36
  "verify": "npm run test && npm run check && npm run format && npm run lint",
35
37
  "types:db": "dotenv -e .env.development.local -- supabase gen types typescript --project-id \"$SUPABASE_PROJECT_ID\" --schema public > ./src/lib/db/database.types.ts"
36
38
  },
37
39
  "devDependencies": {
38
40
  "@eslint/compat": "^1.2.5",
39
41
  "@eslint/js": "^9.18.0",
42
+ "@inlang/paraglide-js": "^2.16.0",
40
43
  "@sveltejs/adapter-auto": "^6.0.0",
41
44
  "@sveltejs/adapter-vercel": "^5.7.2",
42
45
  "@sveltejs/kit": "^2.16.0",
@@ -44,7 +47,7 @@
44
47
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
45
48
  "@testing-library/jest-dom": "^6.6.3",
46
49
  "@testing-library/svelte": "^5.2.4",
47
- "@vitest/coverage-v8": "^3.1.4",
50
+ "@vitest/coverage-v8": "^4.1.5",
48
51
  "dotenv-cli": "^8.0.0",
49
52
  "eslint": "^9.18.0",
50
53
  "eslint-config-prettier": "^10.0.1",
@@ -54,18 +57,18 @@
54
57
  "jsdom": "^26.0.0",
55
58
  "prettier": "^3.4.2",
56
59
  "prettier-plugin-svelte": "^3.3.3",
57
- "supabase": "^2.33.9",
60
+ "supabase": "^2.93.0",
58
61
  "svelte": "^5.0.0",
59
62
  "svelte-check": "^4.0.0",
60
63
  "typescript": "^5.0.0",
61
64
  "typescript-eslint": "^8.58.2",
62
65
  "vite": "^6.2.6",
63
- "vitest": "^3.0.0"
66
+ "vitest": "^4.1.5"
64
67
  },
65
68
  "dependencies": {
66
69
  "@floating-ui/dom": "^1.7.2",
67
70
  "@lucide/svelte": "^0.525.0",
68
- "@supabase/supabase-js": "^2.49.8",
71
+ "@supabase/supabase-js": "^2.104.0",
69
72
  "@upstash/ratelimit": "^2.0.5",
70
73
  "@upstash/redis": "^1.35.0",
71
74
  "@vercel/kv": "^3.0.0",
@@ -1,20 +0,0 @@
1
- import type { BuilderGeneric, BuilderReferences, BuilderStateOf } from './builder';
2
- import type { BuilderState, BuilderStateEmpty } from './state';
3
- import { Builder } from './builder.js';
4
- export declare function builders<const References extends BuilderReferences>(...builders: References): Builder<BuilderBuildersMerge<References>>;
5
- export declare function mergeBuilderParts(parts: ReadonlyArray<unknown>): {
6
- references: import("../..").BuilderRef[];
7
- options: import("..").BuilderOption<string, import("../..").Refable<import("..").BuilderOptionPayload>, import("../..").Refable<readonly (readonly (string | number)[])[]>>[];
8
- components: import("..").BuilderComponent<string, import("../..").Refable<import("..").BuilderComponentPayload>, import("../..").Refable<readonly (readonly (string | number)[])[]>>[];
9
- collections: import("..").BuilderCollection<string, import("../..").Refable<import("..").BuilderCollectionPayload>, import("../..").Refable<readonly (readonly (string | number)[])[]>>[];
10
- expectations: import("..").BuilderExpectation<string, unknown, "option" | "component" | "collection" | "detail">[];
11
- };
12
- export type BuilderBuildersMerge<References extends BuilderReferences> = References extends readonly [infer Head, ...infer Tail extends BuilderReferences] ? Head extends BuilderGeneric ? BuilderStateMerge<BuilderStateOf<Head>, BuilderBuildersMerge<Tail>> : BuilderBuildersMerge<Tail> : BuilderStateEmpty;
13
- type BuilderStateMerge<Left extends BuilderState, Right extends BuilderState> = {
14
- model: Left['model'] & Right['model'];
15
- references: Left['references'] & Right['references'];
16
- options: [...Left['options'], ...Right['options']];
17
- components: [...Left['components'], ...Right['components']];
18
- collections: [...Left['collections'], ...Right['collections']];
19
- };
20
- export {};