@omriashke/dynamico-core 0.1.0 → 0.1.2
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/bookPreview.d.ts +63 -0
- package/dist/bookPreview.d.ts.map +1 -0
- package/dist/bookPreview.js +147 -0
- package/dist/bookPreview.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -1
- package/dist/loader.js.map +1 -1
- package/dist/propsSchema.d.ts.map +1 -1
- package/dist/propsSchema.js +3 -0
- package/dist/propsSchema.js.map +1 -1
- package/dist/react/createRuntime.d.ts +23 -1
- package/dist/react/createRuntime.d.ts.map +1 -1
- package/dist/react/createRuntime.js +29 -11
- package/dist/react/createRuntime.js.map +1 -1
- package/dist/registry.d.ts +6 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +8 -0
- package/dist/registry.js.map +1 -1
- package/dist/sources/remote.d.ts +12 -0
- package/dist/sources/remote.d.ts.map +1 -1
- package/dist/sources/remote.js +34 -2
- package/dist/sources/remote.js.map +1 -1
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -18
- package/src/bookPreview.ts +235 -0
- package/src/index.ts +13 -0
- package/src/loader.ts +4 -1
- package/src/propsSchema.ts +2 -0
- package/src/react/createRuntime.tsx +59 -14
- package/src/registry.ts +9 -0
- package/src/sources/remote.ts +52 -2
- package/src/types.ts +23 -2
- package/LICENSE +0 -184
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/sources/remote.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/sources/remote.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export type Version = string;
|
|
2
2
|
export type Scope = Record<string, unknown>;
|
|
3
3
|
export interface PropsSchemaField {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Runtime type. `function` validates `typeof v === 'function'` — useful for
|
|
6
|
+
* dynamic screens/components that take callbacks. Function values render
|
|
7
|
+
* fine inside dynamic components; the schema just lets you require them.
|
|
8
|
+
*/
|
|
9
|
+
type: "string" | "number" | "boolean" | "object" | "array" | "function" | "any";
|
|
5
10
|
required?: boolean;
|
|
6
11
|
}
|
|
7
12
|
export type PropsSchema = Record<string, PropsSchemaField>;
|
|
@@ -31,7 +36,7 @@ export interface CompiledModuleError {
|
|
|
31
36
|
version: Version;
|
|
32
37
|
code?: undefined;
|
|
33
38
|
error: {
|
|
34
|
-
kind: "compile" | "typecheck";
|
|
39
|
+
kind: "compile" | "typecheck" | "render" | "test";
|
|
35
40
|
message: string;
|
|
36
41
|
stack?: string;
|
|
37
42
|
diagnostics?: Diagnostic[];
|
|
@@ -76,5 +81,14 @@ export interface Source {
|
|
|
76
81
|
subscribe(listener: (update: SourceUpdate) => void): () => void;
|
|
77
82
|
/** Optional disposal hook. */
|
|
78
83
|
dispose?(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Optional: report the host's scope keys to the registry so the
|
|
86
|
+
* server-side validator knows what bare specifiers it can expect
|
|
87
|
+
* components to import. Called once on DynamicoProvider mount.
|
|
88
|
+
*
|
|
89
|
+
* Remote sources (createRemoteSource) implement this as POST /scope.
|
|
90
|
+
* Local/test sources can leave it undefined.
|
|
91
|
+
*/
|
|
92
|
+
reportScope?(keys: readonly string[], reportedBy?: string): Promise<void>;
|
|
79
93
|
}
|
|
80
94
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omriashke/dynamico-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Renderer-agnostic core for dynamico: source adapters, module loader, versioned component registry.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -13,36 +13,28 @@
|
|
|
13
13
|
"import": "./dist/index.js"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"src"
|
|
19
|
-
],
|
|
16
|
+
"files": ["dist", "src"],
|
|
20
17
|
"repository": {
|
|
21
18
|
"type": "git",
|
|
22
19
|
"url": "https://github.com/omriaskenazi/dynamico.git",
|
|
23
20
|
"directory": "packages/core"
|
|
24
21
|
},
|
|
25
22
|
"homepage": "https://github.com/omriaskenazi/dynamico#readme",
|
|
26
|
-
"keywords": [
|
|
27
|
-
"react",
|
|
28
|
-
"runtime",
|
|
29
|
-
"hot-swap",
|
|
30
|
-
"dynamic-components",
|
|
31
|
-
"dynamico"
|
|
32
|
-
],
|
|
23
|
+
"keywords": ["react", "runtime", "hot-swap", "dynamic-components", "dynamico"],
|
|
33
24
|
"publishConfig": {
|
|
34
25
|
"access": "public"
|
|
35
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"dev": "tsc -p tsconfig.json -w"
|
|
31
|
+
},
|
|
36
32
|
"peerDependencies": {
|
|
37
33
|
"react": ">=18"
|
|
38
34
|
},
|
|
39
35
|
"devDependencies": {
|
|
40
36
|
"@types/react": "^18.3.0",
|
|
37
|
+
"react": "^19.0.0",
|
|
41
38
|
"typescript": "^5.6.3"
|
|
42
|
-
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"build": "tsc -p tsconfig.json",
|
|
45
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
|
-
"dev": "tsc -p tsconfig.json -w"
|
|
47
39
|
}
|
|
48
|
-
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { validateProps } from "./propsSchema.js";
|
|
2
|
+
import type { PropsSchema } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export type BookPreviewJson = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export interface BookPreviewConfig {
|
|
7
|
+
fixtures?: Record<string, BookPreviewJson>;
|
|
8
|
+
entries?: BookPreviewEntry[];
|
|
9
|
+
/** Legacy alias — normalized to `entries`. */
|
|
10
|
+
stories?: BookPreviewEntry[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface BookPreviewEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
kind?: "info";
|
|
16
|
+
blocks?: BookPreviewBlock[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type BookPreviewBlock =
|
|
20
|
+
| { type: "component"; component: string; props?: BookPreviewJson }
|
|
21
|
+
| { type: "stack"; items: BookPreviewBlockItem[] }
|
|
22
|
+
| { type: "row"; items: BookPreviewBlockItem[] }
|
|
23
|
+
| {
|
|
24
|
+
type: "variantGrid";
|
|
25
|
+
component: string;
|
|
26
|
+
variants: Array<{ id: string; props?: BookPreviewJson }>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type BookPreviewBlockItem = { component: string; props?: BookPreviewJson };
|
|
30
|
+
|
|
31
|
+
export interface BookPreviewPropSet {
|
|
32
|
+
entryId: string;
|
|
33
|
+
location: string;
|
|
34
|
+
props: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BookPreviewValidationResult {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
errors: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Merge `$fixture` references into concrete prop objects.
|
|
44
|
+
*
|
|
45
|
+
* Supports both:
|
|
46
|
+
* - top-level: `{ "$fixture": "articleCard", "variant": "default" }`
|
|
47
|
+
* - per-field: `{ "data": { "$fixture": "articleCard" } }`
|
|
48
|
+
*/
|
|
49
|
+
export function resolveBookFixtures(
|
|
50
|
+
props: BookPreviewJson | undefined,
|
|
51
|
+
fixtures: Record<string, BookPreviewJson>,
|
|
52
|
+
): BookPreviewJson {
|
|
53
|
+
if (!props) return {};
|
|
54
|
+
|
|
55
|
+
let merged: BookPreviewJson = { ...props };
|
|
56
|
+
if ("$fixture" in merged && typeof merged.$fixture === "string") {
|
|
57
|
+
const fixtureKey = merged.$fixture;
|
|
58
|
+
const base = fixtures[fixtureKey] ?? {};
|
|
59
|
+
const { $fixture: _omit, ...rest } = merged;
|
|
60
|
+
merged = { ...base, ...rest };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const out: BookPreviewJson = {};
|
|
64
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
65
|
+
if (value && typeof value === "object" && !Array.isArray(value) && "$fixture" in value) {
|
|
66
|
+
const fixtureKey = (value as BookPreviewJson).$fixture as string;
|
|
67
|
+
const base = fixtures[fixtureKey] ?? {};
|
|
68
|
+
const { $fixture: _omit, ...rest } = value as BookPreviewJson;
|
|
69
|
+
out[key] = { ...base, ...rest };
|
|
70
|
+
} else {
|
|
71
|
+
out[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve book-config prop DSL to runtime values for schema validation.
|
|
79
|
+
* React element placeholders (`$component`) become null; callbacks (`$fn`) become no-ops.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveBookPropValues(
|
|
82
|
+
props: BookPreviewJson,
|
|
83
|
+
fixtures: Record<string, BookPreviewJson>,
|
|
84
|
+
): Record<string, unknown> {
|
|
85
|
+
const out: Record<string, unknown> = {};
|
|
86
|
+
for (const [key, value] of Object.entries(props)) {
|
|
87
|
+
out[key] = resolveBookPropValue(value, fixtures);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveBookPropValue(value: unknown, fixtures: Record<string, BookPreviewJson>): unknown {
|
|
93
|
+
if (value === null || typeof value !== "object") return value;
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value.map((item) => resolveBookPropValue(item, fixtures));
|
|
96
|
+
}
|
|
97
|
+
const obj = value as BookPreviewJson;
|
|
98
|
+
if ("$fn" in obj && obj.$fn === "noop") {
|
|
99
|
+
return () => undefined;
|
|
100
|
+
}
|
|
101
|
+
if ("$component" in obj && typeof obj.$component === "string") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if ("$fixture" in obj) {
|
|
105
|
+
return resolveBookPropValues(resolveBookFixtures(obj, fixtures), fixtures);
|
|
106
|
+
}
|
|
107
|
+
const nested: Record<string, unknown> = {};
|
|
108
|
+
for (const [key, nestedValue] of Object.entries(obj)) {
|
|
109
|
+
nested[key] = resolveBookPropValue(nestedValue, fixtures);
|
|
110
|
+
}
|
|
111
|
+
return nested;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function normalizeBookPreviewConfig(raw: BookPreviewConfig): BookPreviewConfig {
|
|
115
|
+
const entries = raw.entries ?? raw.stories ?? [];
|
|
116
|
+
return { ...raw, entries };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Collect every resolved prop bag used to preview `componentName` in book.config.json. */
|
|
120
|
+
export function collectBookPreviewPropSets(
|
|
121
|
+
config: BookPreviewConfig,
|
|
122
|
+
componentName: string,
|
|
123
|
+
): BookPreviewPropSet[] {
|
|
124
|
+
const normalized = normalizeBookPreviewConfig(config);
|
|
125
|
+
const fixtures = normalized.fixtures ?? {};
|
|
126
|
+
const out: BookPreviewPropSet[] = [];
|
|
127
|
+
|
|
128
|
+
for (const entry of normalized.entries ?? []) {
|
|
129
|
+
if (entry.kind === "info") continue;
|
|
130
|
+
for (const block of entry.blocks ?? []) {
|
|
131
|
+
collectFromBlock(block, componentName, fixtures, entry.id, out);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function collectFromBlock(
|
|
138
|
+
block: BookPreviewBlock,
|
|
139
|
+
componentName: string,
|
|
140
|
+
fixtures: Record<string, BookPreviewJson>,
|
|
141
|
+
entryId: string,
|
|
142
|
+
out: BookPreviewPropSet[],
|
|
143
|
+
): void {
|
|
144
|
+
switch (block.type) {
|
|
145
|
+
case "component":
|
|
146
|
+
collectFromComponentUse(block.component, block.props, componentName, fixtures, entryId, "preview", out);
|
|
147
|
+
return;
|
|
148
|
+
case "stack":
|
|
149
|
+
case "row":
|
|
150
|
+
for (const item of block.items) {
|
|
151
|
+
collectFromComponentUse(item.component, item.props, componentName, fixtures, entryId, "preview", out);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
case "variantGrid":
|
|
155
|
+
for (const variant of block.variants) {
|
|
156
|
+
collectFromComponentUse(
|
|
157
|
+
block.component,
|
|
158
|
+
variant.props,
|
|
159
|
+
componentName,
|
|
160
|
+
fixtures,
|
|
161
|
+
entryId,
|
|
162
|
+
`variant '${variant.id}'`,
|
|
163
|
+
out,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
default:
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function collectFromComponentUse(
|
|
173
|
+
usedComponent: string,
|
|
174
|
+
props: BookPreviewJson | undefined,
|
|
175
|
+
componentName: string,
|
|
176
|
+
fixtures: Record<string, BookPreviewJson>,
|
|
177
|
+
entryId: string,
|
|
178
|
+
location: string,
|
|
179
|
+
out: BookPreviewPropSet[],
|
|
180
|
+
): void {
|
|
181
|
+
if (usedComponent === componentName) {
|
|
182
|
+
out.push({
|
|
183
|
+
entryId,
|
|
184
|
+
location,
|
|
185
|
+
props: resolveBookPropValues(resolveBookFixtures(props, fixtures), fixtures),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
collectNestedComponentProps(props, componentName, fixtures, entryId, out);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function collectNestedComponentProps(
|
|
192
|
+
props: BookPreviewJson | undefined,
|
|
193
|
+
componentName: string,
|
|
194
|
+
fixtures: Record<string, BookPreviewJson>,
|
|
195
|
+
entryId: string,
|
|
196
|
+
out: BookPreviewPropSet[],
|
|
197
|
+
): void {
|
|
198
|
+
if (!props) return;
|
|
199
|
+
for (const [key, value] of Object.entries(props)) {
|
|
200
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
201
|
+
const obj = value as BookPreviewJson;
|
|
202
|
+
if ("$component" in obj && obj.$component === componentName) {
|
|
203
|
+
const nestedProps = (obj.props as BookPreviewJson | undefined) ?? {};
|
|
204
|
+
out.push({
|
|
205
|
+
entryId,
|
|
206
|
+
location: `prop '${key}'`,
|
|
207
|
+
props: resolveBookPropValues(resolveBookFixtures(nestedProps, fixtures), fixtures),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
collectNestedComponentProps(obj, componentName, fixtures, entryId, out);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Validate every book.config.json preview of a component against its propsSchema. */
|
|
215
|
+
export function validateBookPreviewsForComponent(
|
|
216
|
+
schema: PropsSchema,
|
|
217
|
+
config: BookPreviewConfig,
|
|
218
|
+
componentName: string,
|
|
219
|
+
): BookPreviewValidationResult {
|
|
220
|
+
const sets = collectBookPreviewPropSets(config, componentName);
|
|
221
|
+
if (sets.length === 0) {
|
|
222
|
+
return { ok: true, errors: [] };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const errors: string[] = [];
|
|
226
|
+
for (const set of sets) {
|
|
227
|
+
const result = validateProps(schema, set.props);
|
|
228
|
+
if (!result.ok) {
|
|
229
|
+
errors.push(
|
|
230
|
+
`book entry '${set.entryId}' (${set.location}): ${result.errors.join("; ")}`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { ok: errors.length === 0, errors };
|
|
235
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -20,9 +20,22 @@ export { Registry } from "./registry.js";
|
|
|
20
20
|
export { loadModule } from "./loader.js";
|
|
21
21
|
export { createRemoteSource, type RemoteSourceOptions } from "./sources/remote.js";
|
|
22
22
|
export { validateProps, type PropsValidationResult } from "./propsSchema.js";
|
|
23
|
+
export {
|
|
24
|
+
collectBookPreviewPropSets,
|
|
25
|
+
normalizeBookPreviewConfig,
|
|
26
|
+
resolveBookFixtures,
|
|
27
|
+
resolveBookPropValues,
|
|
28
|
+
validateBookPreviewsForComponent,
|
|
29
|
+
type BookPreviewBlock,
|
|
30
|
+
type BookPreviewConfig,
|
|
31
|
+
type BookPreviewEntry,
|
|
32
|
+
type BookPreviewPropSet,
|
|
33
|
+
type BookPreviewValidationResult,
|
|
34
|
+
} from "./bookPreview.js";
|
|
23
35
|
export {
|
|
24
36
|
createRuntime,
|
|
25
37
|
type RuntimeAPI,
|
|
38
|
+
type CreateRuntimeOptions,
|
|
26
39
|
type DynamicoProviderProps,
|
|
27
40
|
type DynamicComponentProps,
|
|
28
41
|
} from "./react/createRuntime.js";
|
package/src/loader.ts
CHANGED
|
@@ -23,7 +23,10 @@ export function loadModule(
|
|
|
23
23
|
if (name.startsWith("./") || name.startsWith("../") || name.startsWith("/")) {
|
|
24
24
|
return requireRelative(name);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// Use `in` (triggers Proxy `has` traps) so that hosts can supply scope
|
|
27
|
+
// via a Proxy and resolve module bindings lazily. Falls back to
|
|
28
|
+
// `hasOwnProperty` semantics for plain object scopes.
|
|
29
|
+
if (name in scope) {
|
|
27
30
|
return scope[name];
|
|
28
31
|
}
|
|
29
32
|
throw new Error(
|
package/src/propsSchema.ts
CHANGED
|
@@ -11,6 +11,7 @@ const TYPE_CHECKS: Record<string, (v: unknown) => boolean> = {
|
|
|
11
11
|
boolean: (v) => typeof v === "boolean",
|
|
12
12
|
object: (v) => typeof v === "object" && v !== null && !Array.isArray(v),
|
|
13
13
|
array: (v) => Array.isArray(v),
|
|
14
|
+
function: (v) => typeof v === "function",
|
|
14
15
|
any: () => true,
|
|
15
16
|
};
|
|
16
17
|
|
|
@@ -39,5 +40,6 @@ export function validateProps(
|
|
|
39
40
|
function describe(v: unknown): string {
|
|
40
41
|
if (v === null) return "null";
|
|
41
42
|
if (Array.isArray(v)) return "array";
|
|
43
|
+
if (typeof v === "function") return "function";
|
|
42
44
|
return typeof v;
|
|
43
45
|
}
|
|
@@ -14,6 +14,13 @@ export interface RuntimeAPI {
|
|
|
14
14
|
DynamicoProvider: React.ComponentType<DynamicoProviderProps>;
|
|
15
15
|
DynamicComponent: React.ComponentType<DynamicComponentProps>;
|
|
16
16
|
useDynamico: (name: string) => RegistryEntry | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Read the merged host scope from inside React. Useful in dynamic
|
|
19
|
+
* components that want to introspect what the host gave them, or to
|
|
20
|
+
* conditionally use scope features (`useScope()['@my/haptics']` may be
|
|
21
|
+
* undefined if the host didn't register it).
|
|
22
|
+
*/
|
|
23
|
+
useScope: () => Scope;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export interface DynamicoProviderProps {
|
|
@@ -33,8 +40,26 @@ interface RuntimeContextValue {
|
|
|
33
40
|
registry: Registry;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Optional renderer-specific knobs passed by the platform package
|
|
45
|
+
* (`@omriashke/dynamico-web`, `@omriashke/dynamico-native`).
|
|
46
|
+
*
|
|
47
|
+
* The web runtime is fine with the built-in `defaultErrorView` (which renders
|
|
48
|
+
* a plain text Fragment via React.createElement), since DOM accepts text
|
|
49
|
+
* nodes anywhere. React Native does NOT — bare strings throw "Text strings
|
|
50
|
+
* must be rendered within a <Text> component". The native package supplies
|
|
51
|
+
* its own `defaultErrorFallback` that wraps the message in `<Text>`.
|
|
52
|
+
*/
|
|
53
|
+
export interface CreateRuntimeOptions {
|
|
54
|
+
defaultErrorFallback?: React.ComponentType<{ error: DynamicError }>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createRuntime(
|
|
58
|
+
defaultScope: Scope,
|
|
59
|
+
options: CreateRuntimeOptions = {},
|
|
60
|
+
): RuntimeAPI {
|
|
37
61
|
const Ctx = React.createContext<RuntimeContextValue | null>(null);
|
|
62
|
+
const PlatformDefaultErrorFallback = options.defaultErrorFallback;
|
|
38
63
|
|
|
39
64
|
function DynamicoProvider({ source, scope, children }: DynamicoProviderProps) {
|
|
40
65
|
const registry = React.useMemo(() => {
|
|
@@ -42,6 +67,17 @@ export function createRuntime(defaultScope: Scope): RuntimeAPI {
|
|
|
42
67
|
return new Registry(source, merged);
|
|
43
68
|
}, [source, scope]);
|
|
44
69
|
|
|
70
|
+
// Auto-report host scope to the registry on mount so the server-side
|
|
71
|
+
// test validator can enforce "every component imports something the host
|
|
72
|
+
// actually provides". Source implementations that don't support scope
|
|
73
|
+
// reporting (e.g. local in-memory sources) leave the call as a no-op.
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
const merged: Scope = { ...defaultScope, ...(scope ?? {}) };
|
|
76
|
+
const keys = Object.keys(merged);
|
|
77
|
+
void source.reportScope?.(keys, "host");
|
|
78
|
+
// Re-run whenever the set of keys changes (added/removed providers).
|
|
79
|
+
}, [source, scope]);
|
|
80
|
+
|
|
45
81
|
const value = React.useMemo<RuntimeContextValue>(() => ({ registry }), [registry]);
|
|
46
82
|
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
|
|
47
83
|
}
|
|
@@ -54,6 +90,10 @@ export function createRuntime(defaultScope: Scope): RuntimeAPI {
|
|
|
54
90
|
return ctx.registry;
|
|
55
91
|
}
|
|
56
92
|
|
|
93
|
+
function useScope(): Scope {
|
|
94
|
+
return useRegistry().getScope();
|
|
95
|
+
}
|
|
96
|
+
|
|
57
97
|
function useDynamico(name: string): RegistryEntry | undefined {
|
|
58
98
|
const registry = useRegistry();
|
|
59
99
|
const subscribe = React.useCallback(
|
|
@@ -126,7 +166,24 @@ export function createRuntime(defaultScope: Scope): RuntimeAPI {
|
|
|
126
166
|
);
|
|
127
167
|
}
|
|
128
168
|
|
|
129
|
-
|
|
169
|
+
function renderError(
|
|
170
|
+
errorFallback: DynamicComponentProps["errorFallback"],
|
|
171
|
+
error: DynamicError,
|
|
172
|
+
): React.ReactNode {
|
|
173
|
+
if (errorFallback) {
|
|
174
|
+
if (typeof errorFallback === "function") {
|
|
175
|
+
const Fallback = errorFallback as React.ComponentType<{ error: DynamicError }>;
|
|
176
|
+
return <Fallback error={error} />;
|
|
177
|
+
}
|
|
178
|
+
return errorFallback;
|
|
179
|
+
}
|
|
180
|
+
if (PlatformDefaultErrorFallback) {
|
|
181
|
+
return <PlatformDefaultErrorFallback error={error} />;
|
|
182
|
+
}
|
|
183
|
+
return defaultErrorView(error);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { DynamicoProvider, DynamicComponent, useDynamico, useScope };
|
|
130
187
|
}
|
|
131
188
|
|
|
132
189
|
function pickDefault(factory: ComponentFactory): unknown {
|
|
@@ -139,18 +196,6 @@ function pickDefault(factory: ComponentFactory): unknown {
|
|
|
139
196
|
return undefined;
|
|
140
197
|
}
|
|
141
198
|
|
|
142
|
-
function renderError(
|
|
143
|
-
errorFallback: DynamicComponentProps["errorFallback"],
|
|
144
|
-
error: DynamicError,
|
|
145
|
-
): React.ReactNode {
|
|
146
|
-
if (!errorFallback) return defaultErrorView(error);
|
|
147
|
-
if (typeof errorFallback === "function") {
|
|
148
|
-
const Fallback = errorFallback as React.ComponentType<{ error: DynamicError }>;
|
|
149
|
-
return <Fallback error={error} />;
|
|
150
|
-
}
|
|
151
|
-
return errorFallback;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
199
|
function defaultErrorView(error: DynamicError): React.ReactElement {
|
|
155
200
|
// Renderer-agnostic: plain text node so it works on both DOM and RN.
|
|
156
201
|
return React.createElement(
|
package/src/registry.ts
CHANGED
|
@@ -37,6 +37,15 @@ export class Registry {
|
|
|
37
37
|
this.scope = scope;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Return the merged host scope. Dynamic components reach the same values
|
|
42
|
+
* via `require(name)`, but `getScope()` lets host code (or `useScope()`
|
|
43
|
+
* inside a dynamic component) introspect what's available.
|
|
44
|
+
*/
|
|
45
|
+
getScope(): Scope {
|
|
46
|
+
return this.scope;
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
/** Get the current entry for a name, if any. */
|
|
41
50
|
peek(name: string): RegistryEntry | undefined {
|
|
42
51
|
return this.entries.get(name);
|
package/src/sources/remote.ts
CHANGED
|
@@ -11,6 +11,18 @@ export interface RemoteSourceOptions {
|
|
|
11
11
|
WebSocket?: typeof WebSocket;
|
|
12
12
|
/** Reconnection backoff in ms. Default: 1000. */
|
|
13
13
|
reconnectMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Headers to send on every request. Called on each HTTP fetch and on each
|
|
16
|
+
* WebSocket reconnect, so the function can return a freshly-rotated token.
|
|
17
|
+
*
|
|
18
|
+
* - HTTP: merged into the `Authorization: ...` / `x-api-key: ...` request headers.
|
|
19
|
+
* - WebSocket: passed as `new WebSocket(url, undefined, { headers })`. This
|
|
20
|
+
* works on React Native (which extends the standard constructor); browsers
|
|
21
|
+
* silently ignore it because the spec doesn't allow custom WS handshake
|
|
22
|
+
* headers. For browsers behind authenticated reverse proxies, use a
|
|
23
|
+
* query-string token in `wsUrl` or front the registry with cookie auth.
|
|
24
|
+
*/
|
|
25
|
+
headers?: () => Record<string, string>;
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
/**
|
|
@@ -47,7 +59,18 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
47
59
|
function connect(): void {
|
|
48
60
|
if (disposed) return;
|
|
49
61
|
try {
|
|
50
|
-
|
|
62
|
+
const hdrs = options.headers?.();
|
|
63
|
+
// RN's WebSocket constructor accepts a third {headers} arg that lets us
|
|
64
|
+
// attach Bearer / x-api-key tokens to the upgrade request. The standard
|
|
65
|
+
// browser WebSocket ignores extra constructor args, so this is a no-op
|
|
66
|
+
// there (use cookies / a query-string token instead).
|
|
67
|
+
socket = hdrs
|
|
68
|
+
? new (WSCtor as unknown as new (
|
|
69
|
+
url: string,
|
|
70
|
+
protocols?: string | string[],
|
|
71
|
+
options?: { headers?: Record<string, string> },
|
|
72
|
+
) => WebSocket)(wsUrl, undefined, { headers: hdrs })
|
|
73
|
+
: new WSCtor(wsUrl);
|
|
51
74
|
} catch (err) {
|
|
52
75
|
scheduleReconnect();
|
|
53
76
|
return;
|
|
@@ -88,7 +111,13 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
88
111
|
|
|
89
112
|
return {
|
|
90
113
|
async fetch(name: string): Promise<CompiledModule> {
|
|
91
|
-
const
|
|
114
|
+
const init: RequestInit | undefined = options.headers
|
|
115
|
+
? { headers: options.headers() }
|
|
116
|
+
: undefined;
|
|
117
|
+
const res = await fetchImpl(
|
|
118
|
+
`${httpUrl}/component/${encodeURIComponent(name)}`,
|
|
119
|
+
init,
|
|
120
|
+
);
|
|
92
121
|
if (!res.ok) {
|
|
93
122
|
return {
|
|
94
123
|
name,
|
|
@@ -107,6 +136,27 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
107
136
|
listeners.delete(listener);
|
|
108
137
|
};
|
|
109
138
|
},
|
|
139
|
+
/**
|
|
140
|
+
* Tell the registry what bare specifiers the host's scope exposes. The
|
|
141
|
+
* registry uses this to validate that every component's imports resolve
|
|
142
|
+
* against something the host actually provides — so a typo or a forgotten
|
|
143
|
+
* scope entry is caught at push time, not at navigation time.
|
|
144
|
+
*
|
|
145
|
+
* Best-effort: failures (network, 5xx, server doesn't support /scope) are
|
|
146
|
+
* silently swallowed; they don't block the app from running.
|
|
147
|
+
*/
|
|
148
|
+
async reportScope(keys, reportedBy) {
|
|
149
|
+
try {
|
|
150
|
+
const baseHeaders = options.headers?.() ?? {};
|
|
151
|
+
await fetchImpl(`${httpUrl}/scope`, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: { "content-type": "application/json", ...baseHeaders },
|
|
154
|
+
body: JSON.stringify({ keys: [...keys], reportedBy }),
|
|
155
|
+
});
|
|
156
|
+
} catch {
|
|
157
|
+
/* best-effort: never block the host on this */
|
|
158
|
+
}
|
|
159
|
+
},
|
|
110
160
|
dispose() {
|
|
111
161
|
disposed = true;
|
|
112
162
|
try {
|
package/src/types.ts
CHANGED
|
@@ -3,7 +3,19 @@ export type Version = string;
|
|
|
3
3
|
export type Scope = Record<string, unknown>;
|
|
4
4
|
|
|
5
5
|
export interface PropsSchemaField {
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Runtime type. `function` validates `typeof v === 'function'` — useful for
|
|
8
|
+
* dynamic screens/components that take callbacks. Function values render
|
|
9
|
+
* fine inside dynamic components; the schema just lets you require them.
|
|
10
|
+
*/
|
|
11
|
+
type:
|
|
12
|
+
| "string"
|
|
13
|
+
| "number"
|
|
14
|
+
| "boolean"
|
|
15
|
+
| "object"
|
|
16
|
+
| "array"
|
|
17
|
+
| "function"
|
|
18
|
+
| "any";
|
|
7
19
|
required?: boolean;
|
|
8
20
|
}
|
|
9
21
|
|
|
@@ -37,7 +49,7 @@ export interface CompiledModuleError {
|
|
|
37
49
|
version: Version;
|
|
38
50
|
code?: undefined;
|
|
39
51
|
error: {
|
|
40
|
-
kind: "compile" | "typecheck";
|
|
52
|
+
kind: "compile" | "typecheck" | "render" | "test";
|
|
41
53
|
message: string;
|
|
42
54
|
stack?: string;
|
|
43
55
|
diagnostics?: Diagnostic[];
|
|
@@ -93,4 +105,13 @@ export interface Source {
|
|
|
93
105
|
subscribe(listener: (update: SourceUpdate) => void): () => void;
|
|
94
106
|
/** Optional disposal hook. */
|
|
95
107
|
dispose?(): void;
|
|
108
|
+
/**
|
|
109
|
+
* Optional: report the host's scope keys to the registry so the
|
|
110
|
+
* server-side validator knows what bare specifiers it can expect
|
|
111
|
+
* components to import. Called once on DynamicoProvider mount.
|
|
112
|
+
*
|
|
113
|
+
* Remote sources (createRemoteSource) implement this as POST /scope.
|
|
114
|
+
* Local/test sources can leave it undefined.
|
|
115
|
+
*/
|
|
116
|
+
reportScope?(keys: readonly string[], reportedBy?: string): Promise<void>;
|
|
96
117
|
}
|