@builder-builder/builder 0.0.12 → 0.0.13

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.
@@ -2,7 +2,7 @@ import type { EntitiesMap } from './serialise';
2
2
  import * as v from 'valibot';
3
3
  import { entitiesMap } from './serialise.js';
4
4
  export type BuilderEntityKind = keyof typeof entitiesMap;
5
- export declare const BuilderEntityKindSchema: v.PicklistSchema<readonly ("string" | "number" | "boolean" | "builder" | "model" | "ui" | "select" | "toggle" | "paths" | "expectations" | "uiPage" | "uiDescribe" | "uiPages" | "componentDetails" | "collectionConfig" | "uiItems" | "optionWhen" | "componentWhen" | "collectionWhen" | "optionSelectMap" | "componentSelectMap" | "collectionSelectMap" | "path")[], undefined>;
5
+ export declare const BuilderEntityKindSchema: v.PicklistSchema<readonly ("string" | "number" | "boolean" | "builder" | "model" | "ui" | "select" | "toggle" | "paths" | "uiPage" | "uiDescribe" | "uiPages" | "componentDetails" | "collectionConfig" | "expectations" | "uiItems" | "optionWhen" | "componentWhen" | "collectionWhen" | "optionSelectMap" | "componentSelectMap" | "collectionSelectMap" | "path")[], undefined>;
6
6
  export type BuilderEntitySerialised = {
7
7
  [Kind in keyof EntitiesMap]: v.InferOutput<EntitiesMap[Kind]['serialised']>;
8
8
  }[keyof EntitiesMap];
@@ -8,28 +8,28 @@ import type { BuilderComponentSerialised, BuilderComponentsSerialised } from './
8
8
  import type { BuilderModelSerialised } from './model/index';
9
9
  import type { BuilderOptionSerialised, BuilderOptionsSerialised } from './option/index';
10
10
  import type { BuilderUIDescribeSerialised, BuilderUIItemsSerialised, BuilderUIPageSerialised, BuilderUISerialised } from './ui/index';
11
- export type Validated<Input> = Input extends BuilderComponentVariants | BuilderInstance | BuilderModelSerialised | BuilderSerialised | BuilderUISerialised ? Prettify<Validate<Input> & BuilderValidatedBrand> : Validate<Input>;
12
- type Validate<Input> = Input extends BuilderParameter<string> | BuilderParameterSerialised | BuilderRef | BuilderRefSerialised ? never : Input extends ReadonlyArray<unknown> ? ValidatedTuple<Input> : Input extends object ? Prettify<{
11
+ export type Validated<Input> = Input extends BuilderComponentVariants | BuilderInstance | BuilderModelSerialised | BuilderSerialised | BuilderUISerialised ? Validate<Input> & BuilderValidatedBrand : Validate<Input>;
12
+ type Validate<Input> = Input extends BuilderParameter<string> | BuilderParameterSerialised | BuilderRef | BuilderRefSerialised ? never : Input extends ReadonlyArray<unknown> ? ValidatedTuple<Input> : Input extends object ? {
13
13
  readonly [Key in keyof Input]: Validated<Input[Key]>;
14
- }> : Input;
14
+ } : Input;
15
15
  type ValidatedTuple<Input extends ReadonlyArray<unknown>> = Input extends readonly [
16
16
  infer Head,
17
17
  ...infer Rest extends ReadonlyArray<unknown>
18
18
  ] ? [Validated<Head>] extends [never] ? ValidatedTuple<Rest> : readonly [Validated<Head>, ...ValidatedTuple<Rest>] : Input extends readonly [] ? readonly [] : ReadonlyArray<Validated<Input[number]>>;
19
- export type BuilderValidated = Validated<BuilderSerialised>;
20
- export type BuilderModelValidated = Validated<BuilderModelSerialised>;
21
- export type BuilderUIValidated = Validated<BuilderUISerialised>;
22
- export type BuilderInstanceValidated = Validated<BuilderInstance>;
23
- export type BuilderInstancesValidated = Validated<BuilderInstances>;
24
- export type BuilderComponentVariantsValidated = Validated<BuilderComponentVariants>;
25
- export type BuilderOptionValidated = Validated<BuilderOptionSerialised>;
26
- export type BuilderOptionsValidated = Validated<BuilderOptionsSerialised>;
27
- export type BuilderComponentValidated = Validated<BuilderComponentSerialised>;
28
- export type BuilderComponentsValidated = Validated<BuilderComponentsSerialised>;
29
- export type BuilderCollectionValidated = Validated<BuilderCollectionSerialised>;
30
- export type BuilderCollectionsValidated = Validated<BuilderCollectionsSerialised>;
31
- export type BuilderCollectionConfigValidated = Validated<BuilderCollectionConfigSerialised>;
32
- export type BuilderUIItemsValidated = Validated<BuilderUIItemsSerialised>;
33
- export type BuilderUIPageValidated = Validated<BuilderUIPageSerialised>;
34
- export type BuilderUIDescribeValidated = Validated<BuilderUIDescribeSerialised>;
19
+ export type BuilderValidated = Prettify<Validated<BuilderSerialised>>;
20
+ export type BuilderModelValidated = Prettify<Validated<BuilderModelSerialised>>;
21
+ export type BuilderUIValidated = Prettify<Validated<BuilderUISerialised>>;
22
+ export type BuilderInstanceValidated = Prettify<Validated<BuilderInstance>>;
23
+ export type BuilderInstancesValidated = Prettify<Validated<BuilderInstances>>;
24
+ export type BuilderComponentVariantsValidated = Prettify<Validated<BuilderComponentVariants>>;
25
+ export type BuilderOptionValidated = Prettify<Validated<BuilderOptionSerialised>>;
26
+ export type BuilderOptionsValidated = Prettify<Validated<BuilderOptionsSerialised>>;
27
+ export type BuilderComponentValidated = Prettify<Validated<BuilderComponentSerialised>>;
28
+ export type BuilderComponentsValidated = Prettify<Validated<BuilderComponentsSerialised>>;
29
+ export type BuilderCollectionValidated = Prettify<Validated<BuilderCollectionSerialised>>;
30
+ export type BuilderCollectionsValidated = Prettify<Validated<BuilderCollectionsSerialised>>;
31
+ export type BuilderCollectionConfigValidated = Prettify<Validated<BuilderCollectionConfigSerialised>>;
32
+ export type BuilderUIItemsValidated = Prettify<Validated<BuilderUIItemsSerialised>>;
33
+ export type BuilderUIPageValidated = Prettify<Validated<BuilderUIPageSerialised>>;
34
+ export type BuilderUIDescribeValidated = Prettify<Validated<BuilderUIDescribeSerialised>>;
35
35
  export {};
@@ -112,8 +112,6 @@ export declare const BuilderWhenConfigSchema: v.SchemaWithPipe<readonly [v.Varia
112
112
  id: string;
113
113
  }>]>, v.SchemaWithPipe<readonly [v.ArraySchema<v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>, v.ReadonlyAction<(string | number)[]>]>], undefined>;
114
114
  }, undefined>], undefined>, v.ReadonlyAction<{
115
- type: "enable";
116
- } | {
117
115
  type: "match";
118
116
  matchPath: readonly (string | number)[] | Readonly<{
119
117
  type: "parameter";
@@ -133,6 +131,8 @@ export declare const BuilderWhenConfigSchema: v.SchemaWithPipe<readonly [v.Varia
133
131
  type: "ref";
134
132
  id: string;
135
133
  }>;
134
+ } | {
135
+ type: "enable";
136
136
  }>]>;
137
137
  export type BuilderWhenConfig = v.InferOutput<typeof BuilderWhenConfigSchema>;
138
138
  export type BuilderWhenGeneric = BuilderWhen<unknown, unknown, unknown, unknown>;
@@ -1,33 +1,37 @@
1
1
  import { check } from '../check.js';
2
2
  import { modelsMerge, optionValueSchema } from '../entities/index.js';
3
+ import { BuilderInstanceSchema, BuilderInstancesSchema } from '../instance.js';
3
4
  import { resolveCollection, resolveOption } from './resolve.js';
4
5
  export function createInstance(entity, partial = {}, refs = []) {
5
6
  const model = 'model' in entity ? entity.model : entity;
6
- return buildInstance(model, partial, refs);
7
+ const instance = buildInstance(model, partial, refs);
8
+ check.assert(BuilderInstanceSchema, instance);
9
+ return instance;
7
10
  }
8
11
  function buildInstance(model, partial, refs) {
9
12
  const merged = modelsMerge(model);
10
13
  let instance = { ...partial };
11
14
  merged.options.forEach((option) => {
12
15
  const { name } = option;
13
- const payload = resolveOption(option, instance, refs);
16
+ const payload = resolveOption(option, model, instance, refs);
14
17
  if (payload == null) {
15
18
  return;
16
19
  }
17
- const existing = instance[name];
18
- if (check.is(optionValueSchema(payload), existing)) {
20
+ if (check.is(optionValueSchema(payload), instance[name])) {
19
21
  return;
20
22
  }
21
23
  instance = { ...instance, [name]: payload.defaultValue };
22
24
  });
23
25
  merged.collections.forEach((collection) => {
24
26
  const { name } = collection;
25
- const payload = resolveCollection(collection, instance, refs);
27
+ const payload = resolveCollection(collection, model, instance, refs);
26
28
  if (payload == null) {
27
29
  return;
28
30
  }
29
31
  const existing = instance[name];
30
- const existingItems = Array.isArray(existing) ? existing : [];
32
+ const existingItems = check.is(BuilderInstancesSchema, existing)
33
+ ? existing
34
+ : [];
31
35
  const count = Math.max(existingItems.length, payload.min);
32
36
  const items = Array.from({ length: count }, (_, index) => buildInstance(payload.model, existingItems[index] ?? {}, refs));
33
37
  instance = { ...instance, [name]: items };
@@ -1,8 +1,10 @@
1
+ import { check } from '../check.js';
2
+ import { BuilderInstancesSchema } from '../instance.js';
1
3
  import { resolveCollection, resolveComponent } from './resolve.js';
2
4
  export function order(model, instance, variants, refs = []) {
3
5
  const result = {};
4
6
  model.components.forEach((component) => {
5
- if (resolveComponent(component, instance, refs) == null) {
7
+ if (resolveComponent(component, model, instance, refs) == null) {
6
8
  return;
7
9
  }
8
10
  const componentVariants = variants[component.name] ?? [];
@@ -11,11 +13,12 @@ export function order(model, instance, variants, refs = []) {
11
13
  });
12
14
  model.collections.forEach((collection) => {
13
15
  const { name } = collection;
14
- const payload = resolveCollection(collection, instance, refs);
16
+ const payload = resolveCollection(collection, model, instance, refs);
15
17
  if (payload == null) {
16
18
  return;
17
19
  }
18
20
  const itemInstances = instance[name];
21
+ check.assert(BuilderInstancesSchema, itemInstances);
19
22
  result[name] = itemInstances.map((itemInstance) => order(payload.model, itemInstance, variants, refs));
20
23
  });
21
24
  return result;
@@ -1,9 +1,9 @@
1
1
  import * as v from 'valibot';
2
2
  import { check } from '../../check.js';
3
- import { modelsMerge } from '../../entities/model/index.js';
4
3
  import { BuilderInstancesSchema } from '../../instance.js';
5
- import { readPath } from '../../paths.js';
6
- import { resolveCollection, resolveOption, resolveRef } from '../resolve.js';
4
+ import { BuilderPrimitiveSchema } from '../../primitive.js';
5
+ import { createInstance } from '../instance.js';
6
+ import { resolveCollection, resolvePath, resolveRef } from '../resolve.js';
7
7
  import { ordinal } from './ordinal.js';
8
8
  export function render(builder, instance, refs = []) {
9
9
  const layout = [];
@@ -30,7 +30,7 @@ export function render(builder, instance, refs = []) {
30
30
  }
31
31
  const itemInstances = currentInstance[item.name];
32
32
  check.assert(BuilderInstancesSchema, itemInstances, 'Collection field is not an array! ❌');
33
- const mergedItemModel = modelsMerge(collection.model);
33
+ const mergedItemModel = collection.model;
34
34
  itemInstances.forEach((itemInstance, index) => {
35
35
  walkItems(item.items, mergedItemModel, [...collectionPath, item.name, index], [...labelContext, `${ordinal(index)} ${composeLabel(item.label)}`], itemInstance);
36
36
  });
@@ -38,18 +38,18 @@ export function render(builder, instance, refs = []) {
38
38
  }
39
39
  function emitPage(page, model, collectionPath, labelContext, currentInstance) {
40
40
  const options = page.paths.flatMap((path) => {
41
- const found = findOption(model, currentInstance, path);
41
+ const found = resolvePath(model, currentInstance, path, refs);
42
42
  if (found == null) {
43
43
  return [];
44
44
  }
45
45
  const fullPath = [...collectionPath, ...path];
46
- const [name, option] = found;
46
+ check.assert(BuilderPrimitiveSchema, found.value);
47
47
  return [
48
48
  {
49
- name,
50
- option,
51
- value: readPath(currentInstance, path),
52
- update: (updateInstance, updateValue) => setPath(updateInstance, fullPath, updateValue)
49
+ name: found.name,
50
+ option: found.payload,
51
+ value: found.value,
52
+ update: (updateInstance, updateValue) => createInstance(builder, setPath(updateInstance, fullPath, updateValue), refs)
53
53
  }
54
54
  ];
55
55
  });
@@ -61,15 +61,12 @@ export function render(builder, instance, refs = []) {
61
61
  function emitDescribe(describe, model, labelContext, currentInstance) {
62
62
  const composedLabel = composeLabel(describe.label, labelContext);
63
63
  const values = describe.paths.flatMap((path) => {
64
- const found = findOption(model, currentInstance, path);
65
- if (found == null) {
66
- return [];
67
- }
68
- const value = readPath(currentInstance, path);
69
- if (value == null) {
64
+ const found = resolvePath(model, currentInstance, path, refs);
65
+ if (found?.value == null) {
70
66
  return [];
71
67
  }
72
- return [value];
68
+ check.assert(BuilderPrimitiveSchema, found.value);
69
+ return [found.value];
73
70
  });
74
71
  if (values.length === 0) {
75
72
  return;
@@ -87,45 +84,9 @@ export function render(builder, instance, refs = []) {
87
84
  }
88
85
  return `${labelContext.join(', ')}, ${resolved}`;
89
86
  }
90
- function findOption(model, instance, path) {
91
- const optionName = path.at(-1);
92
- check.assert(v.string(), optionName);
93
- const collectionPairs = path.slice(0, -1);
94
- const descended = descendPairs(model, instance, collectionPairs);
95
- if (!descended) {
96
- return null;
97
- }
98
- const [scopeModel, scopeInstance] = descended;
99
- const entry = scopeModel.options.find((option) => option.name === optionName);
100
- if (entry == null) {
101
- return null;
102
- }
103
- const optionValues = resolveOption(entry, scopeInstance, refs);
104
- if (optionValues == null) {
105
- return null;
106
- }
107
- return [entry.name, optionValues];
108
- }
109
- function descendPairs(model, instance, remaining) {
110
- if (remaining.length === 0) {
111
- return [model, instance];
112
- }
113
- const [collectionName, instanceIndex, ...rest] = remaining;
114
- check.assert(v.string(), collectionName);
115
- check.assert(v.number(), instanceIndex);
116
- const collection = findCollection(model, instance, collectionName);
117
- if (!collection) {
118
- return null;
119
- }
120
- const nextInstance = instance[collectionName].at(instanceIndex);
121
- if (!nextInstance) {
122
- return null;
123
- }
124
- return descendPairs(collection.model, nextInstance, rest);
125
- }
126
87
  function findCollection(model, instance, collectionName) {
127
88
  const entry = model.collections.find((candidate) => candidate.name === collectionName);
128
- return entry == null ? null : resolveCollection(entry, instance, refs);
89
+ return entry == null ? null : resolveCollection(entry, model, instance, refs);
129
90
  }
130
91
  }
131
92
  function setPath(container, path, value) {
@@ -1,5 +1,14 @@
1
- import type { BuilderCollectionConfigValidated, BuilderCollectionValidated, BuilderComponentDetailsSerialised, BuilderComponentValidated, BuilderOptionValidated, BuilderOptionValuesSerialised, BuilderRefEntities } from '../entities/index';
2
- export declare function resolveOption(option: BuilderOptionValidated, model: unknown, refs?: BuilderRefEntities): BuilderOptionValuesSerialised | null;
3
- export declare function resolveComponent(component: BuilderComponentValidated, model: unknown, refs?: BuilderRefEntities): BuilderComponentDetailsSerialised | null;
4
- export declare function resolveCollection(collection: BuilderCollectionValidated, model: unknown, refs?: BuilderRefEntities): BuilderCollectionConfigValidated | null;
1
+ import type { BuilderCollectionConfigValidated, BuilderCollectionValidated, BuilderComponentDetailsSerialised, BuilderComponentValidated, BuilderModelValidated, BuilderOptionValidated, BuilderOptionValuesSerialised, BuilderRefEntities } from '../entities/index';
2
+ import type { BuilderInstance } from '../instance';
3
+ import type { BuilderPath } from '../paths';
4
+ export declare function resolveOption(option: BuilderOptionValidated, model: BuilderModelValidated, instance: unknown, refs?: BuilderRefEntities): BuilderOptionValuesSerialised | null;
5
+ export declare function resolveComponent(component: BuilderComponentValidated, model: BuilderModelValidated, instance: unknown, refs?: BuilderRefEntities): BuilderComponentDetailsSerialised | null;
6
+ export declare function resolveCollection(collection: BuilderCollectionValidated, model: BuilderModelValidated, instance: unknown, refs?: BuilderRefEntities): BuilderCollectionConfigValidated | null;
5
7
  export declare function resolveRef(value: unknown, refs: BuilderRefEntities): unknown;
8
+ export type ResolvedPath = {
9
+ readonly name: string;
10
+ readonly payload: BuilderOptionValuesSerialised;
11
+ readonly value: unknown;
12
+ };
13
+ export declare function resolvePath(model: BuilderModelValidated, instance: unknown, path: BuilderPath, refs?: BuilderRefEntities): ResolvedPath | null;
14
+ export declare function resolveItems(model: BuilderModelValidated, instance: unknown, pairs: ReadonlyArray<string | number>, refs?: BuilderRefEntities): readonly [BuilderModelValidated, BuilderInstance] | null;
@@ -1,33 +1,76 @@
1
+ import * as v from 'valibot';
1
2
  import { check } from '../check.js';
2
- import { BuilderCollectionConfigSerialisedSchema, BuilderComponentDetailsSerialisedSchema, BuilderOptionValuesSerialisedSchema } from '../entities/index.js';
3
- import { BuilderPathsSchema, readPath } from '../paths.js';
3
+ import { BuilderCollectionConfigSerialisedSchema, BuilderComponentDetailsSerialisedSchema, BuilderOptionValuesSerialisedSchema, BuilderWhenSerialisedSchema, modelsMerge } from '../entities/index.js';
4
+ import { BuilderInstanceSchema, BuilderInstancesSchema } from '../instance.js';
5
+ import { BuilderPathsSchema } from '../paths.js';
6
+ import { BuilderPrimitiveSchema } from '../primitive.js';
4
7
  import { BuilderRefSerialisedSchema } from '../references.js';
5
- export function resolveOption(option, model, refs = []) {
6
- return resolveEntry(option, BuilderOptionValuesSerialisedSchema, model, refs);
8
+ export function resolveOption(option, model, instance, refs = []) {
9
+ return resolveEntry(option, BuilderOptionValuesSerialisedSchema, model, instance, refs);
7
10
  }
8
- export function resolveComponent(component, model, refs = []) {
9
- return resolveEntry(component, BuilderComponentDetailsSerialisedSchema, model, refs);
11
+ export function resolveComponent(component, model, instance, refs = []) {
12
+ return resolveEntry(component, BuilderComponentDetailsSerialisedSchema, model, instance, refs);
10
13
  }
11
- export function resolveCollection(collection, model, refs = []) {
12
- return resolveEntry(collection, BuilderCollectionConfigSerialisedSchema, model, refs);
14
+ export function resolveCollection(collection, model, instance, refs = []) {
15
+ return resolveEntry(collection, BuilderCollectionConfigSerialisedSchema, model, instance, refs);
13
16
  }
14
17
  export function resolveRef(value, refs) {
15
18
  if (!check.is(BuilderRefSerialisedSchema, value)) {
16
19
  return value;
17
20
  }
18
21
  const found = refs.find((entry) => entry.id === value.id);
19
- return found?.serialised ?? value;
22
+ check.truthy(found, `Reference '${value.id}' not found! ❌`);
23
+ return found.serialised;
20
24
  }
21
- function resolveEntry(entry, schema, model, refs = []) {
25
+ export function resolvePath(model, instance, path, refs = []) {
26
+ const optionName = path.at(-1);
27
+ check.assert(v.string(), optionName, 'Path must end at an option name! ❌');
28
+ const descended = resolveItems(model, instance, path.slice(0, -1), refs);
29
+ if (descended == null) {
30
+ return null;
31
+ }
32
+ const [scopeModel, scopeInstance] = descended;
33
+ const merged = modelsMerge(scopeModel);
34
+ const option = merged.options.find((entry) => entry.name === optionName);
35
+ check.truthy(option, `Path target '${optionName}' is not an option! ❌`);
36
+ const payload = resolveOption(option, scopeModel, scopeInstance, refs);
37
+ if (payload == null) {
38
+ return null;
39
+ }
40
+ return { name: optionName, payload, value: scopeInstance[optionName] };
41
+ }
42
+ export function resolveItems(model, instance, pairs, refs = []) {
43
+ check.assert(BuilderInstanceSchema, instance);
44
+ if (pairs.length === 0) {
45
+ return [model, instance];
46
+ }
47
+ const [name, index, ...rest] = pairs;
48
+ check.assert(v.string(), name);
49
+ check.assert(v.number(), index, `Collection '${name}' must be followed by an index! ❌`);
50
+ const merged = modelsMerge(model);
51
+ const collection = merged.collections.find((entry) => entry.name === name);
52
+ check.truthy(collection, `Path segment '${name}' is not a collection! ❌`);
53
+ const resolved = resolveCollection(collection, model, instance, refs);
54
+ if (resolved == null) {
55
+ return null;
56
+ }
57
+ const items = instance[name];
58
+ check.assert(BuilderInstancesSchema, items);
59
+ const item = items.at(index);
60
+ if (item == null) {
61
+ return null;
62
+ }
63
+ return resolveItems(resolved.model, item, rest, refs);
64
+ }
65
+ function resolveEntry(entry, schema, model, instance, refs = []) {
22
66
  const payload = resolveRef(entry.payload, refs);
23
67
  if (check.is(schema, payload)) {
24
68
  return payload;
25
69
  }
70
+ check.assert(BuilderWhenSerialisedSchema, payload);
26
71
  check.assert(BuilderPathsSchema, entry.paths);
27
- return pickWhenBranch(payload, entry.paths, model);
28
- }
29
- function pickWhenBranch(config, gates, instance) {
30
- if (!gates.every((path) => readPath(instance, path) != null)) {
72
+ const config = payload;
73
+ if (!entry.paths.every((path) => isPresent(resolvePath(model, instance, path, refs)?.value))) {
31
74
  return null;
32
75
  }
33
76
  switch (config.type) {
@@ -36,10 +79,15 @@ function pickWhenBranch(config, gates, instance) {
36
79
  }
37
80
  case 'match': {
38
81
  const selectMap = config.selectMap;
39
- return selectMap[`${readPath(instance, config.matchPath)}`] ?? null;
82
+ const matched = resolvePath(model, instance, config.matchPath, refs);
83
+ return selectMap[`${matched?.value}`] ?? null;
40
84
  }
41
85
  case 'unless': {
42
- const value = readPath(instance, config.unlessPath);
86
+ const value = resolvePath(model, instance, config.unlessPath, refs)?.value;
87
+ if (!isPresent(value)) {
88
+ return null;
89
+ }
90
+ check.assert(BuilderPrimitiveSchema, value);
43
91
  if (config.disabledValues.includes(value)) {
44
92
  return null;
45
93
  }
@@ -47,3 +95,12 @@ function pickWhenBranch(config, gates, instance) {
47
95
  }
48
96
  }
49
97
  }
98
+ function isPresent(value) {
99
+ if (value == null) {
100
+ return false;
101
+ }
102
+ if (typeof value === 'string' && value === '') {
103
+ return false;
104
+ }
105
+ return true;
106
+ }
@@ -1,4 +1,4 @@
1
- import type { BuilderOptionValidated, BuilderWhen } from '../../entities/index';
1
+ import type { BuilderModelValidated, BuilderOptionValidated, BuilderWhen } from '../../entities/index';
2
2
  import type { BuilderInstances } from '../../instance';
3
3
  import type { BuilderPaths } from '../../paths';
4
4
  export type GraphKeys = ReadonlySet<string>;
@@ -15,5 +15,5 @@ export declare class BuilderOptionGraph {
15
15
  readonly paths: GraphPaths;
16
16
  constructor(paths?: GraphPaths);
17
17
  get(name: string): GraphPath;
18
- add(option: BuilderOptionValidated): BuilderOptionGraph;
18
+ add(option: BuilderOptionValidated, model: BuilderModelValidated): BuilderOptionGraph;
19
19
  }
@@ -1,5 +1,6 @@
1
1
  import { check } from '../../check.js';
2
2
  import { BuilderWhenMatchSchema, BuilderWhenUnlessSchema } from '../../entities/index.js';
3
+ import { BuilderPathSchema } from '../../paths.js';
3
4
  import { resolveOption } from '../resolve.js';
4
5
  export function crossProduct(left, right) {
5
6
  return left.flatMap((leftModel) => right.map((rightModel) => ({ ...leftModel, ...rightModel })));
@@ -7,10 +8,12 @@ export function crossProduct(left, right) {
7
8
  export function dependencyKeys(payload, paths = []) {
8
9
  const keys = new Set(paths.map(([first]) => String(first)));
9
10
  if (check.is(BuilderWhenMatchSchema, payload)) {
11
+ check.assert(BuilderPathSchema, payload.matchPath);
10
12
  const [firstMatchSegment] = payload.matchPath;
11
13
  keys.add(String(firstMatchSegment));
12
14
  }
13
15
  else if (check.is(BuilderWhenUnlessSchema, payload)) {
16
+ check.assert(BuilderPathSchema, payload.unlessPath);
14
17
  const [firstUnlessSegment] = payload.unlessPath;
15
18
  keys.add(String(firstUnlessSegment));
16
19
  }
@@ -26,11 +29,11 @@ export class BuilderOptionGraph {
26
29
  check.truthy(graphPath, `Option '${name}' not found in graph! ❌`);
27
30
  return graphPath;
28
31
  }
29
- add(option) {
32
+ add(option, model) {
30
33
  const { paths, payload } = option;
31
34
  const optionDependencyKeys = dependencyKeys(payload, paths);
32
35
  if (optionDependencyKeys.length === 0) {
33
- const payload = resolveOption(option, {});
36
+ const payload = resolveOption(option, model, {});
34
37
  check.truthy(payload, `Option '${option.name}' without dependencies must resolve! ❌`);
35
38
  const values = this.#optionValues(option.name, payload);
36
39
  return new BuilderOptionGraph([
@@ -45,7 +48,7 @@ export class BuilderOptionGraph {
45
48
  const merged = this.#mergePaths(new Set(optionDependencyKeys), this.paths);
46
49
  const finalInstances = [];
47
50
  merged.instances.forEach((instance) => {
48
- const payload = resolveOption(option, instance);
51
+ const payload = resolveOption(option, model, instance);
49
52
  if (payload) {
50
53
  this.#optionValues(option.name, payload).forEach((value) => {
51
54
  finalInstances.push({ ...instance, [option.name]: value });
@@ -1,5 +1,5 @@
1
1
  import { check } from '../../check.js';
2
- import { BuilderSerialisedSchema } from '../../entities/index.js';
2
+ import { BuilderModelSerialisedSchema, BuilderSerialisedSchema } from '../../entities/index.js';
3
3
  import { resolveCollection } from '../resolve.js';
4
4
  import { BuilderOptionGraph, crossProduct, dependencyKeys } from './option-graph.js';
5
5
  export function createVariants(entity) {
@@ -7,10 +7,14 @@ export function createVariants(entity) {
7
7
  return buildVariants(model);
8
8
  }
9
9
  function buildVariants(model) {
10
- const options = optionGraph(model.options);
10
+ const options = optionGraph(model);
11
11
  const nestedVariants = model.collections.flatMap((entry) => variantsFor(entry, options).flatMap((instance) => {
12
- const resolved = resolveCollection(entry, instance);
13
- return resolved == null ? [] : [buildVariants(resolved.model)];
12
+ const resolved = resolveCollection(entry, model, instance);
13
+ if (resolved == null) {
14
+ return [];
15
+ }
16
+ check.assert(BuilderModelSerialisedSchema, resolved.model);
17
+ return [buildVariants(resolved.model)];
14
18
  }));
15
19
  return {
16
20
  ...nestedVariants.reduce((merged, component) => ({ ...merged, ...component }), {}),
@@ -20,8 +24,8 @@ function buildVariants(model) {
20
24
  ]))
21
25
  };
22
26
  }
23
- function optionGraph(options) {
24
- return options.reduce((graph, option) => graph.add(option), new BuilderOptionGraph());
27
+ function optionGraph(model) {
28
+ return model.options.reduce((graph, option) => graph.add(option, model), new BuilderOptionGraph());
25
29
  }
26
30
  function variantsFor(entity, options) {
27
31
  const keys = new Set(dependencyKeys(entity.payload, entity.paths));
package/dist/paths.d.ts CHANGED
@@ -3,4 +3,3 @@ export declare const BuilderPathSchema: v.SchemaWithPipe<readonly [v.ArraySchema
3
3
  export type BuilderPath = v.InferOutput<typeof BuilderPathSchema>;
4
4
  export declare const BuilderPathsSchema: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.ArraySchema<v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>], undefined>, undefined>, v.ReadonlyAction<(string | number)[]>]>, undefined>, v.ReadonlyAction<(readonly (string | number)[])[]>]>;
5
5
  export type BuilderPaths = v.InferOutput<typeof BuilderPathsSchema>;
6
- export declare function readPath(model: unknown, path: BuilderPath): unknown;
package/dist/paths.js CHANGED
@@ -1,11 +1,3 @@
1
1
  import * as v from 'valibot';
2
2
  export const BuilderPathSchema = v.pipe(v.array(v.union([v.string(), v.number()])), v.readonly());
3
3
  export const BuilderPathsSchema = v.pipe(v.array(BuilderPathSchema), v.readonly());
4
- export function readPath(model, path) {
5
- return path.reduce((current, segment) => {
6
- if (current == null) {
7
- return null;
8
- }
9
- return current[segment];
10
- }, model);
11
- }
@@ -4,9 +4,15 @@ export function builderErrorUnvalidated(value, location = []) {
4
4
  return { ...builderError('unvalidated', location), value };
5
5
  }
6
6
  export function validate(value) {
7
+ // Descriptor flags chosen for Svelte 5 `$state` proxy compatibility — `$state`
8
+ // rejects any property that isn't writable, configurable, and enumerable.
9
+ // Symbol keys are skipped by `JSON.stringify` regardless of enumerability,
10
+ // so the brand never leaks through a serialise round-trip.
7
11
  Object.defineProperty(value, VALIDATED, {
8
12
  value: true,
9
- enumerable: false
13
+ writable: true,
14
+ enumerable: true,
15
+ configurable: true
10
16
  });
11
17
  return value;
12
18
  }
@@ -1,6 +1,7 @@
1
1
  import { check } from '../check.js';
2
2
  import { optionValueSchema } from '../entities/index.js';
3
3
  import { builderError } from '../exception.js';
4
+ import { BuilderInstancesSchema } from '../instance.js';
4
5
  import { resolveCollection, resolveOption } from '../mappers/index.js';
5
6
  import { validate } from './brand.js';
6
7
  export function builderErrorInvalidOption(name, value, location = []) {
@@ -17,7 +18,7 @@ function checkInstance(model, instance, location) {
17
18
  const { options, collections } = model;
18
19
  const optionErrors = options.flatMap((option) => {
19
20
  const { name } = option;
20
- const payload = resolveOption(option, instance);
21
+ const payload = resolveOption(option, model, instance);
21
22
  if (!payload) {
22
23
  return [];
23
24
  }
@@ -29,12 +30,12 @@ function checkInstance(model, instance, location) {
29
30
  });
30
31
  const collectionErrors = collections.flatMap((collection) => {
31
32
  const { name } = collection;
32
- const payload = resolveCollection(collection, instance);
33
+ const payload = resolveCollection(collection, model, instance);
33
34
  if (!payload) {
34
35
  return [];
35
36
  }
36
37
  const existing = instance[name];
37
- if (!Array.isArray(existing)) {
38
+ if (!check.is(BuilderInstancesSchema, existing)) {
38
39
  return [builderErrorInvalidCollection(name, [...location, name])];
39
40
  }
40
41
  return existing.flatMap((itemInstance, index) => checkInstance(payload.model, itemInstance, [...location, name, index]).map((error) => ({
@@ -62,7 +62,10 @@ export function validateModelStructure(input, resolve, location) {
62
62
  const resolvedCollections = collections.map((entry, entryIndex) => {
63
63
  const collectionLocation = [...location, 'collections', entryIndex];
64
64
  errors.push(...checkPaths(entry, collectionLocation), ...checkCollectionBounds(entry, collectionLocation));
65
- const [resolvedEntry, entryErrors] = resolveCollectionInnerModels(entry, resolve, [...collectionLocation, 'payload']);
65
+ const [resolvedEntry, entryErrors] = resolveCollectionInnerModels(entry, resolve, [
66
+ ...collectionLocation,
67
+ 'payload'
68
+ ]);
66
69
  errors.push(...entryErrors);
67
70
  return resolvedEntry;
68
71
  });
@@ -29,7 +29,7 @@ export function resolver(references = [], bindings = {}) {
29
29
  if (found == null) {
30
30
  return [reference, [builderErrorMissingReference(reference.id, location)]];
31
31
  }
32
- return [reference, []];
32
+ return [found.serialised, []];
33
33
  }
34
34
  function resolveParameter(parameter, location) {
35
35
  if (!(parameter.name in bindings)) {
@@ -39,9 +39,7 @@ export function resolver(references = [], bindings = {}) {
39
39
  if (!check.is(BuilderRefSerialisedSchema, binding)) {
40
40
  return [binding, []];
41
41
  }
42
- const refTarget = references.find((entry) => entry.id === binding.id);
43
- const errors = refTarget == null ? [builderErrorMissingReference(binding.id, location)] : [];
44
- return [binding, errors];
42
+ return resolveReference(binding, location);
45
43
  }
46
44
  function resolveWhen(when, location) {
47
45
  if (when.type === 'enable') {
@@ -35,7 +35,7 @@ export function validateVariants(model, input, options = {}) {
35
35
  const errors = [
36
36
  ...checkVariants(expected, input, options.partial ?? false),
37
37
  ...checkUnexpectedComponents(expected, input),
38
- ...checkInvalidDetails(input, model.components)
38
+ ...checkInvalidDetails(input, model)
39
39
  ];
40
40
  return [validate(input), errors];
41
41
  }
@@ -66,15 +66,15 @@ function checkUnexpectedComponents(expected, variants) {
66
66
  .filter((component) => expected[component] == null)
67
67
  .map((component) => builderErrorUnexpectedComponent(component, ['variants', component]));
68
68
  }
69
- function checkInvalidDetails(variants, components) {
70
- const componentByName = new Map(components.map((entry) => [entry.name, entry]));
69
+ function checkInvalidDetails(variants, model) {
70
+ const componentByName = new Map(model.components.map((entry) => [entry.name, entry]));
71
71
  return Object.entries(variants).flatMap(([component, componentVariants]) => {
72
72
  const entry = componentByName.get(component);
73
73
  if (entry == null) {
74
74
  return [];
75
75
  }
76
76
  return componentVariants.flatMap(({ instance, details }, index) => {
77
- const resolved = resolveComponent(entry, instance);
77
+ const resolved = resolveComponent(entry, model, instance);
78
78
  const fields = (resolved?.fields ?? []);
79
79
  const expectedNames = new Set(fields.map(({ name }) => name));
80
80
  const actualDetails = details ?? {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@builder-builder/builder",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -54,7 +54,9 @@
54
54
  "eslint-plugin-import-x": "^4.16.2",
55
55
  "eslint-plugin-svelte": "^3.0.0",
56
56
  "globals": "^16.0.0",
57
+ "jose": "^6.2.3",
57
58
  "jsdom": "^26.0.0",
59
+ "postgres": "^3.4.9",
58
60
  "prettier": "^3.4.2",
59
61
  "prettier-plugin-svelte": "^3.3.3",
60
62
  "supabase": "^2.93.0",