@zenbujs/core 0.0.4 → 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 (106) hide show
  1. package/dist/{advice-config-BLXjqjGN.mjs → advice-config-DXSIo0sg.mjs} +49 -38
  2. package/dist/advice.d.mts +8 -8
  3. package/dist/advice.mjs +2 -2
  4. package/dist/base-window-BxBZ2md_.mjs +143 -0
  5. package/dist/build-config-Dzg2frpk.d.mts +215 -0
  6. package/dist/build-config-pWdmLnrk.mjs +53 -0
  7. package/dist/{build-electron-C3Beey84.mjs → build-electron-Dsbb1EMl.mjs} +308 -120
  8. package/dist/{build-source-BvC4bPqH.mjs → build-source-d1J3shV8.mjs} +62 -27
  9. package/dist/chunk-DsiFFCwN.mjs +16 -0
  10. package/dist/cli/bin.mjs +7 -7
  11. package/dist/cli/build.d.mts +2 -2
  12. package/dist/cli/build.mjs +2 -3
  13. package/dist/cli/resolve-config.mjs +3 -2
  14. package/dist/{cli-F0B4dvSg.mjs → cli-kL6mPgBE.mjs} +4 -4
  15. package/dist/{config-BlRXeUXx.mjs → config-BK78JDRI.mjs} +1 -1
  16. package/dist/config.d.mts +3 -2
  17. package/dist/config.mjs +3 -3
  18. package/dist/{db-Cd5ETuPG.mjs → db-Bc292RYo.mjs} +2 -2
  19. package/dist/db.d.mts +1 -1
  20. package/dist/db.mjs +2 -2
  21. package/dist/dev-B2emj0HZ.mjs +301 -0
  22. package/dist/env-bootstrap.d.mts +1 -1
  23. package/dist/env-bootstrap.mjs +52 -1
  24. package/dist/events.d.mts +19 -0
  25. package/dist/events.mjs +1 -0
  26. package/dist/host-version-BIrF8tX7.mjs +65 -0
  27. package/dist/index-w5QyDjuf.d.mts +780 -0
  28. package/dist/index.d.mts +5 -6
  29. package/dist/index.mjs +5 -5
  30. package/dist/installing-preload.cjs +60 -0
  31. package/dist/launcher.mjs +2615 -122
  32. package/dist/{link-BJmsKgPa.mjs → link-glX89NV5.mjs} +215 -89
  33. package/dist/{load-config-BG2tPIfF.mjs → load-config-C4Oe2qZO.mjs} +22 -3
  34. package/dist/loaders/zenbu.d.mts +1 -0
  35. package/dist/loaders/zenbu.mjs +108 -86
  36. package/dist/{monorepo-DCruz9Jx.mjs → monorepo-Dct-kkbQ.mjs} +3 -0
  37. package/dist/node-loader.mjs +1 -1
  38. package/dist/{publish-source-34Hn9zb0.mjs → publish-source-Dq2c0iOw.mjs} +2 -2
  39. package/dist/react.d.mts +56 -7
  40. package/dist/react.mjs +118 -7
  41. package/dist/registry-CMp8FYgS.d.mts +47 -0
  42. package/dist/registry-generated.d.mts +26 -0
  43. package/dist/registry-generated.mjs +1 -0
  44. package/dist/registry.d.mts +2 -2
  45. package/dist/{reloader-DJoCB0bC.mjs → reloader-B22UiNA2.mjs} +7 -7
  46. package/dist/{renderer-host-ztaSIOGx.mjs → renderer-host-DD16MXhI.mjs} +178 -57
  47. package/dist/{rpc-CsgWnlZx.mjs → rpc-C4_NQmpT.mjs} +11 -8
  48. package/dist/rpc.d.mts +1 -1
  49. package/dist/rpc.mjs +1 -1
  50. package/dist/runtime-BQWntcOb.d.mts +218 -0
  51. package/dist/runtime.d.mts +2 -2
  52. package/dist/runtime.mjs +578 -2
  53. package/dist/{schema-DvT61x2_.d.mts → schema-CjrMVk36.d.mts} +3 -3
  54. package/dist/schema.d.mts +1 -1
  55. package/dist/schema.mjs +27 -1
  56. package/dist/{server-DB3Eki_G.mjs → server-CZLMF8Dj.mjs} +6 -6
  57. package/dist/services/default.d.mts +3 -3
  58. package/dist/services/default.mjs +14 -13
  59. package/dist/services/index.d.mts +2 -276
  60. package/dist/services/index.mjs +8 -7
  61. package/dist/setup-gate.d.mts +1 -1
  62. package/dist/setup-gate.mjs +341 -1
  63. package/dist/{transform-DJH3vN4b.mjs → transform-BzrwkEdf.mjs} +23 -917
  64. package/dist/{transport-BMSzG2-F.mjs → transport-F2hv_OEm.mjs} +1 -1
  65. package/dist/updater-DCkz9M1c.mjs +1008 -0
  66. package/dist/{vite-plugins-t4MlFcz3.mjs → vite-plugins-tt6KAtyE.mjs} +27 -26
  67. package/dist/vite.d.mts +3 -3
  68. package/dist/vite.mjs +1 -1
  69. package/dist/{window-DUvMTons.mjs → window-YFKvAM0l.mjs} +36 -19
  70. package/dist/{write-9dRFczGJ.mjs → write-DgIRjo23.mjs} +1 -1
  71. package/package.json +18 -5
  72. package/dist/advice-config-D6K_a7e9.mjs +0 -2
  73. package/dist/base-window-D8CpxMU3.mjs +0 -94
  74. package/dist/base-window-OXg2KSyP.mjs +0 -2
  75. package/dist/build-config-BwnnfrN-.mjs +0 -23
  76. package/dist/chunk-Dm34NbLt.mjs +0 -6
  77. package/dist/config-Ch1FreWU.mjs +0 -2
  78. package/dist/db-Bz_CDIWg.mjs +0 -2
  79. package/dist/dev-DLutFPyo.mjs +0 -85
  80. package/dist/env-bootstrap-rj7I-59x.mjs +0 -53
  81. package/dist/http-B36qtsm0.mjs +0 -2
  82. package/dist/load-config-CQG4297M.mjs +0 -2
  83. package/dist/registry-CioEYLI5.d.mts +0 -61
  84. package/dist/reloader-FeHKV2jd.mjs +0 -2
  85. package/dist/renderer-host-BQpS0ZM2.mjs +0 -2
  86. package/dist/rpc-D_s7-WZe.mjs +0 -2
  87. package/dist/runtime-C95iyVn6.mjs +0 -461
  88. package/dist/runtime-CsiDppGF.d.mts +0 -149
  89. package/dist/schema-dGK6qkfR.mjs +0 -28
  90. package/dist/server-CgzQOPSW.mjs +0 -2
  91. package/dist/setup-gate-D8XfYY52.mjs +0 -140
  92. package/dist/transforms-DVoy8dCu.mjs +0 -47
  93. package/dist/transforms-EVd5Fgyk.d.mts +0 -136
  94. package/dist/view-registry-2zePxTEg.mjs +0 -2
  95. package/dist/window-S3TlTXlK.mjs +0 -2
  96. /package/dist/{env-bootstrap-uCKbw2q8.d.mts → env-bootstrap-rTs8KR3-.d.mts} +0 -0
  97. /package/dist/{index-CE0iPntP.d.mts → index-C-ALz_SH.d.mts} +0 -0
  98. /package/dist/{index-CKKoxA9V.d.mts → index-ClXLQ1fw.d.mts} +0 -0
  99. /package/dist/{index-UK58xuoR.d.mts → index-DeDxePAa.d.mts} +0 -0
  100. /package/dist/{log-CyKv8hQg.mjs → log-6rzaCV0I.mjs} +0 -0
  101. /package/dist/{mirror-sync-EiWvdzTJ.mjs → mirror-sync-pYU6f3-c.mjs} +0 -0
  102. /package/dist/{node-CvZnTx53.mjs → node-BhfLKYCi.mjs} +0 -0
  103. /package/dist/{schema-CIg4GzHQ.mjs → schema-Ca7SxXgS.mjs} +0 -0
  104. /package/dist/{setup-gate-D62nX5lk.d.mts → setup-gate-BQq0QgZH.d.mts} +0 -0
  105. /package/dist/{src-pELM4_iH.mjs → src-Cven45mq.mjs} +0 -0
  106. /package/dist/{trace-DCB7qFzT.mjs → trace-BaVg0rnY.mjs} +0 -0
@@ -1,16 +1,9 @@
1
- import { createRequire } from "node:module";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import { fileURLToPath, pathToFileURL } from "node:url";
5
4
  import { execFileSync } from "node:child_process";
6
5
  //#region src/loaders/zenbu.ts
7
6
  const verbose = process.env.ZENBU_VERBOSE === "1";
8
- const { subscribe } = createRequire(import.meta.url)("@parcel/watcher");
9
- let registerWatcherClosable = () => {};
10
- try {
11
- const pause = await import("@zenbujs/hmr/pause");
12
- registerWatcherClosable = typeof pause.registerWatcherClosable === "function" ? pause.registerWatcherClosable : registerWatcherClosable;
13
- } catch {}
14
7
  const loaderName = "zenbu-loader";
15
8
  let tracePort = null;
16
9
  const stats = {
@@ -19,26 +12,24 @@ const stats = {
19
12
  loadCount: 0,
20
13
  loadMs: 0
21
14
  };
22
- const barrels = /* @__PURE__ */ new Map();
23
- const dirWatchers = /* @__PURE__ */ new Map();
24
15
  let resolvedPayload = null;
25
16
  let resolvedPluginSourceFiles = [];
26
17
  /**
27
- * Number of times we've materialized the plugin root since boot. The first
28
- * call uses the payload that setup-gate sent through `register()`'s `data`
29
- * channel; every subsequent call shells out to `resolve-config.mjs` to
30
- * re-evaluate `zenbu.config.ts` from scratch (so editing the config file
31
- * actually picks up new plugins / removed plugins / changed paths).
32
- *
33
- * We can't `await import()` from inside the loader (Node's ESM loader hooks
34
- * run in a worker thread and serialize on this same hook, deadlocking).
35
- * `execFileSync` blocks the loader for ~300ms per HMR cycle, which is fine.
18
+ * Set of absolute service file paths derived from the resolved payload.
19
+ * Used by the `file://` branch in `load()` to decide which user files
20
+ * should have `runtime.register(...)` auto-injected. Refreshed alongside
21
+ * `resolvedPayload` whenever the loader picks up a new config snapshot.
22
+ */
23
+ let serviceFileSet = /* @__PURE__ */ new Set();
24
+ /**
25
+ * Number of times we've materialized the plugin root since boot.
36
26
  */
37
27
  let pluginsRootInvocations = 0;
38
28
  function initialize(data) {
39
29
  if (data?.payload) {
40
30
  resolvedPayload = data.payload;
41
31
  resolvedPluginSourceFiles = data.pluginSourceFiles ?? [];
32
+ serviceFileSet = collectServiceFiles(data.payload);
42
33
  }
43
34
  if (data?.tracePort) {
44
35
  tracePort = data.tracePort;
@@ -67,56 +58,88 @@ function expandGlob(pattern) {
67
58
  if (!fs.existsSync(dir)) return [];
68
59
  return fs.readdirSync(dir).filter((file) => regex.test(file)).map((file) => path.resolve(dir, file));
69
60
  }
70
- function snapshotDir(dir, regex) {
71
- if (!fs.existsSync(dir)) return /* @__PURE__ */ new Set();
72
- try {
73
- return new Set(fs.readdirSync(dir).filter((file) => regex.test(file)));
74
- } catch {
75
- return /* @__PURE__ */ new Set();
76
- }
61
+ function buildSource(imports) {
62
+ return imports.map((specifier) => `import ${JSON.stringify(specifier)}\n`).join("");
77
63
  }
78
- function handleDirEvent(dir, filename) {
79
- for (const entry of barrels.values()) for (const glob of entry.globs) {
80
- if (glob.dir !== dir) continue;
81
- if (!glob.regex.test(filename)) continue;
82
- const nextSnapshot = snapshotDir(dir, glob.regex);
83
- if (!(nextSnapshot.size !== glob.snapshot.size || [...nextSnapshot].some((file) => !glob.snapshot.has(file)))) continue;
84
- glob.snapshot = nextSnapshot;
85
- try {
86
- entry.hot?.invalidate?.();
87
- if (verbose) console.log(`[zenbu-loader] invalidated barrel (${filename} added/removed in ${dir})`);
88
- } catch (err) {
89
- console.error("[zenbu-loader] invalidate failed:", err);
90
- }
91
- break;
92
- }
64
+ /**
65
+ * Matches the canonical service declaration shape after the upstream HMR
66
+ * loader (dynohot) has transformed the module. Dynohot strips the
67
+ * `export` keyword and minifies the source, so the regex must not
68
+ * require `export` and must tolerate a non-whitespace separator (e.g.
69
+ * `;class Foo extends Service.create(...)`). The `\b` boundary still
70
+ * rejects `subclass Foo` matches inside identifiers/comments.
71
+ *
72
+ * export class FooService extends Service.create({ ... }) { ... }
73
+ * ;class FooService extends Service.create({...}) {...} // post-dynohot
74
+ *
75
+ * `zen link` runs on the original on-disk source (pre-dynohot), so it
76
+ * keeps the `export` requirement in `discoverServices` to avoid matching
77
+ * non-exported helper classes.
78
+ */
79
+ const SERVICE_CLASS_RE = /\bclass\s+(\w+)\s+extends\s+Service\.create\s*\(/g;
80
+ /**
81
+ * Decode the loader's `source` payload (which Node permits as string,
82
+ * Buffer, ArrayBuffer, or any TypedArray) into a plain UTF-8 string.
83
+ */
84
+ function decodeSource(source) {
85
+ if (typeof source === "string") return source;
86
+ if (source instanceof Uint8Array) return Buffer.from(source).toString("utf8");
87
+ if (source instanceof ArrayBuffer) return Buffer.from(source).toString("utf8");
88
+ return String(source);
93
89
  }
94
- function ensureDirWatcher(dir) {
95
- if (dirWatchers.has(dir)) return;
96
- if (!fs.existsSync(dir)) return;
97
- let subscription = null;
98
- let closed = false;
99
- subscribe(dir, (err, events) => {
100
- if (err) return;
101
- for (const event of events) {
102
- if (path.dirname(event.path) !== dir) continue;
103
- handleDirEvent(dir, path.basename(event.path));
104
- }
105
- }).then((sub) => {
106
- if (closed) sub.unsubscribe().catch(() => {});
107
- else subscription = sub;
108
- }).catch((err) => {
109
- console.error(`[zenbu-loader] subscribe failed for ${dir}:`, err);
110
- });
111
- const closable = { close: () => {
112
- closed = true;
113
- if (subscription) return subscription.unsubscribe().catch(() => {});
114
- } };
115
- registerWatcherClosable(closable);
116
- dirWatchers.set(dir, closable);
90
+ /**
91
+ * Tail-append a `runtime.register(<Class>, import.meta)` call to the
92
+ * loader result so user service files don't have to carry the boilerplate.
93
+ *
94
+ * - Idempotent: if the source already contains `runtime.register(`
95
+ * (manual migration leftover, or a user choosing to register
96
+ * explicitly), we leave it untouched.
97
+ * - Errors loudly when zero or multiple service classes are found, since
98
+ * the runtime's HMR model is "one slot per import.meta".
99
+ * - The runtime singleton imported here resolves through the loader's
100
+ * own `@zenbujs/core` short-circuit in `resolve()`, so the registered
101
+ * class lands in the same registry the rest of the framework reads.
102
+ */
103
+ function appendAutoRegister(downstream, filePath) {
104
+ if (!downstream || typeof downstream !== "object") return downstream;
105
+ const result = downstream;
106
+ if (result.format !== "module") return downstream;
107
+ if (result.source == null) return downstream;
108
+ const source = decodeSource(result.source);
109
+ if (/\bruntime\s*\.\s*register\s*\(/.test(source)) return {
110
+ ...result,
111
+ source
112
+ };
113
+ SERVICE_CLASS_RE.lastIndex = 0;
114
+ const matches = [...source.matchAll(SERVICE_CLASS_RE)];
115
+ if (matches.length === 0) throw new Error(`[zenbu-loader] auto-register: no \`class <Name> extends Service.create({ ... })\` found in ${filePath}.\n Either rewrite the class to use Service.create or add an explicit \`runtime.register(<Class>, import.meta)\`.`);
116
+ if (matches.length > 1) {
117
+ const names = matches.map((m) => m[1]).join(", ");
118
+ throw new Error(`[zenbu-loader] auto-register: ${matches.length} service classes (${names}) in ${filePath}.\n The HMR model is one service per file; split into separate files.`);
119
+ }
120
+ const tail = `\n;import { runtime as __zenbu_runtime__ } from "@zenbujs/core/runtime";\n__zenbu_runtime__.register(${matches[0][1]}, import.meta);\n`;
121
+ return {
122
+ ...result,
123
+ source: source + tail
124
+ };
117
125
  }
118
- function buildSource(imports) {
119
- return imports.map((specifier) => `import ${JSON.stringify(specifier)}\n`).join("");
126
+ /**
127
+ * Expand each plugin's `services` glob list into a flat set of absolute
128
+ * file paths. Consulted by the `file://` branch in `load()` to decide
129
+ * whether to auto-inject `runtime.register(...)` for the loaded module.
130
+ *
131
+ * Glob expansion uses the same `expandGlob` (and therefore the same
132
+ * `fs.readdirSync` snapshot) that `buildPluginBarrel` uses, so the set
133
+ * stays in lockstep with what the barrel actually imports.
134
+ */
135
+ function collectServiceFiles(payload) {
136
+ const set = /* @__PURE__ */ new Set();
137
+ for (const plugin of payload.plugins) for (const entry of plugin.services) {
138
+ const resolved = path.isAbsolute(entry) ? entry : path.resolve(plugin.dir, entry);
139
+ if (resolved.includes("*")) for (const f of expandGlob(resolved)) set.add(f);
140
+ else set.add(resolved);
141
+ }
142
+ return set;
120
143
  }
121
144
  /**
122
145
  * Read the resolved config from the process global. setup-gate populates
@@ -164,6 +187,7 @@ function getResolvedConfig(configPath) {
164
187
  const fresh = resolveConfigViaSubprocess(path.dirname(configPath));
165
188
  resolvedPayload = fresh.payload;
166
189
  resolvedPluginSourceFiles = fresh.pluginSourceFiles;
190
+ serviceFileSet = collectServiceFiles(fresh.payload);
167
191
  return fresh;
168
192
  }
169
193
  /**
@@ -190,38 +214,30 @@ function buildRegistryModule(payload) {
190
214
  return [
191
215
  "import { replacePlugins, registerAppEntrypoint } from \"@zenbujs/core/runtime\"",
192
216
  `replacePlugins(${JSON.stringify(payload.plugins)})`,
193
- `registerAppEntrypoint(${JSON.stringify(payload.appEntrypoint)})`,
217
+ `registerAppEntrypoint(${JSON.stringify(payload.appEntrypoint)}, ${JSON.stringify(payload.splashPath)})`,
194
218
  "import.meta.hot?.accept()"
195
219
  ].join("\n") + "\n";
196
220
  }
197
221
  /**
198
222
  * Generate a per-plugin barrel: just service-file imports anchored at the
199
223
  * plugin's `dir`. Glob-form entries get expanded via `fs.readdirSync` and
200
- * a parcel-watcher subscription is opened so dynohot invalidates the
201
- * barrel when service files are added/removed under the watched directory.
224
+ * glob directories are registered with `context.hot.watch()` so dynohot
225
+ * reloads the generated barrel when service files are added/removed.
202
226
  */
203
227
  function buildPluginBarrel(plugin) {
204
228
  const imports = [];
205
229
  const watchPaths = new Set([plugin.dir]);
206
- const globs = [];
207
230
  for (const entry of plugin.services) {
208
231
  const resolved = path.isAbsolute(entry) ? entry : path.resolve(plugin.dir, entry);
209
232
  if (resolved.includes("*")) {
210
233
  const dir = path.dirname(resolved);
211
- const regex = globRegex(path.basename(resolved));
212
234
  watchPaths.add(dir);
213
- globs.push({
214
- dir,
215
- regex,
216
- snapshot: snapshotDir(dir, regex)
217
- });
218
235
  for (const file of expandGlob(resolved)) imports.push(pathToFileURL(file).href);
219
236
  } else imports.push(pathToFileURL(resolved).href);
220
237
  }
221
238
  return {
222
239
  source: `${buildSource(imports)}import.meta.hot?.accept()\n`,
223
- watchPaths,
224
- globs
240
+ watchPaths
225
241
  };
226
242
  }
227
243
  const CORE_PACKAGE_ROOT_FOR_LOADER = (() => {
@@ -316,6 +332,19 @@ function loadImpl(url, context, nextLoad) {
316
332
  shortCircuit: true
317
333
  };
318
334
  }
335
+ if (url.startsWith("file://")) {
336
+ let filePath;
337
+ try {
338
+ filePath = fileURLToPath(url);
339
+ } catch {
340
+ filePath = "";
341
+ }
342
+ if (filePath && serviceFileSet.has(filePath)) {
343
+ const downstream = nextLoad(url, context);
344
+ if (downstream && typeof downstream.then === "function") return Promise.resolve(downstream).then((r) => appendAutoRegister(r, filePath));
345
+ return appendAutoRegister(downstream, filePath);
346
+ }
347
+ }
319
348
  if (url.startsWith("zenbu:barrel?")) {
320
349
  const params = new URL(url).searchParams;
321
350
  const pluginRaw = decodeURIComponent(params.get("plugin") ?? "");
@@ -325,16 +354,9 @@ function loadImpl(url, context, nextLoad) {
325
354
  } catch (err) {
326
355
  throw new Error(`[zenbu-loader] bad plugin payload: ${err.message}`);
327
356
  }
328
- const { source, watchPaths, globs } = buildPluginBarrel(plugin);
357
+ const { source, watchPaths } = buildPluginBarrel(plugin);
329
358
  if (context.hot?.watch) for (const watchPath of watchPaths) context.hot.watch(pathToFileURL(watchPath));
330
- if (context.hot) {
331
- barrels.set(url, {
332
- hot: context.hot,
333
- globs
334
- });
335
- for (const glob of globs) ensureDirWatcher(glob.dir);
336
- }
337
- if (verbose) console.log(`[zenbu-loader] generated barrel for plugin ${plugin.name} (${source.split("\n").filter(Boolean).length} imports, ${watchPaths.size} watches, ${globs.length} globs)`);
359
+ if (verbose) console.log(`[zenbu-loader] generated barrel for plugin ${plugin.name} (${source.split("\n").filter(Boolean).length} imports, ${watchPaths.size} watches)`);
338
360
  return {
339
361
  format: "module",
340
362
  source,
@@ -3,6 +3,9 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { execFileSync } from "node:child_process";
5
5
  //#region src/cli/commands/monorepo.ts
6
+ /**
7
+ * todo: should only be exposed when an env var is passed
8
+ */
6
9
  const MARKER_FILE = ".zenbu-dev-link";
7
10
  const DEFAULT_MONOREPO = path.join(os.homedir(), ".zenbu", "plugins", "zenbu");
8
11
  function resolveProjectDir() {
@@ -1,4 +1,4 @@
1
- import { n as require_lib, t as zenbuAdviceTransform } from "./transform-DJH3vN4b.mjs";
1
+ import { n as require_lib, t as zenbuAdviceTransform } from "./transform-BzrwkEdf.mjs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  //#region ../advice/src/node-loader.ts
4
4
  var import_lib = require_lib();
@@ -1,5 +1,5 @@
1
- import { n as loadConfig } from "./load-config-BG2tPIfF.mjs";
2
- import { n as init, r as push } from "./mirror-sync-EiWvdzTJ.mjs";
1
+ import { t as loadConfig } from "./load-config-C4Oe2qZO.mjs";
2
+ import { n as init, r as push } from "./mirror-sync-pYU6f3-c.mjs";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { execFileSync } from "node:child_process";
package/dist/react.d.mts CHANGED
@@ -1,8 +1,8 @@
1
- import { f as CollectionRefBrand, h as InferCollectionItem, n as connectReplica, o as ClientProxy, p as CollectionRefValue, u as CollectionState, y as SchemaShape } from "./index-UK58xuoR.mjs";
2
- import { c as ResolvedServiceRouter, l as ZenbuRegister, o as ResolvedDbRoot, s as ResolvedEvents } from "./registry-CioEYLI5.mjs";
3
- import { n as EventProxy, r as RouterProxy } from "./index-CE0iPntP.mjs";
1
+ import { f as CollectionRefBrand, h as InferCollectionItem, n as connectReplica, o as ClientProxy, p as CollectionRefValue, u as CollectionState, y as SchemaShape } from "./index-DeDxePAa.mjs";
2
+ import { n as EventProxy, r as RouterProxy } from "./index-C-ALz_SH.mjs";
3
+ import { i as ZenbuRegister, n as ResolvedEvents, r as ResolvedServiceRouter, t as ResolvedDbRoot } from "./registry-CMp8FYgS.mjs";
4
4
  import * as _$react from "react";
5
- import { ReactNode } from "react";
5
+ import { CSSProperties, ReactElement, ReactNode } from "react";
6
6
 
7
7
  //#region ../kyju/src/v2/react/index.d.ts
8
8
  type CollectionResult<Item> = {
@@ -57,9 +57,9 @@ declare function ZenbuProvider({
57
57
  fallback,
58
58
  errorFallback,
59
59
  children
60
- }: ZenbuProviderProps): _$react.ReactElement<{
60
+ }: ZenbuProviderProps): ReactElement<{
61
61
  "data-zenbu-connecting": boolean;
62
- }, string | _$react.JSXElementConstructor<any>> | _$react.ReactElement<{
62
+ }, string | _$react.JSXElementConstructor<any>> | ReactElement<{
63
63
  "data-zenbu-error": boolean;
64
64
  }, string | _$react.JSXElementConstructor<any>> | _$react.FunctionComponentElement<_$react.ProviderProps<ConnectionState | null>>;
65
65
  declare function useRpc(): RouterProxy<RegisteredServiceRouter>;
@@ -72,5 +72,54 @@ type DbClient = {
72
72
  };
73
73
  declare function useDbClient(): DbClient;
74
74
  declare function useEvents(): EventProxy<RegisteredEvents>;
75
+ /**
76
+ * Mounts a registered view (separate Vite root, registered via
77
+ * `ViewRegistryService.register(type, …)`) inside an `<iframe>` and:
78
+ *
79
+ * 1. **Auto-inherits auth from the parent iframe's URL.** Reads `wsPort` /
80
+ * `wsToken` from `window.location.search` and forwards them in the
81
+ * child URL so the child's `<ZenbuProvider>` connects without the
82
+ * consumer wiring anything.
83
+ * 2. **Mount-once src.** The iframe's `src` is set on first mount and
84
+ * *never updated*. Toggling `visible` only flips `style.display` —
85
+ * state inside the child (sockets, ghostty terminals, etc.) survives
86
+ * visibility changes.
87
+ * 3. **Initial args via URL.** `args` is encoded as base64 JSON in
88
+ * `?args=` on first paint so the child can render without waiting on
89
+ * a postMessage handshake. Use `useViewArgs<T>()` inside the child
90
+ * to read.
91
+ * 4. **Reactive args via postMessage.** When `args` changes after mount,
92
+ * we send `{ kind: "zenbu:view-args", args }` to the iframe's
93
+ * `contentWindow`. URL is *not* rewritten (iframe stays alive).
94
+ *
95
+ * Child sessions die on unmount. There is no cache: if the consumer
96
+ * unmounts the `<View>` element, any state inside it (e.g. PTY sockets)
97
+ * is gone. Caller is responsible for explicit teardown via RPC if
98
+ * needed.
99
+ */
100
+ type ViewProps = {
101
+ /** Registered view type — first arg to `ViewRegistryService.register`. */type: string; /** Initial args; serialized into the child URL as base64-JSON. */
102
+ args?: Record<string, unknown>; /** When false, sets `style.display: none`. Iframe is NOT unmounted. */
103
+ visible?: boolean;
104
+ style?: CSSProperties;
105
+ className?: string;
106
+ onLoad?: () => void; /** Rendered while the view registry has not yet reported a URL for this type. */
107
+ fallback?: ReactNode;
108
+ };
109
+ declare function View({
110
+ type,
111
+ args,
112
+ visible,
113
+ style,
114
+ className,
115
+ onLoad,
116
+ fallback
117
+ }: ViewProps): ReactElement;
118
+ /**
119
+ * Read the current view args inside a child iframe rendered by `<View>`.
120
+ * Initial value comes from `?args=` in the iframe's URL; updates arrive
121
+ * via `postMessage` and re-render this hook's caller.
122
+ */
123
+ declare function useViewArgs<T extends Record<string, unknown>>(): T;
75
124
  //#endregion
76
- export { type CollectionRefValue, DbClient, ZenbuProvider, ZenbuProviderProps, type ZenbuRegister, useCollection, useDb, useDbClient, useEvents, useRpc };
125
+ export { type CollectionRefValue, DbClient, View, ViewProps, ZenbuProvider, ZenbuProviderProps, type ZenbuRegister, useCollection, useDb, useDbClient, useEvents, useRpc, useViewArgs };
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as dbStringify, r as dbParse, t as connectReplica } from "./transport-BMSzG2-F.mjs";
2
- import { t as connectRpc } from "./src-pELM4_iH.mjs";
1
+ import { i as dbStringify, r as dbParse, t as connectReplica } from "./transport-F2hv_OEm.mjs";
2
+ import { t as connectRpc } from "./src-Cven45mq.mjs";
3
3
  import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  //#region ../kyju/src/v2/react/index.ts
5
5
  function createKyjuReact() {
@@ -209,13 +209,12 @@ function ZenbuProvider({ wsUrl, fallback, errorFallback, children }) {
209
209
  ws.close();
210
210
  return;
211
211
  }
212
- const viewMatch = window.location.pathname.match(/^\/views\/([^/]+)\//);
213
- const viewScope = viewMatch ? viewMatch[1] : null;
212
+ const viewType = window.location.pathname.match(/^\/views\/([^/]+)\//)?.[1] ?? new URLSearchParams(window.location.search).get("type");
214
213
  let unsubReload = null;
215
- if (viewScope) {
214
+ if (viewType) {
216
215
  const adviceReload = events?.advice?.reload;
217
216
  if (adviceReload?.subscribe) unsubReload = adviceReload.subscribe((data) => {
218
- if (data?.scope === viewScope) location.reload();
217
+ if (data?.type === "*" || data?.type === viewType) location.reload();
219
218
  });
220
219
  }
221
220
  cleanupRef.current = () => {
@@ -287,5 +286,117 @@ function useDbClient() {
287
286
  function useEvents() {
288
287
  return useConnection().events;
289
288
  }
289
+ const VIEW_ARGS_MESSAGE_KIND = "zenbu:view-args";
290
+ const VIEW_ARGS_PARAM_LIMIT = 1500;
291
+ function encodeViewArgs(args) {
292
+ if (!args) return null;
293
+ try {
294
+ return btoa(unescape(encodeURIComponent(JSON.stringify(args))));
295
+ } catch (err) {
296
+ console.warn("[zenbu/View] failed to encode args:", err);
297
+ return null;
298
+ }
299
+ }
300
+ function decodeViewArgs(encoded) {
301
+ if (!encoded) return {};
302
+ try {
303
+ return JSON.parse(decodeURIComponent(escape(atob(encoded))));
304
+ } catch (err) {
305
+ console.warn("[zenbu/View] failed to decode args:", err);
306
+ return {};
307
+ }
308
+ }
309
+ function buildViewUrl(baseUrl, type, encodedArgs) {
310
+ const trimmed = baseUrl.replace(/\/$/, "");
311
+ const parentParams = new URLSearchParams(window.location.search);
312
+ const params = new URLSearchParams();
313
+ const wsPort = parentParams.get("wsPort");
314
+ const wsToken = parentParams.get("wsToken");
315
+ if (wsPort) params.set("wsPort", wsPort);
316
+ if (wsToken) params.set("wsToken", wsToken);
317
+ params.set("type", type);
318
+ if (encodedArgs) params.set("args", encodedArgs);
319
+ return `${trimmed}/?${params.toString()}`;
320
+ }
321
+ function shallowJSONEqual(a, b) {
322
+ try {
323
+ return JSON.stringify(a) === JSON.stringify(b);
324
+ } catch {
325
+ return false;
326
+ }
327
+ }
328
+ function View({ type, args, visible = true, style, className, onLoad, fallback = null }) {
329
+ const url = useDb((root) => root.plugin.core.lastKnownViewRegistry.find((v) => v.type === type)?.url ?? null);
330
+ const iframeRef = useRef(null);
331
+ const initialUrlRef = useRef(null);
332
+ const lastArgsRef = useRef(null);
333
+ const loadedRef = useRef(false);
334
+ if (initialUrlRef.current === null && url) {
335
+ const encoded = encodeViewArgs(args);
336
+ if (encoded && encoded.length > VIEW_ARGS_PARAM_LIMIT) console.warn(`[zenbu/View] args for "${type}" exceed ${VIEW_ARGS_PARAM_LIMIT} chars in URL — consider postMessage-only updates.`);
337
+ lastArgsRef.current = encoded;
338
+ initialUrlRef.current = buildViewUrl(url, type, encoded);
339
+ }
340
+ useEffect(() => {
341
+ if (!iframeRef.current) return;
342
+ const encoded = encodeViewArgs(args);
343
+ if (shallowJSONEqual(encoded, lastArgsRef.current)) return;
344
+ lastArgsRef.current = encoded;
345
+ const send = () => {
346
+ iframeRef.current?.contentWindow?.postMessage({
347
+ kind: VIEW_ARGS_MESSAGE_KIND,
348
+ args: args ?? {}
349
+ }, "*");
350
+ };
351
+ if (loadedRef.current) send();
352
+ else {
353
+ const iframe = iframeRef.current;
354
+ const onceLoaded = () => {
355
+ send();
356
+ iframe.removeEventListener("load", onceLoaded);
357
+ };
358
+ iframe.addEventListener("load", onceLoaded);
359
+ return () => iframe.removeEventListener("load", onceLoaded);
360
+ }
361
+ }, [args]);
362
+ if (!initialUrlRef.current) return createElement("span", { "data-zenbu-view-pending": type }, fallback);
363
+ return createElement("iframe", {
364
+ ref: iframeRef,
365
+ src: initialUrlRef.current,
366
+ className,
367
+ style: {
368
+ border: "none",
369
+ ...style,
370
+ display: visible ? style?.display ?? "block" : "none"
371
+ },
372
+ onLoad: () => {
373
+ loadedRef.current = true;
374
+ onLoad?.();
375
+ }
376
+ });
377
+ }
378
+ /**
379
+ * Read the current view args inside a child iframe rendered by `<View>`.
380
+ * Initial value comes from `?args=` in the iframe's URL; updates arrive
381
+ * via `postMessage` and re-render this hook's caller.
382
+ */
383
+ function useViewArgs() {
384
+ const [args, setArgs] = useState(() => {
385
+ return decodeViewArgs(new URLSearchParams(typeof window !== "undefined" ? window.location.search : "").get("args"));
386
+ });
387
+ useEffect(() => {
388
+ const handler = (event) => {
389
+ if (event.source !== window.parent) return;
390
+ const data = event.data;
391
+ if (!data || typeof data !== "object") return;
392
+ if (data.kind !== VIEW_ARGS_MESSAGE_KIND) return;
393
+ const next = data.args;
394
+ if (next && typeof next === "object") setArgs(next);
395
+ };
396
+ window.addEventListener("message", handler);
397
+ return () => window.removeEventListener("message", handler);
398
+ }, []);
399
+ return args;
400
+ }
290
401
  //#endregion
291
- export { ZenbuProvider, useCollection, useDb, useDbClient, useEvents, useRpc };
402
+ export { View, ZenbuProvider, useCollection, useDb, useDbClient, useEvents, useRpc, useViewArgs };
@@ -0,0 +1,47 @@
1
+ //#region src/registry.d.ts
2
+ /**
3
+ * The registry seam.
4
+ *
5
+ * `ZenbuRegister` is the empty interface that downstream apps (and
6
+ * core's own `pnpm link:types` output) augment via:
7
+ *
8
+ * declare module "@zenbujs/core/registry" {
9
+ * interface ZenbuRegister {
10
+ * db: DbRoot
11
+ * rpc: ServiceRouter
12
+ * events: PluginEvents
13
+ * }
14
+ * }
15
+ *
16
+ * Server services (`DbService.client`, `RpcService.emit`) and renderer
17
+ * hooks (`useDb`, `useRpc`, `useEvents`) all read their types from
18
+ * `ZenbuRegister` via the `Resolved*` aliases below, so each TS program
19
+ * sees its own merged surface without generics at the call site.
20
+ *
21
+ * Concrete types live elsewhere:
22
+ * - core's published surface: `@zenbujs/core/registry-generated`
23
+ * (regenerated by `pnpm link:types`, shipped via tsdown -> dist/)
24
+ * - downstream's user-side surface: `<app>/types/services.ts` etc.
25
+ * (regenerated by `zen link`)
26
+ *
27
+ * registry.ts itself stays minimal on purpose: only the augmentation
28
+ * target + plug-and-play fallbacks. Anything more would need to be
29
+ * hand-maintained, and we want the link generator to be the single
30
+ * source of truth.
31
+ *
32
+ * Mirrors the pattern TanStack Router uses for `Register`.
33
+ */
34
+ interface ZenbuRegister {}
35
+ type ResolvedDbRoot = ZenbuRegister extends {
36
+ db: infer T;
37
+ } ? T : {
38
+ plugin: {};
39
+ };
40
+ type ResolvedServiceRouter = ZenbuRegister extends {
41
+ rpc: infer T;
42
+ } ? T : {};
43
+ type ResolvedEvents = ZenbuRegister extends {
44
+ events: infer T;
45
+ } ? T : {};
46
+ //#endregion
47
+ export { ZenbuRegister as i, ResolvedEvents as n, ResolvedServiceRouter as r, ResolvedDbRoot as t };
@@ -0,0 +1,26 @@
1
+ import { Events } from "./events.mjs";
2
+ import { n as SchemaRoot } from "./schema-CjrMVk36.mjs";
3
+ import { c as DbService, d as ServerService, i as BaseWindowService, l as HttpService, n as WindowService, o as RendererHostService, r as RpcService, s as ViewRegistryService, t as UpdaterService, u as ReloaderService } from "./index-w5QyDjuf.mjs";
4
+
5
+ //#region src/registry-generated.d.ts
6
+ type ServiceBase = "evaluate" | "shutdown" | "constructor" | "effect" | "__cleanupAllEffects" | "__effectCleanups" | "ctx";
7
+ type ExtractRpcMethods<T> = { [K in Exclude<keyof T, ServiceBase | `_${string}`> as T[K] extends ((...args: any[]) => any) ? K : never]: T[K] };
8
+ type CoreServiceRouter = {
9
+ "base-window": ExtractRpcMethods<BaseWindowService>;
10
+ db: ExtractRpcMethods<DbService>;
11
+ http: ExtractRpcMethods<HttpService>;
12
+ reloader: ExtractRpcMethods<ReloaderService>;
13
+ "renderer-host": ExtractRpcMethods<RendererHostService>;
14
+ rpc: ExtractRpcMethods<RpcService>;
15
+ server: ExtractRpcMethods<ServerService>;
16
+ updater: ExtractRpcMethods<UpdaterService>;
17
+ "view-registry": ExtractRpcMethods<ViewRegistryService>;
18
+ window: ExtractRpcMethods<WindowService>;
19
+ };
20
+ type CoreEvents = Events;
21
+ type CoreDbSections = {
22
+ core: SchemaRoot;
23
+ };
24
+ type CorePreloads = {};
25
+ //#endregion
26
+ export { CoreDbSections, CoreEvents, CorePreloads, CoreServiceRouter };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,2 @@
1
- import { a as Events, c as ResolvedServiceRouter, i as CoreServiceRouter, l as ZenbuRegister, n as CoreEvents, o as ResolvedDbRoot, r as CorePreloads, s as ResolvedEvents, t as CoreDbSections } from "./registry-CioEYLI5.mjs";
2
- export { CoreDbSections, CoreEvents, CorePreloads, CoreServiceRouter, Events, ResolvedDbRoot, ResolvedEvents, ResolvedServiceRouter, ZenbuRegister };
1
+ import { i as ZenbuRegister, n as ResolvedEvents, r as ResolvedServiceRouter, t as ResolvedDbRoot } from "./registry-CMp8FYgS.mjs";
2
+ export { ResolvedDbRoot, ResolvedEvents, ResolvedServiceRouter, ZenbuRegister };
@@ -1,6 +1,7 @@
1
- import { t as Service, u as runtime } from "./runtime-C95iyVn6.mjs";
2
- import { t as createLogger } from "./log-CyKv8hQg.mjs";
3
- import { a as zenbuVitePlugins } from "./vite-plugins-t4MlFcz3.mjs";
1
+ import { n as __exportAll } from "./chunk-DsiFFCwN.mjs";
2
+ import { Service, runtime } from "./runtime.mjs";
3
+ import { t as createLogger } from "./log-6rzaCV0I.mjs";
4
+ import { a as zenbuVitePlugins } from "./vite-plugins-tt6KAtyE.mjs";
4
5
  import os from "node:os";
5
6
  import path, { resolve } from "node:path";
6
7
  import { createHash } from "node:crypto";
@@ -15,6 +16,7 @@ const DB_CONFIG_JSON = path.join(INTERNAL_DIR, "db.json");
15
16
  path.join(INTERNAL_DIR, "plugin-setup-state.json");
16
17
  //#endregion
17
18
  //#region src/services/reloader.ts
19
+ var reloader_exports = /* @__PURE__ */ __exportAll({ ReloaderService: () => ReloaderService });
18
20
  const log = createLogger("reloader");
19
21
  function safeCacheSegment(value) {
20
22
  return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "renderer";
@@ -111,9 +113,7 @@ async function startRendererServer(options) {
111
113
  await warmupRendererEntrypoints(server, options.root);
112
114
  return server;
113
115
  }
114
- var ReloaderService = class extends Service {
115
- static key = "reloader";
116
- static deps = {};
116
+ var ReloaderService = class extends Service.create({ key: "reloader" }) {
117
117
  servers = /* @__PURE__ */ new Map();
118
118
  async create(id, root, configFile) {
119
119
  if (this.servers.has(id)) return this.servers.get(id);
@@ -161,4 +161,4 @@ var ReloaderService = class extends Service {
161
161
  };
162
162
  runtime.register(ReloaderService, import.meta);
163
163
  //#endregion
164
- export { DB_CONFIG_JSON as n, INTERNAL_DIR as r, ReloaderService as t };
164
+ export { INTERNAL_DIR as i, reloader_exports as n, DB_CONFIG_JSON as r, ReloaderService as t };