@omriashke/dynamico-core 0.1.0
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/LICENSE +184 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +16 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +31 -0
- package/dist/loader.js.map +1 -0
- package/dist/propsSchema.d.ts +7 -0
- package/dist/propsSchema.d.ts.map +1 -0
- package/dist/propsSchema.js +34 -0
- package/dist/propsSchema.js.map +1 -0
- package/dist/react/createRuntime.d.ts +22 -0
- package/dist/react/createRuntime.d.ts.map +1 -0
- package/dist/react/createRuntime.js +116 -0
- package/dist/react/createRuntime.js.map +1 -0
- package/dist/registry.d.ts +55 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +231 -0
- package/dist/registry.js.map +1 -0
- package/dist/sources/remote.d.ts +21 -0
- package/dist/sources/remote.d.ts.map +1 -0
- package/dist/sources/remote.js +102 -0
- package/dist/sources/remote.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/index.ts +28 -0
- package/src/loader.ts +40 -0
- package/src/propsSchema.ts +43 -0
- package/src/react/createRuntime.tsx +189 -0
- package/src/registry.ts +240 -0
- package/src/sources/remote.ts +119 -0
- package/src/types.ts +96 -0
package/dist/registry.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { loadModule } from "./loader.js";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory, versioned registry of dynamic components.
|
|
4
|
+
*
|
|
5
|
+
* The registry is the single source of truth that runtime packages
|
|
6
|
+
* (@omriashke/dynamico-web, @omriashke/dynamico-native) subscribe to. It receives compiled
|
|
7
|
+
* modules from a Source, evaluates them via the loader using a host-provided
|
|
8
|
+
* Scope, and notifies subscribers when a component's version changes.
|
|
9
|
+
*/
|
|
10
|
+
export class Registry {
|
|
11
|
+
constructor(source, scope) {
|
|
12
|
+
this.source = source;
|
|
13
|
+
this.scope = scope;
|
|
14
|
+
this.entries = new Map();
|
|
15
|
+
this.listeners = new Map();
|
|
16
|
+
this.anyListeners = new Set();
|
|
17
|
+
this.inflight = new Map();
|
|
18
|
+
this.lazyProxies = new Map();
|
|
19
|
+
this.source.subscribe(({ module }) => {
|
|
20
|
+
this.ingest(module.name, module);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** Replace or extend the current scope (rare; typically set once). */
|
|
24
|
+
setScope(scope) {
|
|
25
|
+
this.scope = scope;
|
|
26
|
+
}
|
|
27
|
+
/** Get the current entry for a name, if any. */
|
|
28
|
+
peek(name) {
|
|
29
|
+
return this.entries.get(name);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ensure a component is loaded. Triggers an initial fetch if we don't yet
|
|
33
|
+
* have an entry for this name. Returns the latest known entry.
|
|
34
|
+
*/
|
|
35
|
+
async ensure(name) {
|
|
36
|
+
const existing = this.entries.get(name);
|
|
37
|
+
if (existing)
|
|
38
|
+
return existing;
|
|
39
|
+
const pending = this.inflight.get(name);
|
|
40
|
+
if (pending)
|
|
41
|
+
return pending;
|
|
42
|
+
const p = this.source
|
|
43
|
+
.fetch(name)
|
|
44
|
+
.then((module) => this.ingest(name, module))
|
|
45
|
+
.finally(() => {
|
|
46
|
+
this.inflight.delete(name);
|
|
47
|
+
});
|
|
48
|
+
this.inflight.set(name, p);
|
|
49
|
+
return p;
|
|
50
|
+
}
|
|
51
|
+
/** Subscribe to changes for a specific component. */
|
|
52
|
+
subscribe(name, listener) {
|
|
53
|
+
let set = this.listeners.get(name);
|
|
54
|
+
if (!set) {
|
|
55
|
+
set = new Set();
|
|
56
|
+
this.listeners.set(name, set);
|
|
57
|
+
}
|
|
58
|
+
set.add(listener);
|
|
59
|
+
return () => {
|
|
60
|
+
set.delete(listener);
|
|
61
|
+
if (set.size === 0)
|
|
62
|
+
this.listeners.delete(name);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Subscribe to all changes (used internally / for debugging). */
|
|
66
|
+
subscribeAll(listener) {
|
|
67
|
+
this.anyListeners.add(listener);
|
|
68
|
+
return () => {
|
|
69
|
+
this.anyListeners.delete(listener);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a relative-path require from inside a dynamic component.
|
|
74
|
+
* Cross-component imports look up other components by name in the registry.
|
|
75
|
+
* v1: we map "./Other" -> "Other" (basename, no extension).
|
|
76
|
+
*
|
|
77
|
+
* The returned value is a *lazy proxy module*: it has the same shape as
|
|
78
|
+
* a real CommonJS module (`{ default, ...rest }`), but every member is a
|
|
79
|
+
* React function component that re-resolves the target on each render.
|
|
80
|
+
* This means:
|
|
81
|
+
* - Card can `require("./Hello")` at eval time even before Hello has
|
|
82
|
+
* loaded — the proxy is returned immediately.
|
|
83
|
+
* - When Hello actually arrives (or hot-swaps to a new version), Card's
|
|
84
|
+
* next render automatically picks it up; no manual preload needed.
|
|
85
|
+
*/
|
|
86
|
+
requireByPath(specifier) {
|
|
87
|
+
const base = specifier
|
|
88
|
+
.replace(/^\.+\//, "")
|
|
89
|
+
.replace(/\.[tj]sx?$/, "")
|
|
90
|
+
.split("/")
|
|
91
|
+
.pop();
|
|
92
|
+
if (!base) {
|
|
93
|
+
throw new Error(`dynamico: cannot resolve relative require '${specifier}'`);
|
|
94
|
+
}
|
|
95
|
+
if (!this.entries.has(base) && !this.inflight.has(base)) {
|
|
96
|
+
void this.ensure(base);
|
|
97
|
+
}
|
|
98
|
+
return this.makeLazyProxy(base);
|
|
99
|
+
}
|
|
100
|
+
makeLazyProxy(name) {
|
|
101
|
+
const cached = this.lazyProxies.get(name);
|
|
102
|
+
if (cached)
|
|
103
|
+
return cached;
|
|
104
|
+
const registry = this;
|
|
105
|
+
const proxy = {};
|
|
106
|
+
const make = (key) => {
|
|
107
|
+
const Comp = function LazyDynamic(props) {
|
|
108
|
+
const entry = registry.entries.get(name);
|
|
109
|
+
// Not loaded yet — render nothing. When the dependency arrives, the
|
|
110
|
+
// cross-dep notification in `notify()` refreshes our parent's entry,
|
|
111
|
+
// which causes useSyncExternalStore to re-render and we'll resolve
|
|
112
|
+
// for real on the next pass.
|
|
113
|
+
if (!entry || (!entry.factory && !entry.error))
|
|
114
|
+
return null;
|
|
115
|
+
if (entry.error) {
|
|
116
|
+
// Surface the dep error inline. Parent can wrap in errorFallback at
|
|
117
|
+
// its own level if it wants; we don't have access to it here.
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const target = entry.factory?.[key];
|
|
121
|
+
if (typeof target !== "function")
|
|
122
|
+
return null;
|
|
123
|
+
// The host-scope's React.createElement is what invoked us; we just
|
|
124
|
+
// call the real component function. props is what the parent passed.
|
|
125
|
+
return target(props);
|
|
126
|
+
};
|
|
127
|
+
Object.defineProperty(Comp, "name", { value: `Lazy(${name}.${key})` });
|
|
128
|
+
return Comp;
|
|
129
|
+
};
|
|
130
|
+
proxy.default = make("default");
|
|
131
|
+
// Allow named imports via Proxy: any key access returns a fresh lazy
|
|
132
|
+
// component bound to that key. Default is set above; everything else
|
|
133
|
+
// is created on demand.
|
|
134
|
+
const handler = {
|
|
135
|
+
get(target, prop) {
|
|
136
|
+
if (typeof prop !== "string")
|
|
137
|
+
return undefined;
|
|
138
|
+
if (prop in target)
|
|
139
|
+
return target[prop];
|
|
140
|
+
const c = make(prop);
|
|
141
|
+
target[prop] = c;
|
|
142
|
+
return c;
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const wrapped = new Proxy(proxy, handler);
|
|
146
|
+
this.lazyProxies.set(name, wrapped);
|
|
147
|
+
return wrapped;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Take a CompiledModule from the source, evaluate it (or record a compile
|
|
151
|
+
* error), update the entry, and notify listeners.
|
|
152
|
+
*/
|
|
153
|
+
ingest(name, module) {
|
|
154
|
+
if (module.removed) {
|
|
155
|
+
this.entries.delete(name);
|
|
156
|
+
this.lazyProxies.delete(name);
|
|
157
|
+
const removalEntry = {
|
|
158
|
+
name,
|
|
159
|
+
version: module.version,
|
|
160
|
+
error: {
|
|
161
|
+
kind: "load",
|
|
162
|
+
name,
|
|
163
|
+
version: module.version,
|
|
164
|
+
message: `'${name}' was removed from the registry`,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
this.notify(name, removalEntry);
|
|
168
|
+
return removalEntry;
|
|
169
|
+
}
|
|
170
|
+
let entry;
|
|
171
|
+
if (module.error) {
|
|
172
|
+
entry = {
|
|
173
|
+
name,
|
|
174
|
+
version: module.version,
|
|
175
|
+
error: {
|
|
176
|
+
kind: module.error.kind === "typecheck" ? "load" : "compile",
|
|
177
|
+
name,
|
|
178
|
+
version: module.version,
|
|
179
|
+
message: module.error.message,
|
|
180
|
+
stack: module.error.stack,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
try {
|
|
186
|
+
const factory = loadModule(module.code, this.scope, (rel) => this.requireByPath(rel));
|
|
187
|
+
entry = { name, version: module.version, factory };
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
entry = {
|
|
191
|
+
name,
|
|
192
|
+
version: module.version,
|
|
193
|
+
error: toLoadError(name, module.version, err),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this.entries.set(name, entry);
|
|
198
|
+
this.notify(name, entry);
|
|
199
|
+
return entry;
|
|
200
|
+
}
|
|
201
|
+
notify(name, entry) {
|
|
202
|
+
const set = this.listeners.get(name);
|
|
203
|
+
if (set)
|
|
204
|
+
for (const l of set)
|
|
205
|
+
l(entry);
|
|
206
|
+
for (const l of this.anyListeners)
|
|
207
|
+
l(entry);
|
|
208
|
+
// A new/updated component may be a dependency of others that are already
|
|
209
|
+
// mounted via lazy proxies. For v1 we don't track an explicit dep graph;
|
|
210
|
+
// instead we notify every other subscribed name with a *fresh entry
|
|
211
|
+
// object* (same content, new identity) so that useSyncExternalStore
|
|
212
|
+
// picks up the change and React re-renders, which causes the lazy
|
|
213
|
+
// proxy's render path to re-resolve the now-loaded dependency.
|
|
214
|
+
for (const [otherName, listeners] of this.listeners) {
|
|
215
|
+
if (otherName === name)
|
|
216
|
+
continue;
|
|
217
|
+
const other = this.entries.get(otherName);
|
|
218
|
+
if (!other)
|
|
219
|
+
continue;
|
|
220
|
+
const refreshed = { ...other };
|
|
221
|
+
this.entries.set(otherName, refreshed);
|
|
222
|
+
for (const l of listeners)
|
|
223
|
+
l(refreshed);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function toLoadError(name, version, err) {
|
|
228
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
229
|
+
return { kind: "load", name, version, message: e.message, stack: e.stack };
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Source } from "../types.js";
|
|
2
|
+
export interface RemoteSourceOptions {
|
|
3
|
+
/** Base URL of the registry server, e.g. "http://localhost:4000" or "https://reg.example.com". */
|
|
4
|
+
url: string;
|
|
5
|
+
/** Optional WebSocket URL override. Defaults to `url` with http(s) -> ws(s). */
|
|
6
|
+
wsUrl?: string;
|
|
7
|
+
/** Custom fetch (for environments without a global fetch). */
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
/** Custom WebSocket constructor (RN provides one globally; Node 18+ has it too). */
|
|
10
|
+
WebSocket?: typeof WebSocket;
|
|
11
|
+
/** Reconnection backoff in ms. Default: 1000. */
|
|
12
|
+
reconnectMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Talks to @omriashke/dynamico-registry (or any compatible server).
|
|
16
|
+
*
|
|
17
|
+
* GET {url}/component/:name -> CompiledModule (initial fetch)
|
|
18
|
+
* WS {wsUrl}/subscribe -> stream of CompiledModule updates
|
|
19
|
+
*/
|
|
20
|
+
export declare function createRemoteSource(options: RemoteSourceOptions): Source;
|
|
21
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/sources/remote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,MAAM,EAAgB,MAAM,aAAa,CAAC;AAExE,MAAM,WAAW,mBAAmB;IAClC,kGAAkG;IAClG,GAAG,EAAE,MAAM,CAAC;IACZ,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;IAC7B,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAiGvE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Talks to @omriashke/dynamico-registry (or any compatible server).
|
|
3
|
+
*
|
|
4
|
+
* GET {url}/component/:name -> CompiledModule (initial fetch)
|
|
5
|
+
* WS {wsUrl}/subscribe -> stream of CompiledModule updates
|
|
6
|
+
*/
|
|
7
|
+
export function createRemoteSource(options) {
|
|
8
|
+
const fetchImpl = options.fetch ??
|
|
9
|
+
(typeof fetch !== "undefined"
|
|
10
|
+
? fetch
|
|
11
|
+
: (() => {
|
|
12
|
+
throw new Error("dynamico: no fetch implementation available");
|
|
13
|
+
}));
|
|
14
|
+
const WSCtor = options.WebSocket ??
|
|
15
|
+
(typeof WebSocket !== "undefined"
|
|
16
|
+
? WebSocket
|
|
17
|
+
: function MissingWS() {
|
|
18
|
+
throw new Error("dynamico: no WebSocket implementation available");
|
|
19
|
+
});
|
|
20
|
+
const httpUrl = options.url.replace(/\/$/, "");
|
|
21
|
+
const wsUrl = options.wsUrl ?? httpUrl.replace(/^http/, "ws") + "/subscribe";
|
|
22
|
+
const listeners = new Set();
|
|
23
|
+
let socket = null;
|
|
24
|
+
let disposed = false;
|
|
25
|
+
const reconnectMs = options.reconnectMs ?? 1000;
|
|
26
|
+
function connect() {
|
|
27
|
+
if (disposed)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
socket = new WSCtor(wsUrl);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
scheduleReconnect();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
socket.onmessage = (ev) => {
|
|
37
|
+
try {
|
|
38
|
+
const data = typeof ev.data === "string"
|
|
39
|
+
? JSON.parse(ev.data)
|
|
40
|
+
: JSON.parse(new TextDecoder().decode(ev.data));
|
|
41
|
+
if (data && typeof data.name === "string" && typeof data.version === "string") {
|
|
42
|
+
const module = data;
|
|
43
|
+
for (const l of listeners)
|
|
44
|
+
l({ module });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* ignore malformed frames */
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
socket.onclose = () => {
|
|
52
|
+
socket = null;
|
|
53
|
+
scheduleReconnect();
|
|
54
|
+
};
|
|
55
|
+
socket.onerror = () => {
|
|
56
|
+
try {
|
|
57
|
+
socket?.close();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* noop */
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function scheduleReconnect() {
|
|
65
|
+
if (disposed)
|
|
66
|
+
return;
|
|
67
|
+
setTimeout(connect, reconnectMs);
|
|
68
|
+
}
|
|
69
|
+
connect();
|
|
70
|
+
return {
|
|
71
|
+
async fetch(name) {
|
|
72
|
+
const res = await fetchImpl(`${httpUrl}/component/${encodeURIComponent(name)}`);
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
version: "0",
|
|
77
|
+
error: {
|
|
78
|
+
kind: "compile",
|
|
79
|
+
message: `registry returned ${res.status} for ${name}`,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return (await res.json());
|
|
84
|
+
},
|
|
85
|
+
subscribe(listener) {
|
|
86
|
+
listeners.add(listener);
|
|
87
|
+
return () => {
|
|
88
|
+
listeners.delete(listener);
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
dispose() {
|
|
92
|
+
disposed = true;
|
|
93
|
+
try {
|
|
94
|
+
socket?.close();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
/* noop */
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/sources/remote.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type Version = string;
|
|
2
|
+
export type Scope = Record<string, unknown>;
|
|
3
|
+
export interface PropsSchemaField {
|
|
4
|
+
type: "string" | "number" | "boolean" | "object" | "array" | "any";
|
|
5
|
+
required?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type PropsSchema = Record<string, PropsSchemaField>;
|
|
8
|
+
export interface Diagnostic {
|
|
9
|
+
severity: "error" | "warning";
|
|
10
|
+
message: string;
|
|
11
|
+
/** 1-based line in the source. */
|
|
12
|
+
line?: number;
|
|
13
|
+
/** 1-based column. */
|
|
14
|
+
column?: number;
|
|
15
|
+
/** TypeScript or Babel diagnostic code, e.g. "TS2304". */
|
|
16
|
+
code?: string;
|
|
17
|
+
/** A short snippet of the offending line, when available. */
|
|
18
|
+
snippet?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CompiledModuleOk {
|
|
21
|
+
name: string;
|
|
22
|
+
version: Version;
|
|
23
|
+
code: string;
|
|
24
|
+
/** Type-check warnings that didn't block compilation. */
|
|
25
|
+
warnings?: Diagnostic[];
|
|
26
|
+
error?: undefined;
|
|
27
|
+
removed?: undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface CompiledModuleError {
|
|
30
|
+
name: string;
|
|
31
|
+
version: Version;
|
|
32
|
+
code?: undefined;
|
|
33
|
+
error: {
|
|
34
|
+
kind: "compile" | "typecheck";
|
|
35
|
+
message: string;
|
|
36
|
+
stack?: string;
|
|
37
|
+
diagnostics?: Diagnostic[];
|
|
38
|
+
};
|
|
39
|
+
removed?: undefined;
|
|
40
|
+
}
|
|
41
|
+
/** A removal event broadcast over WS when DELETE /component/:name is called. */
|
|
42
|
+
export interface CompiledModuleRemoved {
|
|
43
|
+
name: string;
|
|
44
|
+
version: Version;
|
|
45
|
+
removed: true;
|
|
46
|
+
code?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
|
+
}
|
|
49
|
+
export type CompiledModule = CompiledModuleOk | CompiledModuleError | CompiledModuleRemoved;
|
|
50
|
+
export interface DynamicError {
|
|
51
|
+
kind: "compile" | "load" | "render";
|
|
52
|
+
name: string;
|
|
53
|
+
version: Version;
|
|
54
|
+
message: string;
|
|
55
|
+
stack?: string;
|
|
56
|
+
}
|
|
57
|
+
export type ComponentFactory = {
|
|
58
|
+
default?: unknown;
|
|
59
|
+
propsSchema?: PropsSchema;
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
export interface RegistryEntry {
|
|
63
|
+
name: string;
|
|
64
|
+
version: Version;
|
|
65
|
+
factory?: ComponentFactory;
|
|
66
|
+
error?: DynamicError;
|
|
67
|
+
}
|
|
68
|
+
export type RegistryListener = (entry: RegistryEntry) => void;
|
|
69
|
+
export interface SourceUpdate {
|
|
70
|
+
module: CompiledModule;
|
|
71
|
+
}
|
|
72
|
+
export interface Source {
|
|
73
|
+
/** Fetch the latest version of a single component (initial load). */
|
|
74
|
+
fetch(name: string): Promise<CompiledModule>;
|
|
75
|
+
/** Subscribe to updates for any component. Returns unsubscribe fn. */
|
|
76
|
+
subscribe(listener: (update: SourceUpdate) => void): () => void;
|
|
77
|
+
/** Optional disposal hook. */
|
|
78
|
+
dispose?(): void;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omriashke/dynamico-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Renderer-agnostic core for dynamico: source adapters, module loader, versioned component registry.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/omriaskenazi/dynamico.git",
|
|
23
|
+
"directory": "packages/core"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/omriaskenazi/dynamico#readme",
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"runtime",
|
|
29
|
+
"hot-swap",
|
|
30
|
+
"dynamic-components",
|
|
31
|
+
"dynamico"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/react": "^18.3.0",
|
|
41
|
+
"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
|
+
}
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
Source,
|
|
3
|
+
SourceUpdate,
|
|
4
|
+
CompiledModule,
|
|
5
|
+
CompiledModuleOk,
|
|
6
|
+
CompiledModuleError,
|
|
7
|
+
CompiledModuleRemoved,
|
|
8
|
+
ComponentFactory,
|
|
9
|
+
RegistryEntry,
|
|
10
|
+
RegistryListener,
|
|
11
|
+
Scope,
|
|
12
|
+
PropsSchema,
|
|
13
|
+
PropsSchemaField,
|
|
14
|
+
DynamicError,
|
|
15
|
+
Diagnostic,
|
|
16
|
+
Version,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
|
|
19
|
+
export { Registry } from "./registry.js";
|
|
20
|
+
export { loadModule } from "./loader.js";
|
|
21
|
+
export { createRemoteSource, type RemoteSourceOptions } from "./sources/remote.js";
|
|
22
|
+
export { validateProps, type PropsValidationResult } from "./propsSchema.js";
|
|
23
|
+
export {
|
|
24
|
+
createRuntime,
|
|
25
|
+
type RuntimeAPI,
|
|
26
|
+
type DynamicoProviderProps,
|
|
27
|
+
type DynamicComponentProps,
|
|
28
|
+
} from "./react/createRuntime.js";
|
package/src/loader.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Scope } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Execute a CommonJS-style code string in a controlled scope.
|
|
5
|
+
*
|
|
6
|
+
* The compiler (server-side Babel) emits code that uses `require(name)`,
|
|
7
|
+
* `module.exports`, and `exports`. We give it a `require` that:
|
|
8
|
+
* - returns the host-registered binding for any bare specifier in `scope`
|
|
9
|
+
* - delegates relative paths ("./Other", "../foo") to `requireRelative`,
|
|
10
|
+
* which the registry implements by looking up other dynamic components
|
|
11
|
+
* - throws otherwise (no arbitrary npm imports at runtime)
|
|
12
|
+
*
|
|
13
|
+
* This is the only place we call `new Function`. It runs in the same realm
|
|
14
|
+
* as the host app — v1 trusts the registry; sandboxing is a v2 concern.
|
|
15
|
+
*/
|
|
16
|
+
export function loadModule(
|
|
17
|
+
code: string,
|
|
18
|
+
scope: Scope,
|
|
19
|
+
requireRelative: (specifier: string) => unknown,
|
|
20
|
+
): unknown {
|
|
21
|
+
const moduleObj = { exports: {} as Record<string, unknown> };
|
|
22
|
+
const requireFn = (name: string): unknown => {
|
|
23
|
+
if (name.startsWith("./") || name.startsWith("../") || name.startsWith("/")) {
|
|
24
|
+
return requireRelative(name);
|
|
25
|
+
}
|
|
26
|
+
if (Object.prototype.hasOwnProperty.call(scope, name)) {
|
|
27
|
+
return scope[name];
|
|
28
|
+
}
|
|
29
|
+
throw new Error(
|
|
30
|
+
`dynamico: '${name}' is not in host scope. Add it via <DynamicoProvider scope={{...}}>.`,
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// The compiled body is just the function body; arguments are well-known.
|
|
35
|
+
// eslint-disable-next-line no-new-func
|
|
36
|
+
const fn = new Function("module", "exports", "require", code);
|
|
37
|
+
fn(moduleObj, moduleObj.exports, requireFn);
|
|
38
|
+
|
|
39
|
+
return moduleObj.exports;
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { PropsSchema } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface PropsValidationResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
errors: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const TYPE_CHECKS: Record<string, (v: unknown) => boolean> = {
|
|
9
|
+
string: (v) => typeof v === "string",
|
|
10
|
+
number: (v) => typeof v === "number" && !Number.isNaN(v),
|
|
11
|
+
boolean: (v) => typeof v === "boolean",
|
|
12
|
+
object: (v) => typeof v === "object" && v !== null && !Array.isArray(v),
|
|
13
|
+
array: (v) => Array.isArray(v),
|
|
14
|
+
any: () => true,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function validateProps(
|
|
18
|
+
schema: PropsSchema | undefined,
|
|
19
|
+
props: Record<string, unknown>,
|
|
20
|
+
): PropsValidationResult {
|
|
21
|
+
if (!schema) return { ok: true, errors: [] };
|
|
22
|
+
const errors: string[] = [];
|
|
23
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
24
|
+
const present = Object.prototype.hasOwnProperty.call(props, key);
|
|
25
|
+
if (!present) {
|
|
26
|
+
if (field.required) errors.push(`missing required prop '${key}' (${field.type})`);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const checker = TYPE_CHECKS[field.type] ?? TYPE_CHECKS.any!;
|
|
30
|
+
if (!checker(props[key])) {
|
|
31
|
+
errors.push(
|
|
32
|
+
`prop '${key}' expected ${field.type}, got ${describe(props[key])}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { ok: errors.length === 0, errors };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function describe(v: unknown): string {
|
|
40
|
+
if (v === null) return "null";
|
|
41
|
+
if (Array.isArray(v)) return "array";
|
|
42
|
+
return typeof v;
|
|
43
|
+
}
|