@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.
- package/dist/entities/kind.d.ts +1 -1
- package/dist/entities/validated.d.ts +19 -19
- package/dist/entities/when.d.ts +2 -2
- package/dist/mappers/instance.js +10 -6
- package/dist/mappers/order.js +5 -2
- package/dist/mappers/render/render.js +15 -54
- package/dist/mappers/resolve.d.ts +13 -4
- package/dist/mappers/resolve.js +73 -16
- package/dist/mappers/variants/option-graph.d.ts +2 -2
- package/dist/mappers/variants/option-graph.js +6 -3
- package/dist/mappers/variants/variants.js +10 -6
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -8
- package/dist/validate/brand.js +7 -1
- package/dist/validate/instance.js +4 -3
- package/dist/validate/model.js +4 -1
- package/dist/validate/resolve.js +2 -4
- package/dist/validate/variants.js +4 -4
- package/package.json +3 -1
package/dist/entities/kind.d.ts
CHANGED
|
@@ -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" | "
|
|
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 ?
|
|
12
|
-
type Validate<Input> = Input extends BuilderParameter<string> | BuilderParameterSerialised | BuilderRef | BuilderRefSerialised ? never : Input extends ReadonlyArray<unknown> ? ValidatedTuple<Input> : Input extends object ?
|
|
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
|
-
}
|
|
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 {};
|
package/dist/entities/when.d.ts
CHANGED
|
@@ -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>;
|
package/dist/mappers/instance.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 };
|
package/dist/mappers/order.js
CHANGED
|
@@ -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 {
|
|
6
|
-
import {
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
46
|
+
check.assert(BuilderPrimitiveSchema, found.value);
|
|
47
47
|
return [
|
|
48
48
|
{
|
|
49
|
-
name,
|
|
50
|
-
option,
|
|
51
|
-
value:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
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;
|
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,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
|
|
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,8 +24,8 @@ function buildVariants(model) {
|
|
|
20
24
|
]))
|
|
21
25
|
};
|
|
22
26
|
}
|
|
23
|
-
function optionGraph(
|
|
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
|
-
}
|
package/dist/validate/brand.js
CHANGED
|
@@ -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
|
-
|
|
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 (!
|
|
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) => ({
|
package/dist/validate/model.js
CHANGED
|
@@ -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, [
|
|
65
|
+
const [resolvedEntry, entryErrors] = resolveCollectionInnerModels(entry, resolve, [
|
|
66
|
+
...collectionLocation,
|
|
67
|
+
'payload'
|
|
68
|
+
]);
|
|
66
69
|
errors.push(...entryErrors);
|
|
67
70
|
return resolvedEntry;
|
|
68
71
|
});
|
package/dist/validate/resolve.js
CHANGED
|
@@ -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 [
|
|
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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",
|