@builder-builder/builder 0.0.5 → 0.0.6

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.
@@ -35,11 +35,14 @@ export class Builder {
35
35
  return this.#componentGraph;
36
36
  }
37
37
  get option() {
38
+ const existingNames = new Set(this.options.map((entry) => entry.name));
38
39
  const addOption = (name, values) => {
40
+ check.falsy(existingNames.has(name), `Duplicate option name '${name}'! ❌`);
39
41
  const entry = new BuilderOption(name, values);
40
42
  return this.#next([...this.options, entry], this.collections, this.components);
41
43
  };
42
44
  const addConditionalOption = (gatePaths, name, config) => {
45
+ check.falsy(existingNames.has(name), `Duplicate option name '${name}'! ❌`);
43
46
  if (v.is(v.instance(BuilderParameter), config)) {
44
47
  const entry = new BuilderOption(name, config, gatePaths, null);
45
48
  return this.#next([...this.options, entry], this.collections, this.components);
@@ -112,6 +115,9 @@ export function builder(...buildersToMerge) {
112
115
  else if (expectation.kind === 'component') {
113
116
  expectation.validate(merged.components);
114
117
  }
118
+ else if (expectation.kind === 'collection') {
119
+ expectation.validate(merged.collections);
120
+ }
115
121
  }
116
122
  });
117
123
  return merged;
@@ -11,7 +11,9 @@ export type BuilderCollectionEntries = ReadonlyArray<BuilderCollection>;
11
11
  export type TupleOf<Item, Length extends number, Result extends ReadonlyArray<Item> = []> = Result['length'] extends Length ? Result : TupleOf<Item, Length, [...Result, Item]>;
12
12
  export type CollectionShape<Item, Min extends number, Max extends number> = number extends Min ? ReadonlyArray<Item> : number extends Max ? ReadonlyArray<Item> : Min extends Max ? TupleOf<Item, Min> : ReadonlyArray<Item>;
13
13
  export type BuilderCollectionOf<Builder extends BuilderGeneric, Name extends string> = CollectionOf<BuilderStateOf<Builder>['collections'], Name>;
14
- export type BuilderCollectionNamesOf<Builder extends BuilderGeneric> = CollectionNamesOf<BuilderStateOf<Builder>['collections']>;
14
+ export type BuilderCollectionNamesOf<Builder extends BuilderGeneric | null> = [Builder] extends [
15
+ BuilderGeneric
16
+ ] ? CollectionNamesOf<BuilderStateOf<Builder>['collections']> : never;
15
17
  export type BuilderCollectionModelOf<CollectionRecipe extends BuilderGeneric, Min extends number, Max extends number> = CollectionShape<BuilderModelOf<CollectionRecipe>, Min, Max>;
16
18
  export declare class BuilderCollection<const Name extends string = string, const CollectionRecipe = any, const Min extends number = number, const Max extends number = number> implements BuilderCollectionConfig<CollectionRecipe, Min, Max> {
17
19
  readonly name: Name;
@@ -1,11 +1,9 @@
1
1
  import type { BuilderPaths } from '../../paths';
2
2
  export type BuilderComponentEntries = ReadonlyArray<BuilderComponent>;
3
- export type BuilderComponentMap = Record<string, BuilderComponent>;
4
3
  export type BuilderComponentNamesOf<Components extends BuilderComponentEntries> = Components[number]['name'];
5
4
  export declare class BuilderComponent<const Name extends string = string> {
6
5
  readonly name: Name;
7
6
  readonly paths: BuilderPaths;
8
7
  readonly dependencies: ReadonlyArray<string>;
9
8
  constructor(name: Name, paths: BuilderPaths, dependencies?: ReadonlyArray<string>);
10
- resolve(resolved: BuilderComponentMap): BuilderComponent<Name> | null;
11
9
  }
@@ -7,11 +7,4 @@ export class BuilderComponent {
7
7
  this.paths = paths;
8
8
  this.dependencies = dependencies;
9
9
  }
10
- resolve(resolved) {
11
- if (this.dependencies.length === 0) {
12
- return this;
13
- }
14
- const ready = this.dependencies.every((dependency) => !!resolved[dependency]);
15
- return ready ? this : null;
16
- }
17
10
  }
@@ -5,6 +5,5 @@ export declare class BuilderComponentGraph {
5
5
  #private;
6
6
  readonly paths: GraphPaths;
7
7
  constructor(paths?: GraphPaths);
8
- has(name: string): boolean;
9
8
  add(component: BuilderComponent, options: BuilderOptionGraph): BuilderComponentGraph;
10
9
  }
@@ -6,16 +6,16 @@ export class BuilderComponentGraph {
6
6
  constructor(paths = []) {
7
7
  this.paths = paths;
8
8
  }
9
- has(name) {
10
- return this.paths.some((graphPath) => graphPath.name === name);
11
- }
12
9
  add(component, options) {
13
- check.falsy(this.has(component.name), `Duplicate component name '${component.name}'! ❌`);
14
- const keys = new Set(component.paths.map((path) => String(path[0])));
10
+ check.falsy(this.#has(component.name), `Duplicate component name '${component.name}'! ❌`);
11
+ const keys = new Set(component.paths.map(([first]) => String(first)));
15
12
  const models = this.#mergeModels(keys, options);
16
13
  const newPath = { name: component.name, keys, models };
17
14
  return new BuilderComponentGraph([...this.paths, newPath]);
18
15
  }
16
+ #has(name) {
17
+ return this.paths.some((graphPath) => graphPath.name === name);
18
+ }
19
19
  #mergeModels(keys, options) {
20
20
  const graphPaths = Array.from(keys)
21
21
  .map((key) => options.get(key))
@@ -1,4 +1,4 @@
1
- export type { BuilderComponentEntries, BuilderComponentMap, BuilderComponentNamesOf } from './component';
1
+ export type { BuilderComponentEntries, BuilderComponentNamesOf } from './component';
2
2
  export type { BuilderComponentMethod } from './method';
3
3
  export { BuilderComponent } from './component.js';
4
4
  export { BuilderComponentGraph } from './graph.js';
@@ -1,39 +1,45 @@
1
1
  import type { BuilderGeneric, BuilderMerge, BuilderStateOf } from './builder';
2
2
  import type { Prettify } from '../prettify';
3
+ import { BuilderCollection } from './collection/index.js';
3
4
  import { BuilderComponent } from './component/index.js';
4
- export type BuilderExpectationKind = 'option' | 'component';
5
+ export type BuilderExpectationKind = 'option' | 'component' | 'collection';
5
6
  export type BuilderExpectationEntries = ReadonlyArray<BuilderExpectation>;
6
7
  export type BuilderExpectations = readonly [
7
8
  BuilderExpectation,
8
9
  ...ReadonlyArray<BuilderExpectation>
9
10
  ];
10
- export type BuilderModelAsserted<BuilderInput extends BuilderGeneric, Expectations extends BuilderExpectations> = BuilderMerge<BuilderStateOf<BuilderInput>, {
11
- model: ModelAssert<BuilderInput, Expectations>;
12
- components: ComponentAssert<BuilderInput, Expectations>;
11
+ export type BuilderModelAsserted<BuilderInput extends BuilderGeneric | null, Expectations extends BuilderExpectations, Resolved extends BuilderGeneric = [BuilderInput] extends [BuilderGeneric] ? BuilderInput : BuilderGeneric> = BuilderMerge<BuilderStateOf<Resolved>, {
12
+ model: ModelAssert<Resolved, Expectations>;
13
+ components: ComponentAssert<Resolved, Expectations>;
14
+ collections: CollectionAssert<Resolved, Expectations>;
13
15
  }>;
14
16
  export type ModelAssert<BuilderInput extends BuilderGeneric, Items extends BuilderExpectations, Current extends BuilderStateOf<BuilderInput> = BuilderStateOf<BuilderInput>, Asserted extends ExpectedModel<Items> = ExpectedModel<Items>> = unknown extends Current['model'] ? Prettify<Asserted> : Prettify<Current['model'] & Asserted>;
15
17
  export type ComponentAssert<BuilderInput extends BuilderGeneric, Items extends BuilderExpectations, Current extends BuilderStateOf<BuilderInput> = BuilderStateOf<BuilderInput>, Asserted extends ExpectedComponents<Items> = ExpectedComponents<Items>> = Current['components'] extends [] ? ExpectedComponents<Items> : number extends Asserted['length'] ? Asserted : [...Current['components'], ...Asserted];
16
- export declare class BuilderExpectation<Name extends string = string, Value = unknown> {
18
+ export type CollectionAssert<BuilderInput extends BuilderGeneric, Items extends BuilderExpectations, Current extends BuilderStateOf<BuilderInput> = BuilderStateOf<BuilderInput>, Asserted extends ExpectedCollections<Items> = ExpectedCollections<Items>> = Current['collections'] extends [] ? ExpectedCollections<Items> : number extends Asserted['length'] ? Asserted : [...Current['collections'], ...Asserted];
19
+ export declare class BuilderExpectation<Name extends string = string, Value = unknown, Kind extends BuilderExpectationKind = BuilderExpectationKind> {
17
20
  readonly name: Name;
18
- readonly kind: BuilderExpectationKind;
19
- constructor(name: Name, kind: BuilderExpectationKind);
20
- optional(): BuilderExpectation<Name, Value | null>;
21
+ readonly kind: Kind;
22
+ constructor(name: Name, kind: Kind);
23
+ optional(): BuilderExpectation<Name, Value | null, Kind>;
21
24
  validate(entries: ReadonlyArray<{
22
25
  readonly name: string;
23
26
  }>): void;
24
27
  }
25
- export declare const option: {
26
- expect: <const Name extends string>(name: Name) => BuilderExpectation<Name, string>;
27
- };
28
- export declare const component: {
29
- expect: <const Name extends string>(name: Name) => BuilderExpectation<Name, never>;
28
+ export declare const has: {
29
+ option: <const Name extends string>(name: Name) => BuilderExpectation<Name, string, "option">;
30
+ component: <const Name extends string>(name: Name) => BuilderExpectation<Name, never, "component">;
31
+ collection: <const Name extends string>(name: Name) => BuilderExpectation<Name, unknown, "collection">;
30
32
  };
31
33
  type ExpectedModel<Items extends ReadonlyArray<BuilderExpectation>> = Items extends readonly [
32
34
  infer Head,
33
35
  ...infer Tail extends ReadonlyArray<BuilderExpectation>
34
- ] ? (Head extends BuilderExpectation<infer Name, infer Value> ? [Value] extends [never] ? unknown : Record<Name, Value> : unknown) & ExpectedModel<Tail> : unknown;
36
+ ] ? (Head extends BuilderExpectation<infer Name, infer Value, 'option'> ? Record<Name, Value> : unknown) & ExpectedModel<Tail> : unknown;
35
37
  type ExpectedComponents<Items extends ReadonlyArray<BuilderExpectation>> = Items extends readonly [
36
38
  infer Head,
37
39
  ...infer Tail extends ReadonlyArray<BuilderExpectation>
38
- ] ? Head extends BuilderExpectation<infer Name, ReadonlyArray<any>> ? [BuilderComponent<Name>, ...ExpectedComponents<Tail>] : ExpectedComponents<Tail> : [];
40
+ ] ? Head extends BuilderExpectation<infer Name, any, 'component'> ? [BuilderComponent<Name>, ...ExpectedComponents<Tail>] : ExpectedComponents<Tail> : [];
41
+ type ExpectedCollections<Items extends ReadonlyArray<BuilderExpectation>> = Items extends readonly [
42
+ infer Head,
43
+ ...infer Tail extends ReadonlyArray<BuilderExpectation>
44
+ ] ? Head extends BuilderExpectation<infer Name, any, 'collection'> ? [BuilderCollection<Name>, ...ExpectedCollections<Tail>] : ExpectedCollections<Tail> : [];
39
45
  export {};
@@ -1,3 +1,4 @@
1
+ import { BuilderCollection } from './collection/index.js';
1
2
  import { BuilderComponent } from './component/index.js';
2
3
  import { check } from '../check.js';
3
4
  export class BuilderExpectation {
@@ -14,9 +15,8 @@ export class BuilderExpectation {
14
15
  check.truthy(entries.some((entry) => entry.name === this.name), `assert: no \`${this.kind}\` named '${this.name}' exists in the builder config! ❌`);
15
16
  }
16
17
  }
17
- export const option = {
18
- expect: (name) => new BuilderExpectation(name, 'option')
19
- };
20
- export const component = {
21
- expect: (name) => new BuilderExpectation(name, 'component')
18
+ export const has = {
19
+ option: (name) => new BuilderExpectation(name, 'option'),
20
+ component: (name) => new BuilderExpectation(name, 'component'),
21
+ collection: (name) => new BuilderExpectation(name, 'collection')
22
22
  };
@@ -1,5 +1,5 @@
1
1
  export type { BuilderCollectionConfig, BuilderCollectionEnableConfig, BuilderCollectionEntries, BuilderCollectionMatchConfig, BuilderCollectionMethod, BuilderCollectionModelOf, BuilderCollectionNamesOf, BuilderCollectionOf, BuilderCollectionUnlessConfig, BuilderCollectionWhenConfig } from './collection/index';
2
- export type { BuilderComponentEntries, BuilderComponentMap, BuilderComponentMethod, BuilderComponentNamesOf } from './component/index';
2
+ export type { BuilderComponentEntries, BuilderComponentMethod, BuilderComponentNamesOf } from './component/index';
3
3
  export type { BuilderExpectationEntries, BuilderExpectations, BuilderModelAsserted } from './expectation';
4
4
  export type { BuilderOptionEnableConfig, BuilderOptionEntries, BuilderOptionMatchConfig, BuilderOptionMethod, BuilderOptionUnlessConfig, BuilderOptionValues, BuilderOptionValuesMap, BuilderOptionWhenConfig, BuilderSelectTypeLabels, BuilderSelectTypeValues } from './option/index';
5
5
  export type { BuilderWhenEnableConfig, BuilderWhenMatchConfig, BuilderWhenUnlessConfig } from './when/index';
@@ -7,6 +7,6 @@ export type { BuilderMerge, BuilderGeneric, BuilderModelOf, Builders, BuilderSta
7
7
  export { Builder, builder } from './builder.js';
8
8
  export { BuilderCollection, collection, resolveCollectionModels, resolveCollectionNames } from './collection/index.js';
9
9
  export { BuilderComponent, BuilderComponentGraph } from './component/index.js';
10
- export { BuilderExpectation, component, option } from './expectation.js';
10
+ export { BuilderExpectation, has } from './expectation.js';
11
11
  export { BuilderOption, BuilderOptionGraph, select, BuilderSelectType, BuilderToggleType, toggleBoolean, toggleNumber, toggleString, enable, match, unless } from './option/index.js';
12
12
  export { BuilderParameter, BuilderParameterSchema } from './parameter.js';
@@ -1,6 +1,6 @@
1
1
  export { Builder, builder } from './builder.js';
2
2
  export { BuilderCollection, collection, resolveCollectionModels, resolveCollectionNames } from './collection/index.js';
3
3
  export { BuilderComponent, BuilderComponentGraph } from './component/index.js';
4
- export { BuilderExpectation, component, option } from './expectation.js';
4
+ export { BuilderExpectation, has } from './expectation.js';
5
5
  export { BuilderOption, BuilderOptionGraph, select, BuilderSelectType, BuilderToggleType, toggleBoolean, toggleNumber, toggleString, enable, match, unless } from './option/index.js';
6
6
  export { BuilderParameter, BuilderParameterSchema } from './parameter.js';
@@ -2,10 +2,9 @@ import type { GraphPath, GraphPaths } from '../graph';
2
2
  import { BuilderOption } from './option.js';
3
3
  export type BuilderOptionGraphs = ReadonlyArray<BuilderOptionGraph>;
4
4
  export declare class BuilderOptionGraph {
5
+ #private;
5
6
  readonly paths: GraphPaths;
6
7
  constructor(paths?: GraphPaths);
7
- static merge(...graphs: BuilderOptionGraphs): BuilderOptionGraph;
8
- has(name: string): boolean;
9
8
  get(name: string): GraphPath;
10
9
  add(option: BuilderOption): BuilderOptionGraph;
11
10
  }
@@ -8,18 +8,13 @@ export class BuilderOptionGraph {
8
8
  constructor(paths = []) {
9
9
  this.paths = paths;
10
10
  }
11
- static merge(...graphs) {
12
- return new BuilderOptionGraph(graphs.flatMap((graph) => graph.paths));
13
- }
14
- has(name) {
15
- return this.paths.some((graphPath) => graphPath.name === name);
16
- }
17
11
  get(name) {
18
12
  const graphPath = this.paths.find((graphPath) => graphPath.name === name);
19
13
  check.truthy(graphPath, `Option '${name}' not found in graph! ❌`);
20
14
  return graphPath;
21
15
  }
22
16
  add(option) {
17
+ check.falsy(this.#has(option.name), `Duplicate option name '${option.name}'! ❌`);
23
18
  const dependencyKeys = option.dependencyKeys();
24
19
  if (dependencyKeys.length === 0) {
25
20
  const values = optionValues(option);
@@ -50,6 +45,9 @@ export class BuilderOptionGraph {
50
45
  { name: option.name, keys: merged.keys, models: finalModels }
51
46
  ]);
52
47
  }
48
+ #has(name) {
49
+ return this.paths.some((graphPath) => graphPath.name === name);
50
+ }
53
51
  }
54
52
  function mergePaths(dependencyKeys, graphPaths) {
55
53
  const dependencyPaths = Array.from(dependencyKeys).map((dependencyKey) => {
@@ -28,13 +28,14 @@ export class BuilderOption {
28
28
  });
29
29
  }
30
30
  dependencyKeys() {
31
- const keys = new Set(this.gatePaths.map((path) => String(path[0])));
31
+ const keys = new Set(this.gatePaths.map(([first]) => String(first)));
32
32
  if (this.config?.type === 'match') {
33
- const matchPath = this.config.matchPath;
34
- keys.add(String(matchPath[0]));
33
+ const [firstMatchSegment] = this.config.matchPath;
34
+ keys.add(String(firstMatchSegment));
35
35
  }
36
36
  else if (this.config?.type === 'unless') {
37
- keys.add(String(this.config.unlessPath[0]));
37
+ const [firstUnlessSegment] = this.config.unlessPath;
38
+ keys.add(String(firstUnlessSegment));
38
39
  }
39
40
  return Array.from(keys);
40
41
  }
package/dist/index.d.ts CHANGED
@@ -4,9 +4,9 @@ export type { BuilderModelPaths, BuilderResolvePath, BuilderValidPath } from './
4
4
  export type { BuilderPath, BuilderPaths } from './schemas/index';
5
5
  export type { BuilderDataValidated, BuilderDescription, BuilderDescriptionItem, BuilderErrorComponentMissing, BuilderErrorComponentUnexpected, BuilderErrorsValidate, BuilderErrorValidate, BuilderErrorVariantInvalid, BuilderErrorVariantMissing, BuilderOrder, BuilderRenderOption, BuilderRenderOptions, BuilderRenderPage, BuilderRenderPages, BuilderRenderResult, BuilderValidateResult } from './resolve/index';
6
6
  export type { BuilderUICollection, BuilderUIDescribe, BuilderUIItem, BuilderUIItems, BuilderUIPage } from './schemas/index';
7
- export type { BuilderUI, BuilderUIFactory, BuilderUIs } from './ui';
7
+ export type { BuilderUI, BuilderUIs } from './ui';
8
8
  export type { BuilderCollectionSerialised, BuilderCollectionWhenConfigSerialised, BuilderComponentSerialised, BuilderOptionSerialised, BuilderOptionWhenConfigSerialised, BuilderParameterSerialised, BuilderSelectTypeSerialised, BuilderSerialised, BuilderToggleTypeSerialised, BuilderUISerialised, BuilderValuesSerialised } from './serialise/index';
9
- export { BuilderExpectation, component, option } from './core/index.js';
9
+ export { BuilderExpectation, has } from './core/index.js';
10
10
  export { builder, BuilderCollection, BuilderComponent, BuilderOption, BuilderParameter, BuilderSelectType, BuilderToggleType, collection, enable, match, select, toggleBoolean, toggleNumber, toggleString, unless } from './core/index.js';
11
11
  export { instance, models, order, ordinal, render, validate } from './resolve/index.js';
12
12
  export { deserialise, serialise } from './serialise/index.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { BuilderExpectation, component, option } from './core/index.js';
1
+ export { BuilderExpectation, has } from './core/index.js';
2
2
  export { builder, BuilderCollection, BuilderComponent, BuilderOption, BuilderParameter, BuilderSelectType, BuilderToggleType, collection, enable, match, select, toggleBoolean, toggleNumber, toggleString, unless } from './core/index.js';
3
3
  export { instance, models, order, ordinal, render, validate } from './resolve/index.js';
4
4
  export { deserialise, serialise } from './serialise/index.js';
package/dist/paths.d.ts CHANGED
@@ -5,7 +5,9 @@ export type BuilderValidPath<Model, Depth extends ReadonlyArray<never> = []> = D
5
5
  [Key in keyof Model & string]: [Key] | (Model[Key] extends BuilderPrimitive | null ? never : [Key, ...BuilderValidPath<Model[Key], [...Depth, never]>]);
6
6
  }[keyof Model & string];
7
7
  export type BuilderValidPaths<Model> = ReadonlyArray<BuilderValidPath<Model>>;
8
- export type BuilderModelPaths<Builder extends BuilderGeneric> = BuilderValidPaths<BuilderModelOf<Builder>>;
8
+ export type BuilderModelPaths<Builder extends BuilderGeneric | null> = [Builder] extends [
9
+ BuilderGeneric
10
+ ] ? BuilderValidPaths<BuilderModelOf<Builder>> : ReadonlyArray<never>;
9
11
  export type BuilderResolvePath<Model, Path extends BuilderPath> = Path extends readonly [
10
12
  infer Head extends keyof Model & string
11
13
  ] ? Model[Head] : Path extends readonly [
@@ -1,15 +1,15 @@
1
1
  import { resolveCollectionModels } from '../core/index.js';
2
+ import { check } from '../check.js';
2
3
  export function order(builder, model, componentModels) {
3
4
  const result = {};
4
- const resolved = {};
5
+ const resolved = new Set();
5
6
  builder.components.forEach((component) => {
6
- const active = component.resolve(resolved);
7
- if (!active) {
8
- return;
9
- }
10
- resolved[active.name] = active;
11
- const variants = componentModels[active.name] ?? [];
12
- result[active.name] =
7
+ component.dependencies.forEach((dependency) => {
8
+ check.truthy(resolved.has(dependency), `Component '${component.name}' depends on '${dependency}' which has not been resolved! ❌`);
9
+ });
10
+ resolved.add(component.name);
11
+ const variants = componentModels[component.name] ?? [];
12
+ result[component.name] =
13
13
  variants.find((variant) => Object.entries(variant.model).every(([key, value]) => model[key] === value)) ?? null;
14
14
  });
15
15
  builder.collections.forEach((entry) => {
@@ -40,7 +40,7 @@ export type BuilderCollectionSerialised = {
40
40
  };
41
41
  export type BuilderExpectationSerialised = {
42
42
  readonly name: string;
43
- readonly kind: 'option' | 'component';
43
+ readonly kind: 'option' | 'component' | 'collection';
44
44
  };
45
45
  export type BuilderSerialised = {
46
46
  readonly options: ReadonlyArray<BuilderOptionSerialised>;
@@ -1133,10 +1133,10 @@ export declare const BuilderCollectionSerialisedSchema: v.SchemaWithPipe<readonl
1133
1133
  }>]>;
1134
1134
  export declare const BuilderExpectationSerialisedSchema: v.SchemaWithPipe<readonly [v.ObjectSchema<{
1135
1135
  readonly name: v.StringSchema<undefined>;
1136
- readonly kind: v.PicklistSchema<["option", "component"], undefined>;
1136
+ readonly kind: v.PicklistSchema<["option", "component", "collection"], undefined>;
1137
1137
  }, undefined>, v.ReadonlyAction<{
1138
1138
  name: string;
1139
- kind: "option" | "component";
1139
+ kind: "collection" | "option" | "component";
1140
1140
  }>]>;
1141
1141
  export declare const BuilderSerialisedSchema: v.SchemaWithPipe<readonly [v.ObjectSchema<{
1142
1142
  readonly options: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.ObjectSchema<{
@@ -1669,13 +1669,13 @@ export declare const BuilderSerialisedSchema: v.SchemaWithPipe<readonly [v.Objec
1669
1669
  }[]>]>;
1670
1670
  readonly expectations: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.ObjectSchema<{
1671
1671
  readonly name: v.StringSchema<undefined>;
1672
- readonly kind: v.PicklistSchema<["option", "component"], undefined>;
1672
+ readonly kind: v.PicklistSchema<["option", "component", "collection"], undefined>;
1673
1673
  }, undefined>, v.ReadonlyAction<{
1674
1674
  name: string;
1675
- kind: "option" | "component";
1675
+ kind: "collection" | "option" | "component";
1676
1676
  }>]>, undefined>, v.ReadonlyAction<Readonly<{
1677
1677
  name: string;
1678
- kind: "option" | "component";
1678
+ kind: "collection" | "option" | "component";
1679
1679
  }>[]>]>;
1680
1680
  }, undefined>, v.ReadonlyAction<{
1681
1681
  readonly options: readonly Readonly<{
@@ -1795,7 +1795,7 @@ export declare const BuilderSerialisedSchema: v.SchemaWithPipe<readonly [v.Objec
1795
1795
  }[];
1796
1796
  readonly expectations: readonly Readonly<{
1797
1797
  name: string;
1798
- kind: "option" | "component";
1798
+ kind: "collection" | "option" | "component";
1799
1799
  }>[];
1800
1800
  }>]>;
1801
1801
  export declare const BuilderUIPageSerialisedSchema: v.SchemaWithPipe<readonly [v.ObjectSchema<{
@@ -118,7 +118,7 @@ export const BuilderCollectionWhenConfigSerialisedSchema = v.pipe(v.variant('typ
118
118
  export const BuilderCollectionSerialisedSchema = v.pipe(BuilderSerialisedCollectionObject, v.readonly());
119
119
  export const BuilderExpectationSerialisedSchema = v.pipe(v.object({
120
120
  name: v.string(),
121
- kind: v.picklist(['option', 'component'])
121
+ kind: v.picklist(['option', 'component', 'collection'])
122
122
  }), v.readonly());
123
123
  export const BuilderSerialisedSchema = v.pipe(v.object({
124
124
  options: v.pipe(v.array(BuilderOptionSerialisedSchema), v.readonly()),
package/dist/ui.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import type { BuilderModelPaths } from './paths';
2
- import type { BuilderCollectionNamesOf, BuilderCollectionOf, BuilderGeneric } from './core/index';
2
+ import type { BuilderCollectionNamesOf, BuilderGeneric } from './core/index';
3
3
  import type { BuilderExpectationEntries, BuilderExpectations, BuilderModelAsserted } from './core/expectation';
4
4
  import type { BuilderUIItems } from './schemas/index';
5
5
  export type BuilderUIGeneric = BuilderUI<BuilderGeneric>;
6
- export type BuilderUIFactory<Builder extends BuilderGeneric = BuilderGeneric> = (ui: BuilderUI<Builder>) => BuilderUIGeneric;
7
- export type BuilderUIs = ReadonlyArray<BuilderUIGeneric>;
8
- export declare class BuilderUI<Builder extends BuilderGeneric = BuilderGeneric> {
6
+ export type BuilderUIs = ReadonlyArray<BuilderUI<BuilderGeneric | null>>;
7
+ export declare class BuilderUI<Builder extends BuilderGeneric | null = BuilderGeneric> {
9
8
  readonly builder: Builder;
10
9
  readonly items: BuilderUIItems;
11
10
  readonly expectations: BuilderExpectationEntries;
@@ -13,6 +12,7 @@ export declare class BuilderUI<Builder extends BuilderGeneric = BuilderGeneric>
13
12
  expect<const Expectations extends BuilderExpectations>(...expectations: Expectations): BuilderUI<BuilderModelAsserted<Builder, Expectations>>;
14
13
  page(label: string | null, paths: BuilderModelPaths<Builder>): BuilderUI<Builder>;
15
14
  describe(label: string | null, paths: BuilderModelPaths<Builder>): BuilderUI<Builder>;
16
- collection<const Name extends BuilderCollectionNamesOf<Builder>>(name: Name, label: string, factory: BuilderUIFactory<BuilderCollectionOf<Builder, Name>['builder']>): BuilderUI<Builder>;
15
+ collection<const Name extends BuilderCollectionNamesOf<Builder>>(name: Name, label: string, itemUI: BuilderUI<BuilderGeneric | null>): BuilderUI<Builder>;
17
16
  }
17
+ export declare function ui(): BuilderUI<null>;
18
18
  export declare function ui<const Builder extends BuilderGeneric>(builder: Builder, ...uis: BuilderUIs): BuilderUI<Builder>;
package/dist/ui.js CHANGED
@@ -21,21 +21,32 @@ export class BuilderUI {
21
21
  describe(label, paths) {
22
22
  return new BuilderUI(this.builder, [...this.items, { type: 'describe', label, paths }], this.expectations);
23
23
  }
24
- collection(name, label, factory) {
25
- const foundCollection = this.builder.collections.find((entry) => entry.name === name);
26
- check.truthy(foundCollection, `Collection '${name}' not found! ❌`);
27
- const templateUI = new BuilderUI(foundCollection.builder);
28
- const result = factory(templateUI);
29
- return new BuilderUI(this.builder, [...this.items, { type: 'collection', name, label, items: result.items }], this.expectations);
24
+ collection(name, label, itemUI) {
25
+ if (this.builder) {
26
+ const foundCollection = this.builder.collections.find((entry) => entry.name === name);
27
+ check.truthy(foundCollection, `Collection '${name}' not found! ❌`);
28
+ }
29
+ return new BuilderUI(this.builder, [...this.items, { type: 'collection', name, label, items: itemUI.items }], this.expectations);
30
30
  }
31
31
  }
32
32
  export function ui(builder, ...uis) {
33
- const merged = uis.reduce((accumulated, ui) => {
34
- return new BuilderUI(accumulated.builder, [...accumulated.items, ...ui.items], [...accumulated.expectations, ...ui.expectations]);
33
+ if (!builder) {
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- null builder for partial UIs, typed as BuilderUI<null>
35
+ return new BuilderUI(null);
36
+ }
37
+ const merged = uis.reduce((accumulated, currentUI) => {
38
+ return new BuilderUI(
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accumulated.builder is always the real builder from the first argument
40
+ accumulated.builder, [...accumulated.items, ...currentUI.items], [...accumulated.expectations, ...currentUI.expectations]);
35
41
  }, new BuilderUI(builder));
36
42
  merged.expectations.forEach((expectation) => {
37
- if (v.is(v.instance(BuilderExpectation), expectation) && expectation.kind === 'option') {
38
- expectation.validate(merged.builder.options);
43
+ if (v.is(v.instance(BuilderExpectation), expectation)) {
44
+ if (expectation.kind === 'option') {
45
+ expectation.validate(merged.builder.options);
46
+ }
47
+ else if (expectation.kind === 'collection') {
48
+ expectation.validate(merged.builder.collections);
49
+ }
39
50
  }
40
51
  });
41
52
  return merged;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builder-builder/builder",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -39,6 +39,7 @@
39
39
  "@sveltejs/vite-plugin-svelte": "^5.0.0",
40
40
  "@testing-library/jest-dom": "^6.6.3",
41
41
  "@testing-library/svelte": "^5.2.4",
42
+ "@vitest/coverage-v8": "^3.1.4",
42
43
  "dotenv-cli": "^8.0.0",
43
44
  "eslint": "^9.18.0",
44
45
  "eslint-config-prettier": "^10.0.1",