@builder-builder/builder 0.0.24 → 0.0.25

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 (74) 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 +22 -14
  8. package/dist/client/schema.js +7 -1
  9. package/dist/entities/collection/collection.d.ts +2 -2
  10. package/dist/entities/collection/config.d.ts +1 -1
  11. package/dist/entities/collection/config.js +1 -1
  12. package/dist/entities/component/component.d.ts +2 -2
  13. package/dist/entities/component/config.d.ts +1 -1
  14. package/dist/entities/component/config.js +1 -1
  15. package/dist/entities/expectation.d.ts +1 -1
  16. package/dist/entities/kind.d.ts +3 -3
  17. package/dist/entities/kind.js +3 -3
  18. package/dist/entities/option/option.d.ts +2 -2
  19. package/dist/entities/option/select.d.ts +1 -1
  20. package/dist/entities/option/select.js +7 -5
  21. package/dist/entities/option/toggle.d.ts +1 -1
  22. package/dist/entities/option/toggle.js +1 -1
  23. package/dist/entities/paths.d.ts +2 -2
  24. package/dist/entities/pricing/rates.d.ts +1 -1
  25. package/dist/entities/serialise.d.ts +3 -3
  26. package/dist/entities/ui/describe.d.ts +1 -1
  27. package/dist/entities/ui/describe.js +2 -2
  28. package/dist/entities/ui/input.d.ts +1 -1
  29. package/dist/entities/ui/page.d.ts +1 -1
  30. package/dist/entities/ui/page.js +2 -2
  31. package/dist/entities/ui/pages.d.ts +2 -2
  32. package/dist/entities/ui/pages.js +2 -2
  33. package/dist/errors/check.d.ts +2 -3
  34. package/dist/errors/check.js +5 -10
  35. package/dist/errors/errors.d.ts +231 -158
  36. package/dist/errors/errors.js +143 -173
  37. package/dist/errors/exception.d.ts +6 -4
  38. package/dist/errors/exception.js +11 -66
  39. package/dist/errors/index.d.ts +2 -4
  40. package/dist/errors/index.js +2 -2
  41. package/dist/errors/public.d.ts +2 -0
  42. package/dist/errors/public.js +1 -0
  43. package/dist/index.d.ts +4 -36
  44. package/dist/index.js +4 -19
  45. package/dist/mappers/price.js +1 -1
  46. package/dist/mappers/render/render.js +7 -7
  47. package/dist/mappers/resolve.js +5 -5
  48. package/dist/mappers/variants/option-graph.js +11 -4
  49. package/dist/mappers/variants/variants.js +7 -6
  50. package/dist/public.d.ts +37 -0
  51. package/dist/public.js +19 -0
  52. package/dist/validate/brand.js +2 -4
  53. package/dist/validate/builder.d.ts +2 -2
  54. package/dist/validate/builder.js +3 -3
  55. package/dist/validate/expectations.d.ts +2 -2
  56. package/dist/validate/expectations.js +1 -1
  57. package/dist/validate/index.d.ts +0 -1
  58. package/dist/validate/instance.d.ts +2 -2
  59. package/dist/validate/instance.js +6 -6
  60. package/dist/validate/model.d.ts +4 -4
  61. package/dist/validate/model.js +118 -101
  62. package/dist/validate/paths.d.ts +2 -2
  63. package/dist/validate/paths.js +45 -21
  64. package/dist/validate/pricing.d.ts +4 -4
  65. package/dist/validate/pricing.js +26 -13
  66. package/dist/validate/resolve.d.ts +2 -2
  67. package/dist/validate/resolve.js +14 -3
  68. package/dist/validate/ui.d.ts +4 -4
  69. package/dist/validate/ui.js +3 -3
  70. package/dist/validate/variants.d.ts +2 -2
  71. package/dist/validate/variants.js +16 -16
  72. package/package.json +11 -7
  73. package/dist/private.d.ts +0 -3
  74. package/dist/private.js +0 -3
@@ -27,12 +27,12 @@ export function resolveReference(value, references) {
27
27
  return value;
28
28
  }
29
29
  const found = references.find((entry) => entry.id === value.id);
30
- check.truthy(found, `Reference '${value.id}' not found! ❌`);
30
+ check.invariant(found);
31
31
  return found.serialised;
32
32
  }
33
33
  export function resolvePath(model, instance, path, references = []) {
34
34
  const optionName = path.at(-1);
35
- check.assert(v.string(), optionName, 'Path must end at an option name! ❌');
35
+ check.assert(v.string(), optionName);
36
36
  const descended = resolveItems(model, instance, path.slice(0, -1), references);
37
37
  if (descended == null) {
38
38
  return null;
@@ -40,7 +40,7 @@ export function resolvePath(model, instance, path, references = []) {
40
40
  const [scopeModel, scopeInstance] = descended;
41
41
  const merged = modelsMerge(scopeModel);
42
42
  const option = merged.options.find((entry) => entry.name === optionName);
43
- check.truthy(option, `Path target '${optionName}' is not an option! ❌`);
43
+ check.invariant(option);
44
44
  const payload = resolveOption(option, scopeModel, scopeInstance, references);
45
45
  if (payload == null) {
46
46
  return null;
@@ -54,10 +54,10 @@ export function resolveItems(model, instance, pairs, references = []) {
54
54
  }
55
55
  const [name, index, ...rest] = pairs;
56
56
  check.assert(v.string(), name);
57
- check.assert(v.number(), index, `Collection '${name}' must be followed by an index! ❌`);
57
+ check.assert(v.number(), index);
58
58
  const merged = modelsMerge(model);
59
59
  const collection = merged.collections.find((entry) => entry.name === name);
60
- check.truthy(collection, `Path segment '${name}' is not a collection! ❌`);
60
+ check.invariant(collection);
61
61
  const resolved = resolveCollection(collection, model, instance, references);
62
62
  if (resolved == null) {
63
63
  return null;
@@ -2,7 +2,14 @@ import { BuilderPathSchema, BuilderWhenMatchSchema, BuilderWhenUnlessSchema } fr
2
2
  import { check } from '../../errors/index.js';
3
3
  import { resolveOption } from '../resolve.js';
4
4
  export function crossProduct(left, right) {
5
- return left.flatMap((leftModel) => right.map((rightModel) => ({ ...leftModel, ...rightModel })));
5
+ return left.flatMap((leftModel) => right.flatMap((rightModel) => {
6
+ const rightKeys = new Set(Object.keys(rightModel));
7
+ const conflicted = Object.keys(leftModel).some((key) => rightKeys.has(key) && leftModel[key] !== rightModel[key]);
8
+ if (conflicted) {
9
+ return [];
10
+ }
11
+ return [{ ...leftModel, ...rightModel }];
12
+ }));
6
13
  }
7
14
  export function dependencyKeys(payload, paths = []) {
8
15
  const keys = new Set(paths.map(([first]) => String(first)));
@@ -25,7 +32,7 @@ export class BuilderOptionGraph {
25
32
  }
26
33
  get(name) {
27
34
  const graphPath = this.paths.find((graphPath) => graphPath.name === name);
28
- check.truthy(graphPath, `Option '${name}' not found in graph! ❌`);
35
+ check.invariant(graphPath);
29
36
  return graphPath;
30
37
  }
31
38
  add(option, model) {
@@ -33,7 +40,7 @@ export class BuilderOptionGraph {
33
40
  const optionDependencyKeys = dependencyKeys(payload, paths);
34
41
  if (optionDependencyKeys.length === 0) {
35
42
  const payload = resolveOption(option, model, {});
36
- check.truthy(payload, `Option '${option.name}' without dependencies must resolve! ❌`);
43
+ check.invariant(payload);
37
44
  const values = this.#optionValues(option.name, payload);
38
45
  return new BuilderOptionGraph([
39
46
  ...this.paths,
@@ -65,7 +72,7 @@ export class BuilderOptionGraph {
65
72
  #mergePaths(dependencyKeys, graphPaths) {
66
73
  const dependencyPaths = Array.from(dependencyKeys).map((dependencyKey) => {
67
74
  const dependencyPath = graphPaths.find((graphPath) => graphPath.name === dependencyKey);
68
- check.truthy(dependencyPath, `Option dependency '${dependencyKey}' not found in graph! ❌`);
75
+ check.invariant(dependencyPath);
69
76
  return dependencyPath;
70
77
  });
71
78
  const [first, ...rest] = dependencyPaths;
@@ -1,4 +1,4 @@
1
- import { BuilderModelSerialisedSchema, BuilderSerialisedSchema } from '../../entities/index.js';
1
+ import { BuilderModelSerialisedSchema, BuilderSerialisedSchema, modelsMerge } from '../../entities/index.js';
2
2
  import { check } from '../../errors/index.js';
3
3
  import { resolveCollection } from '../resolve.js';
4
4
  import { BuilderOptionGraph, crossProduct, dependencyKeys } from './option-graph.js';
@@ -7,9 +7,10 @@ export function createVariants(entity) {
7
7
  return buildVariants(model);
8
8
  }
9
9
  function buildVariants(model) {
10
- const options = optionGraph(model);
11
- const nestedVariants = model.collections.flatMap((entry) => variantsFor(entry, options).flatMap((instance) => {
12
- const resolved = resolveCollection(entry, model, instance);
10
+ const merged = modelsMerge(model);
11
+ const options = optionGraph(merged);
12
+ const nestedVariants = merged.collections.flatMap((entry) => variantsFor(entry, options).flatMap((instance) => {
13
+ const resolved = resolveCollection(entry, merged, instance);
13
14
  if (resolved == null) {
14
15
  return [];
15
16
  }
@@ -17,8 +18,8 @@ function buildVariants(model) {
17
18
  return [buildVariants(resolved.model)];
18
19
  }));
19
20
  return {
20
- ...nestedVariants.reduce((merged, component) => ({ ...merged, ...component }), {}),
21
- ...Object.fromEntries(model.components.map((component) => [
21
+ ...nestedVariants.reduce((accumulated, component) => ({ ...accumulated, ...component }), {}),
22
+ ...Object.fromEntries(merged.components.map((component) => [
22
23
  component.name,
23
24
  variantsFor(component, options).map((instance) => ({ instance }))
24
25
  ]))
@@ -0,0 +1,37 @@
1
+ export type { BB, BBOptions, BuilderEntityValidators } from './bb';
2
+ export type { Builder, BuilderBindings, BuilderBindingsSerialised, BuilderCollection, BuilderCollectionConfig, BuilderCollectionConfigSerialised, BuilderCollections, BuilderCollectionSerialised, BuilderCollectionsSerialised, BuilderCollectionWhen, BuilderCollectionWhenSerialised, BuilderComponent, BuilderComponentConfig, BuilderComponentConfigSerialised, BuilderComponentField, BuilderComponentFields, BuilderComponentFieldSerialised, BuilderComponentFieldsSerialised, BuilderComponentFieldValueType, BuilderComponents, BuilderComponentSerialised, BuilderComponentsSerialised, BuilderComponentWhen, BuilderComponentWhenSerialised, BuilderDescription, BuilderDescriptionItem, BuilderEnableConfig, BuilderEntityKind, BuilderEntitySerialised, BuilderExpectation, BuilderExpectationKind, BuilderExpectations, BuilderExpectationSerialised, BuilderExpectationsSerialised, BuilderInstanceOf, BuilderInstanceValidated, BuilderMatchConfig, BuilderMatchSelectMap, BuilderModel, BuilderModels, BuilderModelSerialised, BuilderModelValidated, BuilderOption, BuilderOptionConfig, BuilderOptionConfigSerialised, BuilderOptions, BuilderOptionSerialised, BuilderOptionsSerialised, BuilderOptionWhen, BuilderOptionWhenSerialised, BuilderPath, BuilderPaths, BuilderPricing, BuilderPricingExpression, BuilderPricingLookupKey, BuilderPricingReduce, BuilderPricingSerialised, BuilderReference, BuilderReferences, BuilderSelectConfig, BuilderSelectConfigLabels, BuilderSelectConfigSerialised, BuilderSelectConfigValues, BuilderSerialised, BuilderTags, BuilderToggleConfig, BuilderToggleConfigSerialised, BuilderToggleValueType, BuilderUI, BuilderUIDescribe, BuilderUIDescribeSerialised, BuilderUIInput, BuilderUIInputMetadata, BuilderUIInputMetadataSerialised, BuilderUIInputs, BuilderUIInputSerialised, BuilderUIInputsSerialised, BuilderUIItem, BuilderUIItems, BuilderUIItemsSerialised, BuilderUIPage, BuilderUIPages, BuilderUIPageSerialised, BuilderUIPagesSerialised, BuilderUIs, BuilderUISerialised, BuilderUIsSerialised, BuilderUIValidated, BuilderUnlessConfig, BuilderValidated, BuilderVariantsValidated, BuilderWhen, BuilderWhenConfig, BuilderWhenSerialised } from './entities/index';
3
+ export type { BuilderEnvironment } from './environment';
4
+ export type { BuilderError, BuilderErrorLocation, BuilderErrors } from './errors/index';
5
+ export type { BuilderErrorKind } from './errors/index.js';
6
+ export type { BuilderComponentVariant, BuilderComponentVariants, BuilderInstance, BuilderInstanceInput, BuilderInstances, BuilderVariants } from './instance';
7
+ export type { BuilderOrder, BuilderRenderMetadata, BuilderRenderOption, BuilderRenderOptions, BuilderRenderPage, BuilderRenderPages, BuilderRenderResult, BuilderRenderUpdate } from './mappers/index';
8
+ export type { BuilderPrimitive, BuilderPrimitives } from './primitive';
9
+ export type { BuilderParameter, BuilderParameterSerialised, BuilderRef, Paramable, ParamableSerialised } from './references';
10
+ export type { BuilderInstanceValidationResult, BuilderModelValidationResult, BuilderPricingValidationResult, BuilderUIValidationResult, BuilderValidationResult, BuilderVariantsValidationOptions, BuilderVariantsValidationResult } from './validate/index';
11
+ export { bb } from './bb.js';
12
+ export { builder, detailBoolean, detailNumber, detailString, input, model, pricing, select, serialise, toggleBoolean, toggleNumber, toggleString } from './entities/index.js';
13
+ export { BuilderException } from './errors/index.js';
14
+ export { ordinal } from './mappers/index.js';
15
+ export { parameter, ref } from './references.js';
16
+ import { collectionConfig, collectionExpectation, componentConfig, componentExpectation, optionExpectation, uis } from './entities/index.js';
17
+ export declare const collection: typeof collectionConfig & {
18
+ enable: <const Values extends import("./references").Paramable<import("./public").BuilderCollectionConfig<import("./references").Paramable<import("./entities/model").BuilderModelGeneric>, import("./references").Paramable<number>, import("./references").Paramable<number>>>>(values: Values) => import("./public").BuilderEnableConfig<Values>;
19
+ match: <const MatchPayload extends import("./references").Paramable<import("./public").BuilderCollectionConfig<import("./references").Paramable<import("./entities/model").BuilderModelGeneric>, import("./references").Paramable<number>, import("./references").Paramable<number>>>, const MatchPath extends import("./references").Paramable<import("./public").BuilderPath>, const SelectMap extends import("./references").Paramable<import("./public").BuilderMatchSelectMap<import("./references").Paramable<import("./public").BuilderCollectionConfig<import("./references").Paramable<import("./entities/model").BuilderModelGeneric>, import("./references").Paramable<number>, import("./references").Paramable<number>>>>>>(matchPath: MatchPath, selectMap: SelectMap) => import("./public").BuilderMatchConfig<MatchPayload, MatchPath, SelectMap>;
20
+ unless: <const Values extends import("./references").Paramable<import("./public").BuilderCollectionConfig<import("./references").Paramable<import("./entities/model").BuilderModelGeneric>, import("./references").Paramable<number>, import("./references").Paramable<number>>>, const UnlessPath extends import("./references").Paramable<import("./public").BuilderPath>>(unlessPath: UnlessPath, disabledValues: import("./primitive").BuilderPrimitives, values: Values) => import("./public").BuilderUnlessConfig<Values, UnlessPath>;
21
+ };
22
+ export declare const component: typeof componentConfig & {
23
+ enable: <const Payload extends import("./references").Paramable<import("./public").BuilderComponentConfig>>(payload?: Payload) => import("./public").BuilderEnableConfig<Payload>;
24
+ match: <const Payload extends import("./references").Paramable<import("./public").BuilderComponentConfig>, const MatchPath extends import("./references").Paramable<import("./public").BuilderPath>, const SelectMap extends import("./references").Paramable<import("./public").BuilderMatchSelectMap<import("./references").Paramable<import("./public").BuilderComponentConfig>>>>(matchPath: MatchPath, selectMap: SelectMap) => import("./public").BuilderMatchConfig<Payload, MatchPath, SelectMap>;
25
+ unless: <const UnlessPath extends import("./references").Paramable<import("./public").BuilderPath>, const Payload extends import("./references").Paramable<import("./public").BuilderComponentConfig>>(unlessPath: UnlessPath, disabledValues: import("./primitive").BuilderPrimitives, payload?: Payload) => import("./public").BuilderUnlessConfig<Payload, UnlessPath>;
26
+ };
27
+ export declare const has: {
28
+ collection: typeof collectionExpectation;
29
+ component: typeof componentExpectation;
30
+ option: typeof optionExpectation;
31
+ };
32
+ export declare const option: {
33
+ enable: <const Values extends import("./references").Paramable<import("./public").BuilderSelectConfig<readonly [string, ...string[]], import("valibot").GenericSchema<string | null>> | import("./public").BuilderToggleConfig<import("valibot").GenericSchema<string | number | boolean | null>>>>(values: Values) => import("./public").BuilderEnableConfig<Values>;
34
+ match: <const MatchPayload extends import("./references").Paramable<import("./public").BuilderSelectConfig<readonly [string, ...string[]], import("valibot").GenericSchema<string | null>> | import("./public").BuilderToggleConfig<import("valibot").GenericSchema<string | number | boolean | null>>>, const MatchPath extends import("./references").Paramable<import("./public").BuilderPath>, const SelectMap extends import("./references").Paramable<import("./public").BuilderMatchSelectMap<import("./references").Paramable<import("./public").BuilderSelectConfig<readonly [string, ...string[]], import("valibot").GenericSchema<string | null>> | import("./public").BuilderToggleConfig<import("valibot").GenericSchema<string | number | boolean | null>>>>>>(matchPath: MatchPath, selectMap: SelectMap) => import("./public").BuilderMatchConfig<MatchPayload, MatchPath, SelectMap>;
35
+ unless: <const Values extends import("./references").Paramable<import("./public").BuilderSelectConfig<readonly [string, ...string[]], import("valibot").GenericSchema<string | null>> | import("./public").BuilderToggleConfig<import("valibot").GenericSchema<string | number | boolean | null>>>, const UnlessPath extends import("./references").Paramable<import("./public").BuilderPath>>(unlessPath: UnlessPath, disabledValues: import("./primitive").BuilderPrimitives, values: Values) => import("./public").BuilderUnlessConfig<Values, UnlessPath>;
36
+ };
37
+ export declare const ui: typeof uis;
package/dist/public.js ADDED
@@ -0,0 +1,19 @@
1
+ export { bb } from './bb.js';
2
+ export { builder, detailBoolean, detailNumber, detailString, input, model, pricing, select, serialise, toggleBoolean, toggleNumber, toggleString } from './entities/index.js';
3
+ export { BuilderException } from './errors/index.js';
4
+ export { ordinal } from './mappers/index.js';
5
+ export { parameter, ref } from './references.js';
6
+ import { collectionConfig, collectionExpectation, collectionWhen, componentConfig, componentExpectation, componentWhen, optionExpectation, optionWhen, uis } from './entities/index.js';
7
+ export const collection = Object.assign(collectionConfig, {
8
+ ...collectionWhen
9
+ });
10
+ export const component = Object.assign(componentConfig, {
11
+ ...componentWhen
12
+ });
13
+ export const has = {
14
+ collection: collectionExpectation,
15
+ component: componentExpectation,
16
+ option: optionExpectation
17
+ };
18
+ export const option = optionWhen;
19
+ export const ui = uis;
@@ -1,4 +1,4 @@
1
- import { BuilderException, BuilderValidateErrors } from '../errors/index.js';
1
+ import { BuilderException } from '../errors/index.js';
2
2
  const VALIDATED_ENTITIES = new WeakSet();
3
3
  export function validate(value) {
4
4
  VALIDATED_ENTITIES.add(value);
@@ -6,8 +6,6 @@ export function validate(value) {
6
6
  }
7
7
  export function assertValidated(value) {
8
8
  if (typeof value !== 'object' || value === null || !VALIDATED_ENTITIES.has(value)) {
9
- const errors = new BuilderValidateErrors();
10
- errors.unvalidated(value);
11
- throw new BuilderException({ category: 'validation', errors: errors.errors });
9
+ throw new BuilderException({ category: 'invariant', value });
12
10
  }
13
11
  }
@@ -1,4 +1,4 @@
1
1
  import type { BuilderReferences, BuilderValidated, ValidationResult } from '../entities/index';
2
- import { BuilderValidateErrors } from '../errors/index.js';
2
+ import { BuilderErrorsScope } from '../errors/index.js';
3
3
  export type BuilderValidationResult = ValidationResult<BuilderValidated>;
4
- export declare function validateBuilder(input: unknown, references?: BuilderReferences, errors?: BuilderValidateErrors): BuilderValidationResult;
4
+ export declare function validateBuilder(input: unknown, references?: BuilderReferences, errors?: BuilderErrorsScope): BuilderValidationResult;
@@ -1,14 +1,14 @@
1
1
  import { builder, BuilderSerialisedSchema, modelsMerge, serialise } from '../entities/index.js';
2
- import { BuilderValidateErrors, check } from '../errors/index.js';
2
+ import { BuilderErrorsScope, check } from '../errors/index.js';
3
3
  import { validate } from './brand.js';
4
4
  import { checkModelExpectations, validateModelStructure } from './model.js';
5
5
  import { checkPricingExpectations, validatePricingStructure } from './pricing.js';
6
6
  import { resolver } from './resolve.js';
7
7
  import { checkUIExpectations, validateUIStructure } from './ui.js';
8
8
  const EMPTY_BUILDER = validate(serialise.builder(builder()));
9
- export function validateBuilder(input, references = [], errors = new BuilderValidateErrors()) {
9
+ export function validateBuilder(input, references = [], errors = new BuilderErrorsScope(input)) {
10
10
  if (!check.is(BuilderSerialisedSchema, input)) {
11
- errors.invalidEntity('builder');
11
+ errors.entityInvalid('builder');
12
12
  return [EMPTY_BUILDER, errors.errors];
13
13
  }
14
14
  const resolve = resolver(errors, references, input.bindings);
@@ -1,3 +1,3 @@
1
1
  import type { BuilderExpectationsSerialised, BuilderModelSerialised } from '../entities/index';
2
- import type { BuilderValidateErrors } from '../errors/index';
3
- export declare function checkExpectations(model: BuilderModelSerialised, expectations: BuilderExpectationsSerialised, errors: BuilderValidateErrors): void;
2
+ import type { BuilderErrorsScope } from '../errors/index';
3
+ export declare function checkExpectations(model: BuilderModelSerialised, expectations: BuilderExpectationsSerialised, errors: BuilderErrorsScope): void;
@@ -4,7 +4,7 @@ export function checkExpectations(model, expectations, errors) {
4
4
  if (model[`${kind}s`].some((entry) => entry.name === name)) {
5
5
  return;
6
6
  }
7
- errors.unmetExpectation(kind, name);
7
+ errors.modelUnmetExpectation();
8
8
  });
9
9
  });
10
10
  }
@@ -1,5 +1,4 @@
1
1
  export type { BuilderValidatedMap, ValidationResult } from '../entities/index';
2
- export type { BuilderErrorKind, BuilderValidateErrors } from '../errors/index';
3
2
  export type { BuilderValidatedBrand } from './brand';
4
3
  export type { BuilderValidationResult } from './builder';
5
4
  export type { BuilderInstanceValidationResult } from './instance';
@@ -1,5 +1,5 @@
1
1
  import type { BuilderInstanceValidated, BuilderModelValidated, ValidationResult } from '../entities/index';
2
2
  import type { BuilderInstance } from '../instance';
3
- import { BuilderValidateErrors } from '../errors/index.js';
3
+ import { BuilderErrorsScope } from '../errors/index.js';
4
4
  export type BuilderInstanceValidationResult = ValidationResult<BuilderInstanceValidated>;
5
- export declare function validateInstance(model: BuilderModelValidated, instance: BuilderInstance, errors?: BuilderValidateErrors): BuilderInstanceValidationResult;
5
+ export declare function validateInstance(model: BuilderModelValidated, instance: BuilderInstance, errors?: BuilderErrorsScope): BuilderInstanceValidationResult;
@@ -1,14 +1,14 @@
1
1
  import { optionValueSchema } from '../entities/index.js';
2
- import { BuilderValidateErrors, check } from '../errors/index.js';
2
+ import { BuilderErrorsScope, check } from '../errors/index.js';
3
3
  import { BuilderInstancesSchema } from '../instance.js';
4
4
  import { resolveCollection, resolveOption } from '../mappers/index.js';
5
5
  import { validate } from './brand.js';
6
- export function validateInstance(model, instance, errors = new BuilderValidateErrors()) {
6
+ export function validateInstance(model, instance, errors = new BuilderErrorsScope(instance)) {
7
7
  errors.scope('instance', () => {
8
8
  checkInstance(model, instance);
9
9
  });
10
10
  return [validate(instance), errors.errors];
11
- function checkInstance(scopeModel, scopeInstance, namePrefix = '') {
11
+ function checkInstance(scopeModel, scopeInstance) {
12
12
  scopeModel.options.forEach((option) => {
13
13
  const { name } = option;
14
14
  const payload = resolveOption(option, scopeModel, scopeInstance);
@@ -18,7 +18,7 @@ export function validateInstance(model, instance, errors = new BuilderValidateEr
18
18
  const value = scopeInstance[name];
19
19
  if (!check.is(optionValueSchema(payload), value)) {
20
20
  errors.scope(name, () => {
21
- errors.invalidOption(`${namePrefix}${name}`, value);
21
+ errors.instanceInvalidOption();
22
22
  });
23
23
  }
24
24
  });
@@ -31,14 +31,14 @@ export function validateInstance(model, instance, errors = new BuilderValidateEr
31
31
  const existing = scopeInstance[name];
32
32
  if (!check.is(BuilderInstancesSchema, existing)) {
33
33
  errors.scope(name, () => {
34
- errors.invalidCollection(`${namePrefix}${name}`);
34
+ errors.instanceInvalidCollection();
35
35
  });
36
36
  return;
37
37
  }
38
38
  errors.scope(name, () => {
39
39
  existing.forEach((itemInstance, index) => {
40
40
  errors.scope(index, () => {
41
- checkInstance(payload.model, itemInstance, `${namePrefix}${name}[${index}].`);
41
+ checkInstance(payload.model, itemInstance);
42
42
  });
43
43
  });
44
44
  });
@@ -1,8 +1,8 @@
1
1
  import type { BuilderModelSerialised, BuilderModelValidated, BuilderReferences, ValidationResult } from '../entities/index';
2
2
  import type { ParamableSerialised } from '../references';
3
3
  import type { BuilderResolve } from './resolve';
4
- import { BuilderValidateErrors } from '../errors/index.js';
4
+ import { BuilderErrorsScope } from '../errors/index.js';
5
5
  export type BuilderModelValidationResult = ValidationResult<BuilderModelValidated>;
6
- export declare function validateModel(input: unknown, references?: BuilderReferences, errors?: BuilderValidateErrors): BuilderModelValidationResult;
7
- export declare function checkModelExpectations(mergedModel: BuilderModelSerialised, rootModel: BuilderModelSerialised, errors: BuilderValidateErrors): void;
8
- export declare function validateModelStructure(input: ParamableSerialised<BuilderModelSerialised>, resolve: BuilderResolve, errors: BuilderValidateErrors): BuilderModelValidated;
6
+ export declare function validateModel(input: unknown, references?: BuilderReferences, errors?: BuilderErrorsScope): BuilderModelValidationResult;
7
+ export declare function checkModelExpectations(mergedModel: BuilderModelSerialised, rootModel: BuilderModelSerialised, errors: BuilderErrorsScope): void;
8
+ export declare function validateModelStructure(input: ParamableSerialised<BuilderModelSerialised>, resolve: BuilderResolve, errors: BuilderErrorsScope): BuilderModelValidated;
@@ -1,19 +1,20 @@
1
1
  import { BuilderCollectionConfigSerialisedSchema, BuilderComponentConfigSerialisedSchema, BuilderModelSerialisedSchema, BuilderOptionConfigSerialisedSchema, BuilderWhenSerialisedSchema, whenBranches, model, modelsMerge, serialise, validateCollectionConfig, validateComponentConfig, validateSelect, validateToggle } from '../entities/index.js';
2
- import { BuilderValidateErrors, check } from '../errors/index.js';
2
+ import { BuilderErrorsScope, check } from '../errors/index.js';
3
3
  import { isParamable } from '../references.js';
4
4
  import { validate } from './brand.js';
5
5
  import { checkExpectations } from './expectations.js';
6
6
  import { checkPath } from './paths.js';
7
7
  import { resolver } from './resolve.js';
8
8
  const EMPTY_MODEL = validate(serialise.model(model()));
9
- export function validateModel(input, references = [], errors = new BuilderValidateErrors()) {
9
+ export function validateModel(input, references = [], errors = new BuilderErrorsScope(input)) {
10
10
  if (!check.is(BuilderModelSerialisedSchema, input)) {
11
- errors.invalidEntity('model');
11
+ errors.entityInvalid('model');
12
12
  return [EMPTY_MODEL, errors.errors];
13
13
  }
14
14
  const resolve = resolver(errors, references);
15
15
  const structure = validateModelStructure(input, resolve, errors);
16
- const data = validate(modelsMerge(structure));
16
+ const merged = modelsMerge(structure);
17
+ const data = validate(merged);
17
18
  checkModelExpectations(data, structure, errors);
18
19
  return [data, errors.errors];
19
20
  }
@@ -33,111 +34,127 @@ export function checkModelExpectations(mergedModel, rootModel, errors) {
33
34
  }
34
35
  }
35
36
  export function validateModelStructure(input, resolve, errors) {
36
- const resolved = resolve(input);
37
- if (!check.is(BuilderModelSerialisedSchema, resolved)) {
38
- return validate(serialise.model(model()));
39
- }
40
- const modelInput = resolved;
41
- const skipPathValidation = modelInput.expectations.length > 0;
42
- const childModels = errors.scope('models', () => modelInput.models.flatMap((part, partIndex) => errors.scope(partIndex, () => [validateModelStructure(part, resolve, errors)])));
43
- const options = errors.scope('options', () => walkEntries(modelInput.options, (resolved) => {
44
- whenBranches(resolved.payload, BuilderOptionConfigSerialisedSchema)
45
- .filter((config) => !isParamable(config))
46
- .forEach((config) => {
47
- if (config.type === 'select') {
48
- validateSelect(config, [], errors);
49
- }
50
- else if (config.type === 'toggle') {
51
- validateToggle(config, [], errors);
52
- }
53
- });
54
- }));
55
- const components = errors.scope('components', () => walkEntries(modelInput.components, (resolved) => {
56
- whenBranches(resolved.payload, BuilderComponentConfigSerialisedSchema)
57
- .filter((config) => !isParamable(config))
58
- .forEach((config) => {
59
- validateComponentConfig(config, [], errors);
60
- });
61
- }));
62
- const collections = errors.scope('collections', () => walkEntries(modelInput.collections, (resolved) => {
63
- whenBranches(resolved.payload, BuilderCollectionConfigSerialisedSchema)
64
- .filter((config) => !isParamable(config))
65
- .forEach((config) => {
66
- validateCollectionConfig(config, [], errors);
67
- });
68
- }));
69
- return {
70
- ...modelInput,
71
- models: childModels,
72
- options,
73
- components,
74
- collections
37
+ const seenGlobal = {
38
+ option: new Set(),
39
+ component: new Set(),
40
+ collection: new Set()
75
41
  };
76
- function walkEntries(entries, validateBranches) {
77
- const seen = new Set();
78
- const items = [];
79
- entries.forEach((entry, entryIndex) => {
80
- errors.scope(entryIndex, () => {
81
- const resolved = resolveEntry(entry, seen);
82
- if (resolved == null) {
83
- return;
42
+ return walkStructure(input);
43
+ function walkStructure(input) {
44
+ const resolved = resolve(input);
45
+ if (!check.is(BuilderModelSerialisedSchema, resolved)) {
46
+ return validate(serialise.model(model()));
47
+ }
48
+ const modelInput = resolved;
49
+ const skipPathValidation = modelInput.expectations.length > 0;
50
+ const childModels = errors.scope('models', () => modelInput.models.flatMap((part, partIndex) => errors.scope(partIndex, () => [walkStructure(part)])));
51
+ const options = errors.scope('options', () => walkEntries(modelInput.options, 'option', (resolved) => {
52
+ whenBranches(resolved.payload, BuilderOptionConfigSerialisedSchema)
53
+ .filter((config) => !isParamable(config))
54
+ .forEach((config) => {
55
+ if (config.type === 'select') {
56
+ validateSelect(config, [], errors);
57
+ }
58
+ else if (config.type === 'toggle') {
59
+ validateToggle(config, [], errors);
84
60
  }
85
- validateBranches(resolved);
86
- items.push(resolved);
87
61
  });
88
- });
89
- return items;
90
- }
91
- function resolveEntry(entry, seen) {
92
- let paths = entry.paths;
93
- if (entry.paths != null) {
94
- const resolvedPaths = errors.scope('paths', () => resolve(entry.paths));
95
- paths = resolvedPaths ?? entry.paths;
96
- }
97
- const payload = errors.scope('payload', () => resolve(entry.payload));
98
- const resolvedEntry = { ...entry, paths, payload: payload ?? entry.payload };
99
- if (seen.has(resolvedEntry.name)) {
100
- errors.duplicateName(resolvedEntry.name);
101
- return null;
102
- }
103
- seen.add(resolvedEntry.name);
104
- checkPaths(resolvedEntry);
105
- return resolvedEntry;
106
- }
107
- function checkPaths(entry) {
108
- if (skipPathValidation) {
109
- return;
110
- }
111
- const { paths, payload } = entry;
112
- if (Array.isArray(paths)) {
113
- errors.scope('paths', () => {
114
- paths.forEach((path, pathIndex) => {
115
- errors.scope(pathIndex, () => {
116
- checkPath(modelInput, path, errors);
117
- });
118
- });
62
+ }));
63
+ const components = errors.scope('components', () => walkEntries(modelInput.components, 'component', (resolved) => {
64
+ whenBranches(resolved.payload, BuilderComponentConfigSerialisedSchema)
65
+ .filter((config) => !isParamable(config))
66
+ .forEach((config) => {
67
+ validateComponentConfig(config, [], errors);
119
68
  });
120
- }
121
- checkWhenPaths(payload);
122
- }
123
- function checkWhenPaths(payload) {
124
- if (!check.is(BuilderWhenSerialisedSchema, payload)) {
125
- return;
126
- }
127
- if (payload.type === 'match' && Array.isArray(payload.matchPath)) {
128
- errors.scope('payload', () => {
129
- errors.scope('matchPath', () => {
130
- checkPath(modelInput, payload.matchPath, errors);
69
+ }));
70
+ const collections = errors.scope('collections', () => walkEntries(modelInput.collections, 'collection', (resolved) => {
71
+ whenBranches(resolved.payload, BuilderCollectionConfigSerialisedSchema)
72
+ .filter((config) => !isParamable(config))
73
+ .forEach((config) => {
74
+ validateCollectionConfig(config, [], errors);
75
+ });
76
+ }));
77
+ return {
78
+ ...modelInput,
79
+ models: childModels,
80
+ options,
81
+ components,
82
+ collections
83
+ };
84
+ function walkEntries(entries, kind, validateBranches) {
85
+ const seenLocal = new Set();
86
+ const items = [];
87
+ entries.forEach((entry, entryIndex) => {
88
+ errors.scope(entryIndex, () => {
89
+ const resolved = resolveEntry(entry, seenLocal, kind);
90
+ if (resolved == null) {
91
+ return;
92
+ }
93
+ validateBranches(resolved);
94
+ items.push(resolved);
131
95
  });
132
96
  });
133
- return;
97
+ return items;
98
+ }
99
+ function resolveEntry(entry, seenLocal, kind) {
100
+ let paths = entry.paths;
101
+ if (entry.paths != null) {
102
+ const resolvedPaths = errors.scope('paths', () => resolve(entry.paths));
103
+ paths = resolvedPaths ?? entry.paths;
104
+ }
105
+ const payload = errors.scope('payload', () => resolve(entry.payload));
106
+ const resolvedEntry = { ...entry, paths, payload: payload ?? entry.payload };
107
+ if (seenLocal.has(resolvedEntry.name)) {
108
+ if (kind === 'option') {
109
+ errors.modelDuplicateOption();
110
+ }
111
+ else if (kind === 'component') {
112
+ errors.modelDuplicateComponent();
113
+ }
114
+ else {
115
+ errors.modelDuplicateCollection();
116
+ }
117
+ return null;
118
+ }
119
+ if (seenGlobal[kind].has(resolvedEntry.name)) {
120
+ if (kind === 'option') {
121
+ errors.modelOverriddenOption();
122
+ }
123
+ else if (kind === 'component') {
124
+ errors.modelOverriddenComponent();
125
+ }
126
+ else {
127
+ errors.modelOverriddenCollection();
128
+ }
129
+ }
130
+ seenLocal.add(resolvedEntry.name);
131
+ seenGlobal[kind].add(resolvedEntry.name);
132
+ checkPaths(resolvedEntry);
133
+ return resolvedEntry;
134
134
  }
135
- if (payload.type === 'unless' && Array.isArray(payload.unlessPath)) {
136
- errors.scope('payload', () => {
137
- errors.scope('unlessPath', () => {
138
- checkPath(modelInput, payload.unlessPath, errors);
135
+ function checkPaths(entry) {
136
+ if (skipPathValidation) {
137
+ return;
138
+ }
139
+ const { paths, payload } = entry;
140
+ if (Array.isArray(paths)) {
141
+ errors.scope('paths', () => {
142
+ paths.forEach((path, pathIndex) => {
143
+ errors.scope(pathIndex, () => {
144
+ checkPath(modelInput, path, errors);
145
+ });
146
+ });
139
147
  });
140
- });
148
+ }
149
+ if (!check.is(BuilderWhenSerialisedSchema, payload)) {
150
+ return;
151
+ }
152
+ if (payload.type === 'match' && Array.isArray(payload.matchPath)) {
153
+ errors.scope('payload', () => errors.scope('matchPath', () => checkPath(modelInput, payload.matchPath, errors)));
154
+ }
155
+ if (payload.type === 'unless' && Array.isArray(payload.unlessPath)) {
156
+ errors.scope('payload', () => errors.scope('unlessPath', () => checkPath(modelInput, payload.unlessPath, errors)));
157
+ }
141
158
  }
142
159
  }
143
160
  }
@@ -1,3 +1,3 @@
1
1
  import type { BuilderModelSerialised, BuilderPath } from '../entities/index';
2
- import type { BuilderValidateErrors } from '../errors/index';
3
- export declare function checkPath(model: BuilderModelSerialised, path: BuilderPath, errors: BuilderValidateErrors): void;
2
+ import type { BuilderErrorsScope } from '../errors/index';
3
+ export declare function checkPath(model: BuilderModelSerialised, path: BuilderPath, errors: BuilderErrorsScope): void;