@builder-builder/builder 0.0.12 → 0.0.14
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.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +53 -0
- package/dist/codegen/index.d.ts +7 -0
- package/dist/codegen/index.js +212 -0
- package/dist/codegen/template.d.ts +5 -0
- package/dist/codegen/template.js +17 -0
- package/dist/entities/index.d.ts +3 -3
- package/dist/entities/index.js +1 -1
- package/dist/entities/kind.d.ts +1 -1
- package/dist/entities/serialise.d.ts +554 -8
- package/dist/entities/serialise.js +5 -3
- package/dist/entities/ui/describe.d.ts +222 -8
- package/dist/entities/ui/describe.js +5 -5
- package/dist/entities/ui/index.d.ts +2 -0
- package/dist/entities/ui/index.js +1 -0
- package/dist/entities/ui/input.d.ts +334 -0
- package/dist/entities/ui/input.js +39 -0
- package/dist/entities/ui/page.d.ts +222 -8
- package/dist/entities/ui/page.js +5 -5
- package/dist/entities/ui/ui.d.ts +3 -3
- package/dist/entities/ui/ui.js +7 -4
- package/dist/entities/validated.d.ts +23 -21
- package/dist/entities/when.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/mappers/index.d.ts +3 -2
- package/dist/mappers/index.js +1 -1
- package/dist/mappers/instance.js +10 -6
- package/dist/mappers/order.js +5 -2
- package/dist/mappers/render/index.d.ts +1 -1
- package/dist/mappers/render/pages.d.ts +8 -0
- package/dist/mappers/render/pages.js +2 -1
- package/dist/mappers/render/render.js +31 -54
- package/dist/mappers/resolve.d.ts +13 -4
- package/dist/mappers/resolve.js +73 -16
- package/dist/mappers/variants/index.d.ts +2 -1
- package/dist/mappers/variants/index.js +2 -1
- package/dist/mappers/variants/option-graph.d.ts +2 -2
- package/dist/mappers/variants/option-graph.js +6 -3
- package/dist/mappers/variants/variants.d.ts +5 -2
- package/dist/mappers/variants/variants.js +11 -7
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -8
- package/dist/validate/brand.d.ts +0 -7
- package/dist/validate/brand.js +12 -6
- package/dist/validate/builder.d.ts +2 -1
- package/dist/validate/builder.js +14 -12
- package/dist/validate/errors.d.ts +130 -0
- package/dist/validate/errors.js +141 -0
- package/dist/validate/expectations.d.ts +3 -10
- package/dist/validate/expectations.js +8 -10
- package/dist/validate/index.d.ts +8 -14
- package/dist/validate/index.js +4 -7
- package/dist/validate/instance.d.ts +2 -15
- package/dist/validate/instance.js +42 -40
- package/dist/validate/model.d.ts +4 -31
- package/dist/validate/model.js +157 -173
- package/dist/validate/resolve.d.ts +3 -15
- package/dist/validate/resolve.js +66 -71
- package/dist/validate/result.d.ts +1 -7
- package/dist/validate/result.js +1 -4
- package/dist/validate/ui.d.ts +4 -4
- package/dist/validate/ui.js +80 -62
- package/dist/validate/variants.d.ts +2 -53
- package/dist/validate/variants.js +83 -86
- package/package.json +14 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { BuilderRenderMetadataSchema } from './pages.js';
|
|
1
2
|
import * as v from 'valibot';
|
|
2
3
|
import { check } from '../../check.js';
|
|
3
|
-
import { modelsMerge } from '../../entities/model/index.js';
|
|
4
4
|
import { BuilderInstancesSchema } from '../../instance.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { BuilderPrimitiveSchema } from '../../primitive.js';
|
|
6
|
+
import { createInstance } from '../instance.js';
|
|
7
|
+
import { resolveCollection, resolvePath, resolveRef } from '../resolve.js';
|
|
7
8
|
import { ordinal } from './ordinal.js';
|
|
8
9
|
export function render(builder, instance, refs = []) {
|
|
9
10
|
const layout = [];
|
|
@@ -30,26 +31,30 @@ export function render(builder, instance, refs = []) {
|
|
|
30
31
|
}
|
|
31
32
|
const itemInstances = currentInstance[item.name];
|
|
32
33
|
check.assert(BuilderInstancesSchema, itemInstances, 'Collection field is not an array! ❌');
|
|
33
|
-
const mergedItemModel =
|
|
34
|
+
const mergedItemModel = collection.model;
|
|
34
35
|
itemInstances.forEach((itemInstance, index) => {
|
|
35
36
|
walkItems(item.items, mergedItemModel, [...collectionPath, item.name, index], [...labelContext, `${ordinal(index)} ${composeLabel(item.label)}`], itemInstance);
|
|
36
37
|
});
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
function emitPage(page, model, collectionPath, labelContext, currentInstance) {
|
|
40
|
-
const options = page.
|
|
41
|
-
const
|
|
41
|
+
const options = page.inputs.flatMap((input) => {
|
|
42
|
+
const { path } = input;
|
|
43
|
+
const found = resolvePath(model, currentInstance, path, refs);
|
|
42
44
|
if (found == null) {
|
|
43
45
|
return [];
|
|
44
46
|
}
|
|
45
47
|
const fullPath = [...collectionPath, ...path];
|
|
46
|
-
|
|
48
|
+
check.assert(BuilderPrimitiveSchema, found.value);
|
|
47
49
|
return [
|
|
48
50
|
{
|
|
49
|
-
name,
|
|
50
|
-
option,
|
|
51
|
-
value:
|
|
52
|
-
update: (updateInstance, updateValue) => setPath(updateInstance, fullPath, updateValue)
|
|
51
|
+
name: found.name,
|
|
52
|
+
option: found.payload,
|
|
53
|
+
value: found.value,
|
|
54
|
+
update: (updateInstance, updateValue) => createInstance(builder, setPath(updateInstance, fullPath, updateValue), refs),
|
|
55
|
+
displayName: input.displayName && composeString(input.displayName, 'displayName'),
|
|
56
|
+
kind: input.kind && composeString(input.kind, 'kind'),
|
|
57
|
+
metadata: input.metadata && composeMetadata(input.metadata)
|
|
53
58
|
}
|
|
54
59
|
];
|
|
55
60
|
});
|
|
@@ -60,16 +65,14 @@ export function render(builder, instance, refs = []) {
|
|
|
60
65
|
}
|
|
61
66
|
function emitDescribe(describe, model, labelContext, currentInstance) {
|
|
62
67
|
const composedLabel = composeLabel(describe.label, labelContext);
|
|
63
|
-
const values = describe.
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
const value = readPath(currentInstance, path);
|
|
69
|
-
if (value == null) {
|
|
68
|
+
const values = describe.inputs.flatMap((input) => {
|
|
69
|
+
const { path } = input;
|
|
70
|
+
const found = resolvePath(model, currentInstance, path, refs);
|
|
71
|
+
if (found?.value == null) {
|
|
70
72
|
return [];
|
|
71
73
|
}
|
|
72
|
-
|
|
74
|
+
check.assert(BuilderPrimitiveSchema, found.value);
|
|
75
|
+
return [found.value];
|
|
73
76
|
});
|
|
74
77
|
if (values.length === 0) {
|
|
75
78
|
return;
|
|
@@ -87,45 +90,19 @@ export function render(builder, instance, refs = []) {
|
|
|
87
90
|
}
|
|
88
91
|
return `${labelContext.join(', ')}, ${resolved}`;
|
|
89
92
|
}
|
|
90
|
-
function
|
|
91
|
-
const
|
|
92
|
-
check.assert(v.string(),
|
|
93
|
-
|
|
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];
|
|
93
|
+
function composeString(value, fieldLabel) {
|
|
94
|
+
const resolved = resolveRef(value, refs);
|
|
95
|
+
check.assert(v.string(), resolved, `Input ${fieldLabel} did not resolve to a string! ❌`);
|
|
96
|
+
return resolved;
|
|
108
97
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
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);
|
|
98
|
+
function composeMetadata(value) {
|
|
99
|
+
const resolved = resolveRef(value, refs);
|
|
100
|
+
check.assert(BuilderRenderMetadataSchema, resolved, 'Input metadata did not resolve to an object! ❌');
|
|
101
|
+
return Object.fromEntries(Object.entries(resolved).map(([key, entry]) => [key, resolveRef(entry, refs)]));
|
|
125
102
|
}
|
|
126
103
|
function findCollection(model, instance, collectionName) {
|
|
127
104
|
const entry = model.collections.find((candidate) => candidate.name === collectionName);
|
|
128
|
-
return entry == null ? null : resolveCollection(entry, instance, refs);
|
|
105
|
+
return entry == null ? null : resolveCollection(entry, model, instance, refs);
|
|
129
106
|
}
|
|
130
107
|
}
|
|
131
108
|
function setPath(container, path, value) {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import type { BuilderCollectionConfigValidated, BuilderCollectionValidated,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
1
|
+
import type { BuilderCollectionConfigValidated, BuilderCollectionValidated, BuilderComponentDetailsValidated, BuilderComponentValidated, BuilderModelValidated, BuilderOptionValidated, BuilderOptionValuesValidated, 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): BuilderOptionValuesValidated | null;
|
|
5
|
+
export declare function resolveComponent(component: BuilderComponentValidated, model: BuilderModelValidated, instance: unknown, refs?: BuilderRefEntities): BuilderComponentDetailsValidated | 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: BuilderOptionValuesValidated;
|
|
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;
|
package/dist/mappers/resolve.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
22
|
+
check.truthy(found, `Reference '${value.id}' not found! ❌`);
|
|
23
|
+
return found.serialised;
|
|
20
24
|
}
|
|
21
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
const matched = resolvePath(model, instance, config.matchPath, refs);
|
|
83
|
+
return selectMap[`${matched?.value}`] ?? null;
|
|
40
84
|
}
|
|
41
85
|
case 'unless': {
|
|
42
|
-
const value =
|
|
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 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { BuilderOptionGraph } from './option-graph.js';
|
|
2
|
+
export { createVariants, optionGraph, variantsFor } from './variants.js';
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { BuilderOptionGraph } from './option-graph.js';
|
|
2
|
+
export { createVariants, optionGraph, variantsFor } from './variants.js';
|
|
@@ -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,3 +1,6 @@
|
|
|
1
|
-
import type { BuilderModelValidated, BuilderValidated } from '../../entities/index';
|
|
2
|
-
import type { BuilderComponentVariants } from '../../instance';
|
|
1
|
+
import type { BuilderCollectionValidated, BuilderComponentValidated, BuilderModelValidated, BuilderValidated } from '../../entities/index';
|
|
2
|
+
import type { BuilderComponentVariants, BuilderInstances } from '../../instance';
|
|
3
|
+
import { BuilderOptionGraph } from './option-graph.js';
|
|
3
4
|
export declare function createVariants(entity: BuilderValidated | BuilderModelValidated): BuilderComponentVariants;
|
|
5
|
+
export declare function optionGraph(model: BuilderModelValidated): BuilderOptionGraph;
|
|
6
|
+
export declare function variantsFor(entity: BuilderComponentValidated | BuilderCollectionValidated, options: BuilderOptionGraph): BuilderInstances;
|
|
@@ -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
|
|
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
|
-
|
|
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,10 +24,10 @@ function buildVariants(model) {
|
|
|
20
24
|
]))
|
|
21
25
|
};
|
|
22
26
|
}
|
|
23
|
-
function optionGraph(
|
|
24
|
-
return options.reduce((graph, option) => graph.add(option), new BuilderOptionGraph());
|
|
27
|
+
export function optionGraph(model) {
|
|
28
|
+
return model.options.reduce((graph, option) => graph.add(option, model), new BuilderOptionGraph());
|
|
25
29
|
}
|
|
26
|
-
function variantsFor(entity, options) {
|
|
30
|
+
export function variantsFor(entity, options) {
|
|
27
31
|
const keys = new Set(dependencyKeys(entity.payload, entity.paths));
|
|
28
32
|
const graphPaths = Array.from(keys)
|
|
29
33
|
.map((key) => options.get(key))
|
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
|
-
}
|
package/dist/validate/brand.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import type { BuilderErrorLocation } from '../exception';
|
|
2
1
|
declare const VALIDATED: unique symbol;
|
|
3
2
|
export type BuilderValidatedBrand = {
|
|
4
3
|
readonly [VALIDATED]: true;
|
|
5
4
|
};
|
|
6
|
-
export declare function builderErrorUnvalidated(value: unknown, location?: BuilderErrorLocation): {
|
|
7
|
-
value: unknown;
|
|
8
|
-
kind: "unvalidated";
|
|
9
|
-
location: BuilderErrorLocation;
|
|
10
|
-
};
|
|
11
|
-
export type BuilderErrorUnvalidated = ReturnType<typeof builderErrorUnvalidated>;
|
|
12
5
|
export declare function validate<Input extends object>(value: Input): Input & BuilderValidatedBrand;
|
|
13
6
|
export declare function assertValidated<Input>(value: Input): asserts value is Input & BuilderValidatedBrand;
|
|
14
7
|
export {};
|
package/dist/validate/brand.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BuilderException } from '../exception.js';
|
|
2
|
+
import { BuilderValidateErrors } from './errors.js';
|
|
2
3
|
const VALIDATED = Symbol('builder.validated');
|
|
3
|
-
export function builderErrorUnvalidated(value, location = []) {
|
|
4
|
-
return { ...builderError('unvalidated', location), value };
|
|
5
|
-
}
|
|
6
4
|
export function validate(value) {
|
|
5
|
+
// Descriptor flags chosen for Svelte 5 `$state` proxy compatibility — `$state`
|
|
6
|
+
// rejects any property that isn't writable, configurable, and enumerable.
|
|
7
|
+
// Symbol keys are skipped by `JSON.stringify` regardless of enumerability,
|
|
8
|
+
// so the brand never leaks through a serialise round-trip.
|
|
7
9
|
Object.defineProperty(value, VALIDATED, {
|
|
8
10
|
value: true,
|
|
9
|
-
|
|
11
|
+
writable: true,
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true
|
|
10
14
|
});
|
|
11
15
|
return value;
|
|
12
16
|
}
|
|
13
17
|
export function assertValidated(value) {
|
|
14
18
|
if (typeof value !== 'object' || value === null || !(VALIDATED in value)) {
|
|
15
|
-
|
|
19
|
+
const errors = new BuilderValidateErrors();
|
|
20
|
+
errors.unvalidated(value);
|
|
21
|
+
throw new BuilderException(errors.errors);
|
|
16
22
|
}
|
|
17
23
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BuilderRefEntities, BuilderValidated } from '../entities/index';
|
|
2
2
|
import type { ValidationResult } from './result';
|
|
3
|
+
import { BuilderValidateErrors } from './errors.js';
|
|
3
4
|
export type BuilderValidationResult = ValidationResult<BuilderValidated>;
|
|
4
|
-
export declare function validateBuilder(input: unknown, references?: BuilderRefEntities): BuilderValidationResult;
|
|
5
|
+
export declare function validateBuilder(input: unknown, references?: BuilderRefEntities, errors?: BuilderValidateErrors): BuilderValidationResult;
|
package/dist/validate/builder.js
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { check } from '../check.js';
|
|
2
2
|
import { builder, BuilderSerialisedSchema, modelsMerge, serialise } from '../entities/index.js';
|
|
3
3
|
import { validate } from './brand.js';
|
|
4
|
+
import { BuilderValidateErrors } from './errors.js';
|
|
4
5
|
import { checkModelExpectations, validateModelStructure } from './model.js';
|
|
5
6
|
import { resolver } from './resolve.js';
|
|
6
|
-
import { builderErrorInvalidInput } from './result.js';
|
|
7
7
|
import { checkUIExpectations, validateUIStructure } from './ui.js';
|
|
8
|
-
export function validateBuilder(input, references = []) {
|
|
8
|
+
export function validateBuilder(input, references = [], errors = new BuilderValidateErrors()) {
|
|
9
9
|
if (!check.is(BuilderSerialisedSchema, input)) {
|
|
10
|
-
|
|
11
|
-
return [validate(serialise.builder(builder())), errors];
|
|
10
|
+
errors.invalidInput('builder');
|
|
11
|
+
return [validate(serialise.builder(builder())), errors.errors];
|
|
12
12
|
}
|
|
13
|
-
const resolve = resolver(references, input.bindings);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const resolve = resolver(errors, references, input.bindings);
|
|
14
|
+
const validatedModel = errors.scope('model', () => validateModelStructure(input.model, resolve, errors));
|
|
15
|
+
const validatedUI = errors.scope('ui', () => validateUIStructure(input.ui, resolve, errors));
|
|
16
16
|
const mergedModel = modelsMerge(validatedModel);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
errors.scope('model', () => {
|
|
18
|
+
checkModelExpectations(mergedModel, validatedModel, errors);
|
|
19
|
+
});
|
|
20
|
+
errors.scope('ui', () => {
|
|
21
|
+
checkUIExpectations(mergedModel, validatedUI, errors);
|
|
22
|
+
});
|
|
21
23
|
const data = validate({
|
|
22
24
|
...serialise.builder(builder()),
|
|
23
25
|
model: mergedModel,
|
|
24
26
|
ui: validatedUI
|
|
25
27
|
});
|
|
26
|
-
return [data,
|
|
28
|
+
return [data, errors.errors];
|
|
27
29
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { BuilderExpectationKind } from '../entities/index';
|
|
2
|
+
import type { BuilderErrorLocation, BuilderErrors } from '../exception';
|
|
3
|
+
import type { BuilderInstance } from '../instance';
|
|
4
|
+
export type BuilderInvalidInputTarget = 'builder' | 'model' | 'ui' | 'variants';
|
|
5
|
+
export type BuilderErrorInvalidPathReason = 'shape' | 'out-of-bounds' | 'missing-collection' | 'option-not-found';
|
|
6
|
+
export declare class BuilderValidateErrors {
|
|
7
|
+
#private;
|
|
8
|
+
get errors(): BuilderErrors;
|
|
9
|
+
scope<Result>(part: string | number, fn: () => Result): Result;
|
|
10
|
+
invalidInput(target: BuilderInvalidInputTarget): {
|
|
11
|
+
target: BuilderInvalidInputTarget;
|
|
12
|
+
kind: "invalid-input";
|
|
13
|
+
location: BuilderErrorLocation;
|
|
14
|
+
};
|
|
15
|
+
invalidOption(name: string, value: unknown): {
|
|
16
|
+
name: string;
|
|
17
|
+
value: unknown;
|
|
18
|
+
kind: "invalid-option";
|
|
19
|
+
location: BuilderErrorLocation;
|
|
20
|
+
};
|
|
21
|
+
invalidCollection(name: string): {
|
|
22
|
+
name: string;
|
|
23
|
+
kind: "invalid-collection";
|
|
24
|
+
location: BuilderErrorLocation;
|
|
25
|
+
};
|
|
26
|
+
duplicateName(name: string): {
|
|
27
|
+
name: string;
|
|
28
|
+
kind: "duplicate-name";
|
|
29
|
+
location: BuilderErrorLocation;
|
|
30
|
+
};
|
|
31
|
+
invalidCollectionBounds(name: string, min: number, max: number): {
|
|
32
|
+
name: string;
|
|
33
|
+
min: number;
|
|
34
|
+
max: number;
|
|
35
|
+
kind: "invalid-collection-bounds";
|
|
36
|
+
location: BuilderErrorLocation;
|
|
37
|
+
};
|
|
38
|
+
invalidPath(reason: BuilderErrorInvalidPathReason): {
|
|
39
|
+
reason: BuilderErrorInvalidPathReason;
|
|
40
|
+
kind: "invalid-path";
|
|
41
|
+
location: BuilderErrorLocation;
|
|
42
|
+
};
|
|
43
|
+
invalidSelectMapKey(key: string): {
|
|
44
|
+
key: string;
|
|
45
|
+
kind: "invalid-select-map-key";
|
|
46
|
+
location: BuilderErrorLocation;
|
|
47
|
+
};
|
|
48
|
+
unboundParameter(name: string): {
|
|
49
|
+
name: string;
|
|
50
|
+
kind: "unbound-parameter";
|
|
51
|
+
location: BuilderErrorLocation;
|
|
52
|
+
};
|
|
53
|
+
missingReference(id: string): {
|
|
54
|
+
id: string;
|
|
55
|
+
kind: "missing-reference";
|
|
56
|
+
location: BuilderErrorLocation;
|
|
57
|
+
};
|
|
58
|
+
unmetExpectation(expectationKind: BuilderExpectationKind, name: string): {
|
|
59
|
+
expectationKind: "option" | "component" | "collection";
|
|
60
|
+
name: string;
|
|
61
|
+
kind: "unmet-expectation";
|
|
62
|
+
location: BuilderErrorLocation;
|
|
63
|
+
};
|
|
64
|
+
unvalidated(value: unknown): {
|
|
65
|
+
value: unknown;
|
|
66
|
+
kind: "unvalidated";
|
|
67
|
+
location: BuilderErrorLocation;
|
|
68
|
+
};
|
|
69
|
+
missingComponent(component: string): {
|
|
70
|
+
component: string;
|
|
71
|
+
kind: "missing-component";
|
|
72
|
+
location: BuilderErrorLocation;
|
|
73
|
+
};
|
|
74
|
+
unexpectedComponent(component: string): {
|
|
75
|
+
component: string;
|
|
76
|
+
kind: "unexpected-component";
|
|
77
|
+
location: BuilderErrorLocation;
|
|
78
|
+
};
|
|
79
|
+
missingVariant(component: string, instance: BuilderInstance): {
|
|
80
|
+
component: string;
|
|
81
|
+
instance: BuilderInstance;
|
|
82
|
+
kind: "missing-variant";
|
|
83
|
+
location: BuilderErrorLocation;
|
|
84
|
+
};
|
|
85
|
+
invalidVariant(component: string, instance: BuilderInstance): {
|
|
86
|
+
component: string;
|
|
87
|
+
instance: BuilderInstance;
|
|
88
|
+
kind: "invalid-variant";
|
|
89
|
+
location: BuilderErrorLocation;
|
|
90
|
+
};
|
|
91
|
+
missingDetail(component: string, detail: string, instance: BuilderInstance): {
|
|
92
|
+
component: string;
|
|
93
|
+
detail: string;
|
|
94
|
+
instance: BuilderInstance;
|
|
95
|
+
kind: "missing-detail";
|
|
96
|
+
location: BuilderErrorLocation;
|
|
97
|
+
};
|
|
98
|
+
unexpectedDetail(component: string, detail: string, instance: BuilderInstance): {
|
|
99
|
+
component: string;
|
|
100
|
+
detail: string;
|
|
101
|
+
instance: BuilderInstance;
|
|
102
|
+
kind: "unexpected-detail";
|
|
103
|
+
location: BuilderErrorLocation;
|
|
104
|
+
};
|
|
105
|
+
invalidDetail(component: string, detail: string, instance: BuilderInstance): {
|
|
106
|
+
component: string;
|
|
107
|
+
detail: string;
|
|
108
|
+
instance: BuilderInstance;
|
|
109
|
+
kind: "invalid-detail";
|
|
110
|
+
location: BuilderErrorLocation;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export type BuilderErrorInvalidInput = ReturnType<BuilderValidateErrors['invalidInput']>;
|
|
114
|
+
export type BuilderErrorInvalidOption = ReturnType<BuilderValidateErrors['invalidOption']>;
|
|
115
|
+
export type BuilderErrorInvalidCollection = ReturnType<BuilderValidateErrors['invalidCollection']>;
|
|
116
|
+
export type BuilderErrorDuplicateName = ReturnType<BuilderValidateErrors['duplicateName']>;
|
|
117
|
+
export type BuilderErrorInvalidCollectionBounds = ReturnType<BuilderValidateErrors['invalidCollectionBounds']>;
|
|
118
|
+
export type BuilderErrorInvalidPath = ReturnType<BuilderValidateErrors['invalidPath']>;
|
|
119
|
+
export type BuilderErrorInvalidSelectMapKey = ReturnType<BuilderValidateErrors['invalidSelectMapKey']>;
|
|
120
|
+
export type BuilderErrorUnboundParameter = ReturnType<BuilderValidateErrors['unboundParameter']>;
|
|
121
|
+
export type BuilderErrorMissingReference = ReturnType<BuilderValidateErrors['missingReference']>;
|
|
122
|
+
export type BuilderErrorUnmetExpectation = ReturnType<BuilderValidateErrors['unmetExpectation']>;
|
|
123
|
+
export type BuilderErrorUnvalidated = ReturnType<BuilderValidateErrors['unvalidated']>;
|
|
124
|
+
export type BuilderErrorMissingComponent = ReturnType<BuilderValidateErrors['missingComponent']>;
|
|
125
|
+
export type BuilderErrorUnexpectedComponent = ReturnType<BuilderValidateErrors['unexpectedComponent']>;
|
|
126
|
+
export type BuilderErrorMissingVariant = ReturnType<BuilderValidateErrors['missingVariant']>;
|
|
127
|
+
export type BuilderErrorInvalidVariant = ReturnType<BuilderValidateErrors['invalidVariant']>;
|
|
128
|
+
export type BuilderErrorMissingDetail = ReturnType<BuilderValidateErrors['missingDetail']>;
|
|
129
|
+
export type BuilderErrorUnexpectedDetail = ReturnType<BuilderValidateErrors['unexpectedDetail']>;
|
|
130
|
+
export type BuilderErrorInvalidDetail = ReturnType<BuilderValidateErrors['invalidDetail']>;
|