@omriashke/dynamico-core 0.1.9 → 0.1.11
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 +3 -0
- package/dist/bookPreview.d.ts.map +1 -1
- package/dist/bookPreview.js +5 -0
- package/dist/bookPreview.js.map +1 -1
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/esbuildFlatten.d.ts +15 -0
- package/dist/esbuildFlatten.d.ts.map +1 -0
- package/dist/esbuildFlatten.js +39 -0
- package/dist/esbuildFlatten.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +2 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +51 -14
- package/dist/loader.js.map +1 -1
- package/dist/node/bookConfig.d.ts +13 -0
- package/dist/node/bookConfig.d.ts.map +1 -0
- package/dist/node/bookConfig.js +54 -0
- package/dist/node/bookConfig.js.map +1 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2 -0
- package/dist/node/index.js.map +1 -0
- package/dist/packageScope.d.ts.map +1 -1
- package/dist/packageScope.js +15 -7
- package/dist/packageScope.js.map +1 -1
- package/dist/propsSchema.d.ts +2 -0
- package/dist/propsSchema.d.ts.map +1 -1
- package/dist/propsSchema.js +36 -0
- package/dist/propsSchema.js.map +1 -1
- package/dist/react/createRuntime.d.ts.map +1 -1
- package/dist/react/createRuntime.js +3 -21
- package/dist/react/createRuntime.js.map +1 -1
- package/dist/react/useRegistryModule.d.ts +4 -0
- package/dist/react/useRegistryModule.d.ts.map +1 -0
- package/dist/react/useRegistryModule.js +8 -0
- package/dist/react/useRegistryModule.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +35 -4
- package/dist/registry.js.map +1 -1
- package/dist/registryModule.d.ts.map +1 -1
- package/dist/registryModule.js +13 -2
- package/dist/registryModule.js.map +1 -1
- package/dist/relativeRequires.d.ts +12 -0
- package/dist/relativeRequires.d.ts.map +1 -1
- package/dist/relativeRequires.js +33 -0
- package/dist/relativeRequires.js.map +1 -1
- package/dist/sources/remote.d.ts +7 -8
- package/dist/sources/remote.d.ts.map +1 -1
- package/dist/sources/remote.js +73 -19
- package/dist/sources/remote.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/bookPreview.ts +7 -0
- package/src/constants.ts +9 -0
- package/src/esbuildFlatten.ts +47 -0
- package/src/index.ts +22 -2
- package/src/loader.ts +42 -14
- package/src/node/bookConfig.ts +63 -0
- package/src/node/index.ts +9 -0
- package/src/packageScope.ts +15 -7
- package/src/propsSchema.ts +35 -0
- package/src/react/createRuntime.tsx +3 -16
- package/src/react/useRegistryModule.ts +15 -0
- package/src/registry.ts +39 -4
- package/src/registryModule.ts +12 -3
- package/src/relativeRequires.ts +48 -0
- package/src/sources/remote.ts +72 -26
- package/src/types.ts +6 -0
package/src/sources/remote.ts
CHANGED
|
@@ -14,22 +14,21 @@ export interface RemoteSourceOptions {
|
|
|
14
14
|
/**
|
|
15
15
|
* Headers to send on every request. Called on each HTTP fetch and on each
|
|
16
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
17
|
*/
|
|
25
18
|
headers?: () => Record<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* When false, skip WebSocket entirely (HTTP fetch only).
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
webSocket?: boolean;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
27
|
* Talks to @omriashke/dynamico-registry (or any compatible server).
|
|
30
28
|
*
|
|
31
29
|
* GET {url}/component/:name -> CompiledModule (initial fetch)
|
|
32
|
-
* WS {wsUrl}/subscribe -> stream
|
|
30
|
+
* WS {wsUrl}/subscribe -> filtered push stream; client sends
|
|
31
|
+
* `{ op: "watch", names: [...] }`
|
|
33
32
|
*/
|
|
34
33
|
export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
35
34
|
const fetchImpl: typeof fetch =
|
|
@@ -52,18 +51,38 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
52
51
|
options.wsUrl ?? httpUrl.replace(/^http/, "ws") + "/subscribe";
|
|
53
52
|
|
|
54
53
|
const listeners = new Set<(u: SourceUpdate) => void>();
|
|
54
|
+
const watchedNames = new Set<string>();
|
|
55
|
+
const watchRefCounts = new Map<string, number>();
|
|
55
56
|
let socket: WebSocket | null = null;
|
|
56
57
|
let disposed = false;
|
|
58
|
+
let pendingWatchSync = false;
|
|
57
59
|
const reconnectMs = options.reconnectMs ?? 1000;
|
|
60
|
+
const useWebSocket = options.webSocket !== false;
|
|
61
|
+
const WS_OPEN = (WSCtor as unknown as { OPEN?: number }).OPEN ?? 1;
|
|
62
|
+
const WS_CONNECTING = (WSCtor as unknown as { CONNECTING?: number }).CONNECTING ?? 0;
|
|
63
|
+
|
|
64
|
+
function pushWatchSet(): void {
|
|
65
|
+
if (!useWebSocket || watchedNames.size === 0) return;
|
|
66
|
+
if (!socket || socket.readyState !== WS_OPEN) {
|
|
67
|
+
pendingWatchSync = true;
|
|
68
|
+
connect();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
pendingWatchSync = false;
|
|
72
|
+
try {
|
|
73
|
+
socket.send(JSON.stringify({ op: "watch", names: [...watchedNames] }));
|
|
74
|
+
} catch {
|
|
75
|
+
/* ignore */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
58
78
|
|
|
59
79
|
function connect(): void {
|
|
60
|
-
if (disposed) return;
|
|
80
|
+
if (disposed || !useWebSocket || watchedNames.size === 0) return;
|
|
81
|
+
if (socket && (socket.readyState === WS_OPEN || socket.readyState === WS_CONNECTING)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
61
84
|
try {
|
|
62
85
|
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
86
|
socket = hdrs
|
|
68
87
|
? new (WSCtor as unknown as new (
|
|
69
88
|
url: string,
|
|
@@ -71,10 +90,13 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
71
90
|
options?: { headers?: Record<string, string> },
|
|
72
91
|
) => WebSocket)(wsUrl, undefined, { headers: hdrs })
|
|
73
92
|
: new WSCtor(wsUrl);
|
|
74
|
-
} catch
|
|
93
|
+
} catch {
|
|
75
94
|
scheduleReconnect();
|
|
76
95
|
return;
|
|
77
96
|
}
|
|
97
|
+
socket.onopen = () => {
|
|
98
|
+
if (pendingWatchSync || watchedNames.size > 0) pushWatchSet();
|
|
99
|
+
};
|
|
78
100
|
socket.onmessage = (ev: MessageEvent) => {
|
|
79
101
|
try {
|
|
80
102
|
const data =
|
|
@@ -103,11 +125,40 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
function scheduleReconnect(): void {
|
|
106
|
-
if (disposed) return;
|
|
128
|
+
if (disposed || !useWebSocket || watchedNames.size === 0) return;
|
|
107
129
|
setTimeout(connect, reconnectMs);
|
|
108
130
|
}
|
|
109
131
|
|
|
110
|
-
|
|
132
|
+
function watch(name: string): () => void {
|
|
133
|
+
if (!useWebSocket) return () => undefined;
|
|
134
|
+
const next = (watchRefCounts.get(name) ?? 0) + 1;
|
|
135
|
+
watchRefCounts.set(name, next);
|
|
136
|
+
if (next === 1) {
|
|
137
|
+
watchedNames.add(name);
|
|
138
|
+
pushWatchSet();
|
|
139
|
+
}
|
|
140
|
+
let released = false;
|
|
141
|
+
return () => {
|
|
142
|
+
if (released) return;
|
|
143
|
+
released = true;
|
|
144
|
+
const count = (watchRefCounts.get(name) ?? 1) - 1;
|
|
145
|
+
if (count <= 0) {
|
|
146
|
+
watchRefCounts.delete(name);
|
|
147
|
+
watchedNames.delete(name);
|
|
148
|
+
pushWatchSet();
|
|
149
|
+
if (watchedNames.size === 0) {
|
|
150
|
+
try {
|
|
151
|
+
socket?.close();
|
|
152
|
+
} catch {
|
|
153
|
+
/* noop */
|
|
154
|
+
}
|
|
155
|
+
socket = null;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
watchRefCounts.set(name, count);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
111
162
|
|
|
112
163
|
return {
|
|
113
164
|
async fetch(name: string): Promise<CompiledModule> {
|
|
@@ -136,15 +187,7 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
136
187
|
listeners.delete(listener);
|
|
137
188
|
};
|
|
138
189
|
},
|
|
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
|
-
*/
|
|
190
|
+
watch,
|
|
148
191
|
async reportScope(keys, reportedBy) {
|
|
149
192
|
try {
|
|
150
193
|
const baseHeaders = options.headers?.() ?? {};
|
|
@@ -154,16 +197,19 @@ export function createRemoteSource(options: RemoteSourceOptions): Source {
|
|
|
154
197
|
body: JSON.stringify({ keys: [...keys], reportedBy }),
|
|
155
198
|
});
|
|
156
199
|
} catch {
|
|
157
|
-
/* best-effort
|
|
200
|
+
/* best-effort */
|
|
158
201
|
}
|
|
159
202
|
},
|
|
160
203
|
dispose() {
|
|
161
204
|
disposed = true;
|
|
205
|
+
watchedNames.clear();
|
|
206
|
+
watchRefCounts.clear();
|
|
162
207
|
try {
|
|
163
208
|
socket?.close();
|
|
164
209
|
} catch {
|
|
165
210
|
/* noop */
|
|
166
211
|
}
|
|
212
|
+
socket = null;
|
|
167
213
|
},
|
|
168
214
|
};
|
|
169
215
|
}
|
package/src/types.ts
CHANGED
|
@@ -103,6 +103,12 @@ export interface Source {
|
|
|
103
103
|
fetch(name: string): Promise<CompiledModule>;
|
|
104
104
|
/** Subscribe to updates for any component. Returns unsubscribe fn. */
|
|
105
105
|
subscribe(listener: (update: SourceUpdate) => void): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* Subscribe to live WebSocket updates for a component. Ref-counted; the
|
|
108
|
+
* socket connects lazily on the first watch and only receives pushes for
|
|
109
|
+
* watched names. Returns a release function.
|
|
110
|
+
*/
|
|
111
|
+
watch?(name: string): () => void;
|
|
106
112
|
/** Optional disposal hook. */
|
|
107
113
|
dispose?(): void;
|
|
108
114
|
/**
|