@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,8 +1,7 @@
1
- import * as v from 'valibot';
2
1
  import { BuilderPricingExpressionSchema } from '../entities/index.js';
3
2
  import { check } from '../errors/index.js';
4
3
  import { BuilderComponentVariantSchema } from '../instance.js';
5
- const NumberSchema = v.number();
4
+ import { NumberSchema } from '../primitive.js';
6
5
  export function price(pricingInput, order) {
7
6
  const { rates, formula } = pricingInput;
8
7
  if (formula == null) {
@@ -91,7 +90,7 @@ function reduceValues(values, mode) {
91
90
  if (mode === 'sum') {
92
91
  return values.reduce((accumulator, value) => accumulator + value, 0);
93
92
  }
94
- check.truthy(values.length > 0, 'Pricing: cannot reduce empty set with "first"! ❌');
93
+ check.invariant(values.length > 0);
95
94
  const [first] = values;
96
95
  return first;
97
96
  }
@@ -0,0 +1,9 @@
1
+ import type { BuilderPath, BuilderReferences, BuilderValidated } from '../../entities/index';
2
+ import type { BuilderInstance } from '../../instance';
3
+ export type BuilderRenderCollectionAddRemove = (instance: BuilderInstance) => BuilderInstance;
4
+ export type BuilderRenderCollection = Readonly<{
5
+ add?: BuilderRenderCollectionAddRemove;
6
+ remove?: BuilderRenderCollectionAddRemove;
7
+ }>;
8
+ export type BuilderRenderCollections = Readonly<Record<string, BuilderRenderCollection>>;
9
+ export declare function renderCollection(itemsPath: BuilderPath, index: number, min: number, max: number, length: number, builder: BuilderValidated, references: BuilderReferences): BuilderRenderCollection;
@@ -0,0 +1,27 @@
1
+ import { createInstance } from '../instance.js';
2
+ import { readPath, writePath } from './paths.js';
3
+ export function renderCollection(itemsPath, index, min, max, length, builder, references) {
4
+ let handlers = {};
5
+ if (length < max) {
6
+ handlers = {
7
+ ...handlers,
8
+ add: (updateInstance) => {
9
+ const current = (readPath(updateInstance, itemsPath) ?? []);
10
+ const clone = current[index] ?? {};
11
+ const next = [...current.slice(0, index + 1), clone, ...current.slice(index + 1)];
12
+ return createInstance(builder, writePath(updateInstance, itemsPath, next), references);
13
+ }
14
+ };
15
+ }
16
+ if (length > min) {
17
+ handlers = {
18
+ ...handlers,
19
+ remove: (updateInstance) => {
20
+ const current = (readPath(updateInstance, itemsPath) ?? []);
21
+ const next = [...current.slice(0, index), ...current.slice(index + 1)];
22
+ return createInstance(builder, writePath(updateInstance, itemsPath, next), references);
23
+ }
24
+ };
25
+ }
26
+ return handlers;
27
+ }
@@ -0,0 +1,5 @@
1
+ import type { BuilderReferences } from '../../entities/index';
2
+ import type { BuilderRenderMetadata } from './option';
3
+ export declare function composeLabel(label: unknown, labels: ReadonlyArray<string>, references: BuilderReferences): string;
4
+ export declare function composeString(value: unknown, references: BuilderReferences): string;
5
+ export declare function composeMetadata(value: unknown, references: BuilderReferences): BuilderRenderMetadata;
@@ -0,0 +1,15 @@
1
+ import { resolveReference } from '../resolve.js';
2
+ import { StringSchema } from '../../primitive.js';
3
+ import { BuilderRenderMetadataSchema } from './option.js';
4
+ export function composeLabel(label, labels, references) {
5
+ return [...labels, composeString(label, references)]
6
+ .filter((segment) => segment !== '')
7
+ .join(', ');
8
+ }
9
+ export function composeString(value, references) {
10
+ return resolveReference(value, references, StringSchema);
11
+ }
12
+ export function composeMetadata(value, references) {
13
+ const resolved = resolveReference(value, references, BuilderRenderMetadataSchema);
14
+ return Object.fromEntries(Object.entries(resolved).map(([key, entry]) => [key, resolveReference(entry, references)]));
15
+ }
@@ -0,0 +1,3 @@
1
+ import type { BuilderDescriptionItem, BuilderModelValidated, BuilderReferences, BuilderUIDescribeValidated } from '../../entities/index';
2
+ import type { BuilderInstance } from '../../instance';
3
+ export declare function renderDescription(describe: BuilderUIDescribeValidated, label: string, model: BuilderModelValidated, instance: BuilderInstance, references: BuilderReferences): BuilderDescriptionItem | null;
@@ -0,0 +1,17 @@
1
+ import { check } from '../../errors/index.js';
2
+ import { BuilderPrimitiveSchema } from '../../primitive.js';
3
+ import { resolvePath } from '../resolve.js';
4
+ export function renderDescription(describe, label, model, instance, references) {
5
+ const values = describe.inputs.flatMap((input) => {
6
+ const found = resolvePath(model, instance, input.path, references);
7
+ if (found?.value == null) {
8
+ return [];
9
+ }
10
+ check.assert(BuilderPrimitiveSchema, found.value);
11
+ return [found.value];
12
+ });
13
+ if (values.length === 0) {
14
+ return null;
15
+ }
16
+ return [label, values.join(' ')];
17
+ }
@@ -1,4 +1,6 @@
1
- export type { BuilderRenderMetadata, BuilderRenderOption, BuilderRenderOptions, BuilderRenderPage, BuilderRenderPages, BuilderRenderUpdate } from './pages';
1
+ export type { BuilderRenderCollection, BuilderRenderCollectionAddRemove, BuilderRenderCollections } from './collection';
2
+ export type { BuilderRenderMetadata, BuilderRenderOption, BuilderRenderOptionUpdate, BuilderRenderOptions } from './option';
3
+ export type { BuilderRenderPage, BuilderRenderPages } from './page';
2
4
  export type { BuilderRenderResult } from './render';
3
5
  export { render } from './render.js';
4
6
  export { ordinal } from './ordinal.js';
@@ -0,0 +1,20 @@
1
+ import type { BuilderModelValidated, BuilderOptionConfigSerialised, BuilderPath, BuilderReferences, BuilderUIInputValidated, BuilderValidated } from '../../entities/index';
2
+ import type { BuilderInstance } from '../../instance';
3
+ import type { BuilderPrimitive } from '../../primitive';
4
+ import * as v from 'valibot';
5
+ export type BuilderRenderOptionUpdate = (instance: BuilderInstance, primitive: BuilderPrimitive) => BuilderInstance;
6
+ export declare const BuilderRenderMetadataSchema: v.SchemaWithPipe<readonly [v.RecordSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, v.UnknownSchema, undefined>, v.ReadonlyAction<{
7
+ [x: string]: unknown;
8
+ }>]>;
9
+ export type BuilderRenderMetadata = v.InferOutput<typeof BuilderRenderMetadataSchema>;
10
+ export type BuilderRenderOption = Readonly<{
11
+ name: string;
12
+ value: BuilderPrimitive;
13
+ update: BuilderRenderOptionUpdate;
14
+ option: BuilderOptionConfigSerialised;
15
+ displayName?: string;
16
+ kind?: string;
17
+ metadata?: BuilderRenderMetadata;
18
+ }>;
19
+ export type BuilderRenderOptions = ReadonlyArray<BuilderRenderOption>;
20
+ export declare function renderOption(input: BuilderUIInputValidated, path: BuilderPath, builder: BuilderValidated, model: BuilderModelValidated, instance: BuilderInstance, references: BuilderReferences): BuilderRenderOption | null;
@@ -0,0 +1,25 @@
1
+ import * as v from 'valibot';
2
+ import { check } from '../../errors/index.js';
3
+ import { BuilderPrimitiveSchema, NameSchema } from '../../primitive.js';
4
+ import { createInstance } from '../instance.js';
5
+ import { resolvePath } from '../resolve.js';
6
+ import { composeMetadata, composeString } from './compose.js';
7
+ import { writePath } from './paths.js';
8
+ export const BuilderRenderMetadataSchema = v.pipe(v.record(NameSchema, v.unknown()), v.readonly());
9
+ export function renderOption(input, path, builder, model, instance, references) {
10
+ const found = resolvePath(model, instance, input.path, references);
11
+ if (found == null) {
12
+ return null;
13
+ }
14
+ const fullPath = [...path, ...input.path];
15
+ check.assert(BuilderPrimitiveSchema, found.value);
16
+ return {
17
+ name: found.name,
18
+ option: found.payload,
19
+ value: found.value,
20
+ update: (updateInstance, updateValue) => createInstance(builder, writePath(updateInstance, fullPath, updateValue), references),
21
+ displayName: input.displayName && composeString(input.displayName, references),
22
+ kind: input.kind && composeString(input.kind, references),
23
+ metadata: input.metadata && composeMetadata(input.metadata, references)
24
+ };
25
+ }
@@ -0,0 +1,11 @@
1
+ import type { BuilderModelValidated, BuilderPath, BuilderReferences, BuilderUIPageValidated, BuilderValidated } from '../../entities/index';
2
+ import type { BuilderInstance } from '../../instance';
3
+ import type { BuilderRenderCollections } from './collection';
4
+ import type { BuilderRenderOptions } from './option';
5
+ export type BuilderRenderPage = Readonly<{
6
+ label: string;
7
+ options: BuilderRenderOptions;
8
+ collections: BuilderRenderCollections;
9
+ }>;
10
+ export type BuilderRenderPages = ReadonlyArray<BuilderRenderPage>;
11
+ export declare function renderPage(page: BuilderUIPageValidated, label: string, path: BuilderPath, collections: BuilderRenderCollections, builder: BuilderValidated, model: BuilderModelValidated, instance: BuilderInstance, references: BuilderReferences): BuilderRenderPage | null;
@@ -0,0 +1,11 @@
1
+ import { renderOption } from './option.js';
2
+ export function renderPage(page, label, path, collections, builder, model, instance, references) {
3
+ const options = page.inputs.flatMap((input) => {
4
+ const option = renderOption(input, path, builder, model, instance, references);
5
+ return option == null ? [] : [option];
6
+ });
7
+ if (options.length === 0) {
8
+ return null;
9
+ }
10
+ return { label, options, collections };
11
+ }
@@ -0,0 +1,3 @@
1
+ import type { BuilderPath } from '../../entities/index';
2
+ export declare function readPath(container: unknown, path: BuilderPath): unknown;
3
+ export declare function writePath(container: unknown, path: BuilderPath, value: unknown): unknown;
@@ -0,0 +1,21 @@
1
+ import { check } from '../../errors/index.js';
2
+ import { NumberSchema } from '../../primitive.js';
3
+ export function readPath(container, path) {
4
+ return path.reduce((current, segment) => current == null ? undefined : current[segment], container);
5
+ }
6
+ export function writePath(container, path, value) {
7
+ if (path.length === 0) {
8
+ return value;
9
+ }
10
+ const [head, ...tail] = path;
11
+ if (check.is(NumberSchema, head)) {
12
+ const next = Array.isArray(container) ? [...container] : [];
13
+ next[head] = writePath(next[head], tail, value);
14
+ return next;
15
+ }
16
+ const next = container != null && !Array.isArray(container)
17
+ ? { ...container }
18
+ : {};
19
+ next[head] = writePath(next[head], tail, value);
20
+ return next;
21
+ }
@@ -1,5 +1,5 @@
1
1
  import type { BuilderDescription, BuilderInstanceValidated, BuilderReferences, BuilderValidated } from '../../entities/index';
2
- import type { BuilderRenderPages } from './pages';
2
+ import type { BuilderRenderPages } from './page';
3
3
  export type BuilderRenderResult = Readonly<{
4
4
  layout: BuilderRenderPages;
5
5
  description: BuilderDescription;
@@ -1,120 +1,55 @@
1
- import * as v from 'valibot';
2
1
  import { check } from '../../errors/index.js';
3
2
  import { BuilderInstancesSchema } from '../../instance.js';
4
- import { BuilderPrimitiveSchema } from '../../primitive.js';
5
- import { createInstance } from '../instance.js';
6
- import { resolveCollection, resolvePath, resolveReference } from '../resolve.js';
3
+ import { resolveCollection } from '../resolve.js';
4
+ import { renderCollection } from './collection.js';
5
+ import { composeLabel, composeString } from './compose.js';
6
+ import { renderDescription } from './description.js';
7
7
  import { ordinal } from './ordinal.js';
8
- import { BuilderRenderMetadataSchema } from './pages.js';
8
+ import { renderPage } from './page.js';
9
9
  export function render(builder, instance, references = []) {
10
10
  const layout = [];
11
11
  const description = [];
12
- walkTree(builder.ui);
12
+ renderUITree(builder.ui);
13
13
  return { layout, description };
14
- function walkTree(ui) {
15
- ui.uis.forEach((nested) => walkTree(nested));
16
- walkItems(ui.items, builder.model, [], [], instance);
14
+ function renderUITree(ui) {
15
+ ui.uis.forEach(renderUITree);
16
+ renderUIItems(ui.items, builder.model, instance, [], [], {});
17
17
  }
18
- function walkItems(items, model, collectionPath, labelContext, currentInstance) {
18
+ function renderUIItems(items, model, instance, labels, path, collections) {
19
19
  items.forEach((item) => {
20
20
  if (item.type === 'page') {
21
- emitPage(item, model, collectionPath, labelContext, currentInstance);
21
+ const page = renderPage(item, composeLabel(item.label, labels, references), path, collections, builder, model, instance, references);
22
+ if (page != null) {
23
+ layout.push(page);
24
+ }
22
25
  return;
23
26
  }
24
27
  if (item.type === 'describe') {
25
- emitDescribe(item, model, labelContext, currentInstance);
28
+ const descriptionItem = renderDescription(item, composeLabel(item.label, labels, references), model, instance, references);
29
+ if (descriptionItem != null) {
30
+ description.push(descriptionItem);
31
+ }
26
32
  return;
27
33
  }
28
- const collection = findCollection(model, currentInstance, item.name);
34
+ const definition = model.collections.find((candidate) => candidate.name === item.name);
35
+ if (definition == null) {
36
+ return;
37
+ }
38
+ const collection = resolveCollection(definition, model, instance, references);
29
39
  if (collection == null) {
30
40
  return;
31
41
  }
32
- const itemInstances = currentInstance[item.name];
33
- check.assert(BuilderInstancesSchema, itemInstances, 'Collection field is not an array! ❌');
34
- const mergedItemModel = collection.model;
35
- itemInstances.forEach((itemInstance, index) => {
36
- walkItems(item.items, mergedItemModel, [...collectionPath, item.name, index], [...labelContext, `${ordinal(index)} ${composeLabel(item.label)}`], itemInstance);
42
+ const instances = instance[item.name];
43
+ check.assert(BuilderInstancesSchema, instances);
44
+ const itemsPath = [...path, item.name];
45
+ const { min, max } = collection;
46
+ const { length } = instances;
47
+ instances.forEach((itemInstance, index) => {
48
+ renderUIItems(item.items, collection.model, itemInstance, [...labels, `${ordinal(index)} ${composeString(item.label, references)}`], [...itemsPath, index], {
49
+ ...collections,
50
+ [item.name]: renderCollection(itemsPath, index, min, max, length, builder, references)
51
+ });
37
52
  });
38
53
  });
39
54
  }
40
- function emitPage(page, model, collectionPath, labelContext, currentInstance) {
41
- const options = page.inputs.flatMap((input) => {
42
- const { path } = input;
43
- const found = resolvePath(model, currentInstance, path, references);
44
- if (found == null) {
45
- return [];
46
- }
47
- const fullPath = [...collectionPath, ...path];
48
- check.assert(BuilderPrimitiveSchema, found.value);
49
- return [
50
- {
51
- name: found.name,
52
- option: found.payload,
53
- value: found.value,
54
- update: (updateInstance, updateValue) => createInstance(builder, setPath(updateInstance, fullPath, updateValue), references),
55
- displayName: input.displayName && composeString(input.displayName, 'displayName'),
56
- kind: input.kind && composeString(input.kind, 'kind'),
57
- metadata: input.metadata && composeMetadata(input.metadata)
58
- }
59
- ];
60
- });
61
- if (options.length === 0) {
62
- return;
63
- }
64
- layout.push({ label: composeLabel(page.label, labelContext), options });
65
- }
66
- function emitDescribe(describe, model, labelContext, currentInstance) {
67
- const composedLabel = composeLabel(describe.label, labelContext);
68
- const values = describe.inputs.flatMap((input) => {
69
- const { path } = input;
70
- const found = resolvePath(model, currentInstance, path, references);
71
- if (found?.value == null) {
72
- return [];
73
- }
74
- check.assert(BuilderPrimitiveSchema, found.value);
75
- return [found.value];
76
- });
77
- if (values.length === 0) {
78
- return;
79
- }
80
- description.push([composedLabel, values.join(' ')]);
81
- }
82
- function composeLabel(label, labelContext = []) {
83
- const resolved = resolveReference(label, references);
84
- check.assert(v.string(), resolved, 'Label did not resolve to a string! ❌');
85
- if (labelContext.length === 0) {
86
- return resolved;
87
- }
88
- if (resolved === '') {
89
- return labelContext.join(', ');
90
- }
91
- return `${labelContext.join(', ')}, ${resolved}`;
92
- }
93
- function composeString(value, fieldLabel) {
94
- const resolved = resolveReference(value, references);
95
- check.assert(v.string(), resolved, `Input ${fieldLabel} did not resolve to a string! ❌`);
96
- return resolved;
97
- }
98
- function composeMetadata(value) {
99
- const resolved = resolveReference(value, references);
100
- check.assert(BuilderRenderMetadataSchema, resolved, 'Input metadata did not resolve to an object! ❌');
101
- return Object.fromEntries(Object.entries(resolved).map(([key, entry]) => [key, resolveReference(entry, references)]));
102
- }
103
- function findCollection(model, instance, collectionName) {
104
- const entry = model.collections.find((candidate) => candidate.name === collectionName);
105
- return entry == null ? null : resolveCollection(entry, model, instance, references);
106
- }
107
- }
108
- function setPath(container, path, value) {
109
- const [head, ...tail] = path;
110
- if (typeof head === 'number') {
111
- const arrayContainer = container ? [...container] : [];
112
- arrayContainer[head] = tail.length === 0 ? value : setPath(arrayContainer[head], tail, value);
113
- return arrayContainer;
114
- }
115
- const objectContainer = container ? container : {};
116
- return {
117
- ...objectContainer,
118
- [head]: tail.length === 0 ? value : setPath(objectContainer[head], tail, value)
119
- };
120
55
  }
@@ -1,12 +1,13 @@
1
1
  import type { BuilderCollectionConfigsSerialised, BuilderCollectionConfigValidated, BuilderCollectionSerialised, BuilderCollectionValidated, BuilderComponentConfigsSerialised, BuilderComponentConfigValidated, BuilderComponentSerialised, BuilderComponentValidated, BuilderModelValidated, BuilderOptionConfigsSerialised, BuilderOptionConfigValidated, BuilderOptionSerialised, BuilderOptionValidated, BuilderPath, BuilderReferences } from '../entities/index';
2
2
  import type { BuilderInstance } from '../instance';
3
+ import * as v from 'valibot';
3
4
  export declare function resolveOption(option: BuilderOptionValidated, model: BuilderModelValidated, instance: unknown, references?: BuilderReferences): BuilderOptionConfigValidated | null;
4
5
  export declare function resolveOptions(option: BuilderOptionSerialised, references?: BuilderReferences): BuilderOptionConfigsSerialised;
5
6
  export declare function resolveComponent(component: BuilderComponentValidated, model: BuilderModelValidated, instance: unknown, references?: BuilderReferences): BuilderComponentConfigValidated | null;
6
7
  export declare function resolveComponents(component: BuilderComponentSerialised, references?: BuilderReferences): BuilderComponentConfigsSerialised;
7
8
  export declare function resolveCollection(collection: BuilderCollectionValidated, model: BuilderModelValidated, instance: unknown, references?: BuilderReferences): BuilderCollectionConfigValidated | null;
8
9
  export declare function resolveCollections(collection: BuilderCollectionSerialised, references?: BuilderReferences): BuilderCollectionConfigsSerialised;
9
- export declare function resolveReference(value: unknown, references: BuilderReferences): unknown;
10
+ export declare function resolveReference<Schema extends v.GenericSchema = v.GenericSchema<unknown>>(value: unknown, references: BuilderReferences, schema?: Schema): v.InferOutput<Schema>;
10
11
  export type ResolvedPath = {
11
12
  readonly name: string;
12
13
  readonly payload: BuilderOptionConfigValidated;
@@ -1,38 +1,40 @@
1
1
  import * as v from 'valibot';
2
- import { BuilderCollectionConfigSerialisedSchema, BuilderComponentConfigSerialisedSchema, BuilderOptionConfigSerialisedSchema, BuilderPathsSchema, BuilderWhenSerialisedSchema, modelsMerge, whenBranches } from '../entities/index.js';
2
+ import { BuilderCollectionConfigSerialisedSchema, BuilderCollectionWhenSerialisedSchema, BuilderComponentConfigSerialisedSchema, BuilderComponentWhenSerialisedSchema, BuilderOptionConfigSerialisedSchema, BuilderOptionWhenSerialisedSchema, BuilderPathsSchema, modelsMerge, whenBranches } from '../entities/index.js';
3
3
  import { check } from '../errors/index.js';
4
4
  import { BuilderInstanceSchema, BuilderInstancesSchema } from '../instance.js';
5
- import { BuilderPrimitiveSchema } from '../primitive.js';
6
- import { BuilderRefSerialisedSchema } from '../references.js';
5
+ import { BuilderPrimitiveSchema, NameSchema, NumberSchema } from '../primitive.js';
6
+ import { BuilderRefSerialisedSchema, paramable } from '../references.js';
7
7
  export function resolveOption(option, model, instance, references = []) {
8
- return resolveEntryInstance(option, BuilderOptionConfigSerialisedSchema, model, instance, references);
8
+ return resolveEntryInstance(option, BuilderOptionConfigSerialisedSchema, BuilderOptionWhenSerialisedSchema, model, instance, references);
9
9
  }
10
10
  export function resolveOptions(option, references = []) {
11
- return resolveEntryExhaustive(option, BuilderOptionConfigSerialisedSchema, references);
11
+ return resolveEntryExhaustive(option, BuilderOptionConfigSerialisedSchema, BuilderOptionWhenSerialisedSchema, references);
12
12
  }
13
13
  export function resolveComponent(component, model, instance, references = []) {
14
- return resolveEntryInstance(component, BuilderComponentConfigSerialisedSchema, model, instance, references);
14
+ return resolveEntryInstance(component, BuilderComponentConfigSerialisedSchema, BuilderComponentWhenSerialisedSchema, model, instance, references);
15
15
  }
16
16
  export function resolveComponents(component, references = []) {
17
- return resolveEntryExhaustive(component, BuilderComponentConfigSerialisedSchema, references);
17
+ return resolveEntryExhaustive(component, BuilderComponentConfigSerialisedSchema, BuilderComponentWhenSerialisedSchema, references);
18
18
  }
19
19
  export function resolveCollection(collection, model, instance, references = []) {
20
- return resolveEntryInstance(collection, BuilderCollectionConfigSerialisedSchema, model, instance, references);
20
+ return resolveEntryInstance(collection, BuilderCollectionConfigSerialisedSchema, BuilderCollectionWhenSerialisedSchema, model, instance, references);
21
21
  }
22
22
  export function resolveCollections(collection, references = []) {
23
- return resolveEntryExhaustive(collection, BuilderCollectionConfigSerialisedSchema, references);
23
+ return resolveEntryExhaustive(collection, BuilderCollectionConfigSerialisedSchema, BuilderCollectionWhenSerialisedSchema, references);
24
24
  }
25
- export function resolveReference(value, references) {
26
- if (!check.is(BuilderRefSerialisedSchema, value)) {
27
- return value;
25
+ export function resolveReference(value, references, schema = v.unknown()) {
26
+ let resolved = value;
27
+ if (check.is(BuilderRefSerialisedSchema, value)) {
28
+ const found = references.find((entry) => entry.id === value.id);
29
+ check.invariant(found);
30
+ resolved = found.serialised;
28
31
  }
29
- const found = references.find((entry) => entry.id === value.id);
30
- check.truthy(found, `Reference '${value.id}' not found! ❌`);
31
- return found.serialised;
32
+ check.assert(schema, resolved);
33
+ return resolved;
32
34
  }
33
35
  export function resolvePath(model, instance, path, references = []) {
34
36
  const optionName = path.at(-1);
35
- check.assert(v.string(), optionName, 'Path must end at an option name! ❌');
37
+ check.assert(NameSchema, optionName);
36
38
  const descended = resolveItems(model, instance, path.slice(0, -1), references);
37
39
  if (descended == null) {
38
40
  return null;
@@ -40,7 +42,7 @@ export function resolvePath(model, instance, path, references = []) {
40
42
  const [scopeModel, scopeInstance] = descended;
41
43
  const merged = modelsMerge(scopeModel);
42
44
  const option = merged.options.find((entry) => entry.name === optionName);
43
- check.truthy(option, `Path target '${optionName}' is not an option! ❌`);
45
+ check.invariant(option);
44
46
  const payload = resolveOption(option, scopeModel, scopeInstance, references);
45
47
  if (payload == null) {
46
48
  return null;
@@ -53,11 +55,11 @@ export function resolveItems(model, instance, pairs, references = []) {
53
55
  return [model, instance];
54
56
  }
55
57
  const [name, index, ...rest] = pairs;
56
- check.assert(v.string(), name);
57
- check.assert(v.number(), index, `Collection '${name}' must be followed by an index! ❌`);
58
+ check.assert(NameSchema, name);
59
+ check.assert(NumberSchema, index);
58
60
  const merged = modelsMerge(model);
59
61
  const collection = merged.collections.find((entry) => entry.name === name);
60
- check.truthy(collection, `Path segment '${name}' is not a collection! ❌`);
62
+ check.invariant(collection);
61
63
  const resolved = resolveCollection(collection, model, instance, references);
62
64
  if (resolved == null) {
63
65
  return null;
@@ -70,12 +72,11 @@ export function resolveItems(model, instance, pairs, references = []) {
70
72
  }
71
73
  return resolveItems(resolved.model, item, rest, references);
72
74
  }
73
- function resolveEntryInstance(entry, schema, model, instance, references) {
74
- const payload = resolveReference(entry.payload, references);
75
+ function resolveEntryInstance(entry, schema, whenSchema, model, instance, references) {
76
+ const payload = resolveReference(entry.payload, references, paramable(v.union([schema, whenSchema])));
75
77
  if (check.is(schema, payload)) {
76
78
  return payload;
77
79
  }
78
- check.assert(BuilderWhenSerialisedSchema, payload);
79
80
  check.assert(BuilderPathsSchema, entry.paths);
80
81
  const config = payload;
81
82
  if (!entry.paths.every((path) => Boolean(resolvePath(model, instance, path, references)?.value))) {
@@ -103,6 +104,6 @@ function resolveEntryInstance(entry, schema, model, instance, references) {
103
104
  }
104
105
  }
105
106
  }
106
- function resolveEntryExhaustive(entry, schema, references) {
107
- return whenBranches(resolveReference(entry.payload, references), schema);
107
+ function resolveEntryExhaustive(entry, schema, whenSchema, references) {
108
+ return whenBranches(resolveReference(entry.payload, references, paramable(v.union([schema, whenSchema]))), schema);
108
109
  }
@@ -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
  ]))
@@ -1,4 +1,9 @@
1
1
  import * as v from 'valibot';
2
+ export declare const StringSchema: v.StringSchema<undefined>;
3
+ export declare const NumberSchema: v.NumberSchema<undefined>;
4
+ export declare const BooleanSchema: v.BooleanSchema<undefined>;
5
+ export declare const NameSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
6
+ export declare const IdSchema: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
2
7
  export declare const BuilderPrimitiveSchema: v.NullableSchema<v.UnionSchema<[v.StringSchema<undefined>, v.BooleanSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>;
3
8
  export type BuilderPrimitive = v.InferOutput<typeof BuilderPrimitiveSchema>;
4
9
  export declare const BuilderPrimitivesSchema: v.SchemaWithPipe<readonly [v.ArraySchema<v.NullableSchema<v.UnionSchema<[v.StringSchema<undefined>, v.BooleanSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>, undefined>, v.ReadonlyAction<(string | number | boolean | null)[]>]>;
package/dist/primitive.js CHANGED
@@ -1,3 +1,8 @@
1
1
  import * as v from 'valibot';
2
- export const BuilderPrimitiveSchema = v.nullable(v.union([v.string(), v.boolean(), v.number()]));
2
+ export const StringSchema = v.string();
3
+ export const NumberSchema = v.number();
4
+ export const BooleanSchema = v.boolean();
5
+ export const NameSchema = v.pipe(StringSchema, v.minLength(1));
6
+ export const IdSchema = v.pipe(StringSchema, v.minLength(1));
7
+ export const BuilderPrimitiveSchema = v.nullable(v.union([StringSchema, BooleanSchema, NumberSchema]));
3
8
  export const BuilderPrimitivesSchema = v.pipe(v.array(BuilderPrimitiveSchema), v.readonly());