@zenbujs/core 0.0.5 → 0.0.8

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.
Files changed (72) hide show
  1. package/dist/{advice-config-QYB2qEd_.mjs → advice-config-DXSIo0sg.mjs} +40 -39
  2. package/dist/advice.d.mts +8 -8
  3. package/dist/advice.mjs +2 -2
  4. package/dist/{base-window-BbFRRhKP.mjs → base-window-BxBZ2md_.mjs} +51 -7
  5. package/dist/{transforms-CuTODvDx.d.mts → build-config-Dzg2frpk.d.mts} +98 -28
  6. package/dist/build-config-pWdmLnrk.mjs +53 -0
  7. package/dist/{build-electron-CNJ0dLND.mjs → build-electron-Dsbb1EMl.mjs} +308 -120
  8. package/dist/{build-source-C2puqEVr.mjs → build-source-d1J3shV8.mjs} +62 -27
  9. package/dist/cli/bin.mjs +7 -7
  10. package/dist/cli/build.d.mts +2 -2
  11. package/dist/cli/build.mjs +2 -3
  12. package/dist/cli/resolve-config.mjs +1 -1
  13. package/dist/{cli-C3R1LBMY.mjs → cli-kL6mPgBE.mjs} +2 -2
  14. package/dist/config.d.mts +3 -3
  15. package/dist/config.mjs +2 -3
  16. package/dist/{db-xjvahRFJ.mjs → db-Bc292RYo.mjs} +2 -2
  17. package/dist/db.d.mts +1 -1
  18. package/dist/dev-B2emj0HZ.mjs +301 -0
  19. package/dist/env-bootstrap.d.mts +1 -1
  20. package/dist/events.d.mts +19 -0
  21. package/dist/events.mjs +1 -0
  22. package/dist/host-version-BIrF8tX7.mjs +65 -0
  23. package/dist/index-w5QyDjuf.d.mts +780 -0
  24. package/dist/index.d.mts +5 -6
  25. package/dist/index.mjs +2 -2
  26. package/dist/installing-preload.cjs +60 -0
  27. package/dist/launcher.mjs +2615 -122
  28. package/dist/{link-c0_aLWQ3.mjs → link-glX89NV5.mjs} +215 -89
  29. package/dist/{load-config-xMf2wxH8.mjs → load-config-C4Oe2qZO.mjs} +5 -1
  30. package/dist/loaders/zenbu.mjs +102 -0
  31. package/dist/node-loader.mjs +1 -1
  32. package/dist/{publish-source-Dill72NS.mjs → publish-source-Dq2c0iOw.mjs} +2 -2
  33. package/dist/react.d.mts +55 -6
  34. package/dist/react.mjs +116 -5
  35. package/dist/registry-CMp8FYgS.d.mts +47 -0
  36. package/dist/registry-generated.d.mts +26 -0
  37. package/dist/registry-generated.mjs +1 -0
  38. package/dist/registry.d.mts +2 -2
  39. package/dist/{reloader-DzEO8kJr.mjs → reloader-B22UiNA2.mjs} +2 -4
  40. package/dist/{renderer-host-Cau9JK0v.mjs → renderer-host-DD16MXhI.mjs} +152 -43
  41. package/dist/{rpc-JfGv-Wuw.mjs → rpc-C4_NQmpT.mjs} +5 -4
  42. package/dist/{runtime-pCeVzj--.d.mts → runtime-BQWntcOb.d.mts} +85 -48
  43. package/dist/runtime.d.mts +2 -2
  44. package/dist/runtime.mjs +139 -83
  45. package/dist/{schema-Dl85YjXW.d.mts → schema-CjrMVk36.d.mts} +3 -3
  46. package/dist/schema.d.mts +1 -1
  47. package/dist/schema.mjs +1 -1
  48. package/dist/{server-y3PPbh3l.mjs → server-CZLMF8Dj.mjs} +1 -3
  49. package/dist/services/default.d.mts +3 -3
  50. package/dist/services/default.mjs +14 -13
  51. package/dist/services/index.d.mts +2 -280
  52. package/dist/services/index.mjs +8 -7
  53. package/dist/setup-gate.d.mts +1 -1
  54. package/dist/setup-gate.mjs +117 -24
  55. package/dist/{transform-CmFYPmt8.mjs → transform-BzrwkEdf.mjs} +22 -916
  56. package/dist/updater-DCkz9M1c.mjs +1008 -0
  57. package/dist/{vite-plugins-Do7liKi_.mjs → vite-plugins-tt6KAtyE.mjs} +26 -25
  58. package/dist/vite.d.mts +3 -3
  59. package/dist/vite.mjs +1 -1
  60. package/dist/{window-o2NGUsIb.mjs → window-YFKvAM0l.mjs} +30 -16
  61. package/package.json +15 -2
  62. package/dist/build-config-C3a-o3_B.mjs +0 -23
  63. package/dist/dev-Dazhu66l.mjs +0 -85
  64. package/dist/registry-eX6e2oql.d.mts +0 -61
  65. package/dist/transforms-htxfTwsY.mjs +0 -47
  66. /package/dist/{config-DXRCDUxG.mjs → config-BK78JDRI.mjs} +0 -0
  67. /package/dist/{env-bootstrap-DW2hVhSO.d.mts → env-bootstrap-rTs8KR3-.d.mts} +0 -0
  68. /package/dist/{index-M_lSNBrq.d.mts → index-DeDxePAa.d.mts} +0 -0
  69. /package/dist/{mirror-sync-PDzxhf1w.mjs → mirror-sync-pYU6f3-c.mjs} +0 -0
  70. /package/dist/{monorepo-3avKJwzJ.mjs → monorepo-Dct-kkbQ.mjs} +0 -0
  71. /package/dist/{node-_8xShqxr.mjs → node-BhfLKYCi.mjs} +0 -0
  72. /package/dist/{setup-gate-Dcy8gGPJ.d.mts → setup-gate-BQq0QgZH.d.mts} +0 -0
@@ -5,22 +5,74 @@ type SetupFn = () => SetupCleanup;
5
5
  interface ServiceSlot {
6
6
  error: unknown | null;
7
7
  instance: Service | null;
8
- ServiceClass: typeof Service;
8
+ ServiceClass: ServiceConstructor;
9
9
  status: "blocked" | "evaluating" | "ready" | "failed";
10
10
  }
11
- type AnyServiceClass = abstract new (...args: any[]) => Service;
12
- type DepRef = AnyServiceClass | string;
13
- type OptionalDep<R extends DepRef = DepRef> = {
14
- __optional: true;
15
- ref: R;
11
+ type AnyServiceClass = (abstract new (...args: any[]) => Service) & {
12
+ key: string;
16
13
  };
17
- type DepEntry = DepRef | OptionalDep;
18
- type DepInstance<D> = D extends OptionalDep<infer R> ? R extends AnyServiceClass ? InstanceType<R> | undefined : unknown : D extends AnyServiceClass ? InstanceType<D> : unknown;
14
+ type DepEntry = AnyServiceClass | string;
15
+ type DepInstance<D> = D extends AnyServiceClass ? InstanceType<D> : unknown;
19
16
  type ResolveCtx<TDeps> = { [K in keyof TDeps]: DepInstance<TDeps[K]> };
20
- declare function optional<R extends DepRef>(ref: R): OptionalDep<R>;
17
+ /**
18
+ * A concrete service class registered with the runtime. Always has a
19
+ * `static key` (set by `Service.create`) and an optional `static deps`
20
+ * map. Used as the parameter type of `runtime.register`.
21
+ */
22
+ type ServiceConstructor = (new (...args: any[]) => Service) & {
23
+ key: string;
24
+ deps?: Record<string, DepEntry>;
25
+ };
21
26
  declare abstract class Service {
22
- static key: string;
23
27
  static deps: Record<string, DepEntry>;
28
+ /**
29
+ * Define a Service base class. The returned abstract class has
30
+ * `static key`, `static deps`, and a typed `this.ctx` already set up;
31
+ * extend it and add your `evaluate()` body.
32
+ *
33
+ * export class WindowService extends Service.create({
34
+ * key: "window",
35
+ * deps: {
36
+ * baseWindow: BaseWindowService,
37
+ * http: HttpService,
38
+ * },
39
+ * }) {
40
+ * evaluate() {
41
+ * this.ctx.baseWindow // BaseWindowService
42
+ * this.ctx.http // HttpService
43
+ * }
44
+ * }
45
+ *
46
+ * `key` is a required field on the config object, so TypeScript errors
47
+ * if you forget it. `deps` is optional (defaults to no deps).
48
+ *
49
+ * For dynamic / optional access to another service, use
50
+ * `runtime.get(SomeService, cb)` instead of declaring it in `deps`.
51
+ */
52
+ static create<TKey extends string, TDeps extends Record<string, DepEntry> = {}>(config: {
53
+ key: TKey;
54
+ deps?: TDeps;
55
+ }): (abstract new () => {
56
+ ctx: ResolveCtx<TDeps>; /** @internal */
57
+ __setupCleanups: Map<string, (reason: CleanupReason) => void | Promise<void>>;
58
+ evaluate(): void | Promise<void>;
59
+ setup(key: string, fn: SetupFn): void;
60
+ /**
61
+ * Run `fn` and return its result. Historically reported a boot-trace span;
62
+ * now a thin wrapper preserved for caller ergonomics inside service
63
+ * `evaluate()` bodies.
64
+ */
65
+ trace<T>(_name: string, fn: () => T | Promise<T>, _meta?: Record<string, unknown>): Promise<T>;
66
+ traceSync<T>(_name: string, fn: () => T, _meta?: Record<string, unknown>): T; /** @internal */
67
+ __cleanupAllSetups(reason?: CleanupReason): Promise<void>;
68
+ }) & {
69
+ key: TKey;
70
+ deps: Record<string, DepEntry>;
71
+ create<TKey extends string, TDeps extends Record<string, DepEntry> = {}>(config: {
72
+ key: TKey;
73
+ deps?: TDeps;
74
+ }): /*elided*/any;
75
+ };
24
76
  ctx: any;
25
77
  /** @internal */
26
78
  __setupCleanups: Map<string, (reason: CleanupReason) => void | Promise<void>>;
@@ -36,41 +88,6 @@ declare abstract class Service {
36
88
  /** @internal */
37
89
  __cleanupAllSetups(reason?: CleanupReason): Promise<void>;
38
90
  }
39
- /**
40
- * Declare a Service base class with typed deps. The returned class has
41
- * `static deps = <your map>` already set, and `this.ctx` is auto-typed
42
- * from the dep classes — no `declare ctx` needed in the subclass.
43
- *
44
- * export class WindowService extends serviceWithDeps({
45
- * baseWindow: BaseWindowService,
46
- * http: HttpService,
47
- * }) {
48
- * static key = "window"
49
- * evaluate() {
50
- * this.ctx.baseWindow // BaseWindowService
51
- * this.ctx.http // HttpService
52
- * }
53
- * }
54
- *
55
- * `optional(SomeService)` is supported and produces `Instance | undefined`.
56
- */
57
- declare function serviceWithDeps<TDeps extends Record<string, DepEntry>>(deps: TDeps): (abstract new () => {
58
- ctx: ResolveCtx<TDeps>; /** @internal */
59
- __setupCleanups: Map<string, (reason: CleanupReason) => void | Promise<void>>;
60
- evaluate(): void | Promise<void>;
61
- setup(key: string, fn: SetupFn): void;
62
- /**
63
- * Run `fn` and return its result. Historically reported a boot-trace span;
64
- * now a thin wrapper preserved for caller ergonomics inside service
65
- * `evaluate()` bodies.
66
- */
67
- trace<T>(_name: string, fn: () => T | Promise<T>, _meta?: Record<string, unknown>): Promise<T>;
68
- traceSync<T>(_name: string, fn: () => T, _meta?: Record<string, unknown>): T; /** @internal */
69
- __cleanupAllSetups(reason?: CleanupReason): Promise<void>;
70
- }) & {
71
- deps: Record<string, DepEntry>;
72
- key: string;
73
- };
74
91
  declare class ServiceRuntime {
75
92
  private definitions;
76
93
  private dependentsIndex;
@@ -80,15 +97,35 @@ declare class ServiceRuntime {
80
97
  private registrationTokens;
81
98
  private slots;
82
99
  private onReconciledCallbacks;
83
- register(ServiceClass: typeof Service, importMeta?: ImportMeta | null): void;
100
+ private subscribers;
101
+ register(ServiceClass: ServiceConstructor, importMeta?: ImportMeta | null): void;
84
102
  getAllKeys(): string[];
85
103
  getSlot(key: string): ServiceSlot | undefined;
86
104
  whenIdle(): Promise<void>;
87
105
  reloadAll(): Promise<void>;
106
+ /**
107
+ * Reload a single service by key. No-op if the key is not registered.
108
+ * Used by infrastructure that watches resources outside dynohot's
109
+ * import-graph (e.g. the migrations directory watcher in DbService) and
110
+ * needs to nudge a specific service to re-evaluate without leaning on
111
+ * the devtools-only `__zenbu_dev__.reloadService` hook.
112
+ */
113
+ reload(key: string): Promise<void>;
88
114
  shutdown(): Promise<void>;
115
+ /**
116
+ * Subscribe to a service. Behavior-subject style: the callback fires
117
+ * synchronously once with the current value (the live instance if ready,
118
+ * `undefined` otherwise), then again on every reconcile of that service —
119
+ * so you see the new instance after each HMR. Pass through `undefined`
120
+ * when the service tears down or unregisters.
121
+ *
122
+ * Returns an unsubscribe function. Always call it from a `setup()` cleanup
123
+ * (or wherever you'd otherwise leak callbacks across reloads).
124
+ */
89
125
  get<T extends Service>(ref: {
90
126
  key: string;
91
- }): T;
127
+ }, cb: (instance: T | undefined) => void): () => void;
128
+ private fireSubscribers;
92
129
  buildRouter(): Record<string, Record<string, (...args: any[]) => any>>;
93
130
  onReconciled(cb: (changedKeys: string[]) => void): () => void;
94
131
  private resolveDepSlot;
@@ -178,4 +215,4 @@ declare function getConfig(): ConfigSnapshot;
178
215
  */
179
216
  declare function subscribeConfig(callback: (snapshot: ConfigSnapshot) => void): () => void;
180
217
  //#endregion
181
- export { subscribeConfig as _, ServiceRuntime as a, getPlugin as c, optional as d, registerAppEntrypoint as f, serviceWithDeps as g, runtime as h, Service as i, getPlugins as l, replacePlugins as m, ConfigSnapshot as n, getAppEntrypoint as o, registerPlugin as p, PluginRecord as r, getConfig as s, CleanupReason as t, getSplashPath as u, unregisterPlugin as v };
218
+ export { unregisterPlugin as _, ServiceConstructor as a, getConfig as c, getSplashPath as d, registerAppEntrypoint as f, subscribeConfig as g, runtime as h, Service as i, getPlugin as l, replacePlugins as m, ConfigSnapshot as n, ServiceRuntime as o, registerPlugin as p, PluginRecord as r, getAppEntrypoint as s, CleanupReason as t, getPlugins as u };
@@ -1,2 +1,2 @@
1
- import { _ as subscribeConfig, a as ServiceRuntime, c as getPlugin, d as optional, f as registerAppEntrypoint, g as serviceWithDeps, h as runtime, i as Service, l as getPlugins, m as replacePlugins, n as ConfigSnapshot, o as getAppEntrypoint, p as registerPlugin, r as PluginRecord, s as getConfig, t as CleanupReason, u as getSplashPath, v as unregisterPlugin } from "./runtime-pCeVzj--.mjs";
2
- export { CleanupReason, ConfigSnapshot, PluginRecord, Service, ServiceRuntime, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, optional, registerAppEntrypoint, registerPlugin, replacePlugins, runtime, serviceWithDeps, subscribeConfig, unregisterPlugin };
1
+ import { _ as unregisterPlugin, a as ServiceConstructor, c as getConfig, d as getSplashPath, f as registerAppEntrypoint, g as subscribeConfig, h as runtime, i as Service, l as getPlugin, m as replacePlugins, n as ConfigSnapshot, o as ServiceRuntime, p as registerPlugin, r as PluginRecord, s as getAppEntrypoint, t as CleanupReason, u as getPlugins } from "./runtime-BQWntcOb.mjs";
2
+ export { CleanupReason, ConfigSnapshot, PluginRecord, Service, ServiceConstructor, ServiceRuntime, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, registerAppEntrypoint, registerPlugin, replacePlugins, runtime, subscribeConfig, unregisterPlugin };
package/dist/runtime.mjs CHANGED
@@ -1,30 +1,50 @@
1
1
  //#region src/runtime.ts
2
- function optional(ref) {
3
- return {
4
- __optional: true,
5
- ref
6
- };
2
+ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 5e3;
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;
7
8
  }
8
- function resolveDep(entry) {
9
- if (typeof entry === "string") return {
10
- key: entry,
11
- optional: false
12
- };
13
- if (typeof entry === "object" && entry !== null && "__optional" in entry) {
14
- const ref = entry.ref;
15
- return {
16
- key: typeof ref === "string" ? ref : ref.key,
17
- optional: true
18
- };
19
- }
20
- return {
21
- key: entry.key,
22
- optional: false
23
- };
9
+ function resolveDepKey(entry) {
10
+ if (typeof entry === "string") return entry;
11
+ return entry.key;
24
12
  }
25
13
  var Service = class {
26
- static key;
27
- static deps;
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
+ }
28
48
  ctx;
29
49
  /** @internal */
30
50
  __setupCleanups = /* @__PURE__ */ new Map();
@@ -61,30 +81,6 @@ var Service = class {
61
81
  this.__setupCleanups.clear();
62
82
  }
63
83
  };
64
- /**
65
- * Declare a Service base class with typed deps. The returned class has
66
- * `static deps = <your map>` already set, and `this.ctx` is auto-typed
67
- * from the dep classes — no `declare ctx` needed in the subclass.
68
- *
69
- * export class WindowService extends serviceWithDeps({
70
- * baseWindow: BaseWindowService,
71
- * http: HttpService,
72
- * }) {
73
- * static key = "window"
74
- * evaluate() {
75
- * this.ctx.baseWindow // BaseWindowService
76
- * this.ctx.http // HttpService
77
- * }
78
- * }
79
- *
80
- * `optional(SomeService)` is supported and produces `Instance | undefined`.
81
- */
82
- function serviceWithDeps(deps) {
83
- class ServiceWithDeps extends Service {
84
- static deps = deps;
85
- }
86
- return ServiceWithDeps;
87
- }
88
84
  const SERVICE_BASE_METHODS = new Set(Object.getOwnPropertyNames(Service.prototype));
89
85
  var ServiceRuntime = class {
90
86
  definitions = /* @__PURE__ */ new Map();
@@ -95,11 +91,14 @@ var ServiceRuntime = class {
95
91
  registrationTokens = /* @__PURE__ */ new Map();
96
92
  slots = /* @__PURE__ */ new Map();
97
93
  onReconciledCallbacks = [];
94
+ subscribers = /* @__PURE__ */ new Map();
98
95
  register(ServiceClass, importMeta) {
99
96
  const hot = importMeta?.hot ?? null;
100
- const baseKey = ServiceClass.key;
101
- if (!baseKey) throw new Error("Service must have a static key property");
102
- const slotKey = baseKey;
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
+ }
103
102
  this.definitions.set(slotKey, ServiceClass);
104
103
  const slot = this.slots.get(slotKey);
105
104
  if (slot) slot.ServiceClass = ServiceClass;
@@ -138,6 +137,18 @@ var ServiceRuntime = class {
138
137
  await this.scheduleReconcile(keys);
139
138
  await this.whenIdle();
140
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
+ }
141
152
  async shutdown() {
142
153
  try {
143
154
  await this.whenIdle();
@@ -151,10 +162,49 @@ var ServiceRuntime = class {
151
162
  this.dirtyKeys.clear();
152
163
  this.registrationTokens.clear();
153
164
  }
154
- get(ref) {
155
- const slot = this.slots.get(ref.key);
156
- if (!slot || slot.status !== "ready" || !slot.instance) throw new Error(`Service "${ref.key}" not ready. Is it registered and evaluated?`);
157
- return slot.instance;
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
+ }
158
208
  }
159
209
  buildRouter() {
160
210
  const router = {};
@@ -188,15 +238,9 @@ var ServiceRuntime = class {
188
238
  const deps = ServiceClass.deps ?? {};
189
239
  const ctx = {};
190
240
  for (const [name, entry] of Object.entries(deps)) {
191
- const { key, optional: isOptional } = resolveDep(entry);
241
+ const key = resolveDepKey(entry);
192
242
  const slot = this.resolveDepSlot(key);
193
- if (!slot || slot.status !== "ready" || !slot.instance) {
194
- if (isOptional) {
195
- ctx[name] = void 0;
196
- continue;
197
- }
198
- throw new Error(`Dependency "${key}" not ready for "${ServiceClass.key}"`);
199
- }
243
+ if (!slot || slot.status !== "ready" || !slot.instance) throw new Error(`Dependency "${key}" not ready for "${ServiceClass.key}"`);
200
244
  ctx[name] = slot.instance;
201
245
  }
202
246
  instance.ctx = ctx;
@@ -206,7 +250,7 @@ var ServiceRuntime = class {
206
250
  for (const [slotKey, ServiceClass] of this.definitions) {
207
251
  const deps = ServiceClass.deps ?? {};
208
252
  for (const entry of Object.values(deps)) {
209
- const { key: depKey } = resolveDep(entry);
253
+ const depKey = resolveDepKey(entry);
210
254
  const dependents = next.get(depKey) ?? /* @__PURE__ */ new Set();
211
255
  dependents.add(slotKey);
212
256
  next.set(depKey, dependents);
@@ -228,8 +272,7 @@ var ServiceRuntime = class {
228
272
  const deps = ServiceClass.deps ?? {};
229
273
  const missing = [];
230
274
  for (const entry of Object.values(deps)) {
231
- const { key, optional: isOptional } = resolveDep(entry);
232
- if (isOptional) continue;
275
+ const key = resolveDepKey(entry);
233
276
  const slot = this.resolveDepSlot(key);
234
277
  if (!slot || slot.status !== "ready" || !slot.instance) missing.push(key);
235
278
  }
@@ -254,7 +297,24 @@ var ServiceRuntime = class {
254
297
  slot.instance = null;
255
298
  slot.status = "blocked";
256
299
  slot.error = null;
257
- if (instance) await instance.__cleanupAllSetups(options.reason ?? "shutdown");
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
+ }
258
318
  if (options.removeSlot) this.slots.delete(key);
259
319
  }
260
320
  async unregister(key, token) {
@@ -263,6 +323,7 @@ var ServiceRuntime = class {
263
323
  this.definitions.delete(key);
264
324
  await this.teardownService(key, { removeSlot: true });
265
325
  this.rebuildDependentsIndex();
326
+ this.fireSubscribers(key);
266
327
  await this.scheduleReconcile([key]);
267
328
  }
268
329
  async scheduleReconcile(keys) {
@@ -294,38 +355,33 @@ var ServiceRuntime = class {
294
355
  if (ServiceClass) affected.set(key, ServiceClass);
295
356
  }
296
357
  const levels = this.topologicalLevels(affected);
297
- for (const level of [...levels].reverse()) await Promise.all(level.map(async (key) => {
298
- const slot = this.slots.get(key);
299
- if (slot?.instance) await slot.instance.__cleanupAllSetups("reload");
300
- }));
301
- for (const level of levels) await Promise.all(level.map((key) => this.reconcileKey(key)));
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);
302
363
  if (affectedKeys.length > 0) for (const cb of this.onReconciledCallbacks) try {
303
364
  cb(affectedKeys);
304
365
  } catch (e) {
305
366
  console.error("[hot] onReconciled callback failed:", e);
306
367
  }
307
368
  }
308
- async reconcileKey(key) {
369
+ async reconcileKey(key, wasReady) {
309
370
  const ServiceClass = this.definitions.get(key);
310
371
  if (!ServiceClass) return;
311
372
  const slot = this.ensureSlot(key, ServiceClass);
312
373
  slot.ServiceClass = ServiceClass;
313
374
  const missingDeps = this.listMissingDeps(ServiceClass);
314
375
  if (missingDeps.length > 0) {
315
- const shouldLog = slot.instance !== null || slot.status !== "blocked";
316
376
  await this.teardownService(key, { reason: "reload" });
317
- if (shouldLog) console.log(`[hot] ${key} waiting on: ${missingDeps.join(", ")}`);
377
+ if (wasReady.has(key)) console.log(`[hot] ${key} waiting on: ${missingDeps.join(", ")}`);
318
378
  return;
319
379
  }
320
- let instance = slot.instance;
321
- if (!instance) {
322
- instance = new ServiceClass();
323
- slot.instance = instance;
324
- } else Object.setPrototypeOf(instance, ServiceClass.prototype);
380
+ const instance = new ServiceClass();
381
+ slot.instance = instance;
325
382
  slot.status = "evaluating";
326
383
  slot.error = null;
327
384
  try {
328
- await instance.__cleanupAllSetups("reload");
329
385
  this.injectCtx(instance, ServiceClass);
330
386
  await instance.evaluate();
331
387
  slot.status = "ready";
@@ -347,11 +403,11 @@ var ServiceRuntime = class {
347
403
  const deps = ServiceClass.deps ?? {};
348
404
  let degree = 0;
349
405
  for (const entry of Object.values(deps)) {
350
- const { key: depKey, optional: isOptional } = resolveDep(entry);
406
+ const depKey = resolveDepKey(entry);
351
407
  if (keys.has(depKey)) {
352
408
  degree++;
353
409
  dependents.get(depKey).push(slotKey);
354
- } else if (isOptional) continue;
410
+ }
355
411
  }
356
412
  inDegree.set(slotKey, degree);
357
413
  }
@@ -519,4 +575,4 @@ function subscribeConfig(callback) {
519
575
  };
520
576
  }
521
577
  //#endregion
522
- export { Service, ServiceRuntime, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, optional, registerAppEntrypoint, registerPlugin, replacePlugins, runtime, serviceWithDeps, subscribeConfig, unregisterPlugin };
578
+ export { Service, ServiceRuntime, getAppEntrypoint, getConfig, getPlugin, getPlugins, getSplashPath, registerAppEntrypoint, registerPlugin, replacePlugins, runtime, subscribeConfig, unregisterPlugin };
@@ -1,9 +1,9 @@
1
- import { m as Field, v as Schema } from "./index-M_lSNBrq.mjs";
1
+ import { m as Field, v as Schema } from "./index-DeDxePAa.mjs";
2
2
  import zod from "zod";
3
3
 
4
4
  //#region src/schema.d.ts
5
5
  declare const viewRegistryEntrySchema: zod.ZodObject<{
6
- scope: zod.ZodString;
6
+ type: zod.ZodString;
7
7
  url: zod.ZodString;
8
8
  port: zod.ZodNumber;
9
9
  icon: zod.ZodOptional<zod.ZodString>;
@@ -30,7 +30,7 @@ declare const windowPrefsSchema: zod.ZodObject<{
30
30
  }, zod.core.$strip>;
31
31
  declare const schema: Schema<{
32
32
  lastKnownViewRegistry: Field<zod.ZodArray<zod.ZodObject<{
33
- scope: zod.ZodString;
33
+ type: zod.ZodString;
34
34
  url: zod.ZodString;
35
35
  port: zod.ZodNumber;
36
36
  icon: zod.ZodOptional<zod.ZodString>;
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-Dl85YjXW.mjs";
1
+ import { a as WindowPrefs, i as WindowBounds, n as SchemaRoot, o as schema, r as ViewRegistryEntry, t as CoreSchema } from "./schema-CjrMVk36.mjs";
2
2
  export { CoreSchema, SchemaRoot, ViewRegistryEntry, WindowBounds, WindowPrefs, schema };
package/dist/schema.mjs CHANGED
@@ -2,7 +2,7 @@ import { a as f, i as createSchema } from "./schema-Ca7SxXgS.mjs";
2
2
  import zod from "zod";
3
3
  //#region src/schema.ts
4
4
  const viewRegistryEntrySchema = zod.object({
5
- scope: zod.string(),
5
+ type: zod.string(),
6
6
  url: zod.string(),
7
7
  port: zod.number(),
8
8
  icon: zod.string().optional(),
@@ -7,9 +7,7 @@ import { WebSocketServer } from "ws";
7
7
  //#region src/services/server.ts
8
8
  var server_exports = /* @__PURE__ */ __exportAll({ ServerService: () => ServerService });
9
9
  const log = createLogger("server");
10
- var ServerService = class extends Service {
11
- static key = "server";
12
- static deps = {};
10
+ var ServerService = class extends Service.create({ key: "server" }) {
13
11
  server = null;
14
12
  wss = null;
15
13
  port = 0;
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * Loads and registers all built-in services. Kept separate from
4
4
  * `./index.ts` (which statically re-exports the service classes for
5
- * `serviceWithDeps(...)` consumers) so that `setup-gate` can import it without
6
- * eagerly loading every service module before `setupGate()` has had a
7
- * chance to bootstrap env vars and `process.chdir(projectRoot)`.
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)`.
8
8
  */
9
9
  declare function defaultServices(): Promise<void>;
10
10
  //#endregion
@@ -2,21 +2,22 @@
2
2
  /**
3
3
  * Loads and registers all built-in services. Kept separate from
4
4
  * `./index.ts` (which statically re-exports the service classes for
5
- * `serviceWithDeps(...)` consumers) so that `setup-gate` can import it without
6
- * eagerly loading every service module before `setupGate()` has had a
7
- * chance to bootstrap env vars and `process.chdir(projectRoot)`.
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)`.
8
8
  */
9
9
  async function defaultServices() {
10
- await import("../server-y3PPbh3l.mjs").then((n) => n.n);
11
- await import("../reloader-DzEO8kJr.mjs").then((n) => n.n);
12
- await import("../renderer-host-Cau9JK0v.mjs").then((n) => n.n);
13
- await import("../renderer-host-Cau9JK0v.mjs").then((n) => n.c);
14
- await import("../renderer-host-Cau9JK0v.mjs").then((n) => n.o);
15
- await import("../base-window-BbFRRhKP.mjs").then((n) => n.r);
16
- await import("../rpc-JfGv-Wuw.mjs").then((n) => n.n);
17
- await import("../renderer-host-Cau9JK0v.mjs").then((n) => n.i);
18
- await import("../window-o2NGUsIb.mjs").then((n) => n.n);
19
- await import("../advice-config-QYB2qEd_.mjs").then((n) => n.t);
10
+ await import("../server-CZLMF8Dj.mjs").then((n) => n.n);
11
+ await import("../reloader-B22UiNA2.mjs").then((n) => n.n);
12
+ await import("../renderer-host-DD16MXhI.mjs").then((n) => n.n);
13
+ await import("../renderer-host-DD16MXhI.mjs").then((n) => n.c);
14
+ await import("../renderer-host-DD16MXhI.mjs").then((n) => n.o);
15
+ await import("../base-window-BxBZ2md_.mjs").then((n) => n.r);
16
+ await import("../rpc-C4_NQmpT.mjs").then((n) => n.n);
17
+ await import("../renderer-host-DD16MXhI.mjs").then((n) => n.i);
18
+ await import("../window-YFKvAM0l.mjs").then((n) => n.n);
19
+ await import("../advice-config-DXSIo0sg.mjs").then((n) => n.t);
20
+ await import("../updater-DCkz9M1c.mjs").then((n) => n.n);
20
21
  }
21
22
  //#endregion
22
23
  export { defaultServices };