@zenbujs/core 0.0.9 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advice-config-BiYhyeTz.d.mts +41 -0
- package/dist/advice.d.mts +2 -36
- package/dist/advice.mjs +2 -2
- package/dist/{base-window-BxBZ2md_.mjs → base-window-4P-fVvC_.mjs} +37 -26
- package/dist/{build-config-Dzg2frpk.d.mts → build-config-GF0XzR_Y.d.mts} +42 -18
- package/dist/{build-config-pWdmLnrk.mjs → build-config-HMMqpXI1.mjs} +0 -8
- package/dist/{build-electron-Dsbb1EMl.mjs → build-electron-Di_FE62r.mjs} +10 -6
- package/dist/{build-source-d1J3shV8.mjs → build-source-BIaWpaxE.mjs} +2 -2
- package/dist/cli/bin.mjs +7 -7
- package/dist/cli/build.d.mts +1 -1
- package/dist/cli/build.mjs +1 -1
- package/dist/cli/resolve-config.mjs +6 -1
- package/dist/{cli-kL6mPgBE.mjs → cli-5jFDJWM4.mjs} +4 -4
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +2 -2
- package/dist/{db-Bc292RYo.mjs → db-MkOccvBS.mjs} +2 -2
- package/dist/db.d.mts +3 -2
- package/dist/db.mjs +2 -10
- package/dist/{dev-B2emj0HZ.mjs → dev-BSDyzO4j.mjs} +3 -9
- package/dist/env-bootstrap.d.mts +1 -1
- package/dist/events.d.mts +0 -9
- package/dist/{host-version-BIrF8tX7.mjs → host-version-Cog_odmD.mjs} +4 -3
- package/dist/{index-CVF768Xs.d.mts → index-C0mXKol5.d.mts} +188 -143
- package/dist/{index-DeDxePAa.d.mts → index-FaexRVl_.d.mts} +13 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/launcher.mjs +64 -6
- package/dist/link-Bt3LB_NW.mjs +586 -0
- package/dist/{load-config-C4Oe2qZO.mjs → load-config-C2XloBaQ.mjs} +68 -5
- package/dist/node-loader.mjs +1 -1
- package/dist/{publish-source-Dq2c0iOw.mjs → publish-source-v93eB9kA.mjs} +6 -2
- package/dist/react.d.mts +6 -6
- package/dist/react.mjs +4 -4
- package/dist/registry-generated.d.mts +19 -14
- package/dist/registry-saQDMUhT.d.mts +13 -0
- package/dist/registry.d.mts +1 -1
- package/dist/{reloader-B22UiNA2.mjs → reloader-CFzxYa67.mjs} +3 -3
- package/dist/{renderer-host-DD16MXhI.mjs → renderer-host-Cw38dSDe.mjs} +35 -24
- package/dist/{rpc-C4_NQmpT.mjs → rpc-Dg9zwZ33.mjs} +4 -4
- package/dist/rpc.d.mts +1 -1
- package/dist/rpc.mjs +1 -1
- package/dist/runtime-DYUONc3S.mjs +861 -0
- package/dist/{runtime-BQWntcOb.d.mts → runtime-fnPDZFYM.d.mts} +100 -3
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.mjs +2 -578
- package/dist/{schema-CjrMVk36.d.mts → schema-brYpUjYO.d.mts} +13 -25
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +9 -2
- package/dist/{server-CZLMF8Dj.mjs → server-BJ2ZC2z2.mjs} +2 -2
- package/dist/services/default.d.mts +1 -5
- package/dist/services/default.mjs +12 -16
- package/dist/services/index.d.mts +1 -1
- package/dist/services/index.mjs +7 -7
- package/dist/setup-gate.d.mts +1 -1
- package/dist/setup-gate.mjs +20 -12
- package/dist/{transport-F2hv_OEm.mjs → transport-Bqlv9pmJ.mjs} +1 -1
- package/dist/updater-Bs1Jtem6.mjs +480 -0
- package/dist/{vite-plugins-tt6KAtyE.mjs → vite-plugins-Df-cfldF.mjs} +2 -49
- package/dist/vite.d.mts +0 -5
- package/dist/vite.mjs +1 -1
- package/dist/{window-YFKvAM0l.mjs → window-DgB70qeZ.mjs} +113 -22
- package/dist/{write-DgIRjo23.mjs → write-7IfKa_nq.mjs} +1 -1
- package/dist/zenbu-bg-parse-CIyPkJOY.mjs +46 -0
- package/package.json +6 -5
- package/dist/advice-config-DXSIo0sg.mjs +0 -154
- package/dist/link-glX89NV5.mjs +0 -673
- package/dist/registry-CMp8FYgS.d.mts +0 -47
- package/dist/updater-BtB_Ki1r.mjs +0 -1011
- /package/dist/{config-BK78JDRI.mjs → config-DfciRzDu.mjs} +0 -0
- /package/dist/{env-bootstrap-rTs8KR3-.d.mts → env-bootstrap-UBug-4Kw.d.mts} +0 -0
- /package/dist/{index-C-ALz_SH.d.mts → index-CSMHYi3u.d.mts} +0 -0
- /package/dist/{index-ClXLQ1fw.d.mts → index-DJOHDG5e.d.mts} +0 -0
- /package/dist/{log-6rzaCV0I.mjs → log-BkRqDwwB.mjs} +0 -0
- /package/dist/{mirror-sync-pYU6f3-c.mjs → mirror-sync-snqh9kEp.mjs} +0 -0
- /package/dist/{monorepo-Dct-kkbQ.mjs → monorepo-CBzK3l2i.mjs} +0 -0
- /package/dist/{node-BhfLKYCi.mjs → node-BuHlEsE4.mjs} +0 -0
- /package/dist/{schema-Ca7SxXgS.mjs → schema-C6k0SroY.mjs} +0 -0
- /package/dist/{setup-gate-BQq0QgZH.d.mts → setup-gate-DkysEZQO.d.mts} +0 -0
- /package/dist/{src-Cven45mq.mjs → src-BpZAt9zL.mjs} +0 -0
- /package/dist/{trace-BaVg0rnY.mjs → trace-BVcQSD59.mjs} +0 -0
- /package/dist/{transform-BzrwkEdf.mjs → transform-czrcGnVV.mjs} +0 -0
package/dist/runtime.mjs
CHANGED
|
@@ -1,578 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
function readShutdownTimeoutMs() {
|
|
4
|
-
const raw = process.env.ZENBU_SHUTDOWN_TIMEOUT_MS;
|
|
5
|
-
if (!raw) return DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
6
|
-
const value = Number(raw);
|
|
7
|
-
return Number.isFinite(value) && value > 0 ? value : DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
8
|
-
}
|
|
9
|
-
function resolveDepKey(entry) {
|
|
10
|
-
if (typeof entry === "string") return entry;
|
|
11
|
-
return entry.key;
|
|
12
|
-
}
|
|
13
|
-
var Service = class {
|
|
14
|
-
static deps = {};
|
|
15
|
-
/**
|
|
16
|
-
* Define a Service base class. The returned abstract class has
|
|
17
|
-
* `static key`, `static deps`, and a typed `this.ctx` already set up;
|
|
18
|
-
* extend it and add your `evaluate()` body.
|
|
19
|
-
*
|
|
20
|
-
* export class WindowService extends Service.create({
|
|
21
|
-
* key: "window",
|
|
22
|
-
* deps: {
|
|
23
|
-
* baseWindow: BaseWindowService,
|
|
24
|
-
* http: HttpService,
|
|
25
|
-
* },
|
|
26
|
-
* }) {
|
|
27
|
-
* evaluate() {
|
|
28
|
-
* this.ctx.baseWindow // BaseWindowService
|
|
29
|
-
* this.ctx.http // HttpService
|
|
30
|
-
* }
|
|
31
|
-
* }
|
|
32
|
-
*
|
|
33
|
-
* `key` is a required field on the config object, so TypeScript errors
|
|
34
|
-
* if you forget it. `deps` is optional (defaults to no deps).
|
|
35
|
-
*
|
|
36
|
-
* For dynamic / optional access to another service, use
|
|
37
|
-
* `runtime.get(SomeService, cb)` instead of declaring it in `deps`.
|
|
38
|
-
*/
|
|
39
|
-
static create(config) {
|
|
40
|
-
const { key, deps } = config;
|
|
41
|
-
const resolvedDeps = deps ?? {};
|
|
42
|
-
class ConfiguredService extends Service {
|
|
43
|
-
static key = key;
|
|
44
|
-
static deps = resolvedDeps;
|
|
45
|
-
}
|
|
46
|
-
return ConfiguredService;
|
|
47
|
-
}
|
|
48
|
-
ctx;
|
|
49
|
-
/** @internal */
|
|
50
|
-
__setupCleanups = /* @__PURE__ */ new Map();
|
|
51
|
-
evaluate() {}
|
|
52
|
-
setup(key, fn) {
|
|
53
|
-
const existing = this.__setupCleanups.get(key);
|
|
54
|
-
if (existing) try {
|
|
55
|
-
existing("reload");
|
|
56
|
-
} catch (e) {
|
|
57
|
-
console.error(`[hot] setup cleanup "${key}" failed:`, e);
|
|
58
|
-
}
|
|
59
|
-
const cleanup = fn();
|
|
60
|
-
if (cleanup) this.__setupCleanups.set(key, cleanup);
|
|
61
|
-
else this.__setupCleanups.delete(key);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Run `fn` and return its result. Historically reported a boot-trace span;
|
|
65
|
-
* now a thin wrapper preserved for caller ergonomics inside service
|
|
66
|
-
* `evaluate()` bodies.
|
|
67
|
-
*/
|
|
68
|
-
trace(_name, fn, _meta) {
|
|
69
|
-
return Promise.resolve(fn());
|
|
70
|
-
}
|
|
71
|
-
traceSync(_name, fn, _meta) {
|
|
72
|
-
return fn();
|
|
73
|
-
}
|
|
74
|
-
/** @internal */
|
|
75
|
-
async __cleanupAllSetups(reason = "shutdown") {
|
|
76
|
-
for (const [key, cleanup] of this.__setupCleanups) try {
|
|
77
|
-
await cleanup(reason);
|
|
78
|
-
} catch (e) {
|
|
79
|
-
console.error(`[hot] setup cleanup "${key}" failed:`, e);
|
|
80
|
-
}
|
|
81
|
-
this.__setupCleanups.clear();
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const SERVICE_BASE_METHODS = new Set(Object.getOwnPropertyNames(Service.prototype));
|
|
85
|
-
var ServiceRuntime = class {
|
|
86
|
-
definitions = /* @__PURE__ */ new Map();
|
|
87
|
-
dependentsIndex = /* @__PURE__ */ new Map();
|
|
88
|
-
dirtyKeys = /* @__PURE__ */ new Set();
|
|
89
|
-
drainError = null;
|
|
90
|
-
draining = null;
|
|
91
|
-
registrationTokens = /* @__PURE__ */ new Map();
|
|
92
|
-
slots = /* @__PURE__ */ new Map();
|
|
93
|
-
onReconciledCallbacks = [];
|
|
94
|
-
subscribers = /* @__PURE__ */ new Map();
|
|
95
|
-
register(ServiceClass, importMeta) {
|
|
96
|
-
const hot = importMeta?.hot ?? null;
|
|
97
|
-
const slotKey = ServiceClass.key;
|
|
98
|
-
if (typeof slotKey !== "string" || slotKey.length === 0) {
|
|
99
|
-
const name = ServiceClass.name ?? "<anonymous>";
|
|
100
|
-
throw new Error(`[runtime] service "${name}" is missing \`static key\`. Define it via \`Service.create({ key: "...", ... })\`.`);
|
|
101
|
-
}
|
|
102
|
-
this.definitions.set(slotKey, ServiceClass);
|
|
103
|
-
const slot = this.slots.get(slotKey);
|
|
104
|
-
if (slot) slot.ServiceClass = ServiceClass;
|
|
105
|
-
else this.slots.set(slotKey, {
|
|
106
|
-
error: null,
|
|
107
|
-
instance: null,
|
|
108
|
-
ServiceClass,
|
|
109
|
-
status: "blocked"
|
|
110
|
-
});
|
|
111
|
-
this.rebuildDependentsIndex();
|
|
112
|
-
hot?.accept();
|
|
113
|
-
const token = Symbol(slotKey);
|
|
114
|
-
this.registrationTokens.set(slotKey, token);
|
|
115
|
-
hot?.prune?.(() => {
|
|
116
|
-
this.unregister(slotKey, token);
|
|
117
|
-
});
|
|
118
|
-
this.scheduleReconcile([slotKey]);
|
|
119
|
-
}
|
|
120
|
-
getAllKeys() {
|
|
121
|
-
return [...this.slots.keys()];
|
|
122
|
-
}
|
|
123
|
-
getSlot(key) {
|
|
124
|
-
return this.slots.get(key);
|
|
125
|
-
}
|
|
126
|
-
async whenIdle() {
|
|
127
|
-
while (this.draining) await this.draining;
|
|
128
|
-
if (this.drainError) {
|
|
129
|
-
const error = this.drainError;
|
|
130
|
-
this.drainError = null;
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async reloadAll() {
|
|
135
|
-
const keys = [...this.slots.keys()];
|
|
136
|
-
if (keys.length === 0) return;
|
|
137
|
-
await this.scheduleReconcile(keys);
|
|
138
|
-
await this.whenIdle();
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Reload a single service by key. No-op if the key is not registered.
|
|
142
|
-
* Used by infrastructure that watches resources outside dynohot's
|
|
143
|
-
* import-graph (e.g. the migrations directory watcher in DbService) and
|
|
144
|
-
* needs to nudge a specific service to re-evaluate without leaning on
|
|
145
|
-
* the devtools-only `__zenbu_dev__.reloadService` hook.
|
|
146
|
-
*/
|
|
147
|
-
async reload(key) {
|
|
148
|
-
if (!this.slots.has(key)) return;
|
|
149
|
-
await this.scheduleReconcile([key]);
|
|
150
|
-
await this.whenIdle();
|
|
151
|
-
}
|
|
152
|
-
async shutdown() {
|
|
153
|
-
try {
|
|
154
|
-
await this.whenIdle();
|
|
155
|
-
} catch (error) {
|
|
156
|
-
console.error("[hot] runtime idle wait failed during shutdown:", error);
|
|
157
|
-
}
|
|
158
|
-
const keys = [...this.slots.keys()].reverse();
|
|
159
|
-
for (const key of keys) await this.teardownService(key, { removeSlot: true });
|
|
160
|
-
this.definitions.clear();
|
|
161
|
-
this.dependentsIndex.clear();
|
|
162
|
-
this.dirtyKeys.clear();
|
|
163
|
-
this.registrationTokens.clear();
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Subscribe to a service. Behavior-subject style: the callback fires
|
|
167
|
-
* synchronously once with the current value (the live instance if ready,
|
|
168
|
-
* `undefined` otherwise), then again on every reconcile of that service —
|
|
169
|
-
* so you see the new instance after each HMR. Pass through `undefined`
|
|
170
|
-
* when the service tears down or unregisters.
|
|
171
|
-
*
|
|
172
|
-
* Returns an unsubscribe function. Always call it from a `setup()` cleanup
|
|
173
|
-
* (or wherever you'd otherwise leak callbacks across reloads).
|
|
174
|
-
*/
|
|
175
|
-
get(ref, cb) {
|
|
176
|
-
const key = ref.key;
|
|
177
|
-
let subs = this.subscribers.get(key);
|
|
178
|
-
if (!subs) {
|
|
179
|
-
subs = /* @__PURE__ */ new Set();
|
|
180
|
-
this.subscribers.set(key, subs);
|
|
181
|
-
}
|
|
182
|
-
const wrapped = cb;
|
|
183
|
-
subs.add(wrapped);
|
|
184
|
-
const slot = this.slots.get(key);
|
|
185
|
-
const current = slot?.status === "ready" && slot.instance ? slot.instance : void 0;
|
|
186
|
-
try {
|
|
187
|
-
cb(current);
|
|
188
|
-
} catch (e) {
|
|
189
|
-
console.error(`[hot] runtime.get subscriber for "${key}" threw:`, e);
|
|
190
|
-
}
|
|
191
|
-
return () => {
|
|
192
|
-
const set = this.subscribers.get(key);
|
|
193
|
-
if (!set) return;
|
|
194
|
-
set.delete(wrapped);
|
|
195
|
-
if (set.size === 0) this.subscribers.delete(key);
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
fireSubscribers(key) {
|
|
199
|
-
const subs = this.subscribers.get(key);
|
|
200
|
-
if (!subs || subs.size === 0) return;
|
|
201
|
-
const slot = this.slots.get(key);
|
|
202
|
-
const instance = slot?.status === "ready" && slot.instance ? slot.instance : void 0;
|
|
203
|
-
for (const cb of [...subs]) try {
|
|
204
|
-
cb(instance);
|
|
205
|
-
} catch (e) {
|
|
206
|
-
console.error(`[hot] runtime.get subscriber for "${key}" threw:`, e);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
buildRouter() {
|
|
210
|
-
const router = {};
|
|
211
|
-
for (const [slotKey, slot] of this.slots) {
|
|
212
|
-
if (slot.status !== "ready" || !slot.instance) continue;
|
|
213
|
-
const proto = Object.getPrototypeOf(slot.instance);
|
|
214
|
-
const methods = {};
|
|
215
|
-
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
216
|
-
if (SERVICE_BASE_METHODS.has(name)) continue;
|
|
217
|
-
if (name.startsWith("_")) continue;
|
|
218
|
-
const desc = Object.getOwnPropertyDescriptor(proto, name);
|
|
219
|
-
if (!desc || typeof desc.value !== "function") continue;
|
|
220
|
-
const instance = slot.instance;
|
|
221
|
-
methods[name] = (...args) => instance[name](...args);
|
|
222
|
-
}
|
|
223
|
-
if (Object.keys(methods).length > 0) router[slotKey] = methods;
|
|
224
|
-
}
|
|
225
|
-
return router;
|
|
226
|
-
}
|
|
227
|
-
onReconciled(cb) {
|
|
228
|
-
this.onReconciledCallbacks.push(cb);
|
|
229
|
-
return () => {
|
|
230
|
-
const idx = this.onReconciledCallbacks.indexOf(cb);
|
|
231
|
-
if (idx >= 0) this.onReconciledCallbacks.splice(idx, 1);
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
resolveDepSlot(depKey) {
|
|
235
|
-
return this.slots.get(depKey);
|
|
236
|
-
}
|
|
237
|
-
injectCtx(instance, ServiceClass) {
|
|
238
|
-
const deps = ServiceClass.deps ?? {};
|
|
239
|
-
const ctx = {};
|
|
240
|
-
for (const [name, entry] of Object.entries(deps)) {
|
|
241
|
-
const key = resolveDepKey(entry);
|
|
242
|
-
const slot = this.resolveDepSlot(key);
|
|
243
|
-
if (!slot || slot.status !== "ready" || !slot.instance) throw new Error(`Dependency "${key}" not ready for "${ServiceClass.key}"`);
|
|
244
|
-
ctx[name] = slot.instance;
|
|
245
|
-
}
|
|
246
|
-
instance.ctx = ctx;
|
|
247
|
-
}
|
|
248
|
-
rebuildDependentsIndex() {
|
|
249
|
-
const next = /* @__PURE__ */ new Map();
|
|
250
|
-
for (const [slotKey, ServiceClass] of this.definitions) {
|
|
251
|
-
const deps = ServiceClass.deps ?? {};
|
|
252
|
-
for (const entry of Object.values(deps)) {
|
|
253
|
-
const depKey = resolveDepKey(entry);
|
|
254
|
-
const dependents = next.get(depKey) ?? /* @__PURE__ */ new Set();
|
|
255
|
-
dependents.add(slotKey);
|
|
256
|
-
next.set(depKey, dependents);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
this.dependentsIndex = next;
|
|
260
|
-
}
|
|
261
|
-
getAffectedKeys(changedKeys) {
|
|
262
|
-
const affected = /* @__PURE__ */ new Set();
|
|
263
|
-
const visit = (key) => {
|
|
264
|
-
if (affected.has(key)) return;
|
|
265
|
-
affected.add(key);
|
|
266
|
-
for (const dependent of this.dependentsIndex.get(key) ?? []) visit(dependent);
|
|
267
|
-
};
|
|
268
|
-
for (const key of changedKeys) visit(key);
|
|
269
|
-
return [...affected].filter((key) => this.definitions.has(key));
|
|
270
|
-
}
|
|
271
|
-
listMissingDeps(ServiceClass) {
|
|
272
|
-
const deps = ServiceClass.deps ?? {};
|
|
273
|
-
const missing = [];
|
|
274
|
-
for (const entry of Object.values(deps)) {
|
|
275
|
-
const key = resolveDepKey(entry);
|
|
276
|
-
const slot = this.resolveDepSlot(key);
|
|
277
|
-
if (!slot || slot.status !== "ready" || !slot.instance) missing.push(key);
|
|
278
|
-
}
|
|
279
|
-
return missing;
|
|
280
|
-
}
|
|
281
|
-
ensureSlot(key, ServiceClass) {
|
|
282
|
-
const existing = this.slots.get(key);
|
|
283
|
-
if (existing) return existing;
|
|
284
|
-
const slot = {
|
|
285
|
-
error: null,
|
|
286
|
-
instance: null,
|
|
287
|
-
ServiceClass,
|
|
288
|
-
status: "blocked"
|
|
289
|
-
};
|
|
290
|
-
this.slots.set(key, slot);
|
|
291
|
-
return slot;
|
|
292
|
-
}
|
|
293
|
-
async teardownService(key, options = {}) {
|
|
294
|
-
const slot = this.slots.get(key);
|
|
295
|
-
if (!slot) return;
|
|
296
|
-
const instance = slot.instance;
|
|
297
|
-
slot.instance = null;
|
|
298
|
-
slot.status = "blocked";
|
|
299
|
-
slot.error = null;
|
|
300
|
-
if (instance) {
|
|
301
|
-
const reason = options.reason ?? "shutdown";
|
|
302
|
-
const timeoutMs = readShutdownTimeoutMs();
|
|
303
|
-
const cleanup = instance.__cleanupAllSetups(reason);
|
|
304
|
-
let timer = null;
|
|
305
|
-
const timeout = new Promise((resolve) => {
|
|
306
|
-
timer = setTimeout(() => {
|
|
307
|
-
console.error(`[hot] ${key} ${reason} cleanup timed out after ${timeoutMs}ms; forcing teardown`);
|
|
308
|
-
resolve();
|
|
309
|
-
}, timeoutMs);
|
|
310
|
-
timer.unref?.();
|
|
311
|
-
});
|
|
312
|
-
try {
|
|
313
|
-
await Promise.race([cleanup, timeout]);
|
|
314
|
-
} finally {
|
|
315
|
-
if (timer) clearTimeout(timer);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
if (options.removeSlot) this.slots.delete(key);
|
|
319
|
-
}
|
|
320
|
-
async unregister(key, token) {
|
|
321
|
-
if (this.registrationTokens.get(key) !== token) return;
|
|
322
|
-
this.registrationTokens.delete(key);
|
|
323
|
-
this.definitions.delete(key);
|
|
324
|
-
await this.teardownService(key, { removeSlot: true });
|
|
325
|
-
this.rebuildDependentsIndex();
|
|
326
|
-
this.fireSubscribers(key);
|
|
327
|
-
await this.scheduleReconcile([key]);
|
|
328
|
-
}
|
|
329
|
-
async scheduleReconcile(keys) {
|
|
330
|
-
for (const key of keys) this.dirtyKeys.add(key);
|
|
331
|
-
if (this.draining) return this.draining;
|
|
332
|
-
this.draining = (async () => {
|
|
333
|
-
try {
|
|
334
|
-
while (this.dirtyKeys.size > 0) {
|
|
335
|
-
const batch = [...this.dirtyKeys];
|
|
336
|
-
this.dirtyKeys.clear();
|
|
337
|
-
await this.reconcileBatch(batch);
|
|
338
|
-
}
|
|
339
|
-
} catch (error) {
|
|
340
|
-
this.drainError = error;
|
|
341
|
-
console.error("[hot] runtime reconcile failed:", error);
|
|
342
|
-
} finally {
|
|
343
|
-
this.draining = null;
|
|
344
|
-
if (this.dirtyKeys.size > 0) this.scheduleReconcile([]);
|
|
345
|
-
}
|
|
346
|
-
})();
|
|
347
|
-
return this.draining;
|
|
348
|
-
}
|
|
349
|
-
async reconcileBatch(changedKeys) {
|
|
350
|
-
const affectedKeys = this.getAffectedKeys(changedKeys);
|
|
351
|
-
if (affectedKeys.length === 0) return;
|
|
352
|
-
const affected = /* @__PURE__ */ new Map();
|
|
353
|
-
for (const key of affectedKeys) {
|
|
354
|
-
const ServiceClass = this.definitions.get(key);
|
|
355
|
-
if (ServiceClass) affected.set(key, ServiceClass);
|
|
356
|
-
}
|
|
357
|
-
const levels = this.topologicalLevels(affected);
|
|
358
|
-
const wasReady = /* @__PURE__ */ new Set();
|
|
359
|
-
for (const key of affectedKeys) if (this.slots.get(key)?.status === "ready") wasReady.add(key);
|
|
360
|
-
for (const level of [...levels].reverse()) await Promise.all(level.map((key) => this.teardownService(key, { reason: "reload" })));
|
|
361
|
-
for (const level of levels) await Promise.all(level.map((key) => this.reconcileKey(key, wasReady)));
|
|
362
|
-
for (const key of affectedKeys) this.fireSubscribers(key);
|
|
363
|
-
if (affectedKeys.length > 0) for (const cb of this.onReconciledCallbacks) try {
|
|
364
|
-
cb(affectedKeys);
|
|
365
|
-
} catch (e) {
|
|
366
|
-
console.error("[hot] onReconciled callback failed:", e);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
async reconcileKey(key, wasReady) {
|
|
370
|
-
const ServiceClass = this.definitions.get(key);
|
|
371
|
-
if (!ServiceClass) return;
|
|
372
|
-
const slot = this.ensureSlot(key, ServiceClass);
|
|
373
|
-
slot.ServiceClass = ServiceClass;
|
|
374
|
-
const missingDeps = this.listMissingDeps(ServiceClass);
|
|
375
|
-
if (missingDeps.length > 0) {
|
|
376
|
-
await this.teardownService(key, { reason: "reload" });
|
|
377
|
-
if (wasReady.has(key)) console.log(`[hot] ${key} waiting on: ${missingDeps.join(", ")}`);
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
const instance = new ServiceClass();
|
|
381
|
-
slot.instance = instance;
|
|
382
|
-
slot.status = "evaluating";
|
|
383
|
-
slot.error = null;
|
|
384
|
-
try {
|
|
385
|
-
this.injectCtx(instance, ServiceClass);
|
|
386
|
-
await instance.evaluate();
|
|
387
|
-
slot.status = "ready";
|
|
388
|
-
} catch (e) {
|
|
389
|
-
slot.status = "failed";
|
|
390
|
-
slot.error = e;
|
|
391
|
-
console.error(`[hot] ${key} failed to evaluate:`, e);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
topologicalLevels(services) {
|
|
395
|
-
const keys = new Set(services.keys());
|
|
396
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
397
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
398
|
-
for (const key of keys) {
|
|
399
|
-
inDegree.set(key, 0);
|
|
400
|
-
dependents.set(key, []);
|
|
401
|
-
}
|
|
402
|
-
for (const [slotKey, ServiceClass] of services) {
|
|
403
|
-
const deps = ServiceClass.deps ?? {};
|
|
404
|
-
let degree = 0;
|
|
405
|
-
for (const entry of Object.values(deps)) {
|
|
406
|
-
const depKey = resolveDepKey(entry);
|
|
407
|
-
if (keys.has(depKey)) {
|
|
408
|
-
degree++;
|
|
409
|
-
dependents.get(depKey).push(slotKey);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
inDegree.set(slotKey, degree);
|
|
413
|
-
}
|
|
414
|
-
const levels = [];
|
|
415
|
-
let queue = [...keys].filter((k) => inDegree.get(k) === 0);
|
|
416
|
-
while (queue.length > 0) {
|
|
417
|
-
levels.push(queue);
|
|
418
|
-
const next = [];
|
|
419
|
-
for (const key of queue) for (const dep of dependents.get(key)) {
|
|
420
|
-
const d = inDegree.get(dep) - 1;
|
|
421
|
-
inDegree.set(dep, d);
|
|
422
|
-
if (d === 0) next.push(dep);
|
|
423
|
-
}
|
|
424
|
-
queue = next;
|
|
425
|
-
}
|
|
426
|
-
const resolved = levels.flat();
|
|
427
|
-
if (resolved.length !== keys.size) {
|
|
428
|
-
const missing = [...keys].filter((k) => !resolved.includes(k));
|
|
429
|
-
throw new Error(`Circular dependency detected involving: ${missing.join(", ")}`);
|
|
430
|
-
}
|
|
431
|
-
return levels;
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
/**
|
|
435
|
-
* fixme: i don't think these commments make sense
|
|
436
|
-
*/
|
|
437
|
-
/**
|
|
438
|
-
* Devtools-only handles, attached to the global so they aren't part of the
|
|
439
|
-
* public `ServiceRuntime` autocomplete surface. Same shape React DevTools
|
|
440
|
-
* uses (`__REACT_DEVTOOLS_GLOBAL_HOOK__`): user code reading
|
|
441
|
-
* `runtime.<...>` never lands on these methods; you only find them if you
|
|
442
|
-
* specifically type `globalThis.__zenbu_dev__`. Kept here (next to the
|
|
443
|
-
* runtime that owns the state) because the hook reaches into private
|
|
444
|
-
* implementation details — `scheduleReconcile` stays `private` on the
|
|
445
|
-
* class; the cast lives inside the encapsulation boundary.
|
|
446
|
-
*/
|
|
447
|
-
function installDevHook(rt) {
|
|
448
|
-
const internals = rt;
|
|
449
|
-
globalThis.__zenbu_dev__ = { reloadService: async (key) => {
|
|
450
|
-
if (!internals.definitions.has(key)) throw new Error(`No service registered for key "${key}"`);
|
|
451
|
-
await internals.scheduleReconcile([key]);
|
|
452
|
-
await rt.whenIdle();
|
|
453
|
-
} };
|
|
454
|
-
}
|
|
455
|
-
const runtime = (() => {
|
|
456
|
-
const existing = globalThis.__zenbu_service_runtime__;
|
|
457
|
-
if (existing) {
|
|
458
|
-
Object.setPrototypeOf(existing, ServiceRuntime.prototype);
|
|
459
|
-
installDevHook(existing);
|
|
460
|
-
return existing;
|
|
461
|
-
}
|
|
462
|
-
const fresh = new ServiceRuntime();
|
|
463
|
-
globalThis.__zenbu_service_runtime__ = fresh;
|
|
464
|
-
installDevHook(fresh);
|
|
465
|
-
return fresh;
|
|
466
|
-
})();
|
|
467
|
-
function getPluginRegistry() {
|
|
468
|
-
const slot = globalThis;
|
|
469
|
-
if (!slot.__zenbu_plugin_registry__) slot.__zenbu_plugin_registry__ = {
|
|
470
|
-
plugins: /* @__PURE__ */ new Map(),
|
|
471
|
-
appEntrypoint: null,
|
|
472
|
-
splashPath: null,
|
|
473
|
-
subscribers: /* @__PURE__ */ new Set()
|
|
474
|
-
};
|
|
475
|
-
else if (!slot.__zenbu_plugin_registry__.subscribers) slot.__zenbu_plugin_registry__.subscribers = /* @__PURE__ */ new Set();
|
|
476
|
-
return slot.__zenbu_plugin_registry__;
|
|
477
|
-
}
|
|
478
|
-
function snapshotConfig(reg) {
|
|
479
|
-
return {
|
|
480
|
-
plugins: [...reg.plugins.values()],
|
|
481
|
-
appEntrypoint: reg.appEntrypoint,
|
|
482
|
-
splashPath: reg.splashPath
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
function notifySubscribers(reg) {
|
|
486
|
-
if (reg.subscribers.size === 0) return;
|
|
487
|
-
const snapshot = snapshotConfig(reg);
|
|
488
|
-
for (const cb of reg.subscribers) try {
|
|
489
|
-
cb(snapshot);
|
|
490
|
-
} catch (err) {
|
|
491
|
-
console.error("[zenbu config subscriber] threw:", err);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Register a plugin's resolved manifest. Idempotent — replaces any existing
|
|
496
|
-
* entry for the same `name`. Called by the loader-emitted barrel; user code
|
|
497
|
-
* normally does not call this directly.
|
|
498
|
-
*/
|
|
499
|
-
function registerPlugin(record) {
|
|
500
|
-
const reg = getPluginRegistry();
|
|
501
|
-
reg.plugins.set(record.name, record);
|
|
502
|
-
notifySubscribers(reg);
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Drop a plugin from the registry. Used when the loader regenerates the
|
|
506
|
-
* barrel and needs to clear stale entries.
|
|
507
|
-
*/
|
|
508
|
-
function unregisterPlugin(name) {
|
|
509
|
-
const reg = getPluginRegistry();
|
|
510
|
-
if (!reg.plugins.delete(name)) return;
|
|
511
|
-
notifySubscribers(reg);
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Replace the entire plugin set in one shot. The loader uses this on every
|
|
515
|
-
* barrel regeneration so removed plugins disappear cleanly.
|
|
516
|
-
*/
|
|
517
|
-
function replacePlugins(records) {
|
|
518
|
-
const reg = getPluginRegistry();
|
|
519
|
-
reg.plugins.clear();
|
|
520
|
-
for (const record of records) reg.plugins.set(record.name, record);
|
|
521
|
-
notifySubscribers(reg);
|
|
522
|
-
}
|
|
523
|
-
function getPlugins() {
|
|
524
|
-
return [...getPluginRegistry().plugins.values()];
|
|
525
|
-
}
|
|
526
|
-
function getPlugin(name) {
|
|
527
|
-
return getPluginRegistry().plugins.get(name);
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Set the renderer entrypoint directory + the absolute path to splash.html
|
|
531
|
-
* inside it. Called once by the loader-emitted barrel. Consumers
|
|
532
|
-
* (`view-registry`, `vite-plugins`, `setup-gate`'s splash window) read
|
|
533
|
-
* via `getAppEntrypoint()` / `getSplashPath()`.
|
|
534
|
-
*/
|
|
535
|
-
function registerAppEntrypoint(rendererDir, splashPath) {
|
|
536
|
-
const reg = getPluginRegistry();
|
|
537
|
-
reg.appEntrypoint = rendererDir;
|
|
538
|
-
reg.splashPath = splashPath;
|
|
539
|
-
notifySubscribers(reg);
|
|
540
|
-
}
|
|
541
|
-
function getAppEntrypoint() {
|
|
542
|
-
return getPluginRegistry().appEntrypoint;
|
|
543
|
-
}
|
|
544
|
-
function getSplashPath() {
|
|
545
|
-
return getPluginRegistry().splashPath;
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Snapshot of the current resolved config — plugins + entrypoints. Cheap;
|
|
549
|
-
* just walks the in-memory registry. The returned object is a fresh copy,
|
|
550
|
-
* safe to retain or pass through serialization boundaries.
|
|
551
|
-
*/
|
|
552
|
-
function getConfig() {
|
|
553
|
-
return snapshotConfig(getPluginRegistry());
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Subscribe to config changes. The callback fires:
|
|
557
|
-
* - immediately on subscription (with the current snapshot),
|
|
558
|
-
* - after each `replacePlugins(...)` / `registerPlugin(...)` /
|
|
559
|
-
* `registerAppEntrypoint(...)` call — i.e. every time the loader
|
|
560
|
-
* regenerates the plugin barrel after a `zenbu.config.ts` edit.
|
|
561
|
-
*
|
|
562
|
-
* Callback exceptions are logged and swallowed so one buggy subscriber
|
|
563
|
-
* can't break others. The returned function unsubscribes.
|
|
564
|
-
*/
|
|
565
|
-
function subscribeConfig(callback) {
|
|
566
|
-
const reg = getPluginRegistry();
|
|
567
|
-
reg.subscribers.add(callback);
|
|
568
|
-
try {
|
|
569
|
-
callback(snapshotConfig(reg));
|
|
570
|
-
} catch (err) {
|
|
571
|
-
console.error("[zenbu config subscriber] initial fire threw:", err);
|
|
572
|
-
}
|
|
573
|
-
return () => {
|
|
574
|
-
reg.subscribers.delete(callback);
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
//#endregion
|
|
578
|
-
export { Service, ServiceRuntime, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, registerAppEntrypoint, registerPlugin, replacePlugins, runtime, subscribeConfig, unregisterPlugin };
|
|
1
|
+
import { a as getConfig, c as getSplashPath, d as replacePlugins, f as runtime, i as getAppEntrypoint, l as registerAppEntrypoint, m as unregisterPlugin, n as Service, o as getPlugin, p as subscribeConfig, r as ServiceRuntime, s as getPlugins, t as CORE_PLUGIN_NAME, u as registerPlugin } from "./runtime-DYUONc3S.mjs";
|
|
2
|
+
export { CORE_PLUGIN_NAME, Service, ServiceRuntime, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, registerAppEntrypoint, registerPlugin, replacePlugins, runtime, subscribeConfig, unregisterPlugin };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { m as Field, v as Schema } from "./index-
|
|
1
|
+
import { m as Field, v as InferSchemaRoot, y as Schema } from "./index-FaexRVl_.mjs";
|
|
2
|
+
import * as _$zod from "zod";
|
|
2
3
|
import zod from "zod";
|
|
4
|
+
import * as _$zod_v4_core0 from "zod/v4/core";
|
|
3
5
|
|
|
4
6
|
//#region src/schema.d.ts
|
|
5
7
|
declare const viewRegistryEntrySchema: zod.ZodObject<{
|
|
@@ -29,34 +31,20 @@ declare const windowPrefsSchema: zod.ZodObject<{
|
|
|
29
31
|
}, zod.core.$strip>>;
|
|
30
32
|
}, zod.core.$strip>;
|
|
31
33
|
declare const schema: Schema<{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
label: zod.ZodOptional<zod.ZodString>;
|
|
42
|
-
}, zod.core.$strip>>;
|
|
43
|
-
}, zod.core.$strip>>, true>;
|
|
44
|
-
windowPrefs: Field<zod.ZodRecord<zod.ZodString, zod.ZodObject<{
|
|
45
|
-
lastKnownBounds: zod.ZodOptional<zod.ZodObject<{
|
|
46
|
-
x: zod.ZodNumber;
|
|
47
|
-
y: zod.ZodNumber;
|
|
48
|
-
width: zod.ZodNumber;
|
|
49
|
-
height: zod.ZodNumber;
|
|
50
|
-
}, zod.core.$strip>>;
|
|
51
|
-
}, zod.core.$strip>>, true>;
|
|
34
|
+
/**
|
|
35
|
+
*
|
|
36
|
+
* this needs to be changed, and we probably
|
|
37
|
+
* should have an api for reading in memory state
|
|
38
|
+
* on the service so we don't need to do these hacks
|
|
39
|
+
*
|
|
40
|
+
* */
|
|
41
|
+
lastKnownViewRegistry: Field<_$zod.ZodArray<_$zod.ZodType<unknown, unknown, _$zod_v4_core0.$ZodTypeInternals<unknown, unknown>>>, true>;
|
|
42
|
+
windowPrefs: Field<_$zod.ZodRecord<_$zod.ZodType<string | number | symbol, unknown, _$zod_v4_core0.$ZodTypeInternals<string | number | symbol, unknown>>, _$zod.ZodType<unknown, unknown, _$zod_v4_core0.$ZodTypeInternals<unknown, unknown>>>, true>;
|
|
52
43
|
}>;
|
|
53
44
|
type ViewRegistryEntry = zod.infer<typeof viewRegistryEntrySchema>;
|
|
54
45
|
type WindowBounds = zod.infer<typeof windowBoundsSchema>;
|
|
55
46
|
type WindowPrefs = zod.infer<typeof windowPrefsSchema>;
|
|
56
|
-
type SchemaRoot =
|
|
57
|
-
lastKnownViewRegistry: ViewRegistryEntry[];
|
|
58
|
-
windowPrefs: Record<string, WindowPrefs>;
|
|
59
|
-
};
|
|
47
|
+
type SchemaRoot = InferSchemaRoot<typeof schema>;
|
|
60
48
|
type CoreSchema = typeof schema.shape;
|
|
61
49
|
//#endregion
|
|
62
50
|
export { WindowPrefs as a, WindowBounds as i, SchemaRoot as n, schema as o, ViewRegistryEntry as r, CoreSchema as t };
|
package/dist/schema.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as WindowPrefs, i as WindowBounds, n as SchemaRoot, o as schema, r as ViewRegistryEntry, t as CoreSchema } from "./schema-
|
|
2
|
-
export { CoreSchema, SchemaRoot, ViewRegistryEntry, WindowBounds, WindowPrefs, schema };
|
|
1
|
+
import { a as WindowPrefs, i as WindowBounds, n as SchemaRoot, o as schema, r as ViewRegistryEntry, t as CoreSchema } from "./schema-brYpUjYO.mjs";
|
|
2
|
+
export { CoreSchema, SchemaRoot, ViewRegistryEntry, WindowBounds, WindowPrefs, schema as default, schema };
|
package/dist/schema.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as f, i as createSchema } from "./schema-
|
|
1
|
+
import { a as f, i as createSchema } from "./schema-C6k0SroY.mjs";
|
|
2
2
|
import zod from "zod";
|
|
3
3
|
//#region src/schema.ts
|
|
4
4
|
const viewRegistryEntrySchema = zod.object({
|
|
@@ -21,8 +21,15 @@ const windowBoundsSchema = zod.object({
|
|
|
21
21
|
});
|
|
22
22
|
const windowPrefsSchema = zod.object({ lastKnownBounds: windowBoundsSchema.optional() });
|
|
23
23
|
const schema = createSchema({
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* this needs to be changed, and we probably
|
|
27
|
+
* should have an api for reading in memory state
|
|
28
|
+
* on the service so we don't need to do these hacks
|
|
29
|
+
*
|
|
30
|
+
* */
|
|
24
31
|
lastKnownViewRegistry: f.array(viewRegistryEntrySchema).default([]),
|
|
25
32
|
windowPrefs: f.record(zod.string(), windowPrefsSchema).default({})
|
|
26
33
|
});
|
|
27
34
|
//#endregion
|
|
28
|
-
export { schema };
|
|
35
|
+
export { schema as default, schema };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as __exportAll } from "./chunk-DsiFFCwN.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { t as createLogger } from "./log-
|
|
2
|
+
import { f as runtime, n as Service } from "./runtime-DYUONc3S.mjs";
|
|
3
|
+
import { t as createLogger } from "./log-BkRqDwwB.mjs";
|
|
4
4
|
import http from "node:http";
|
|
5
5
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
6
6
|
import { WebSocketServer } from "ws";
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
//#region src/services/default.d.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* `./index.ts` (which statically re-exports the service classes for
|
|
5
|
-
* `Service.withDeps(...)` consumers) so that `setup-gate` can import it
|
|
6
|
-
* without eagerly loading every service module before `setupGate()` has
|
|
7
|
-
* had a chance to bootstrap env vars and `process.chdir(projectRoot)`.
|
|
3
|
+
* parallelize? i forgor is it serial no matter what?
|
|
8
4
|
*/
|
|
9
5
|
declare function defaultServices(): Promise<void>;
|
|
10
6
|
//#endregion
|