@jfdevelops/react-layout 0.17.5 → 0.18.1
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/create-config/component-keys.cjs +44 -0
- package/dist/create-config/component-keys.cjs.map +1 -0
- package/dist/create-config/component-keys.mjs +39 -0
- package/dist/create-config/component-keys.mjs.map +1 -0
- package/dist/create-config/define-layout.cjs +16 -1
- package/dist/create-config/define-layout.cjs.map +1 -1
- package/dist/create-config/define-layout.d.cts +5 -3
- package/dist/create-config/define-layout.d.cts.map +1 -1
- package/dist/create-config/define-layout.d.mts +5 -3
- package/dist/create-config/define-layout.d.mts.map +1 -1
- package/dist/create-config/define-layout.mjs +16 -1
- package/dist/create-config/define-layout.mjs.map +1 -1
- package/dist/create-config/for-paths/builder.cjs +192 -0
- package/dist/create-config/for-paths/builder.cjs.map +1 -0
- package/dist/create-config/for-paths/builder.d.cts +77 -0
- package/dist/create-config/for-paths/builder.d.cts.map +1 -0
- package/dist/create-config/for-paths/builder.d.mts +77 -0
- package/dist/create-config/for-paths/builder.d.mts.map +1 -0
- package/dist/create-config/for-paths/builder.mjs +192 -0
- package/dist/create-config/for-paths/builder.mjs.map +1 -0
- package/dist/create-config/for-paths/get-component-for-path.cjs +55 -0
- package/dist/create-config/for-paths/get-component-for-path.cjs.map +1 -0
- package/dist/create-config/for-paths/get-component-for-path.d.cts +55 -0
- package/dist/create-config/for-paths/get-component-for-path.d.cts.map +1 -0
- package/dist/create-config/for-paths/get-component-for-path.d.mts +55 -0
- package/dist/create-config/for-paths/get-component-for-path.d.mts.map +1 -0
- package/dist/create-config/for-paths/get-component-for-path.mjs +55 -0
- package/dist/create-config/for-paths/get-component-for-path.mjs.map +1 -0
- package/dist/create-config/for-paths/index.d.cts +2 -0
- package/dist/create-config/for-paths/index.d.mts +2 -0
- package/dist/create-config/get-component.cjs +60 -11
- package/dist/create-config/get-component.cjs.map +1 -1
- package/dist/create-config/get-component.d.cts +75 -6
- package/dist/create-config/get-component.d.cts.map +1 -1
- package/dist/create-config/get-component.d.mts +75 -6
- package/dist/create-config/get-component.d.mts.map +1 -1
- package/dist/create-config/get-component.mjs +61 -11
- package/dist/create-config/get-component.mjs.map +1 -1
- package/dist/create-config/index.d.cts +6 -1
- package/dist/create-config/index.d.mts +6 -1
- package/dist/create-config/is-sub-resource-key.cjs +15 -0
- package/dist/create-config/is-sub-resource-key.cjs.map +1 -0
- package/dist/create-config/is-sub-resource-key.d.cts +30 -0
- package/dist/create-config/is-sub-resource-key.d.cts.map +1 -0
- package/dist/create-config/is-sub-resource-key.d.mts +30 -0
- package/dist/create-config/is-sub-resource-key.d.mts.map +1 -0
- package/dist/create-config/is-sub-resource-key.mjs +15 -0
- package/dist/create-config/is-sub-resource-key.mjs.map +1 -0
- package/dist/create-config/paths/generate.cjs +77 -0
- package/dist/create-config/paths/generate.cjs.map +1 -0
- package/dist/create-config/paths/generate.mjs +77 -0
- package/dist/create-config/paths/generate.mjs.map +1 -0
- package/dist/create-config/paths/index.d.cts +2 -0
- package/dist/create-config/paths/index.d.mts +2 -0
- package/dist/create-config/paths/introspect.cjs +123 -0
- package/dist/create-config/paths/introspect.cjs.map +1 -0
- package/dist/create-config/paths/introspect.mjs +118 -0
- package/dist/create-config/paths/introspect.mjs.map +1 -0
- package/dist/create-config/paths/resolve.cjs +130 -0
- package/dist/create-config/paths/resolve.cjs.map +1 -0
- package/dist/create-config/paths/resolve.mjs +129 -0
- package/dist/create-config/paths/resolve.mjs.map +1 -0
- package/dist/create-config/paths/types.d.cts +104 -0
- package/dist/create-config/paths/types.d.cts.map +1 -0
- package/dist/create-config/paths/types.d.mts +104 -0
- package/dist/create-config/paths/types.d.mts.map +1 -0
- package/dist/create-config/paths/variables.cjs +32 -0
- package/dist/create-config/paths/variables.cjs.map +1 -0
- package/dist/create-config/paths/variables.d.cts +32 -0
- package/dist/create-config/paths/variables.d.cts.map +1 -0
- package/dist/create-config/paths/variables.d.mts +32 -0
- package/dist/create-config/paths/variables.d.mts.map +1 -0
- package/dist/create-config/paths/variables.mjs +29 -0
- package/dist/create-config/paths/variables.mjs.map +1 -0
- package/dist/create-config/types.d.cts +28 -8
- package/dist/create-config/types.d.cts.map +1 -1
- package/dist/create-config/types.d.mts +28 -8
- package/dist/create-config/types.d.mts.map +1 -1
- package/dist/errors/invalid-component.cjs +21 -0
- package/dist/errors/invalid-component.cjs.map +1 -0
- package/dist/errors/invalid-component.d.cts +18 -0
- package/dist/errors/invalid-component.d.cts.map +1 -0
- package/dist/errors/invalid-component.d.mts +18 -0
- package/dist/errors/invalid-component.d.mts.map +1 -0
- package/dist/errors/invalid-component.mjs +21 -0
- package/dist/errors/invalid-component.mjs.map +1 -0
- package/dist/errors/invalid-config.cjs +13 -0
- package/dist/errors/invalid-config.cjs.map +1 -0
- package/dist/errors/invalid-config.d.cts +15 -0
- package/dist/errors/invalid-config.d.cts.map +1 -0
- package/dist/errors/invalid-config.d.mts +15 -0
- package/dist/errors/invalid-config.d.mts.map +1 -0
- package/dist/errors/invalid-config.mjs +13 -0
- package/dist/errors/invalid-config.mjs.map +1 -0
- package/dist/errors/invalid-path.cjs +18 -0
- package/dist/errors/invalid-path.cjs.map +1 -0
- package/dist/errors/invalid-path.d.cts +16 -0
- package/dist/errors/invalid-path.d.cts.map +1 -0
- package/dist/errors/invalid-path.d.mts +16 -0
- package/dist/errors/invalid-path.d.mts.map +1 -0
- package/dist/errors/invalid-path.mjs +18 -0
- package/dist/errors/invalid-path.mjs.map +1 -0
- package/dist/errors/invalid-resource.cjs +18 -0
- package/dist/errors/invalid-resource.cjs.map +1 -0
- package/dist/errors/invalid-resource.d.cts +16 -0
- package/dist/errors/invalid-resource.d.cts.map +1 -0
- package/dist/errors/invalid-resource.d.mts +16 -0
- package/dist/errors/invalid-resource.d.mts.map +1 -0
- package/dist/errors/invalid-resource.mjs +18 -0
- package/dist/errors/invalid-resource.mjs.map +1 -0
- package/dist/errors/invalid-sub-resource.cjs +18 -0
- package/dist/errors/invalid-sub-resource.cjs.map +1 -0
- package/dist/errors/invalid-sub-resource.d.cts +17 -0
- package/dist/errors/invalid-sub-resource.d.cts.map +1 -0
- package/dist/errors/invalid-sub-resource.d.mts +17 -0
- package/dist/errors/invalid-sub-resource.d.mts.map +1 -0
- package/dist/errors/invalid-sub-resource.mjs +18 -0
- package/dist/errors/invalid-sub-resource.mjs.map +1 -0
- package/dist/errors/missing-path-variable.cjs +18 -0
- package/dist/errors/missing-path-variable.cjs.map +1 -0
- package/dist/errors/missing-path-variable.d.cts +16 -0
- package/dist/errors/missing-path-variable.d.cts.map +1 -0
- package/dist/errors/missing-path-variable.d.mts +16 -0
- package/dist/errors/missing-path-variable.d.mts.map +1 -0
- package/dist/errors/missing-path-variable.mjs +18 -0
- package/dist/errors/missing-path-variable.mjs.map +1 -0
- package/dist/errors/react-layout-error.cjs +56 -0
- package/dist/errors/react-layout-error.cjs.map +1 -0
- package/dist/errors/react-layout-error.d.cts +46 -0
- package/dist/errors/react-layout-error.d.cts.map +1 -0
- package/dist/errors/react-layout-error.d.mts +46 -0
- package/dist/errors/react-layout-error.d.mts.map +1 -0
- package/dist/errors/react-layout-error.mjs +54 -0
- package/dist/errors/react-layout-error.mjs.map +1 -0
- package/dist/index.cjs +15 -0
- package/dist/index.d.cts +13 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +8 -1
- package/dist/props.d.cts +1 -1
- package/dist/props.d.cts.map +1 -1
- package/dist/props.d.mts +1 -1
- package/dist/props.d.mts.map +1 -1
- package/dist/resource.cjs.map +1 -1
- package/dist/resource.d.cts +2 -2
- package/dist/resource.d.cts.map +1 -1
- package/dist/resource.d.mts +2 -2
- package/dist/resource.d.mts.map +1 -1
- package/dist/resource.mjs.map +1 -1
- package/dist/utils.d.cts +2 -1
- package/dist/utils.d.cts.map +1 -1
- package/dist/utils.d.mts +2 -1
- package/dist/utils.d.mts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { InvalidPathError } from "../../errors/invalid-path.mjs";
|
|
2
|
+
import { InvalidResourceError } from "../../errors/invalid-resource.mjs";
|
|
3
|
+
import { InvalidSubResourceError } from "../../errors/invalid-sub-resource.mjs";
|
|
4
|
+
import { InvalidComponentError } from "../../errors/invalid-component.mjs";
|
|
5
|
+
import { extractPathVariables } from "../paths/variables.mjs";
|
|
6
|
+
import { generateResourceConfigPaths } from "../paths/generate.mjs";
|
|
7
|
+
import { collectPathComponentKeys, collectPathResources, collectPathSubResourceKeys } from "../paths/introspect.mjs";
|
|
8
|
+
import { flattenSubResourceParam } from "../paths/resolve.mjs";
|
|
9
|
+
import { createGetComponentForPath } from "./get-component-for-path.mjs";
|
|
10
|
+
|
|
11
|
+
//#region src/create-config/for-paths/builder.ts
|
|
12
|
+
const DEFAULT_DISPLAY_NAME = "ResourcePathComponent";
|
|
13
|
+
function collectValidationSets(config, paths) {
|
|
14
|
+
const resources = /* @__PURE__ */ new Set();
|
|
15
|
+
const subResources = /* @__PURE__ */ new Set();
|
|
16
|
+
const components = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const path of paths) {
|
|
18
|
+
for (const resource of collectPathResources(config, path)) resources.add(resource);
|
|
19
|
+
for (const subResource of collectPathSubResourceKeys(config, path)) subResources.add(subResource);
|
|
20
|
+
for (const component of collectPathComponentKeys(config, path)) components.add(component);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
resources: [...resources],
|
|
24
|
+
subResources: [...subResources],
|
|
25
|
+
components: [...components]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validates the params a caller passed before the renderer runs, so a bad resource or
|
|
30
|
+
* slot fails with a typed error naming what is available.
|
|
31
|
+
*/
|
|
32
|
+
function validateParams(params, state, sets) {
|
|
33
|
+
const { resource, subResource, component, componentKey } = params;
|
|
34
|
+
if (state.declaredPathParams.includes("resource") && resource !== void 0) {
|
|
35
|
+
if (typeof resource !== "string" || !sets.resources.includes(resource)) throw new InvalidResourceError({
|
|
36
|
+
resource,
|
|
37
|
+
validResources: sets.resources
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (state.declaredPathParams.includes("subResource") && subResource !== void 0) {
|
|
41
|
+
const slugs = flattenSubResourceParam(subResource);
|
|
42
|
+
for (const slug of slugs) if (!sets.subResources.includes(slug)) throw new InvalidSubResourceError({
|
|
43
|
+
subResource: slug,
|
|
44
|
+
resource: typeof resource === "string" ? resource : void 0,
|
|
45
|
+
validSubResources: sets.subResources
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (state.declaredPathParams.includes("component") && component !== void 0) {
|
|
49
|
+
if (typeof component !== "string" || !sets.components.includes(component)) throw new InvalidComponentError({
|
|
50
|
+
component,
|
|
51
|
+
validComponents: sets.components
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (!state.componentKeyDeclared) return;
|
|
55
|
+
if (componentKey === void 0) {
|
|
56
|
+
if (!state.componentKeyOptional) throw new InvalidComponentError({
|
|
57
|
+
component: componentKey,
|
|
58
|
+
validComponents: sets.components,
|
|
59
|
+
reason: "A \"componentKey\" prop is required for this component"
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (typeof componentKey !== "string" || !sets.components.includes(componentKey)) throw new InvalidComponentError({
|
|
64
|
+
component: componentKey,
|
|
65
|
+
validComponents: sets.components
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function readClosureProps(selected, closure) {
|
|
69
|
+
if (selected.length === 0) return {};
|
|
70
|
+
const [only] = selected;
|
|
71
|
+
if (selected.length === 1 && only !== void 0) return { [only]: closure };
|
|
72
|
+
if (typeof closure === "object" && closure !== null && !Array.isArray(closure)) return { ...closure };
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
function createRenderedComponent(runRenderer, paramTemplate, resolveDisplayName, boundProps = {}) {
|
|
76
|
+
function RenderedPathView(props) {
|
|
77
|
+
const merged = {
|
|
78
|
+
...boundProps,
|
|
79
|
+
...props
|
|
80
|
+
};
|
|
81
|
+
if (resolveDisplayName) RenderedPathView.displayName = resolveDisplayName(merged);
|
|
82
|
+
return runRenderer(merged);
|
|
83
|
+
}
|
|
84
|
+
RenderedPathView.displayName = DEFAULT_DISPLAY_NAME;
|
|
85
|
+
function asHOF(options) {
|
|
86
|
+
const selected = Object.entries(options?.args ?? {}).filter(([, enabled]) => enabled === true).map(([key]) => key);
|
|
87
|
+
function hof(closure) {
|
|
88
|
+
const bound = createRenderedComponent(runRenderer, void 0, resolveDisplayName, {
|
|
89
|
+
...boundProps,
|
|
90
|
+
...readClosureProps(selected, closure)
|
|
91
|
+
});
|
|
92
|
+
bound.displayName = RenderedPathView.displayName;
|
|
93
|
+
return bound;
|
|
94
|
+
}
|
|
95
|
+
return Object.assign(hof, { _args: void 0 });
|
|
96
|
+
}
|
|
97
|
+
return Object.assign(RenderedPathView, {
|
|
98
|
+
_args: paramTemplate,
|
|
99
|
+
asHOF
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function createTargetPathBuilder(config, paths) {
|
|
103
|
+
const validationSets = collectValidationSets(config, paths);
|
|
104
|
+
const makeBuilder = (state) => {
|
|
105
|
+
const builder = {};
|
|
106
|
+
if (!state.usedComponentKeyParams) builder.addComponentKeyParams = (options) => makeBuilder({
|
|
107
|
+
...state,
|
|
108
|
+
usedComponentKeyParams: true,
|
|
109
|
+
componentKeyDeclared: true,
|
|
110
|
+
componentKeyOptional: options?.optional === true
|
|
111
|
+
});
|
|
112
|
+
if (!state.usedPathParams) builder.addPathParams = (options) => {
|
|
113
|
+
const available = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const path of state.paths) for (const name of extractPathVariables(path)) available.add(name);
|
|
115
|
+
const picked = Object.entries(options?.pick ?? {}).filter(([, enabled]) => enabled === true).map(([key]) => key);
|
|
116
|
+
const declared = picked.length === 0 ? [...available] : picked.filter((name) => available.has(name));
|
|
117
|
+
return makeBuilder({
|
|
118
|
+
...state,
|
|
119
|
+
usedPathParams: true,
|
|
120
|
+
declaredPathParams: [...state.declaredPathParams, ...declared]
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
if (!state.usedParams) builder.addParams = (params) => makeBuilder({
|
|
124
|
+
...state,
|
|
125
|
+
usedParams: true,
|
|
126
|
+
paramDefaults: {
|
|
127
|
+
...state.paramDefaults,
|
|
128
|
+
...params
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
builder.render = (renderer) => {
|
|
132
|
+
const buildContext = (props) => {
|
|
133
|
+
const params = {
|
|
134
|
+
...state.paramDefaults,
|
|
135
|
+
...props
|
|
136
|
+
};
|
|
137
|
+
validateParams(params, state, validationSets);
|
|
138
|
+
return {
|
|
139
|
+
params,
|
|
140
|
+
getComponentForPath: (path) => {
|
|
141
|
+
if (!state.paths.includes(path)) throw new InvalidPathError({
|
|
142
|
+
path,
|
|
143
|
+
validPaths: state.paths,
|
|
144
|
+
reason: "This component was not built for that path"
|
|
145
|
+
});
|
|
146
|
+
return createGetComponentForPath(config, path);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const runRenderer = (props) => renderer(buildContext(props));
|
|
151
|
+
const component = createRenderedComponent(runRenderer, { ...state.paramDefaults });
|
|
152
|
+
return Object.assign(component, { setDisplayName: (name) => {
|
|
153
|
+
if (typeof name === "string") {
|
|
154
|
+
const named = createRenderedComponent(runRenderer, { ...state.paramDefaults });
|
|
155
|
+
named.displayName = name;
|
|
156
|
+
return named;
|
|
157
|
+
}
|
|
158
|
+
return createRenderedComponent(runRenderer, { ...state.paramDefaults }, (props) => name(buildContext(props)));
|
|
159
|
+
} });
|
|
160
|
+
};
|
|
161
|
+
return builder;
|
|
162
|
+
};
|
|
163
|
+
return makeBuilder({
|
|
164
|
+
paths,
|
|
165
|
+
paramDefaults: {},
|
|
166
|
+
declaredPathParams: [],
|
|
167
|
+
componentKeyDeclared: false,
|
|
168
|
+
componentKeyOptional: false,
|
|
169
|
+
usedComponentKeyParams: false,
|
|
170
|
+
usedPathParams: false,
|
|
171
|
+
usedParams: false
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function createForPaths(config) {
|
|
175
|
+
const validPaths = generateResourceConfigPaths(config);
|
|
176
|
+
return ((...paths) => {
|
|
177
|
+
if (paths.length === 0) throw new InvalidPathError({
|
|
178
|
+
path: paths,
|
|
179
|
+
validPaths,
|
|
180
|
+
reason: "At least one path is required"
|
|
181
|
+
});
|
|
182
|
+
for (const path of paths) if (!validPaths.includes(path)) throw new InvalidPathError({
|
|
183
|
+
path,
|
|
184
|
+
validPaths
|
|
185
|
+
});
|
|
186
|
+
return createTargetPathBuilder(config, paths);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
export { createForPaths };
|
|
192
|
+
//# sourceMappingURL=builder.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.mjs","names":[],"sources":["../../../src/create-config/for-paths/builder.ts"],"sourcesContent":["import type { JSX } from 'react';\nimport {\n InvalidComponentError,\n InvalidPathError,\n InvalidResourceError,\n InvalidSubResourceError,\n} from '../../errors';\nimport type { ResourceDefinition } from '../../resource';\nimport type { IsUnion, Show } from '../../utils';\nimport {\n collectPathComponentKeys,\n collectPathResources,\n collectPathSubResourceKeys,\n extractPathVariables,\n flattenSubResourceParam,\n generateResourceConfigPaths,\n} from '../paths';\nimport type {\n PathComponentKeys,\n PathVariables,\n SubResourceParamValue,\n} from '../paths';\nimport {\n createGetComponentForPath,\n type CreateGetComponentForPath,\n} from './get-component-for-path';\n\ntype UnwrapTargetPaths<Paths> = Paths extends readonly [\n infer First,\n ...infer Rest,\n]\n ? (First & string) | UnwrapTargetPaths<Rest>\n : Paths extends readonly (infer Path)[]\n ? Path & string\n : never;\n\ntype PathVariablesForPaths<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Paths extends ReadonlyArray<string>,\n> =\n UnwrapTargetPaths<Paths> extends infer Path\n ? Path extends string\n ? PathVariables<Resources, Config, Path>\n : never\n : never;\n\ntype ComponentKeysForPaths<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Paths extends ReadonlyArray<string>,\n> =\n UnwrapTargetPaths<Paths> extends infer Path\n ? Path extends string\n ? PathComponentKeys<Resources, Config, Path>\n : never\n : never;\n\ntype ValueOrFunction<Value, Input> = Value | ((input: Input) => Value);\n\n/** Context handed to `render` and to `setDisplayName`. */\nexport type ForPathsRenderContext<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Paths extends ReadonlyArray<string>,\n Params,\n> = {\n /** Declared params merged with the props the component was called with. */\n params: Params;\n /** Creates a getter for any of the paths this component was built for. */\n getComponentForPath: CreateGetComponentForPath<\n Resources,\n Config,\n UnwrapTargetPaths<Paths>\n >;\n};\n\ntype HOFArgSelection<Params> = {\n [Key in keyof Params]?: true;\n};\n\n/**\n * Value closed over by `asHOF`. Selecting a single arg closes over that value directly;\n * selecting several closes over an object.\n */\ntype HOFClosureValue<Params, Args> = [keyof Args] extends [never]\n ? {}\n : IsUnion<keyof Args> extends false\n ? Params[keyof Args & keyof Params]\n : Show<Pick<Params, keyof Args & keyof Params>>;\n\nexport interface RenderedPathComponentHOF<Params, Args> {\n (closure: HOFClosureValue<Params, Args>): RenderedPathComponent<\n Show<Omit<Params, keyof Args>>\n >;\n /** Type-only view of the value this HOF closes over. At runtime this is `undefined`. */\n _args: HOFClosureValue<Params, Args>;\n}\n\nexport interface RenderedPathComponent<\n Params,\n DisplayName extends string = string,\n> {\n (props: Params): JSX.Element;\n displayName: DisplayName;\n /** Type-only view of the props this component accepts. */\n _args: Params;\n /**\n * Splits the props into a closure and the remaining props, so shared params can be\n * bound once and the component reused. Chained `asHOF` calls keep prior closures.\n */\n asHOF<Args extends HOFArgSelection<Params> = {}>(options?: {\n /** Params to close over. Anything omitted stays a prop. */\n args: Args;\n }): RenderedPathComponentHOF<Params, Args>;\n}\n\ntype WithComponentKeyParam<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Paths extends ReadonlyArray<string>,\n Optional extends boolean,\n> = Optional extends true\n ? { componentKey?: ComponentKeysForPaths<Resources, Config, Paths> }\n : { componentKey: ComponentKeysForPaths<Resources, Config, Paths> };\n\ntype PickedPathParams<\n Variables,\n Picked,\n> = [keyof Picked] extends [never]\n ? Variables\n : Pick<Variables, keyof Picked & keyof Variables>;\n\nexport type TargetPathBuilder<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Paths extends ReadonlyArray<string>,\n Params extends Record<string, unknown> = {},\n Used extends\n | 'addComponentKeyParams'\n | 'addPathParams'\n | 'addParams' = never,\n> = {\n render: (\n renderer: (\n context: ForPathsRenderContext<Resources, Config, Paths, Params>,\n ) => JSX.Element,\n ) => RenderedPathComponent<Params> & {\n /** Names the component, either statically or from the render context. */\n setDisplayName<Name extends string>(\n name: ValueOrFunction<\n Name,\n ForPathsRenderContext<Resources, Config, Paths, Params>\n >,\n ): RenderedPathComponent<Params, Name>;\n };\n} & ('addComponentKeyParams' extends Used\n ? {}\n : {\n /**\n * Adds a `componentKey` prop narrowed to the component slots configured for these\n * paths. Call it before `render` when building a reusable component.\n */\n addComponentKeyParams<Optional extends boolean = false>(options?: {\n /** Whether the caller may omit `componentKey`. */\n optional?: Optional;\n }): TargetPathBuilder<\n Resources,\n Config,\n Paths,\n Show<\n Params & WithComponentKeyParam<Resources, Config, Paths, Optional>\n >,\n Used | 'addComponentKeyParams'\n >;\n }) &\n ('addPathParams' extends Used\n ? {}\n : {\n /**\n * Turns the variables these paths leave open into props. Without `pick`, every\n * variable becomes a prop.\n */\n addPathParams<\n Variables extends PathVariablesForPaths<Resources, Config, Paths>,\n Picked extends { [Key in keyof Variables]?: true } = {},\n >(options?: {\n /** Variables to expose as props. Defaults to all of them. */\n pick?: Picked;\n }): TargetPathBuilder<\n Resources,\n Config,\n Paths,\n Show<Params & PickedPathParams<Variables, Picked>>,\n Used | 'addPathParams'\n >;\n }) &\n ('addParams' extends Used\n ? {}\n : {\n /**\n * Adds custom params with the given values as defaults. Defaults are\n * optional at the call site and filled in before `render` runs.\n */\n addParams<NewParams extends Record<string, unknown>>(\n params: NewParams,\n ): TargetPathBuilder<\n Resources,\n Config,\n Paths,\n Show<Params & Partial<NewParams>>,\n Used | 'addParams'\n >;\n });\n\nexport type ForPaths<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n AllowedPath extends string,\n> = <const Paths extends readonly [AllowedPath, ...AllowedPath[]]>(\n ...paths: Paths\n) => TargetPathBuilder<Resources, Config, Paths>;\n\nconst DEFAULT_DISPLAY_NAME = 'ResourcePathComponent';\n\ntype BuilderState = {\n paths: readonly string[];\n paramDefaults: Record<string, unknown>;\n declaredPathParams: readonly string[];\n componentKeyDeclared: boolean;\n componentKeyOptional: boolean;\n usedComponentKeyParams: boolean;\n usedPathParams: boolean;\n usedParams: boolean;\n};\n\ntype ValidationSets = {\n resources: readonly string[];\n subResources: readonly string[];\n components: readonly string[];\n};\n\nfunction collectValidationSets(\n config: Record<string, unknown>,\n paths: readonly string[],\n): ValidationSets {\n const resources = new Set<string>();\n const subResources = new Set<string>();\n const components = new Set<string>();\n\n for (const path of paths) {\n for (const resource of collectPathResources(config, path)) {\n resources.add(resource);\n }\n\n for (const subResource of collectPathSubResourceKeys(config, path)) {\n subResources.add(subResource);\n }\n\n for (const component of collectPathComponentKeys(config, path)) {\n components.add(component);\n }\n }\n\n return {\n resources: [...resources],\n subResources: [...subResources],\n components: [...components],\n };\n}\n\n/**\n * Validates the params a caller passed before the renderer runs, so a bad resource or\n * slot fails with a typed error naming what is available.\n */\nfunction validateParams(\n params: Record<string, unknown>,\n state: BuilderState,\n sets: ValidationSets,\n): void {\n const { resource, subResource, component, componentKey } = params as {\n resource?: unknown;\n subResource?: unknown;\n component?: unknown;\n componentKey?: unknown;\n };\n\n if (state.declaredPathParams.includes('resource') && resource !== undefined) {\n if (typeof resource !== 'string' || !sets.resources.includes(resource)) {\n throw new InvalidResourceError({\n resource,\n validResources: sets.resources,\n });\n }\n }\n\n if (\n state.declaredPathParams.includes('subResource') &&\n subResource !== undefined\n ) {\n const slugs = flattenSubResourceParam(subResource as SubResourceParamValue);\n\n for (const slug of slugs) {\n if (!sets.subResources.includes(slug)) {\n throw new InvalidSubResourceError({\n subResource: slug,\n resource: typeof resource === 'string' ? resource : undefined,\n validSubResources: sets.subResources,\n });\n }\n }\n }\n\n if (\n state.declaredPathParams.includes('component') &&\n component !== undefined\n ) {\n if (typeof component !== 'string' || !sets.components.includes(component)) {\n throw new InvalidComponentError({\n component,\n validComponents: sets.components,\n });\n }\n }\n\n if (!state.componentKeyDeclared) {\n return;\n }\n\n if (componentKey === undefined) {\n if (!state.componentKeyOptional) {\n throw new InvalidComponentError({\n component: componentKey,\n validComponents: sets.components,\n reason: 'A \"componentKey\" prop is required for this component',\n });\n }\n\n return;\n }\n\n if (\n typeof componentKey !== 'string' ||\n !sets.components.includes(componentKey)\n ) {\n throw new InvalidComponentError({\n component: componentKey,\n validComponents: sets.components,\n });\n }\n}\n\nfunction readClosureProps(\n selected: string[],\n closure: unknown,\n): Record<string, unknown> {\n if (selected.length === 0) {\n return {};\n }\n\n const [only] = selected;\n\n if (selected.length === 1 && only !== undefined) {\n return { [only]: closure };\n }\n\n if (\n typeof closure === 'object' &&\n closure !== null &&\n !Array.isArray(closure)\n ) {\n return { ...(closure as Record<string, unknown>) };\n }\n\n return {};\n}\n\nfunction createRenderedComponent(\n runRenderer: (props: Record<string, unknown>) => JSX.Element,\n paramTemplate: Record<string, unknown> | undefined,\n resolveDisplayName?: (props: Record<string, unknown>) => string,\n boundProps: Record<string, unknown> = {},\n) {\n function RenderedPathView(props: Record<string, unknown>): JSX.Element {\n const merged = { ...boundProps, ...props };\n\n if (resolveDisplayName) {\n RenderedPathView.displayName = resolveDisplayName(merged);\n }\n\n return runRenderer(merged);\n }\n\n RenderedPathView.displayName = DEFAULT_DISPLAY_NAME;\n\n function asHOF(options?: { args?: Record<string, true> }) {\n const selected = Object.entries(options?.args ?? {})\n .filter(([, enabled]) => enabled === true)\n .map(([key]) => key);\n\n function hof(closure: unknown) {\n const bound = createRenderedComponent(\n runRenderer,\n undefined,\n resolveDisplayName,\n { ...boundProps, ...readClosureProps(selected, closure) },\n );\n\n bound.displayName = RenderedPathView.displayName;\n\n return bound;\n }\n\n return Object.assign(hof, { _args: undefined as never });\n }\n\n return Object.assign(RenderedPathView, {\n _args: paramTemplate as never,\n asHOF,\n });\n}\n\nexport function createTargetPathBuilder<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config extends Record<string, unknown>,\n Paths extends ReadonlyArray<string>,\n>(config: Config, paths: Paths): TargetPathBuilder<Resources, Config, Paths> {\n const validationSets = collectValidationSets(config, paths);\n\n const makeBuilder = (state: BuilderState): Record<string, unknown> => {\n const builder: Record<string, unknown> = {};\n\n if (!state.usedComponentKeyParams) {\n builder.addComponentKeyParams = (options?: { optional?: boolean }) =>\n makeBuilder({\n ...state,\n usedComponentKeyParams: true,\n componentKeyDeclared: true,\n componentKeyOptional: options?.optional === true,\n });\n }\n\n if (!state.usedPathParams) {\n builder.addPathParams = (options?: { pick?: Record<string, true> }) => {\n const available = new Set<string>();\n\n for (const path of state.paths) {\n for (const name of extractPathVariables(path)) {\n available.add(name);\n }\n }\n\n const picked = Object.entries(options?.pick ?? {})\n .filter(([, enabled]) => enabled === true)\n .map(([key]) => key);\n const declared =\n picked.length === 0\n ? [...available]\n : picked.filter((name) => available.has(name));\n\n return makeBuilder({\n ...state,\n usedPathParams: true,\n declaredPathParams: [...state.declaredPathParams, ...declared],\n });\n };\n }\n\n if (!state.usedParams) {\n builder.addParams = (params: Record<string, unknown>) =>\n makeBuilder({\n ...state,\n usedParams: true,\n paramDefaults: { ...state.paramDefaults, ...params },\n });\n }\n\n builder.render = (\n renderer: (context: {\n params: Record<string, unknown>;\n getComponentForPath: unknown;\n }) => JSX.Element,\n ) => {\n const buildContext = (props: Record<string, unknown>) => {\n const params = { ...state.paramDefaults, ...props };\n\n validateParams(params, state, validationSets);\n\n return {\n params,\n getComponentForPath: (path: string) => {\n if (!state.paths.includes(path)) {\n throw new InvalidPathError({\n path,\n validPaths: state.paths,\n reason: 'This component was not built for that path',\n });\n }\n\n return createGetComponentForPath(config, path);\n },\n };\n };\n\n const runRenderer = (props: Record<string, unknown>) =>\n renderer(buildContext(props));\n\n const component = createRenderedComponent(runRenderer, {\n ...state.paramDefaults,\n });\n\n return Object.assign(component, {\n setDisplayName: (\n name:\n | string\n | ((context: {\n params: Record<string, unknown>;\n getComponentForPath: unknown;\n }) => string),\n ) => {\n if (typeof name === 'string') {\n const named = createRenderedComponent(runRenderer, {\n ...state.paramDefaults,\n });\n\n named.displayName = name;\n\n return named;\n }\n\n return createRenderedComponent(\n runRenderer,\n { ...state.paramDefaults },\n (props) => name(buildContext(props)),\n );\n },\n });\n };\n\n return builder;\n };\n\n return makeBuilder({\n paths,\n paramDefaults: {},\n declaredPathParams: [],\n componentKeyDeclared: false,\n componentKeyOptional: false,\n usedComponentKeyParams: false,\n usedPathParams: false,\n usedParams: false,\n }) as unknown as TargetPathBuilder<Resources, Config, Paths>;\n}\n\nexport function createForPaths<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config extends Record<string, unknown>,\n>(config: Config): ForPaths<Resources, Config, string> {\n const validPaths = generateResourceConfigPaths(config);\n\n return ((...paths: string[]) => {\n if (paths.length === 0) {\n throw new InvalidPathError({\n path: paths,\n validPaths,\n reason: 'At least one path is required',\n });\n }\n\n for (const path of paths) {\n if (!validPaths.includes(path)) {\n throw new InvalidPathError({ path, validPaths });\n }\n }\n\n return createTargetPathBuilder(config, paths);\n }) as unknown as ForPaths<Resources, Config, string>;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const require_component_keys = require('../component-keys.cjs');
|
|
2
|
+
const require_variables = require('../paths/variables.cjs');
|
|
3
|
+
const require_introspect = require('../paths/introspect.cjs');
|
|
4
|
+
const require_resolve = require('../paths/resolve.cjs');
|
|
5
|
+
|
|
6
|
+
//#region src/create-config/for-paths/get-component-for-path.ts
|
|
7
|
+
const componentGetters = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* Attaches a slot reader to a branch node without mutating the config. Wrappers are
|
|
10
|
+
* cached so repeated reads of the same node stay referentially stable.
|
|
11
|
+
*/
|
|
12
|
+
function attachComponentGetter(value) {
|
|
13
|
+
if (!require_component_keys.isConfigNode(value)) return value;
|
|
14
|
+
const cached = componentGetters.get(value);
|
|
15
|
+
if (cached !== void 0) return cached;
|
|
16
|
+
const wrapper = {
|
|
17
|
+
...value,
|
|
18
|
+
getComponent(componentKey) {
|
|
19
|
+
return componentKey in value ? value[componentKey] : null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
componentGetters.set(value, wrapper);
|
|
23
|
+
return wrapper;
|
|
24
|
+
}
|
|
25
|
+
function createGetComponentForPath(config, path) {
|
|
26
|
+
const variableNames = require_variables.extractPathVariables(path);
|
|
27
|
+
const resources = require_introspect.collectPathResources(config, path);
|
|
28
|
+
const subResourceKeys = require_introspect.collectPathSubResourceKeys(config, path);
|
|
29
|
+
const componentKeys = require_introspect.collectPathComponentKeys(config, path);
|
|
30
|
+
function getComponentForPath(variables) {
|
|
31
|
+
return attachComponentGetter(require_resolve.resolveParameterizedPath(config, path, variables ?? {}));
|
|
32
|
+
}
|
|
33
|
+
const isResource = (key) => typeof key === "string" && resources.includes(key);
|
|
34
|
+
const isSubResource = (key) => typeof key === "string" && subResourceKeys.includes(key);
|
|
35
|
+
const isComponent = (key) => typeof key === "string" && componentKeys.includes(key);
|
|
36
|
+
const guardForVariable = (name) => {
|
|
37
|
+
if (name === "resource") return isResource;
|
|
38
|
+
if (name === "subResource") return isSubResource;
|
|
39
|
+
return isComponent;
|
|
40
|
+
};
|
|
41
|
+
const variableGuards = Object.fromEntries(variableNames.map((name) => [name, { isVariable: guardForVariable(name) }]));
|
|
42
|
+
return Object.assign(getComponentForPath, {
|
|
43
|
+
path,
|
|
44
|
+
variables: variableNames,
|
|
45
|
+
resources,
|
|
46
|
+
isResource,
|
|
47
|
+
isSubResource,
|
|
48
|
+
isComponent,
|
|
49
|
+
isValidSubResource: (resource, subResource) => typeof subResource === "string" && require_introspect.collectResourceSubResourceKeys(config, resource).includes(subResource)
|
|
50
|
+
}, variableGuards);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
exports.createGetComponentForPath = createGetComponentForPath;
|
|
55
|
+
//# sourceMappingURL=get-component-for-path.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-component-for-path.cjs","names":["isConfigNode","extractPathVariables","collectPathResources","collectPathSubResourceKeys","collectPathComponentKeys","resolveParameterizedPath","collectResourceSubResourceKeys"],"sources":["../../../src/create-config/for-paths/get-component-for-path.ts"],"sourcesContent":["import type { JSX } from 'react';\nimport type { ResourceDefinition } from '../../resource';\nimport { isConfigNode } from '../component-keys';\nimport {\n collectPathComponentKeys,\n collectPathResources,\n collectPathSubResourceKeys,\n collectResourceSubResourceKeys,\n extractPathVariables,\n resolveParameterizedPath,\n} from '../paths';\nimport type {\n ExtractPathVariables,\n PathComponentKeys,\n PathHasVariables,\n PathResourceCandidates,\n PathSubResourceKeys,\n PathVariables,\n PathVariableValues,\n ResolveParameterizedPathValue,\n ResourceSubResourceKeys,\n} from '../paths';\n\ntype KeysOfUnion<Value> = Value extends unknown ? keyof Value : never;\n\ntype ValueOfKeyInUnion<Value, Key> = Value extends unknown\n ? Key extends keyof Value\n ? Value[Key]\n : never\n : never;\n\nexport interface PathValueComponentGetter<Value> {\n /**\n * Reads one component slot from the resolved node. Configured slots auto-complete\n * while any string stays allowed; unknown slots resolve to `null`.\n */\n getComponent<\n Key extends KeysOfUnion<Value> | (string & {}),\n Component = ValueOfKeyInUnion<Value, Key>,\n >(\n componentKey: Key,\n ): [Component] extends [never] ? null : Component;\n}\n\n/**\n * Value a path resolves to. Branch nodes gain a slot reader; component slots resolve to\n * the element itself. The slot reader is attached once to the whole node union so it\n * stays callable when several resources remain possible.\n */\nexport type ResolvedPathValue<Value> = [Extract<Value, JSX.Element>] extends [\n never,\n]\n ? [Extract<Value, object>] extends [never]\n ? Value\n : Extract<Value, object> & PathValueComponentGetter<Extract<Value, object>>\n : [Exclude<Value, JSX.Element>] extends [never]\n ? Extract<Value, JSX.Element>\n : Extract<Value, JSX.Element> | ResolvedPathValue<Exclude<Value, JSX.Element>>;\n\ntype PathVariableDomain<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n Name,\n> = Name extends 'resource'\n ? PathResourceCandidates<Resources, Config, Path>\n : Name extends 'subResource'\n ? PathSubResourceKeys<Resources, Config, Path>\n : Name extends 'component'\n ? PathComponentKeys<Resources, Config, Path>\n : never;\n\n/** One guard per variable the path leaves open, e.g. `getTemplate.resource.isVariable(x)`. */\nexport type PathVariableGuards<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> = {\n [Name in ExtractPathVariables<Path>]: {\n /** Narrows a value to what this variable accepts for this path. */\n isVariable: (\n key: unknown,\n ) => key is PathVariableDomain<Resources, Config, Path, Name>;\n };\n};\n\nexport interface GetComponentForPathApi<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> {\n /** The path this getter reads. */\n readonly path: Path;\n /** Variable names this path leaves open, in order of appearance. */\n readonly variables: ReadonlyArray<ExtractPathVariables<Path>>;\n /** Resources this path can target. */\n readonly resources: ReadonlyArray<\n PathResourceCandidates<Resources, Config, Path>\n >;\n isResource: (\n key: unknown,\n ) => key is PathResourceCandidates<Resources, Config, Path>;\n isSubResource: (\n key: unknown,\n ) => key is PathSubResourceKeys<Resources, Config, Path>;\n isComponent: (\n key: unknown,\n ) => key is PathComponentKeys<Resources, Config, Path>;\n /** Narrows a sub-resource against one specific resource rather than the whole path. */\n isValidSubResource: <\n Resource extends PathResourceCandidates<Resources, Config, Path>,\n >(\n resource: Resource,\n subResource: unknown,\n ) => subResource is ResourceSubResourceKeys<Config, Resource>;\n}\n\ntype GetComponentForPathCallable<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> =\n PathHasVariables<Path> extends true\n ? (\n variables: PathVariables<Resources, Config, Path>,\n ) => ResolvedPathValue<\n ResolveParameterizedPathValue<\n Resources,\n Config,\n Path,\n PathVariables<Resources, Config, Path>\n >\n >\n : () => ResolvedPathValue<\n ResolveParameterizedPathValue<Resources, Config, Path, {}>\n >;\n\n/**\n * Reusable getter bound to one path. Call it with the variables the path left open.\n *\n * @example\n * ```ts\n * const getDetail = createGetComponent('$resource.detail.$component');\n *\n * getDetail({ resource: 'templates', component: 'errorComponent' });\n * ```\n */\nexport type GetComponentForPath<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> = GetComponentForPathCallable<Resources, Config, Path> &\n GetComponentForPathApi<Resources, Config, Path> &\n PathVariableGuards<Resources, Config, Path>;\n\nexport type CreateGetComponentForPath<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n AllowedPath extends string,\n> = <const Path extends AllowedPath>(\n path: Path,\n) => GetComponentForPath<Resources, Config, Path>;\n\nconst componentGetters = new WeakMap<object, object>();\n\n/**\n * Attaches a slot reader to a branch node without mutating the config. Wrappers are\n * cached so repeated reads of the same node stay referentially stable.\n */\nfunction attachComponentGetter(value: unknown): unknown {\n if (!isConfigNode(value)) {\n return value;\n }\n\n const cached = componentGetters.get(value);\n\n if (cached !== undefined) {\n return cached;\n }\n\n const wrapper = {\n ...value,\n getComponent(componentKey: string) {\n return componentKey in value ? value[componentKey] : null;\n },\n };\n\n componentGetters.set(value, wrapper);\n\n return wrapper;\n}\n\nexport function createGetComponentForPath<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config extends Record<string, unknown>,\n Path extends string,\n>(config: Config, path: Path): GetComponentForPath<Resources, Config, Path> {\n const variableNames = extractPathVariables(path);\n const resources = collectPathResources(config, path);\n const subResourceKeys = collectPathSubResourceKeys(config, path);\n const componentKeys = collectPathComponentKeys(config, path);\n\n function getComponentForPath(variables?: PathVariableValues) {\n return attachComponentGetter(\n resolveParameterizedPath(config, path, variables ?? {}),\n );\n }\n\n const isResource = (key: unknown) =>\n typeof key === 'string' && resources.includes(key);\n const isSubResource = (key: unknown) =>\n typeof key === 'string' && subResourceKeys.includes(key);\n const isComponent = (key: unknown) =>\n typeof key === 'string' && componentKeys.includes(key);\n\n const guardForVariable = (name: string) => {\n if (name === 'resource') {\n return isResource;\n }\n\n if (name === 'subResource') {\n return isSubResource;\n }\n\n return isComponent;\n };\n\n const variableGuards = Object.fromEntries(\n variableNames.map((name) => [name, { isVariable: guardForVariable(name) }]),\n );\n\n return Object.assign(\n getComponentForPath,\n {\n path,\n variables: variableNames,\n resources,\n isResource,\n isSubResource,\n isComponent,\n isValidSubResource: (resource: string, subResource: unknown) =>\n typeof subResource === 'string' &&\n collectResourceSubResourceKeys(config, resource).includes(subResource),\n },\n variableGuards,\n ) as unknown as GetComponentForPath<Resources, Config, Path>;\n}\n"],"mappings":";;;;;;AAmKA,MAAM,mCAAmB,IAAI,QAAwB;;;;;AAMrD,SAAS,sBAAsB,OAAyB;CACtD,IAAI,CAACA,oCAAa,KAAK,GACrB,OAAO;CAGT,MAAM,SAAS,iBAAiB,IAAI,KAAK;CAEzC,IAAI,WAAW,QACb,OAAO;CAGT,MAAM,UAAU;EACd,GAAG;EACH,aAAa,cAAsB;GACjC,OAAO,gBAAgB,QAAQ,MAAM,gBAAgB;EACvD;CACF;CAEA,iBAAiB,IAAI,OAAO,OAAO;CAEnC,OAAO;AACT;AAEA,SAAgB,0BAId,QAAgB,MAA0D;CAC1E,MAAM,gBAAgBC,uCAAqB,IAAI;CAC/C,MAAM,YAAYC,wCAAqB,QAAQ,IAAI;CACnD,MAAM,kBAAkBC,8CAA2B,QAAQ,IAAI;CAC/D,MAAM,gBAAgBC,4CAAyB,QAAQ,IAAI;CAE3D,SAAS,oBAAoB,WAAgC;EAC3D,OAAO,sBACLC,yCAAyB,QAAQ,MAAM,aAAa,CAAC,CAAC,CACxD;CACF;CAEA,MAAM,cAAc,QAClB,OAAO,QAAQ,YAAY,UAAU,SAAS,GAAG;CACnD,MAAM,iBAAiB,QACrB,OAAO,QAAQ,YAAY,gBAAgB,SAAS,GAAG;CACzD,MAAM,eAAe,QACnB,OAAO,QAAQ,YAAY,cAAc,SAAS,GAAG;CAEvD,MAAM,oBAAoB,SAAiB;EACzC,IAAI,SAAS,YACX,OAAO;EAGT,IAAI,SAAS,eACX,OAAO;EAGT,OAAO;CACT;CAEA,MAAM,iBAAiB,OAAO,YAC5B,cAAc,KAAK,SAAS,CAAC,MAAM,EAAE,YAAY,iBAAiB,IAAI,EAAE,CAAC,CAAC,CAC5E;CAEA,OAAO,OAAO,OACZ,qBACA;EACE;EACA,WAAW;EACX;EACA;EACA;EACA;EACA,qBAAqB,UAAkB,gBACrC,OAAO,gBAAgB,YACvBC,kDAA+B,QAAQ,QAAQ,EAAE,SAAS,WAAW;CACzE,GACA,cACF;AACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ResourceDefinition } from "../../resource.cjs";
|
|
2
|
+
import { ExtractPathVariables, PathHasVariables } from "../paths/variables.cjs";
|
|
3
|
+
import { PathComponentKeys, PathResourceCandidates, PathSubResourceKeys, PathVariables, ResolveParameterizedPathValue, ResourceSubResourceKeys } from "../paths/types.cjs";
|
|
4
|
+
import { JSX } from "react";
|
|
5
|
+
|
|
6
|
+
//#region src/create-config/for-paths/get-component-for-path.d.ts
|
|
7
|
+
type KeysOfUnion<Value> = Value extends unknown ? keyof Value : never;
|
|
8
|
+
type ValueOfKeyInUnion<Value, Key> = Value extends unknown ? Key extends keyof Value ? Value[Key] : never : never;
|
|
9
|
+
interface PathValueComponentGetter<Value> {
|
|
10
|
+
/**
|
|
11
|
+
* Reads one component slot from the resolved node. Configured slots auto-complete
|
|
12
|
+
* while any string stays allowed; unknown slots resolve to `null`.
|
|
13
|
+
*/
|
|
14
|
+
getComponent<Key extends KeysOfUnion<Value> | (string & {}), Component = ValueOfKeyInUnion<Value, Key>>(componentKey: Key): [Component] extends [never] ? null : Component;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Value a path resolves to. Branch nodes gain a slot reader; component slots resolve to
|
|
18
|
+
* the element itself. The slot reader is attached once to the whole node union so it
|
|
19
|
+
* stays callable when several resources remain possible.
|
|
20
|
+
*/
|
|
21
|
+
type ResolvedPathValue<Value> = [Extract<Value, JSX.Element>] extends [never] ? [Extract<Value, object>] extends [never] ? Value : Extract<Value, object> & PathValueComponentGetter<Extract<Value, object>> : [Exclude<Value, JSX.Element>] extends [never] ? Extract<Value, JSX.Element> : Extract<Value, JSX.Element> | ResolvedPathValue<Exclude<Value, JSX.Element>>;
|
|
22
|
+
type PathVariableDomain<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string, Name> = Name extends 'resource' ? PathResourceCandidates<Resources, Config, Path> : Name extends 'subResource' ? PathSubResourceKeys<Resources, Config, Path> : Name extends 'component' ? PathComponentKeys<Resources, Config, Path> : never;
|
|
23
|
+
/** One guard per variable the path leaves open, e.g. `getTemplate.resource.isVariable(x)`. */
|
|
24
|
+
type PathVariableGuards<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> = { [Name in ExtractPathVariables<Path>]: {
|
|
25
|
+
/** Narrows a value to what this variable accepts for this path. */isVariable: (key: unknown) => key is PathVariableDomain<Resources, Config, Path, Name>;
|
|
26
|
+
} };
|
|
27
|
+
interface GetComponentForPathApi<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> {
|
|
28
|
+
/** The path this getter reads. */
|
|
29
|
+
readonly path: Path;
|
|
30
|
+
/** Variable names this path leaves open, in order of appearance. */
|
|
31
|
+
readonly variables: ReadonlyArray<ExtractPathVariables<Path>>;
|
|
32
|
+
/** Resources this path can target. */
|
|
33
|
+
readonly resources: ReadonlyArray<PathResourceCandidates<Resources, Config, Path>>;
|
|
34
|
+
isResource: (key: unknown) => key is PathResourceCandidates<Resources, Config, Path>;
|
|
35
|
+
isSubResource: (key: unknown) => key is PathSubResourceKeys<Resources, Config, Path>;
|
|
36
|
+
isComponent: (key: unknown) => key is PathComponentKeys<Resources, Config, Path>;
|
|
37
|
+
/** Narrows a sub-resource against one specific resource rather than the whole path. */
|
|
38
|
+
isValidSubResource: <Resource extends PathResourceCandidates<Resources, Config, Path>>(resource: Resource, subResource: unknown) => subResource is ResourceSubResourceKeys<Config, Resource>;
|
|
39
|
+
}
|
|
40
|
+
type GetComponentForPathCallable<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> = PathHasVariables<Path> extends true ? (variables: PathVariables<Resources, Config, Path>) => ResolvedPathValue<ResolveParameterizedPathValue<Resources, Config, Path, PathVariables<Resources, Config, Path>>> : () => ResolvedPathValue<ResolveParameterizedPathValue<Resources, Config, Path, {}>>;
|
|
41
|
+
/**
|
|
42
|
+
* Reusable getter bound to one path. Call it with the variables the path left open.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const getDetail = createGetComponent('$resource.detail.$component');
|
|
47
|
+
*
|
|
48
|
+
* getDetail({ resource: 'templates', component: 'errorComponent' });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
type GetComponentForPath<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> = GetComponentForPathCallable<Resources, Config, Path> & GetComponentForPathApi<Resources, Config, Path> & PathVariableGuards<Resources, Config, Path>;
|
|
52
|
+
type CreateGetComponentForPath<Resources extends ReadonlyArray<ResourceDefinition>, Config, AllowedPath extends string> = <const Path extends AllowedPath>(path: Path) => GetComponentForPath<Resources, Config, Path>;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { CreateGetComponentForPath, GetComponentForPath, GetComponentForPathApi, PathValueComponentGetter, PathVariableGuards, ResolvedPathValue };
|
|
55
|
+
//# sourceMappingURL=get-component-for-path.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-component-for-path.d.cts","names":[],"sources":["../../../src/create-config/for-paths/get-component-for-path.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ResourceDefinition } from "../../resource.mjs";
|
|
2
|
+
import { ExtractPathVariables, PathHasVariables } from "../paths/variables.mjs";
|
|
3
|
+
import { PathComponentKeys, PathResourceCandidates, PathSubResourceKeys, PathVariables, ResolveParameterizedPathValue, ResourceSubResourceKeys } from "../paths/types.mjs";
|
|
4
|
+
import { JSX } from "react";
|
|
5
|
+
|
|
6
|
+
//#region src/create-config/for-paths/get-component-for-path.d.ts
|
|
7
|
+
type KeysOfUnion<Value> = Value extends unknown ? keyof Value : never;
|
|
8
|
+
type ValueOfKeyInUnion<Value, Key> = Value extends unknown ? Key extends keyof Value ? Value[Key] : never : never;
|
|
9
|
+
interface PathValueComponentGetter<Value> {
|
|
10
|
+
/**
|
|
11
|
+
* Reads one component slot from the resolved node. Configured slots auto-complete
|
|
12
|
+
* while any string stays allowed; unknown slots resolve to `null`.
|
|
13
|
+
*/
|
|
14
|
+
getComponent<Key extends KeysOfUnion<Value> | (string & {}), Component = ValueOfKeyInUnion<Value, Key>>(componentKey: Key): [Component] extends [never] ? null : Component;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Value a path resolves to. Branch nodes gain a slot reader; component slots resolve to
|
|
18
|
+
* the element itself. The slot reader is attached once to the whole node union so it
|
|
19
|
+
* stays callable when several resources remain possible.
|
|
20
|
+
*/
|
|
21
|
+
type ResolvedPathValue<Value> = [Extract<Value, JSX.Element>] extends [never] ? [Extract<Value, object>] extends [never] ? Value : Extract<Value, object> & PathValueComponentGetter<Extract<Value, object>> : [Exclude<Value, JSX.Element>] extends [never] ? Extract<Value, JSX.Element> : Extract<Value, JSX.Element> | ResolvedPathValue<Exclude<Value, JSX.Element>>;
|
|
22
|
+
type PathVariableDomain<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string, Name> = Name extends 'resource' ? PathResourceCandidates<Resources, Config, Path> : Name extends 'subResource' ? PathSubResourceKeys<Resources, Config, Path> : Name extends 'component' ? PathComponentKeys<Resources, Config, Path> : never;
|
|
23
|
+
/** One guard per variable the path leaves open, e.g. `getTemplate.resource.isVariable(x)`. */
|
|
24
|
+
type PathVariableGuards<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> = { [Name in ExtractPathVariables<Path>]: {
|
|
25
|
+
/** Narrows a value to what this variable accepts for this path. */isVariable: (key: unknown) => key is PathVariableDomain<Resources, Config, Path, Name>;
|
|
26
|
+
} };
|
|
27
|
+
interface GetComponentForPathApi<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> {
|
|
28
|
+
/** The path this getter reads. */
|
|
29
|
+
readonly path: Path;
|
|
30
|
+
/** Variable names this path leaves open, in order of appearance. */
|
|
31
|
+
readonly variables: ReadonlyArray<ExtractPathVariables<Path>>;
|
|
32
|
+
/** Resources this path can target. */
|
|
33
|
+
readonly resources: ReadonlyArray<PathResourceCandidates<Resources, Config, Path>>;
|
|
34
|
+
isResource: (key: unknown) => key is PathResourceCandidates<Resources, Config, Path>;
|
|
35
|
+
isSubResource: (key: unknown) => key is PathSubResourceKeys<Resources, Config, Path>;
|
|
36
|
+
isComponent: (key: unknown) => key is PathComponentKeys<Resources, Config, Path>;
|
|
37
|
+
/** Narrows a sub-resource against one specific resource rather than the whole path. */
|
|
38
|
+
isValidSubResource: <Resource extends PathResourceCandidates<Resources, Config, Path>>(resource: Resource, subResource: unknown) => subResource is ResourceSubResourceKeys<Config, Resource>;
|
|
39
|
+
}
|
|
40
|
+
type GetComponentForPathCallable<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> = PathHasVariables<Path> extends true ? (variables: PathVariables<Resources, Config, Path>) => ResolvedPathValue<ResolveParameterizedPathValue<Resources, Config, Path, PathVariables<Resources, Config, Path>>> : () => ResolvedPathValue<ResolveParameterizedPathValue<Resources, Config, Path, {}>>;
|
|
41
|
+
/**
|
|
42
|
+
* Reusable getter bound to one path. Call it with the variables the path left open.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const getDetail = createGetComponent('$resource.detail.$component');
|
|
47
|
+
*
|
|
48
|
+
* getDetail({ resource: 'templates', component: 'errorComponent' });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
type GetComponentForPath<Resources extends ReadonlyArray<ResourceDefinition>, Config, Path extends string> = GetComponentForPathCallable<Resources, Config, Path> & GetComponentForPathApi<Resources, Config, Path> & PathVariableGuards<Resources, Config, Path>;
|
|
52
|
+
type CreateGetComponentForPath<Resources extends ReadonlyArray<ResourceDefinition>, Config, AllowedPath extends string> = <const Path extends AllowedPath>(path: Path) => GetComponentForPath<Resources, Config, Path>;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { CreateGetComponentForPath, GetComponentForPath, GetComponentForPathApi, PathValueComponentGetter, PathVariableGuards, ResolvedPathValue };
|
|
55
|
+
//# sourceMappingURL=get-component-for-path.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-component-for-path.d.mts","names":[],"sources":["../../../src/create-config/for-paths/get-component-for-path.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isConfigNode } from "../component-keys.mjs";
|
|
2
|
+
import { extractPathVariables } from "../paths/variables.mjs";
|
|
3
|
+
import { collectPathComponentKeys, collectPathResources, collectPathSubResourceKeys, collectResourceSubResourceKeys } from "../paths/introspect.mjs";
|
|
4
|
+
import { resolveParameterizedPath } from "../paths/resolve.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/create-config/for-paths/get-component-for-path.ts
|
|
7
|
+
const componentGetters = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* Attaches a slot reader to a branch node without mutating the config. Wrappers are
|
|
10
|
+
* cached so repeated reads of the same node stay referentially stable.
|
|
11
|
+
*/
|
|
12
|
+
function attachComponentGetter(value) {
|
|
13
|
+
if (!isConfigNode(value)) return value;
|
|
14
|
+
const cached = componentGetters.get(value);
|
|
15
|
+
if (cached !== void 0) return cached;
|
|
16
|
+
const wrapper = {
|
|
17
|
+
...value,
|
|
18
|
+
getComponent(componentKey) {
|
|
19
|
+
return componentKey in value ? value[componentKey] : null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
componentGetters.set(value, wrapper);
|
|
23
|
+
return wrapper;
|
|
24
|
+
}
|
|
25
|
+
function createGetComponentForPath(config, path) {
|
|
26
|
+
const variableNames = extractPathVariables(path);
|
|
27
|
+
const resources = collectPathResources(config, path);
|
|
28
|
+
const subResourceKeys = collectPathSubResourceKeys(config, path);
|
|
29
|
+
const componentKeys = collectPathComponentKeys(config, path);
|
|
30
|
+
function getComponentForPath(variables) {
|
|
31
|
+
return attachComponentGetter(resolveParameterizedPath(config, path, variables ?? {}));
|
|
32
|
+
}
|
|
33
|
+
const isResource = (key) => typeof key === "string" && resources.includes(key);
|
|
34
|
+
const isSubResource = (key) => typeof key === "string" && subResourceKeys.includes(key);
|
|
35
|
+
const isComponent = (key) => typeof key === "string" && componentKeys.includes(key);
|
|
36
|
+
const guardForVariable = (name) => {
|
|
37
|
+
if (name === "resource") return isResource;
|
|
38
|
+
if (name === "subResource") return isSubResource;
|
|
39
|
+
return isComponent;
|
|
40
|
+
};
|
|
41
|
+
const variableGuards = Object.fromEntries(variableNames.map((name) => [name, { isVariable: guardForVariable(name) }]));
|
|
42
|
+
return Object.assign(getComponentForPath, {
|
|
43
|
+
path,
|
|
44
|
+
variables: variableNames,
|
|
45
|
+
resources,
|
|
46
|
+
isResource,
|
|
47
|
+
isSubResource,
|
|
48
|
+
isComponent,
|
|
49
|
+
isValidSubResource: (resource, subResource) => typeof subResource === "string" && collectResourceSubResourceKeys(config, resource).includes(subResource)
|
|
50
|
+
}, variableGuards);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { createGetComponentForPath };
|
|
55
|
+
//# sourceMappingURL=get-component-for-path.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-component-for-path.mjs","names":[],"sources":["../../../src/create-config/for-paths/get-component-for-path.ts"],"sourcesContent":["import type { JSX } from 'react';\nimport type { ResourceDefinition } from '../../resource';\nimport { isConfigNode } from '../component-keys';\nimport {\n collectPathComponentKeys,\n collectPathResources,\n collectPathSubResourceKeys,\n collectResourceSubResourceKeys,\n extractPathVariables,\n resolveParameterizedPath,\n} from '../paths';\nimport type {\n ExtractPathVariables,\n PathComponentKeys,\n PathHasVariables,\n PathResourceCandidates,\n PathSubResourceKeys,\n PathVariables,\n PathVariableValues,\n ResolveParameterizedPathValue,\n ResourceSubResourceKeys,\n} from '../paths';\n\ntype KeysOfUnion<Value> = Value extends unknown ? keyof Value : never;\n\ntype ValueOfKeyInUnion<Value, Key> = Value extends unknown\n ? Key extends keyof Value\n ? Value[Key]\n : never\n : never;\n\nexport interface PathValueComponentGetter<Value> {\n /**\n * Reads one component slot from the resolved node. Configured slots auto-complete\n * while any string stays allowed; unknown slots resolve to `null`.\n */\n getComponent<\n Key extends KeysOfUnion<Value> | (string & {}),\n Component = ValueOfKeyInUnion<Value, Key>,\n >(\n componentKey: Key,\n ): [Component] extends [never] ? null : Component;\n}\n\n/**\n * Value a path resolves to. Branch nodes gain a slot reader; component slots resolve to\n * the element itself. The slot reader is attached once to the whole node union so it\n * stays callable when several resources remain possible.\n */\nexport type ResolvedPathValue<Value> = [Extract<Value, JSX.Element>] extends [\n never,\n]\n ? [Extract<Value, object>] extends [never]\n ? Value\n : Extract<Value, object> & PathValueComponentGetter<Extract<Value, object>>\n : [Exclude<Value, JSX.Element>] extends [never]\n ? Extract<Value, JSX.Element>\n : Extract<Value, JSX.Element> | ResolvedPathValue<Exclude<Value, JSX.Element>>;\n\ntype PathVariableDomain<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n Name,\n> = Name extends 'resource'\n ? PathResourceCandidates<Resources, Config, Path>\n : Name extends 'subResource'\n ? PathSubResourceKeys<Resources, Config, Path>\n : Name extends 'component'\n ? PathComponentKeys<Resources, Config, Path>\n : never;\n\n/** One guard per variable the path leaves open, e.g. `getTemplate.resource.isVariable(x)`. */\nexport type PathVariableGuards<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> = {\n [Name in ExtractPathVariables<Path>]: {\n /** Narrows a value to what this variable accepts for this path. */\n isVariable: (\n key: unknown,\n ) => key is PathVariableDomain<Resources, Config, Path, Name>;\n };\n};\n\nexport interface GetComponentForPathApi<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> {\n /** The path this getter reads. */\n readonly path: Path;\n /** Variable names this path leaves open, in order of appearance. */\n readonly variables: ReadonlyArray<ExtractPathVariables<Path>>;\n /** Resources this path can target. */\n readonly resources: ReadonlyArray<\n PathResourceCandidates<Resources, Config, Path>\n >;\n isResource: (\n key: unknown,\n ) => key is PathResourceCandidates<Resources, Config, Path>;\n isSubResource: (\n key: unknown,\n ) => key is PathSubResourceKeys<Resources, Config, Path>;\n isComponent: (\n key: unknown,\n ) => key is PathComponentKeys<Resources, Config, Path>;\n /** Narrows a sub-resource against one specific resource rather than the whole path. */\n isValidSubResource: <\n Resource extends PathResourceCandidates<Resources, Config, Path>,\n >(\n resource: Resource,\n subResource: unknown,\n ) => subResource is ResourceSubResourceKeys<Config, Resource>;\n}\n\ntype GetComponentForPathCallable<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> =\n PathHasVariables<Path> extends true\n ? (\n variables: PathVariables<Resources, Config, Path>,\n ) => ResolvedPathValue<\n ResolveParameterizedPathValue<\n Resources,\n Config,\n Path,\n PathVariables<Resources, Config, Path>\n >\n >\n : () => ResolvedPathValue<\n ResolveParameterizedPathValue<Resources, Config, Path, {}>\n >;\n\n/**\n * Reusable getter bound to one path. Call it with the variables the path left open.\n *\n * @example\n * ```ts\n * const getDetail = createGetComponent('$resource.detail.$component');\n *\n * getDetail({ resource: 'templates', component: 'errorComponent' });\n * ```\n */\nexport type GetComponentForPath<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n Path extends string,\n> = GetComponentForPathCallable<Resources, Config, Path> &\n GetComponentForPathApi<Resources, Config, Path> &\n PathVariableGuards<Resources, Config, Path>;\n\nexport type CreateGetComponentForPath<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config,\n AllowedPath extends string,\n> = <const Path extends AllowedPath>(\n path: Path,\n) => GetComponentForPath<Resources, Config, Path>;\n\nconst componentGetters = new WeakMap<object, object>();\n\n/**\n * Attaches a slot reader to a branch node without mutating the config. Wrappers are\n * cached so repeated reads of the same node stay referentially stable.\n */\nfunction attachComponentGetter(value: unknown): unknown {\n if (!isConfigNode(value)) {\n return value;\n }\n\n const cached = componentGetters.get(value);\n\n if (cached !== undefined) {\n return cached;\n }\n\n const wrapper = {\n ...value,\n getComponent(componentKey: string) {\n return componentKey in value ? value[componentKey] : null;\n },\n };\n\n componentGetters.set(value, wrapper);\n\n return wrapper;\n}\n\nexport function createGetComponentForPath<\n Resources extends ReadonlyArray<ResourceDefinition>,\n Config extends Record<string, unknown>,\n Path extends string,\n>(config: Config, path: Path): GetComponentForPath<Resources, Config, Path> {\n const variableNames = extractPathVariables(path);\n const resources = collectPathResources(config, path);\n const subResourceKeys = collectPathSubResourceKeys(config, path);\n const componentKeys = collectPathComponentKeys(config, path);\n\n function getComponentForPath(variables?: PathVariableValues) {\n return attachComponentGetter(\n resolveParameterizedPath(config, path, variables ?? {}),\n );\n }\n\n const isResource = (key: unknown) =>\n typeof key === 'string' && resources.includes(key);\n const isSubResource = (key: unknown) =>\n typeof key === 'string' && subResourceKeys.includes(key);\n const isComponent = (key: unknown) =>\n typeof key === 'string' && componentKeys.includes(key);\n\n const guardForVariable = (name: string) => {\n if (name === 'resource') {\n return isResource;\n }\n\n if (name === 'subResource') {\n return isSubResource;\n }\n\n return isComponent;\n };\n\n const variableGuards = Object.fromEntries(\n variableNames.map((name) => [name, { isVariable: guardForVariable(name) }]),\n );\n\n return Object.assign(\n getComponentForPath,\n {\n path,\n variables: variableNames,\n resources,\n isResource,\n isSubResource,\n isComponent,\n isValidSubResource: (resource: string, subResource: unknown) =>\n typeof subResource === 'string' &&\n collectResourceSubResourceKeys(config, resource).includes(subResource),\n },\n variableGuards,\n ) as unknown as GetComponentForPath<Resources, Config, Path>;\n}\n"],"mappings":";;;;;;AAmKA,MAAM,mCAAmB,IAAI,QAAwB;;;;;AAMrD,SAAS,sBAAsB,OAAyB;CACtD,IAAI,CAAC,aAAa,KAAK,GACrB,OAAO;CAGT,MAAM,SAAS,iBAAiB,IAAI,KAAK;CAEzC,IAAI,WAAW,QACb,OAAO;CAGT,MAAM,UAAU;EACd,GAAG;EACH,aAAa,cAAsB;GACjC,OAAO,gBAAgB,QAAQ,MAAM,gBAAgB;EACvD;CACF;CAEA,iBAAiB,IAAI,OAAO,OAAO;CAEnC,OAAO;AACT;AAEA,SAAgB,0BAId,QAAgB,MAA0D;CAC1E,MAAM,gBAAgB,qBAAqB,IAAI;CAC/C,MAAM,YAAY,qBAAqB,QAAQ,IAAI;CACnD,MAAM,kBAAkB,2BAA2B,QAAQ,IAAI;CAC/D,MAAM,gBAAgB,yBAAyB,QAAQ,IAAI;CAE3D,SAAS,oBAAoB,WAAgC;EAC3D,OAAO,sBACL,yBAAyB,QAAQ,MAAM,aAAa,CAAC,CAAC,CACxD;CACF;CAEA,MAAM,cAAc,QAClB,OAAO,QAAQ,YAAY,UAAU,SAAS,GAAG;CACnD,MAAM,iBAAiB,QACrB,OAAO,QAAQ,YAAY,gBAAgB,SAAS,GAAG;CACzD,MAAM,eAAe,QACnB,OAAO,QAAQ,YAAY,cAAc,SAAS,GAAG;CAEvD,MAAM,oBAAoB,SAAiB;EACzC,IAAI,SAAS,YACX,OAAO;EAGT,IAAI,SAAS,eACX,OAAO;EAGT,OAAO;CACT;CAEA,MAAM,iBAAiB,OAAO,YAC5B,cAAc,KAAK,SAAS,CAAC,MAAM,EAAE,YAAY,iBAAiB,IAAI,EAAE,CAAC,CAAC,CAC5E;CAEA,OAAO,OAAO,OACZ,qBACA;EACE;EACA,WAAW;EACX;EACA;EACA;EACA;EACA,qBAAqB,UAAkB,gBACrC,OAAO,gBAAgB,YACvB,+BAA+B,QAAQ,QAAQ,EAAE,SAAS,WAAW;CACzE,GACA,cACF;AACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { CreateGetComponentForPath, GetComponentForPath, GetComponentForPathApi, PathValueComponentGetter, PathVariableGuards, ResolvedPathValue } from "./get-component-for-path.cjs";
|
|
2
|
+
import { ForPaths, ForPathsRenderContext, RenderedPathComponent, RenderedPathComponentHOF, TargetPathBuilder } from "./builder.cjs";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { CreateGetComponentForPath, GetComponentForPath, GetComponentForPathApi, PathValueComponentGetter, PathVariableGuards, ResolvedPathValue } from "./get-component-for-path.mjs";
|
|
2
|
+
import { ForPaths, ForPathsRenderContext, RenderedPathComponent, RenderedPathComponentHOF, TargetPathBuilder } from "./builder.mjs";
|