@deepseek-ai/dsh-client-hmr 0.0.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, DeepSeek
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/client/hmr/README.md
5
+ README.md: 9228292547376d3fbb0ea5ce56b9e0a35ced17b2
6
+ README.zh.md: ea62600911458556a3dcc7c46854e97db751c3ef
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @deepseek-ai/dsh-client-hmr
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Hot reload for script-loaded client plugins. A static-arrival entry composed only into `--dev` graphs (`dsh web --dev`); production graphs omit the row, so the shell-bundled code stays inert.
6
+
7
+ The browser half subscribes to the system SSE channel (`GET /plugins/events`) and reloads one plugin per `rebuilt` frame through a serialized queue. The sequence per frame — `invalidate`, `prefetch` (load and register the new bundle while the old fiber still serves), `registry.delete` (before the fiber: a bare fiber dispose trips the vendored Loader's self-dispose branch, which would mark the entry disabled), drain the old fiber, delete `entry.fiber`, remove owned `<style data-plugin>` tags, `entry.refresh()` re-imports and remounts, `fiber.await()` rethrows startup failures loud. Dependents reload through cordis itself: a fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber cascades every dependent with zero client-side graph analysis. The node half detects rebuilds with one interval that stat-polls each graph bundle from a synchronous baseline, immediately re-hashes after adding a row, retains missing rows as dirty, and broadcasts only real rev changes; any tsdown watch process producing the bundle therefore triggers HMR with no builder→host channel.
8
+
9
+ ## Model Experience
10
+
11
+ None, as the reload driver is browser-side machinery; nothing here reaches a model request.
12
+
13
+ #### KV Cache effect
14
+
15
+ None; this package neither assembles nor sends a provider request.
16
+
17
+ ## Known Limitations and Deferred Work
18
+
19
+ - **Reload is coarse by design** — a fresh fiber and fresh components; React state inside the reloaded plugin is lost while the data layer (connection/runtime fibers, Session objects) is untouched. react-refresh-grade state preservation conflicts with "re-executing the bundle re-runs the factory" and is deliberately out.
20
+ - **No failure rollback** — a reload that fails leaves the entry FAILED and visible in the loader status projection; the previous bundle is not restored automatically.
21
+ - **Graph rev is not refreshed by rebuilt frames** — the stale rev is harmless because the bundle endpoint serves no-cache; only reconnect refreshes it.
package/README.zh.md ADDED
@@ -0,0 +1,21 @@
1
+ # @deepseek-ai/dsh-client-hmr
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 为通过脚本加载的客户端插件提供热重载。该静态加载配置项只组合进 `--dev` 图(`dsh web --dev`);生产图省略该项,因此打包进 shell 的代码保持不活动。
6
+
7
+ 浏览器侧订阅系统 SSE(Server-Sent Events)通道(`GET /plugins/events`),每个 `rebuilt` 帧重载一个插件,并通过队列串行执行。每帧的顺序是:`invalidate`、`prefetch`(旧 fiber 仍在服务时加载并注册新组合包)、`registry.delete`(在 fiber dispose(资源释放)之前执行:仅 dispose fiber 会触发 vendored Loader 的 self-dispose 分支,把配置项标为禁用)、排空旧 fiber、删除 `entry.fiber`、移除自身拥有的 `<style data-plugin>` 标签、通过 `entry.refresh()` 重新导入并挂载、通过 `fiber.await()` 直接重新抛出启动失败。依赖方由 Cordis 自身重载:fiber 的激活 epoch 会串联其服务提供方的 uid,因此替换提供方 fiber 会级联所有依赖方,无需客户端图分析。node 侧使用一个 interval 检测重建:从同步基线开始 stat-poll 每个图组合包;新增一行后立即重新计算 hash;缺失行保持 dirty;只广播真实 rev 变更。因此,任何生成组合包的 tsdown watch 进程都能触发 HMR(热模块替换),无需 builder→host 通道。
8
+
9
+ ## 模型体验
10
+
11
+ 无。重载驱动器属于浏览器侧机制;这里没有任何内容进入模型请求。
12
+
13
+ #### KV Cache 影响
14
+
15
+ 无;该包(package)既不组装也不发送提供方请求。
16
+
17
+ ## 已知限制与暂缓事项
18
+
19
+ - **重载有意保持粗粒度**:会创建全新的 fiber 和组件;重载插件中的 React 状态会丢失,数据层(连接 fiber、运行时 fiber 和 Session 对象)不受影响。react-refresh 级状态保留与「重新执行组合包会重新运行 factory」冲突,因此有意排除。
20
+ - **失败时不回滚**:失败的重载会使配置项处于 FAILED 状态,并在 loader 状态投影中显示;系统不会自动恢复先前组合包。
21
+ - **重建帧不会刷新图 rev**:陈旧 rev 无害,因为组合包端点以 no-cache 提供内容;只有重新连接时才会刷新。
package/lib/client.js ADDED
@@ -0,0 +1,96 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@deepseek-ai/dsh-client-hmr",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region lib/types/events.js
8
+ /**
9
+ * Wire protocol of the `/plugins/events` dev SSE channel — single source for
10
+ * both halves of this package. Frames still cross a wire boundary: the
11
+ * browser half validates them at its JSON parse point; sharing the type keeps
12
+ * the two ends from drifting, not from parsing.
13
+ */
14
+ /** System SSE endpoint pushing graph/rebuilt frames (wire protocol constant). */
15
+ const EVENTS_ENDPOINT = "/plugins/events";
16
+ //#endregion
17
+ //#region lib/types/client/index.js
18
+ /** Cordis plugin name. */
19
+ const name = "client-hmr";
20
+ /** Required services: the vendored Loader (entry governance) and the client module system (boot provide, service name `modules`). */
21
+ const inject = ["loader", "modules"];
22
+ /** Find the loader entry whose module specifier is `id` (entry tree ids are random; the package name lives in `options.name`). */
23
+ function findEntry(loader, id) {
24
+ for (const entry of loader.entries()) if (entry.options.name === id) return entry;
25
+ }
26
+ /** Remove every `<style data-plugin>` tag owned by `id` (attribute compared verbatim — no CSS-selector escaping pitfalls). */
27
+ function removeOwnedStyles(id) {
28
+ for (const el of document.querySelectorAll("style[data-plugin]")) if (el.getAttribute("data-plugin") === id) el.remove();
29
+ }
30
+ /**
31
+ * Mount the HMR driver: subscribe to the system SSE channel and hot-swap
32
+ * rebuilt entries.
33
+ * @param ctx - plugin context with `loader` and `modules` available.
34
+ */
35
+ function apply(ctx) {
36
+ const modLoader = ctx.modules;
37
+ const loader = ctx.loader;
38
+ async function reload(id) {
39
+ const entry = findEntry(loader, id);
40
+ if (entry === void 0) {
41
+ ctx.logger.warn(`client-hmr: rebuilt frame for unknown entry "${id}" (not in the loader tree)`);
42
+ return;
43
+ }
44
+ modLoader.invalidate(id);
45
+ await modLoader.prefetch(id);
46
+ const oldFiber = entry.fiber;
47
+ if (oldFiber !== void 0) {
48
+ const runtime = oldFiber.runtime;
49
+ if (runtime !== null) entry.ctx.registry.delete(runtime.callback);
50
+ while (oldFiber.inertia !== void 0) await oldFiber.inertia;
51
+ delete entry.fiber;
52
+ }
53
+ removeOwnedStyles(id);
54
+ await entry.refresh();
55
+ await entry.fiber?.await();
56
+ }
57
+ let queue = Promise.resolve();
58
+ const handle = (frame) => {
59
+ switch (frame.type) {
60
+ case "rebuilt":
61
+ queue = queue.then(() => reload(frame.id)).catch((error) => {
62
+ ctx.logger.error(`client-hmr: reload of "${frame.id}" failed`);
63
+ ctx.logger.error(error);
64
+ });
65
+ break;
66
+ case "graph": break;
67
+ default: break;
68
+ }
69
+ };
70
+ ctx.effect(() => {
71
+ const source = new EventSource(EVENTS_ENDPOINT);
72
+ source.addEventListener("message", (event) => {
73
+ let frame;
74
+ try {
75
+ frame = JSON.parse(event.data);
76
+ } catch {
77
+ ctx.logger.warn(`client-hmr: unparseable event frame: ${event.data}`);
78
+ return;
79
+ }
80
+ handle(frame);
81
+ });
82
+ return () => {
83
+ source.close();
84
+ };
85
+ }, "client-hmr: event source");
86
+ }
87
+ //#endregion
88
+ exports.EVENTS_ENDPOINT = EVENTS_ENDPOINT;
89
+ exports.apply = apply;
90
+ exports.inject = inject;
91
+ exports.name = name;
92
+ return module.exports;
93
+ }
94
+ });
95
+
96
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,160 @@
1
+ import { statSync } from "node:fs";
2
+ import z from "@deepseek-ai/schemastery";
3
+ //#region lib/types/events.js
4
+ /**
5
+ * Wire protocol of the `/plugins/events` dev SSE channel — single source for
6
+ * both halves of this package. Frames still cross a wire boundary: the
7
+ * browser half validates them at its JSON parse point; sharing the type keeps
8
+ * the two ends from drifting, not from parsing.
9
+ */
10
+ /** System SSE endpoint pushing graph/rebuilt frames (wire protocol constant). */
11
+ const EVENTS_ENDPOINT = "/plugins/events";
12
+ //#endregion
13
+ //#region lib/types/index.js
14
+ /**
15
+ * HMR plugin, node half: the host end of the dev reload chain. One interval
16
+ * stat-polls every graph row's client bundle (polling by design: network
17
+ * mounts deliver no inotify events), reports content changes through
18
+ * `clientModuleHost.rebuilt(id)`, and serves the `/plugins/events` SSE channel
19
+ * broadcasting graph/rebuilt frames to the browser half (src/client/).
20
+ * Dev-only row: prod compositions never mount this plugin.
21
+ */
22
+ /** Cordis plugin name. */
23
+ const name = "client-hmr";
24
+ /** Required services: the web plugin table and the route registry. */
25
+ const inject = ["clientModuleHost", "httpServer"];
26
+ const Config = z.object({ pollIntervalMs: z.number().step(1).min(1).default(500) });
27
+ /** Serialize one frame as an SSE data line. */
28
+ function sseData(frame) {
29
+ return `data: ${JSON.stringify(frame)}\n\n`;
30
+ }
31
+ /**
32
+ * Mount the dev chain: bundle watches, rebuilt reporting, and the SSE channel.
33
+ * @param ctx - host plugin context carrying clientModuleHost and httpServer.
34
+ * @param config - validated {@link Config}.
35
+ */
36
+ function apply(ctx, config) {
37
+ const pollIntervalMs = config.pollIntervalMs;
38
+ const watched = /* @__PURE__ */ new Map();
39
+ const rehash = (id, watch, current) => {
40
+ try {
41
+ ctx.clientModuleHost.rebuilt(id);
42
+ } catch (error) {
43
+ if (error.code === "ENOENT") {
44
+ watch.dirty = true;
45
+ return;
46
+ }
47
+ ctx.logger.warn(error);
48
+ }
49
+ watch.mtimeMs = current.mtimeMs;
50
+ watch.size = current.size;
51
+ watch.dirty = false;
52
+ };
53
+ const watchRow = (id, path) => {
54
+ let baseline;
55
+ try {
56
+ baseline = statSync(path);
57
+ } catch (error) {
58
+ watched.set(id, {
59
+ path,
60
+ mtimeMs: 0,
61
+ size: 0,
62
+ dirty: true
63
+ });
64
+ if (error.code !== "ENOENT") ctx.logger.warn(error);
65
+ return;
66
+ }
67
+ const watch = {
68
+ path,
69
+ mtimeMs: baseline.mtimeMs,
70
+ size: baseline.size,
71
+ dirty: false
72
+ };
73
+ watched.set(id, watch);
74
+ rehash(id, watch, baseline);
75
+ };
76
+ const pollWatches = () => {
77
+ for (const [id, watch] of watched) {
78
+ let current;
79
+ try {
80
+ current = statSync(watch.path);
81
+ } catch (error) {
82
+ watch.dirty = true;
83
+ if (error.code !== "ENOENT") ctx.logger.warn(error);
84
+ continue;
85
+ }
86
+ if (!watch.dirty && current.mtimeMs === watch.mtimeMs && current.size === watch.size) continue;
87
+ rehash(id, watch, current);
88
+ }
89
+ };
90
+ const syncWatches = () => {
91
+ const rows = /* @__PURE__ */ new Map();
92
+ for (const row of ctx.clientModuleHost.graph().entries) {
93
+ const path = ctx.clientModuleHost.clientPath(row.id);
94
+ if (path !== void 0) rows.set(row.id, path);
95
+ }
96
+ for (const [id, watch] of watched) {
97
+ if (rows.get(id) === watch.path) continue;
98
+ watched.delete(id);
99
+ }
100
+ for (const [id, path] of rows) if (!watched.has(id)) watchRow(id, path);
101
+ };
102
+ ctx.effect(() => {
103
+ syncWatches();
104
+ const unsubscribe = ctx.clientModuleHost.onGraphChanged(syncWatches);
105
+ const timer = setInterval(pollWatches, pollIntervalMs);
106
+ timer.unref();
107
+ return () => {
108
+ unsubscribe();
109
+ clearInterval(timer);
110
+ watched.clear();
111
+ };
112
+ }, "client-hmr: bundle watches");
113
+ const connections = /* @__PURE__ */ new Set();
114
+ const connect = (res) => {
115
+ res.writeHead(200, {
116
+ "content-type": "text/event-stream",
117
+ "cache-control": "no-cache",
118
+ "connection": "keep-alive"
119
+ });
120
+ res.write(": connected\n\n");
121
+ res.write(sseData({
122
+ type: "graph",
123
+ graph: ctx.clientModuleHost.graph()
124
+ }));
125
+ connections.add(res);
126
+ res.on("close", () => {
127
+ connections.delete(res);
128
+ });
129
+ };
130
+ ctx.effect(() => {
131
+ const disposeRoute = ctx.httpServer.register({
132
+ kind: "exact",
133
+ path: EVENTS_ENDPOINT,
134
+ handler: (req, res) => {
135
+ if (req.method !== "GET" && req.method !== "HEAD") {
136
+ res.writeHead(405);
137
+ res.end();
138
+ return;
139
+ }
140
+ connect(res);
141
+ }
142
+ });
143
+ const unsubscribe = ctx.clientModuleHost.onRebuilt((id, rev) => {
144
+ const line = sseData({
145
+ type: "rebuilt",
146
+ id,
147
+ rev
148
+ });
149
+ for (const res of connections) res.write(line);
150
+ });
151
+ return () => {
152
+ unsubscribe();
153
+ disposeRoute();
154
+ for (const res of connections) res.destroy();
155
+ connections.clear();
156
+ };
157
+ }, "client-hmr: /plugins/events channel");
158
+ }
159
+ //#endregion
160
+ export { Config, EVENTS_ENDPOINT, apply, inject, name };
@@ -0,0 +1,48 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-client-hmr`.
4
+ * @module @deepseek-ai/dsh-client-hmr/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-client-hmr";
7
+ /** Cordis companion plugin name. */
8
+ const name = "client-hmr-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /** Live fs.watchFile pollers (this package is the composition's only stat-poll user). */
12
+ function statWatchers() {
13
+ return process.getActiveResourcesInfo().filter((kind) => kind === "StatWatcher").length;
14
+ }
15
+ /**
16
+ * Owned relation: every bundle stat watcher the node half starts must die
17
+ * with its fiber — a surviving poller would keep re-hashing bundles for a
18
+ * torn-down dev chain forever. Checked as a baseline delta: the StatWatcher
19
+ * count observed at fiber creation must be restored once disposal has drained
20
+ * the fiber's effects (`internal/plugin` fires at dispose start; the microtask
21
+ * hop lets the disposer queue its unload before `fiber.await()` joins it).
22
+ * SSE-connection and listener teardown live inside the same ctx.effect
23
+ * disposers, so the watcher count is the relation's observable proxy.
24
+ */
25
+ const install = (ctx, fail) => {
26
+ const baselines = /* @__PURE__ */ new WeakMap();
27
+ ctx.on("internal/plugin", async (fiber) => {
28
+ if (fiber.name !== "client-hmr") return;
29
+ if (fiber.uid !== null) {
30
+ baselines.set(fiber, statWatchers());
31
+ return;
32
+ }
33
+ const baseline = baselines.get(fiber);
34
+ if (baseline === void 0) return;
35
+ await Promise.resolve();
36
+ await fiber.await();
37
+ const remaining = statWatchers();
38
+ if (remaining > baseline) fail(`client-hmr fiber disposed but ${remaining - baseline} bundle stat watcher(s) survived teardown`);
39
+ }, { global: true });
40
+ };
41
+ /**
42
+ * Register this package's invariant companion.
43
+ * @param ctx - Cordis context carrying the invariant service.
44
+ * @returns the installed registration's disposer after setup succeeds.
45
+ */
46
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
47
+ //#endregion
48
+ export { apply, inject, name };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * client-hmr, browser half: hot-reload driver for client plugin entries.
3
+ *
4
+ * Listens on the host's system SSE channel (`GET /plugins/events`); on a
5
+ * `rebuilt` frame it reloads the entry's bundle and swaps the cordis
6
+ * fiber in place. Every graph entry is a plugin bundle
7
+ * — `immediately` rows differ only in stage-one prefetch (a boot
8
+ * optimization), so all rostered plugin packages share these reload semantics;
9
+ * normal packages (react family, cordis, shell, pure libs) are not entries
10
+ * and shell changes still mean a page reload. Cascade is zero-touch:
11
+ * downstream fibers key their activation epoch on provider fiber uids
12
+ * (vendor/cordis/src/fiber.ts `_refresh`), so replacing a provider fiber
13
+ * re-cascades natively — reloading a data-layer plugin (connection/runtime)
14
+ * cascades into its UI dependents with no HMR-side bookkeeping.
15
+ *
16
+ * Reload order (lazy CJS table): invalidate (drop the stale factory and
17
+ * materialized record) → prefetch (load and register the fresh
18
+ * factory) → registry-first teardown → drain old fiber unload → remove
19
+ * owned `<style data-plugin>` tags → `entry.refresh()` materializes the new
20
+ * factory. Invalidate MUST precede prefetch: a live factory makes prefetch
21
+ * a no-op, and re-executing a bundle over an undeleted registration is a
22
+ * loud duplicate. The swap is safe because execution is pure registration
23
+ * under the lazy model — every module side effect (CSS injection included)
24
+ * lives in the factory closure and runs at materialization, inside
25
+ * refresh(). That also keeps the CSS ordering guarantee: owned styles are
26
+ * removed after the old fiber's disposers drained (SlotCore one-owner
27
+ * unregister) and before materialization re-injects tags under the same
28
+ * stable tag ids.
29
+ *
30
+ * Failure window: if prefetch rejects after invalidate, the module is left
31
+ * unregistered while the OLD fiber keeps running untouched (teardown never
32
+ * started) — degraded but recoverable, the next rebuilt frame retries from
33
+ * scratch. Consistent with the no-rollback policy below. Known dev-only
34
+ * race: a rebuilt frame overlapping a still-in-flight boot arrival shares
35
+ * that arrival's task and may materialize the pre-rebuild bytes; the next
36
+ * rebuilt frame self-heals.
37
+ *
38
+ * Why not the naive `entry.fiber.dispose()` → `entry.refresh()` path:
39
+ * 1. `Entry.fiber` is never cleared on dispose (vendor/loader/src/config/
40
+ * entry.ts assigns it only in `_init`), so `refresh()` hits its
41
+ * `if (this.fiber) return` guard and no-ops.
42
+ * 2. A bare `fiber.dispose()` lands in Loader's self-dispose branch
43
+ * (vendor/loader/src/index.ts `internal/plugin` case 4: the registry
44
+ * still holds the runtime at emit time), which flags the entry
45
+ * `disabled: true` — permanently.
46
+ * vendor/hmr's reload skeleton documents the fix: delete the runtime record
47
+ * FIRST (`registry.delete` → case 4 returns early, the entry stays enabled),
48
+ * then rebuild. `entry.fiber` is additionally cleared so
49
+ * `entry.refresh()` re-imports and re-plugins through the Loader's own
50
+ * `_init` (entry-resolved config, automatic `fiber.entry` rebinding) instead
51
+ * of hand-rolling `registry.plugin`. Client entries have exactly one fiber
52
+ * per runtime, so `registry.delete` never collaterally disposes siblings.
53
+ *
54
+ * Self-reload: this plugin is itself a graph entry, so a rebuilt frame may
55
+ * name it. The in-flight reload keeps running in the old bundle's closure
56
+ * (its EventSource closes with the old fiber's effects); the new bundle's
57
+ * apply opens a fresh channel. Frames arriving during the gap are lost —
58
+ * acceptable for the dev channel, the next rebuild renotifies.
59
+ *
60
+ * Failure policy: no rollback. An import failure leaves the entry
61
+ * fiberless (the next rebuilt frame retries from scratch); an apply failure
62
+ * leaves a FAILED fiber for the shell's status projection. Both log loudly.
63
+ */
64
+ import type { Context } from '@deepseek-ai/cordis';
65
+ export type { PluginsEventFrame } from '../events.ts';
66
+ export { EVENTS_ENDPOINT } from '../events.ts';
67
+ /** Cordis plugin name. */
68
+ export declare const name = "client-hmr";
69
+ /** Required services: the vendored Loader (entry governance) and the client module system (boot provide, service name `modules`). */
70
+ export declare const inject: string[];
71
+ /**
72
+ * Mount the HMR driver: subscribe to the system SSE channel and hot-swap
73
+ * rebuilt entries.
74
+ * @param ctx - plugin context with `loader` and `modules` available.
75
+ */
76
+ export declare function apply(ctx: Context): void;
77
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Wire protocol of the `/plugins/events` dev SSE channel — single source for
3
+ * both halves of this package. Frames still cross a wire boundary: the
4
+ * browser half validates them at its JSON parse point; sharing the type keeps
5
+ * the two ends from drifting, not from parsing.
6
+ */
7
+ import type { WebBootGraph } from '@deepseek-ai/dsh-client-modules';
8
+ /** One SSE frame: the full graph on connect, or one rebuilt bundle notice. */
9
+ export type PluginsEventFrame = {
10
+ type: 'graph';
11
+ graph: WebBootGraph;
12
+ } | {
13
+ type: 'rebuilt';
14
+ id: string;
15
+ rev: string;
16
+ };
17
+ /** System SSE endpoint pushing graph/rebuilt frames (wire protocol constant). */
18
+ export declare const EVENTS_ENDPOINT = "/plugins/events";
19
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1,21 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import z from '@deepseek-ai/schemastery';
3
+ export type { PluginsEventFrame } from './events.ts';
4
+ export { EVENTS_ENDPOINT } from './events.ts';
5
+ /** Cordis plugin name. */
6
+ export declare const name = "client-hmr";
7
+ /** Required services: the web plugin table and the route registry. */
8
+ export declare const inject: string[];
9
+ /** Plugin config, validated by the same-named schemastery schema. */
10
+ export interface Config {
11
+ /** Bundle stat-poll interval in milliseconds (default 500, the build-side watcher's polling default). */
12
+ pollIntervalMs?: number;
13
+ }
14
+ export declare const Config: z<Config>;
15
+ /**
16
+ * Mount the dev chain: bundle watches, rebuilt reporting, and the SSE channel.
17
+ * @param ctx - host plugin context carrying clientModuleHost and httpServer.
18
+ * @param config - validated {@link Config}.
19
+ */
20
+ export declare function apply(ctx: Context, config: Config): void;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-client-hmr`.
3
+ * @module @deepseek-ai/dsh-client-hmr/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "client-hmr-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@deepseek-ai/dsh-client-hmr",
3
+ "description": "Dev-only hot-reload driver for script-loaded client entries: SSE rebuilt frames → invalidate/prefetch → fiber swap through the vendored Loader entry",
4
+ "version": "0.0.1-rc.1",
5
+ "publishConfig": {
6
+ "access": "restricted"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/client/hmr"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./invariant": {
22
+ "types": "./lib/types/invariant.d.ts",
23
+ "default": "./lib/invariant.js"
24
+ },
25
+ "./client": {
26
+ "types": "./lib/types/client/index.d.ts",
27
+ "default": "./lib/client.js"
28
+ },
29
+ "./src/*": "./src/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "dsh": {
33
+ "client": {
34
+ "inject": [],
35
+ "platform": "web",
36
+ "immediately": true
37
+ }
38
+ },
39
+ "license": "BSD-3-Clause",
40
+ "dependencies": {
41
+ "@deepseek-ai/schemastery": "^3.18.1-rc.1"
42
+ },
43
+ "peerDependencies": {
44
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
45
+ "@deepseek-ai/dsh-client-modules": "^0.0.1-rc.1",
46
+ "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.1",
47
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
48
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
49
+ },
50
+ "devDependencies": {
51
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
52
+ "@deepseek-ai/dsh-client-modules": "^0.0.1-rc.1",
53
+ "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.1",
54
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
55
+ "@deepseek-ai/cordis": "^4.0.1-rc.1"
56
+ },
57
+ "files": [
58
+ "lib/index.js",
59
+ "lib/invariant.js",
60
+ "lib/client.js",
61
+ "lib/types/**/*.d.ts"
62
+ ]
63
+ }