@builder-builder/builder 0.0.24 → 0.0.26

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 (120) hide show
  1. package/dist/client/client.d.ts +3 -1
  2. package/dist/client/client.js +29 -20
  3. package/dist/client/index.d.ts +1 -4
  4. package/dist/client/index.js +1 -2
  5. package/dist/client/public.d.ts +4 -0
  6. package/dist/client/public.js +2 -0
  7. package/dist/client/schema.d.ts +26 -18
  8. package/dist/client/schema.js +7 -1
  9. package/dist/entities/builder/builder.js +2 -1
  10. package/dist/entities/collection/collection.d.ts +2 -2
  11. package/dist/entities/collection/collection.js +2 -1
  12. package/dist/entities/collection/config.d.ts +21 -21
  13. package/dist/entities/collection/config.js +4 -3
  14. package/dist/entities/component/component.d.ts +32 -32
  15. package/dist/entities/component/component.js +2 -1
  16. package/dist/entities/component/config.d.ts +13 -13
  17. package/dist/entities/component/config.js +1 -1
  18. package/dist/entities/component/field.d.ts +4 -4
  19. package/dist/entities/component/field.js +4 -3
  20. package/dist/entities/expectation.d.ts +3 -3
  21. package/dist/entities/expectation.js +2 -1
  22. package/dist/entities/index.d.ts +1 -1
  23. package/dist/entities/kind.d.ts +4 -4
  24. package/dist/entities/kind.js +44 -4
  25. package/dist/entities/model/models.d.ts +3 -3
  26. package/dist/entities/option/config.d.ts +4 -4
  27. package/dist/entities/option/option.d.ts +24 -24
  28. package/dist/entities/option/option.js +2 -1
  29. package/dist/entities/option/select.d.ts +2 -2
  30. package/dist/entities/option/select.js +12 -9
  31. package/dist/entities/option/toggle.d.ts +2 -2
  32. package/dist/entities/option/toggle.js +5 -4
  33. package/dist/entities/paths.d.ts +4 -4
  34. package/dist/entities/paths.js +2 -1
  35. package/dist/entities/pricing/expression.js +5 -4
  36. package/dist/entities/pricing/rates.d.ts +1 -1
  37. package/dist/entities/pricing/rates.js +2 -1
  38. package/dist/entities/references.d.ts +2 -2
  39. package/dist/entities/references.js +2 -1
  40. package/dist/entities/serialise.d.ts +204 -116
  41. package/dist/entities/serialise.js +4 -3
  42. package/dist/entities/tags.d.ts +1 -1
  43. package/dist/entities/tags.js +2 -1
  44. package/dist/entities/ui/describe.d.ts +79 -35
  45. package/dist/entities/ui/describe.js +5 -4
  46. package/dist/entities/ui/input.d.ts +93 -49
  47. package/dist/entities/ui/input.js +5 -4
  48. package/dist/entities/ui/page.d.ts +77 -33
  49. package/dist/entities/ui/page.js +4 -3
  50. package/dist/entities/ui/pages.d.ts +5 -5
  51. package/dist/entities/ui/pages.js +5 -4
  52. package/dist/entities/validated.d.ts +1 -0
  53. package/dist/entities/when.d.ts +16 -16
  54. package/dist/entities/when.js +2 -2
  55. package/dist/errors/check.d.ts +2 -3
  56. package/dist/errors/check.js +5 -10
  57. package/dist/errors/errors.d.ts +241 -158
  58. package/dist/errors/errors.js +149 -173
  59. package/dist/errors/exception.d.ts +6 -4
  60. package/dist/errors/exception.js +11 -66
  61. package/dist/errors/index.d.ts +2 -4
  62. package/dist/errors/index.js +2 -2
  63. package/dist/errors/public.d.ts +2 -0
  64. package/dist/errors/public.js +1 -0
  65. package/dist/index.d.ts +4 -36
  66. package/dist/index.js +4 -19
  67. package/dist/instance.d.ts +7 -7
  68. package/dist/instance.js +4 -4
  69. package/dist/mappers/index.d.ts +1 -1
  70. package/dist/mappers/price.js +2 -3
  71. package/dist/mappers/render/collection.d.ts +9 -0
  72. package/dist/mappers/render/collection.js +27 -0
  73. package/dist/mappers/render/compose.d.ts +5 -0
  74. package/dist/mappers/render/compose.js +15 -0
  75. package/dist/mappers/render/description.d.ts +3 -0
  76. package/dist/mappers/render/description.js +17 -0
  77. package/dist/mappers/render/index.d.ts +3 -1
  78. package/dist/mappers/render/option.d.ts +20 -0
  79. package/dist/mappers/render/option.js +25 -0
  80. package/dist/mappers/render/page.d.ts +11 -0
  81. package/dist/mappers/render/page.js +11 -0
  82. package/dist/mappers/render/paths.d.ts +3 -0
  83. package/dist/mappers/render/paths.js +21 -0
  84. package/dist/mappers/render/render.d.ts +1 -1
  85. package/dist/mappers/render/render.js +33 -98
  86. package/dist/mappers/resolve.d.ts +2 -1
  87. package/dist/mappers/resolve.js +26 -25
  88. package/dist/mappers/variants/option-graph.js +11 -4
  89. package/dist/mappers/variants/variants.js +7 -6
  90. package/dist/primitive.d.ts +5 -0
  91. package/dist/primitive.js +6 -1
  92. package/dist/public.d.ts +37 -0
  93. package/dist/public.js +19 -0
  94. package/dist/references.d.ts +6 -6
  95. package/dist/references.js +3 -2
  96. package/dist/validate/brand.js +2 -4
  97. package/dist/validate/builder.d.ts +2 -2
  98. package/dist/validate/builder.js +3 -3
  99. package/dist/validate/expectations.d.ts +2 -2
  100. package/dist/validate/expectations.js +1 -1
  101. package/dist/validate/index.d.ts +0 -1
  102. package/dist/validate/instance.d.ts +2 -2
  103. package/dist/validate/instance.js +6 -6
  104. package/dist/validate/model.d.ts +4 -4
  105. package/dist/validate/model.js +129 -101
  106. package/dist/validate/paths.d.ts +2 -2
  107. package/dist/validate/paths.js +47 -25
  108. package/dist/validate/pricing.d.ts +4 -4
  109. package/dist/validate/pricing.js +30 -18
  110. package/dist/validate/resolve.d.ts +4 -3
  111. package/dist/validate/resolve.js +88 -63
  112. package/dist/validate/ui.d.ts +4 -4
  113. package/dist/validate/ui.js +17 -16
  114. package/dist/validate/variants.d.ts +2 -2
  115. package/dist/validate/variants.js +16 -16
  116. package/package.json +11 -7
  117. package/dist/mappers/render/pages.d.ts +0 -24
  118. package/dist/mappers/render/pages.js +0 -2
  119. package/dist/private.d.ts +0 -3
  120. package/dist/private.js +0 -3
@@ -1,51 +1,73 @@
1
- import * as v from 'valibot';
2
1
  import { BuilderModelSerialisedSchema } from '../entities/index.js';
3
2
  import { check } from '../errors/index.js';
4
3
  import { resolveCollections } from '../mappers/index.js';
5
- const NumberSchema = v.number();
6
- const StringSchema = v.string();
4
+ import { NameSchema, NumberSchema } from '../primitive.js';
7
5
  export function checkPath(model, path, errors) {
8
- const models = walkPath([model], path.slice(0, -1), errors);
6
+ if (path.length === 0) {
7
+ errors.pathEmpty();
8
+ return;
9
+ }
10
+ const lastIndex = path.length - 1;
11
+ const models = walkPath([model], path.slice(0, -1), 0, errors);
9
12
  if (models == null) {
10
13
  return;
11
14
  }
12
15
  const optionName = path.at(-1);
13
- if (!check.is(StringSchema, optionName)) {
14
- errors.invalidPath('shape');
16
+ if (!check.is(NameSchema, optionName)) {
17
+ errors.scope(lastIndex, () => errors.pathInvalidSegment());
15
18
  return;
16
19
  }
17
- if (!models.some((model) => model.options.some((option) => option.name === optionName))) {
18
- errors.invalidPath('option-not-found');
20
+ if (models.some((model) => model.options.some((option) => option.name === optionName))) {
21
+ return;
22
+ }
23
+ const isCollection = models.some((model) => model.collections.some((entry) => entry.name === optionName));
24
+ if (isCollection) {
25
+ errors.scope(lastIndex, () => errors.pathTargetIsCollection());
26
+ return;
19
27
  }
28
+ const isComponent = models.some((model) => model.components.some((entry) => entry.name === optionName));
29
+ if (isComponent) {
30
+ errors.scope(lastIndex, () => errors.pathTargetIsComponent());
31
+ return;
32
+ }
33
+ errors.scope(lastIndex, () => errors.pathMissingOption());
20
34
  }
21
- function walkPath(candidates, remaining, errors) {
35
+ function walkPath(models, remaining, offset, errors) {
22
36
  if (remaining.length === 0) {
23
- return candidates;
37
+ return models;
24
38
  }
25
39
  const [collectionName, index, ...rest] = remaining;
26
- if (!check.is(StringSchema, collectionName) || !check.is(NumberSchema, index)) {
27
- errors.invalidPath('shape');
40
+ if (!check.is(NameSchema, collectionName)) {
41
+ errors.scope(offset, () => errors.pathInvalidSegment());
42
+ return null;
43
+ }
44
+ if (!check.is(NumberSchema, index)) {
45
+ errors.scope(offset + 1, () => errors.pathInvalidSegment());
28
46
  return null;
29
47
  }
30
- const matchedConfigs = candidates.flatMap((current) => {
31
- const collection = current.collections.find((entry) => entry.name === collectionName);
32
- return collection == null ? [] : [resolveCollections(collection)];
48
+ const collectionConfigs = models.flatMap((model) => {
49
+ const collection = model.collections.find((entry) => entry.name === collectionName);
50
+ return collection == null ? [] : resolveCollections(collection);
33
51
  });
34
- if (matchedConfigs.length === 0) {
35
- errors.invalidPath('missing-collection');
52
+ if (collectionConfigs.length === 0) {
53
+ errors.scope(offset, () => errors.pathMissingCollection());
36
54
  return null;
37
55
  }
38
- const anyOutOfBounds = matchedConfigs.some((configs) => configs.length > 0 && configs.every(({ max }) => check.is(NumberSchema, max) && index >= max));
39
- if (anyOutOfBounds) {
40
- errors.invalidPath('out-of-bounds');
56
+ const indexableConfigs = collectionConfigs.filter(({ max }) => !check.is(NumberSchema, max) || index < max);
57
+ if (indexableConfigs.length === 0) {
58
+ const max = collectionConfigs.reduce((highest, { max }) => {
59
+ if (!check.is(NumberSchema, max)) {
60
+ return highest;
61
+ }
62
+ return max > highest ? max : highest;
63
+ }, 0);
64
+ errors.scope(offset + 1, () => errors.pathInvalidIndex(max));
41
65
  return null;
42
66
  }
43
- const next = matchedConfigs.flatMap((configs) => configs
44
- .filter(({ max }) => !check.is(NumberSchema, max) || index < max)
45
- .flatMap(({ model: configModel }) => check.is(BuilderModelSerialisedSchema, configModel) ? [configModel] : []));
67
+ const next = indexableConfigs.flatMap((collectionConfig) => check.is(BuilderModelSerialisedSchema, collectionConfig.model) ? [collectionConfig.model] : []);
46
68
  if (next.length === 0) {
47
- errors.invalidPath('missing-collection');
69
+ errors.scope(offset, () => errors.pathMissingCollection());
48
70
  return null;
49
71
  }
50
- return walkPath(next, rest, errors);
72
+ return walkPath(next, rest, offset + 2, errors);
51
73
  }
@@ -1,7 +1,7 @@
1
1
  import type { BuilderModelSerialised, BuilderPricingSerialised, BuilderPricingValidated, BuilderReferences, ValidationResult } from '../entities/index';
2
2
  import type { BuilderResolve } from './resolve';
3
- import { BuilderValidateErrors } from '../errors/index.js';
3
+ import { BuilderErrorsScope } from '../errors/index.js';
4
4
  export type BuilderPricingValidationResult = ValidationResult<BuilderPricingValidated>;
5
- export declare function validatePricing(input: unknown, references?: BuilderReferences, errors?: BuilderValidateErrors): BuilderPricingValidationResult;
6
- export declare function validatePricingStructure(input: BuilderPricingSerialised, resolve: BuilderResolve, errors: BuilderValidateErrors): BuilderPricingSerialised;
7
- export declare function checkPricingExpectations(model: BuilderModelSerialised, pricing: BuilderPricingSerialised, errors: BuilderValidateErrors): void;
5
+ export declare function validatePricing(input: unknown, references?: BuilderReferences, errors?: BuilderErrorsScope): BuilderPricingValidationResult;
6
+ export declare function validatePricingStructure(input: BuilderPricingSerialised, resolve: BuilderResolve, errors: BuilderErrorsScope): BuilderPricingSerialised;
7
+ export declare function checkPricingExpectations(model: BuilderModelSerialised, pricing: BuilderPricingSerialised, errors: BuilderErrorsScope): void;
@@ -1,13 +1,12 @@
1
- import * as v from 'valibot';
2
1
  import { BuilderPricingExpressionSchema, BuilderPricingSerialisedSchema, BuilderRatesSchema, pricing, serialise } from '../entities/index.js';
3
- import { BuilderValidateErrors, check } from '../errors/index.js';
2
+ import { BuilderErrorsScope, check } from '../errors/index.js';
3
+ import { NumberSchema } from '../primitive.js';
4
4
  import { validate } from './brand.js';
5
5
  import { resolver } from './resolve.js';
6
6
  const EMPTY_PRICING = validate(serialise.pricing(pricing()));
7
- const NumberSchema = v.number();
8
- export function validatePricing(input, references = [], errors = new BuilderValidateErrors()) {
7
+ export function validatePricing(input, references = [], errors = new BuilderErrorsScope(input)) {
9
8
  if (!check.is(BuilderPricingSerialisedSchema, input)) {
10
- errors.invalidEntity('pricing');
9
+ errors.entityInvalid('pricing');
11
10
  return [EMPTY_PRICING, errors.errors];
12
11
  }
13
12
  const resolve = resolver(errors, references);
@@ -15,18 +14,23 @@ export function validatePricing(input, references = [], errors = new BuilderVali
15
14
  return [validate(structure), errors.errors];
16
15
  }
17
16
  export function validatePricingStructure(input, resolve, errors) {
18
- const rates = input.rates.flatMap((entry) => {
19
- const resolved = resolve(entry);
20
- return v.is(BuilderRatesSchema, resolved) ? [resolved] : [];
21
- });
17
+ const rates = errors.scope('rates', () => input.rates.flatMap((entry, index) => {
18
+ const resolved = resolve(entry, BuilderRatesSchema);
19
+ if (resolved != null) {
20
+ return [resolved];
21
+ }
22
+ errors.scope(index, () => errors.pricingInvalidRateValue());
23
+ return [];
24
+ }));
22
25
  const formula = input.formula == null ? null : resolveExpression(input.formula);
23
26
  if (formula != null) {
24
27
  walkExpression(formula, false);
25
28
  }
26
29
  return { rates, formula };
27
30
  function resolveExpression(input) {
28
- const resolved = resolve(input);
29
- if (!check.is(BuilderPricingExpressionSchema, resolved)) {
31
+ const resolved = resolve(input, BuilderPricingExpressionSchema);
32
+ if (resolved == null) {
33
+ errors.pricingMalformedExpression();
30
34
  return null;
31
35
  }
32
36
  if (check.is(NumberSchema, resolved)) {
@@ -50,24 +54,29 @@ export function validatePricingStructure(input, resolve, errors) {
50
54
  switch (expression.kind) {
51
55
  case 'variantPrice':
52
56
  if (!insideVariants) {
53
- errors.invalidPricing('scope');
57
+ errors.pricingInvalidScope();
54
58
  }
55
59
  return;
56
60
  case 'lookup':
57
61
  errors.scope('lookup', () => {
58
- const exists = rates.some((entry) => entry[expression.rate] != null);
59
- if (!exists) {
60
- errors.missingRate(expression.rate);
62
+ if (rates.length === 0) {
63
+ errors.pricingEmptyRates();
64
+ }
65
+ else {
66
+ const exists = rates.some((entry) => entry[expression.rate] != null);
67
+ if (!exists) {
68
+ errors.pricingMissingRate();
69
+ }
61
70
  }
62
71
  if (!insideVariants) {
63
- errors.invalidPricing('scope');
72
+ errors.pricingInvalidScope();
64
73
  }
65
74
  });
66
75
  return;
67
76
  case 'variants':
68
77
  errors.scope('variants', () => {
69
78
  if (insideVariants) {
70
- errors.invalidPricing('nested-variants');
79
+ errors.pricingNestedVariants();
71
80
  }
72
81
  const inner = expression.expression;
73
82
  check.assert(BuilderPricingExpressionSchema, inner);
@@ -81,6 +90,9 @@ export function validatePricingStructure(input, resolve, errors) {
81
90
  const { left, right } = expression;
82
91
  check.assert(BuilderPricingExpressionSchema, left);
83
92
  check.assert(BuilderPricingExpressionSchema, right);
93
+ if (expression.kind === 'div' && check.is(NumberSchema, right) && right === 0) {
94
+ errors.pricingDivideByZero();
95
+ }
84
96
  errors.scope('left', () => walkExpression(left, insideVariants));
85
97
  errors.scope('right', () => walkExpression(right, insideVariants));
86
98
  return;
@@ -105,7 +117,7 @@ export function checkPricingExpectations(model, pricing, errors) {
105
117
  case 'lookup':
106
118
  errors.scope('lookup', () => {
107
119
  if (expression.key.kind === 'option' && !optionNames.has(expression.key.name)) {
108
- errors.unmetExpectation('option', expression.key.name);
120
+ errors.pricingMissingOption();
109
121
  }
110
122
  });
111
123
  return;
@@ -1,4 +1,5 @@
1
1
  import type { BuilderBindings, BuilderReferences } from '../entities/index';
2
- import type { BuilderValidateErrors } from '../errors/index';
3
- export type BuilderResolve = (value: unknown) => unknown;
4
- export declare function resolver(errors: BuilderValidateErrors, references?: BuilderReferences, bindings?: BuilderBindings): BuilderResolve;
2
+ import type { BuilderErrorsScope } from '../errors/index';
3
+ import * as v from 'valibot';
4
+ export type BuilderResolve = <S extends v.GenericSchema = v.GenericSchema<unknown>>(value: unknown, schema?: S) => v.InferOutput<S> | null;
5
+ export declare function resolver(errors: BuilderErrorsScope, references?: BuilderReferences, bindings?: BuilderBindings): BuilderResolve;
@@ -1,86 +1,111 @@
1
- import { BuilderCollectionConfigSerialisedSchema, BuilderModelSerialisedSchema, BuilderWhenSerialisedSchema } from '../entities/index.js';
1
+ import * as v from 'valibot';
2
+ import { BuilderCollectionConfigSerialisedSchema, BuilderModelSerialisedSchema, BuilderPathSchema, BuilderWhenSerialisedSchema } from '../entities/index.js';
2
3
  import { check } from '../errors/index.js';
4
+ import { NumberSchema, StringSchema } from '../primitive.js';
3
5
  import { BuilderParameterSerialisedSchema, BuilderRefSerialisedSchema } from '../references.js';
4
6
  import { validateModelStructure } from './model.js';
5
7
  export function resolver(errors, references = [], bindings = {}) {
6
- function resolve(value) {
8
+ const resolving = new Set();
9
+ function resolve(value, schema = v.unknown()) {
10
+ const errorsBefore = errors.errors.length;
11
+ const resolved = dispatch(value, schema);
12
+ if (check.is(schema, resolved)) {
13
+ return resolved;
14
+ }
15
+ if (errors.errors.length === errorsBefore) {
16
+ errors.referenceInvalid();
17
+ }
18
+ return null;
19
+ }
20
+ function dispatch(value, schema) {
7
21
  if (check.is(BuilderParameterSerialisedSchema, value)) {
8
- return resolveParameter(value);
22
+ return resolveParameter(value, schema);
9
23
  }
10
24
  if (check.is(BuilderRefSerialisedSchema, value)) {
11
- return resolveReference(value);
25
+ return resolveReference(value, schema);
12
26
  }
13
27
  if (check.is(BuilderWhenSerialisedSchema, value)) {
14
- return resolveWhen(value);
28
+ return resolveWhen(value, schema);
15
29
  }
16
30
  if (check.is(BuilderCollectionConfigSerialisedSchema, value)) {
17
31
  return resolveCollectionConfig(value);
18
32
  }
19
33
  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;
34
+ }
35
+ function resolveReference(reference, schema) {
36
+ if (resolving.has(reference.id)) {
37
+ errors.referenceCircular();
38
+ return reference;
27
39
  }
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);
40
+ const found = references.find((entry) => entry.id === reference.id);
41
+ if (found == null) {
42
+ errors.referenceMissing();
43
+ return reference;
38
44
  }
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 };
43
- }
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 };
45
+ resolving.add(reference.id);
46
+ try {
47
+ return dispatch(found.serialised, schema);
66
48
  }
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));
49
+ finally {
50
+ resolving.delete(reference.id);
51
+ }
52
+ }
53
+ function resolveParameter(parameter, schema) {
54
+ if (!(parameter.name in bindings)) {
55
+ errors.referenceUnboundParameter();
56
+ return null;
57
+ }
58
+ const binding = bindings[parameter.name];
59
+ if (!check.is(BuilderRefSerialisedSchema, binding)) {
60
+ return binding;
61
+ }
62
+ return resolveReference(binding, schema);
63
+ }
64
+ function resolveWhen(when, payloadSchema) {
65
+ if (when.type === 'enable') {
66
+ const payload = errors.scope('payload', () => resolve(when.payload, payloadSchema));
67
+ return { ...when, payload: payload ?? when.payload };
68
+ }
69
+ if (when.type === 'unless') {
70
+ const unlessPath = errors.scope('unlessPath', () => resolve(when.unlessPath, BuilderPathSchema));
71
+ const payload = errors.scope('payload', () => resolve(when.payload, payloadSchema));
77
72
  return {
78
- ...config,
79
- model: model ?? config.model,
80
- min: min ?? config.min,
81
- max: max ?? config.max
73
+ ...when,
74
+ unlessPath: unlessPath ?? when.unlessPath,
75
+ payload: payload ?? when.payload
82
76
  };
83
77
  }
78
+ const matchPath = errors.scope('matchPath', () => resolve(when.matchPath, BuilderPathSchema));
79
+ const selectMapSchema = v.record(StringSchema, v.nullable(v.unknown()));
80
+ const map = errors.scope('selectMap', () => resolve(when.selectMap, selectMapSchema));
81
+ if (map == null) {
82
+ return { ...when, matchPath: matchPath ?? when.matchPath };
83
+ }
84
+ const selectMap = errors.scope('selectMap', () => Object.entries(map).reduce((values, [key, entryValue]) => {
85
+ if (entryValue == null) {
86
+ return { ...values, [key]: null };
87
+ }
88
+ const resolved = errors.scope(key, () => resolve(entryValue, payloadSchema));
89
+ return { ...values, [key]: resolved ?? entryValue };
90
+ }, {}));
91
+ return { ...when, matchPath: matchPath ?? when.matchPath, selectMap };
92
+ }
93
+ function resolveCollectionConfig(config) {
94
+ const model = errors.scope('model', () => {
95
+ const resolved = resolve(config.model, BuilderModelSerialisedSchema);
96
+ if (resolved == null) {
97
+ return null;
98
+ }
99
+ return validateModelStructure(resolved, resolve, errors);
100
+ });
101
+ const min = errors.scope('min', () => resolve(config.min, NumberSchema));
102
+ const max = errors.scope('max', () => resolve(config.max, NumberSchema));
103
+ return {
104
+ ...config,
105
+ model: model ?? config.model,
106
+ min: min ?? config.min,
107
+ max: max ?? config.max
108
+ };
84
109
  }
85
110
  return resolve;
86
111
  }
@@ -1,7 +1,7 @@
1
1
  import type { BuilderModelSerialised, BuilderReferences, BuilderUISerialised, BuilderUIValidated, ValidationResult } from '../entities/index';
2
2
  import type { BuilderResolve } from './resolve';
3
- import { BuilderValidateErrors } from '../errors/index.js';
3
+ import { BuilderErrorsScope } from '../errors/index.js';
4
4
  export type BuilderUIValidationResult = ValidationResult<BuilderUIValidated>;
5
- export declare function validateUI(input: unknown, references?: BuilderReferences, errors?: BuilderValidateErrors): BuilderUIValidationResult;
6
- export declare function validateUIStructure(ui: BuilderUISerialised, resolve: BuilderResolve, errors: BuilderValidateErrors): BuilderUIValidated;
7
- export declare function checkUIExpectations(mergedModel: BuilderModelSerialised, ui: BuilderUIValidated, errors: BuilderValidateErrors): void;
5
+ export declare function validateUI(input: unknown, references?: BuilderReferences, errors?: BuilderErrorsScope): BuilderUIValidationResult;
6
+ export declare function validateUIStructure(ui: BuilderUISerialised, resolve: BuilderResolve, errors: BuilderErrorsScope): BuilderUIValidated;
7
+ export declare function checkUIExpectations(mergedModel: BuilderModelSerialised, ui: BuilderUIValidated, errors: BuilderErrorsScope): void;
@@ -1,13 +1,14 @@
1
- import { BuilderModelSerialisedSchema, BuilderUIInputMetadataSchema, BuilderUISerialisedSchema, modelsMerge, serialise, uis, validateUIDescribe, validateUIInput, validateUIPage, validateUIPages } from '../entities/index.js';
2
- import { BuilderValidateErrors, check } from '../errors/index.js';
1
+ import { BuilderModelSerialisedSchema, BuilderPathSchema, BuilderUIInputMetadataSchema, BuilderUIInputSerialisedSchema, BuilderUIInputsSerialisedSchema, BuilderUIItemSerialisedSchema, BuilderUIItemsSerialisedSchema, BuilderUISerialisedSchema, modelsMerge, serialise, uis, validateUIDescribe, validateUIInput, validateUIPage, validateUIPages } from '../entities/index.js';
2
+ import { BuilderErrorsScope, check } from '../errors/index.js';
3
3
  import { resolveCollections } from '../mappers/index.js';
4
+ import { StringSchema } from '../primitive.js';
4
5
  import { validate } from './brand.js';
5
6
  import { checkExpectations } from './expectations.js';
6
7
  import { checkPath } from './paths.js';
7
8
  import { resolver } from './resolve.js';
8
- export function validateUI(input, references = [], errors = new BuilderValidateErrors()) {
9
+ export function validateUI(input, references = [], errors = new BuilderErrorsScope(input)) {
9
10
  if (!check.is(BuilderUISerialisedSchema, input)) {
10
- errors.invalidEntity('ui');
11
+ errors.entityInvalid('ui');
11
12
  return [validate(serialise.ui(uis())), errors.errors];
12
13
  }
13
14
  const resolve = resolver(errors, references);
@@ -25,8 +26,8 @@ export function validateUIStructure(ui, resolve, errors) {
25
26
  return data;
26
27
  function walkChildUIs() {
27
28
  return errors.scope('uis', () => ui.uis.flatMap((part, partIndex) => errors.scope(partIndex, () => {
28
- const resolved = resolve(part);
29
- if (resolved == null || !check.is(BuilderUISerialisedSchema, resolved)) {
29
+ const resolved = resolve(part, BuilderUISerialisedSchema);
30
+ if (resolved == null) {
30
31
  return [];
31
32
  }
32
33
  return [validateUIStructure(resolved, resolve, errors)];
@@ -34,7 +35,7 @@ export function validateUIStructure(ui, resolve, errors) {
34
35
  }
35
36
  function walkItems(parts) {
36
37
  return parts.map((item, itemIndex) => errors.scope(itemIndex, () => {
37
- const resolved = resolve(item);
38
+ const resolved = resolve(item, BuilderUIItemSerialisedSchema);
38
39
  return walkUIItem((resolved ?? item));
39
40
  }));
40
41
  }
@@ -48,17 +49,17 @@ export function validateUIStructure(ui, resolve, errors) {
48
49
  else if (item.type === 'describe') {
49
50
  validateUIDescribe(item, [], errors);
50
51
  }
51
- const label = errors.scope('label', () => resolve(item.label) ?? item.label);
52
+ const label = errors.scope('label', () => resolve(item.label, StringSchema) ?? item.label);
52
53
  if (item.type === 'pages') {
53
54
  const innerItems = errors.scope('items', () => {
54
- const resolved = resolve(item.items);
55
+ const resolved = resolve(item.items, BuilderUIItemsSerialisedSchema);
55
56
  const resolvedItems = (Array.isArray(resolved) ? resolved : item.items);
56
57
  return walkItems(resolvedItems);
57
58
  });
58
59
  return { ...item, label, items: innerItems };
59
60
  }
60
61
  const inputs = errors.scope('inputs', () => {
61
- const resolved = resolve(item.inputs);
62
+ const resolved = resolve(item.inputs, BuilderUIInputsSerialisedSchema);
62
63
  if (!Array.isArray(resolved)) {
63
64
  return resolved ?? item.inputs;
64
65
  }
@@ -68,22 +69,22 @@ export function validateUIStructure(ui, resolve, errors) {
68
69
  }
69
70
  function walkInputs(inputs) {
70
71
  return inputs.map((entry, entryIndex) => errors.scope(entryIndex, () => {
71
- const resolvedEntry = resolve(entry);
72
+ const resolvedEntry = resolve(entry, BuilderUIInputSerialisedSchema);
72
73
  return walkInput((resolvedEntry ?? entry));
73
74
  }));
74
75
  }
75
76
  function walkInput(value) {
76
77
  validateUIInput(value, [], errors);
77
- const path = errors.scope('path', () => resolve(value.path) ?? value.path);
78
+ const path = errors.scope('path', () => resolve(value.path, BuilderPathSchema) ?? value.path);
78
79
  const displayName = value.displayName &&
79
- errors.scope('displayName', () => resolve(value.displayName) ?? value.displayName);
80
- const kind = value.kind && errors.scope('kind', () => resolve(value.kind) ?? value.kind);
80
+ errors.scope('displayName', () => resolve(value.displayName, StringSchema) ?? value.displayName);
81
+ const kind = value.kind && errors.scope('kind', () => resolve(value.kind, StringSchema) ?? value.kind);
81
82
  const metadata = value.metadata && errors.scope('metadata', () => walkMetadata(value.metadata));
82
83
  return { ...value, path, displayName, kind, metadata };
83
84
  }
84
85
  function walkMetadata(rawMetadata) {
85
- const resolved = resolve(rawMetadata);
86
- if (!check.is(BuilderUIInputMetadataSchema, resolved)) {
86
+ const resolved = resolve(rawMetadata, BuilderUIInputMetadataSchema);
87
+ if (resolved == null) {
87
88
  return rawMetadata;
88
89
  }
89
90
  return Object.entries(resolved).reduce((entries, [key, entryValue]) => {
@@ -1,7 +1,7 @@
1
1
  import type { BuilderModelValidated, BuilderVariantsValidated, ValidationResult } from '../entities/index';
2
- import { BuilderValidateErrors } from '../errors/index.js';
2
+ import { BuilderErrorsScope } from '../errors/index.js';
3
3
  export type BuilderVariantsValidationOptions = {
4
4
  readonly partial?: boolean;
5
5
  };
6
6
  export type BuilderVariantsValidationResult = ValidationResult<BuilderVariantsValidated>;
7
- export declare function validateVariants(model: BuilderModelValidated, input: unknown, options?: BuilderVariantsValidationOptions, errors?: BuilderValidateErrors): BuilderVariantsValidationResult;
7
+ export declare function validateVariants(model: BuilderModelValidated, input: unknown, options?: BuilderVariantsValidationOptions, errors?: BuilderErrorsScope): BuilderVariantsValidationResult;
@@ -1,17 +1,17 @@
1
1
  import { BuilderComponentConfigSerialisedSchema, detailValueSchema } from '../entities/index.js';
2
- import { BuilderValidateErrors, check } from '../errors/index.js';
2
+ import { BuilderErrorsScope, check } from '../errors/index.js';
3
3
  import { BuilderVariantsSchema } from '../instance.js';
4
4
  import { createVariants } from '../mappers/index.js';
5
5
  import { validate } from './brand.js';
6
- export function validateVariants(model, input, options = {}, errors = new BuilderValidateErrors()) {
6
+ export function validateVariants(model, input, options = {}, errors = new BuilderErrorsScope(input)) {
7
7
  if (!check.is(BuilderVariantsSchema, input)) {
8
- errors.invalidVariants();
8
+ errors.variantsInvalid();
9
9
  return [validate({}), errors.errors];
10
10
  }
11
11
  const variants = input;
12
12
  const expected = createVariants(model);
13
13
  if (Object.keys(expected).length === 0) {
14
- errors.noComponents();
14
+ errors.modelEmptyComponents();
15
15
  return [validate({}), errors.errors];
16
16
  }
17
17
  errors.scope('variants', () => {
@@ -21,26 +21,26 @@ export function validateVariants(model, input, options = {}, errors = new Builde
21
21
  function checkVariants() {
22
22
  Object.entries(expected).forEach(([component, expectedVariants]) => {
23
23
  const componentVariants = variants[component];
24
- if (componentVariants == null) {
25
- if (options.partial !== false) {
26
- errors.missingComponent(component);
27
- }
28
- return;
29
- }
30
24
  errors.scope(component, () => {
25
+ if (componentVariants == null) {
26
+ if (options.partial !== false) {
27
+ errors.variantsMissingComponent();
28
+ }
29
+ return;
30
+ }
31
31
  const expectedKeys = new Map(expectedVariants.map(({ instance }) => [sortedKey(instance), instance]));
32
32
  const actualKeys = new Set(componentVariants.map(({ instance }) => sortedKey(instance)));
33
33
  componentVariants.forEach((variant, index) => {
34
34
  errors.scope(index, () => {
35
35
  const { instance } = variant;
36
36
  if (!expectedKeys.has(sortedKey(instance))) {
37
- errors.invalidVariant(component, instance);
37
+ errors.variantsInvalidVariant();
38
38
  }
39
39
  });
40
40
  });
41
41
  Array.from(expectedKeys).forEach(([key, instance]) => {
42
42
  if (!actualKeys.has(key)) {
43
- errors.missingVariant(component, instance);
43
+ errors.variantsMissingVariant(instance);
44
44
  }
45
45
  });
46
46
  });
@@ -50,7 +50,7 @@ export function validateVariants(model, input, options = {}, errors = new Builde
50
50
  Object.keys(variants).forEach((component) => {
51
51
  errors.scope(component, () => {
52
52
  if (expected[component] == null) {
53
- errors.unexpectedComponent(component);
53
+ errors.variantsUnexpectedComponent();
54
54
  }
55
55
  });
56
56
  });
@@ -72,18 +72,18 @@ export function validateVariants(model, input, options = {}, errors = new Builde
72
72
  fields.forEach(({ name, valueType, isOptional }) => {
73
73
  errors.scope(name, () => {
74
74
  if (!(name in details)) {
75
- errors.missingDetail(component, name, instance);
75
+ errors.variantsMissingDetail();
76
76
  return;
77
77
  }
78
78
  if (!check.is(detailValueSchema(valueType, isOptional), details[name])) {
79
- errors.invalidDetail(component, name, instance);
79
+ errors.variantsInvalidDetail();
80
80
  }
81
81
  });
82
82
  });
83
83
  Object.keys(details).forEach((detail) => {
84
84
  if (!expectedNames.has(detail)) {
85
85
  errors.scope(detail, () => {
86
- errors.unexpectedDetail(component, detail, instance);
86
+ errors.variantsUnexpectedDetail();
87
87
  });
88
88
  }
89
89
  });
package/package.json CHANGED
@@ -1,25 +1,29 @@
1
1
  {
2
2
  "name": "@builder-builder/builder",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
7
7
  },
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "default": "./dist/index.js"
10
+ "types": "./dist/public.d.ts",
11
+ "default": "./dist/public.js"
12
12
  },
13
13
  "./client": {
14
- "types": "./dist/client/index.d.ts",
15
- "default": "./dist/client/index.js"
14
+ "types": "./dist/client/public.d.ts",
15
+ "default": "./dist/client/public.js"
16
+ },
17
+ "./errors": {
18
+ "types": "./dist/errors/public.d.ts",
19
+ "default": "./dist/errors/public.js"
16
20
  }
17
21
  },
18
22
  "files": [
19
23
  "dist"
20
24
  ],
21
- "svelte": "./dist/index.js",
22
- "types": "./dist/index.d.ts",
25
+ "svelte": "./dist/public.js",
26
+ "types": "./dist/public.d.ts",
23
27
  "publishConfig": {
24
28
  "access": "public"
25
29
  },