@hudhod/core 0.1.0

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hudhod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,165 @@
1
+ # @hudhod/core
2
+
3
+ The headless runtime behind [hudhod](../../README.md), an in-browser IDE built on
4
+ WebContainers.
5
+
6
+ This package implements the API described by
7
+ [`@hudhod/sdk`](../sdk/README.md): file system, search, diff, process, command,
8
+ and extension-host services. It contains **no UI and no framework code** — no
9
+ React, no state library — so it can be driven by any front end and unit tested in
10
+ plain Node.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add @hudhod/core @hudhod/sdk
16
+ ```
17
+
18
+ Use `@hudhod/webcontainer` when a browser host needs WebContainer adapters.
19
+
20
+ ## Entry points
21
+
22
+ | Import | Environment | Contents |
23
+ | ---------------------- | ----------- | -------------------------------------------- |
24
+ | `@hudhod/core` | Any | Services, runtime factory, extension host |
25
+ | `@hudhod/webcontainer` | Browser | WebContainer filesystem and process adapters |
26
+
27
+ The split is deliberate. `@webcontainer/api` requires `SharedArrayBuffer` and
28
+ cross-origin isolation, so it cannot load in Node. Keeping those adapters in a
29
+ separate package means the main entry stays importable from tests, scripts, and
30
+ server code — which is what makes the runtime testable without a browser.
31
+
32
+ ```ts
33
+ // Safe anywhere.
34
+ import { DisposableStore, Emitter } from "@hudhod/core";
35
+
36
+ // Browser only.
37
+ import { createWebContainerServices } from "@hudhod/webcontainer";
38
+ ```
39
+
40
+ ## Base primitives
41
+
42
+ ### `Disposable` and `DisposableStore`
43
+
44
+ Every subscription returns a `Disposable`. `DisposableStore` collects them and
45
+ releases them together, newest-first:
46
+
47
+ ```ts
48
+ import { DisposableStore, toDisposable } from "@hudhod/core";
49
+
50
+ const store = new DisposableStore();
51
+ store.add(emitter.event(handler));
52
+ store.add(toDisposable(() => socket.close()));
53
+ store.dispose();
54
+ ```
55
+
56
+ Two behaviours are worth knowing:
57
+
58
+ - Adding to an already-disposed store **disposes the argument immediately**, so
59
+ a late registration cannot leak.
60
+ - If one disposable throws, the rest still run; the failures are collected and
61
+ rethrown together as an `AggregateError`.
62
+
63
+ ### `Emitter`
64
+
65
+ The `Event<T>` producer:
66
+
67
+ ```ts
68
+ import { Emitter } from "@hudhod/core";
69
+
70
+ const emitter = new Emitter<string>();
71
+ const sub = emitter.event((name) => console.log(name));
72
+ emitter.fire("world");
73
+ sub.dispose();
74
+ ```
75
+
76
+ `fire()` iterates a snapshot of the listener set, so subscribing or
77
+ unsubscribing during dispatch cannot disturb the in-flight delivery. A throwing
78
+ listener is reported to `onListenerError` rather than propagating to whoever
79
+ called `fire()` — a producer usually cannot do anything useful about a
80
+ consumer's bug.
81
+
82
+ ### `CancellationTokenSource`
83
+
84
+ ```ts
85
+ import { CancellationTokenSource } from "@hudhod/core";
86
+
87
+ const source = new CancellationTokenSource();
88
+ const results = await search(query, source.token);
89
+ source.cancel();
90
+ ```
91
+
92
+ Listeners registered _after_ cancellation are invoked immediately, so a late
93
+ subscriber cannot miss the signal. `tokenFromAbortSignal()` adapts a standard
94
+ `AbortSignal` if you already have one.
95
+
96
+ ### Paths
97
+
98
+ hudhod paths are always absolute and POSIX-style, rooted at the workspace root.
99
+ These helpers are deliberately independent of Node's `path` so behaviour is
100
+ identical in the browser and free of platform separator quirks.
101
+
102
+ ```ts
103
+ import { basename, dirname, isSubPath, joinPath, normalizePath } from "@hudhod/core";
104
+
105
+ normalizePath("/src//lib/../index.ts"); // "/src/index.ts"
106
+ joinPath("/src", "lib", "index.ts"); // "/src/lib/index.ts"
107
+ isSubPath("/src", "/src-old"); // false — whole segments only
108
+ ```
109
+
110
+ `normalizePath()` throws `InvalidPath` for relative paths, null bytes, and any
111
+ path traversing above the root. Every service normalises its inputs, so path
112
+ traversal is rejected at the boundary rather than reaching the file system.
113
+
114
+ ### Errors
115
+
116
+ ```ts
117
+ import { fileNotFound } from "@hudhod/core";
118
+
119
+ throw fileNotFound("/missing.ts"); // code: "FileNotFound", path: "/missing.ts"
120
+ ```
121
+
122
+ Consumers should branch on `error.code` via `isHudhodError()` from
123
+ `@hudhod/sdk`. Message text is not part of the contract.
124
+
125
+ ## Extensions
126
+
127
+ `InProcessExtensionHost` loads the curated, first-party extension modules used
128
+ by hudhod. It validates each manifest with Zod before registration, supports
129
+ `onStartup`, `onCommand:*`, `onFileOpen:*`, and `onView:*` activation events,
130
+ and automatically disposes resources pushed onto `context.subscriptions` when
131
+ an extension deactivates.
132
+
133
+ ```ts
134
+ import { createHudhodRuntime } from "@hudhod/core";
135
+
136
+ const runtime = createHudhodRuntime({
137
+ fileSystemProvider,
138
+ processSpawner,
139
+ windowUiProvider,
140
+ });
141
+ runtime.extensions.register(extension);
142
+ await runtime.extensions.activateByEvent("onStartup");
143
+ ```
144
+
145
+ Concurrent activation triggers are deduplicated, so an extension's `activate()`
146
+ hook runs once even if a view and command arrive together.
147
+
148
+ For a React/Dockview host, use `createHudhodReactHost()` and `HudhodWorkbench`
149
+ from `@hudhod/react`. See [COMPOSABLE-IDE-DEVELOPMENT.md](../../COMPOSABLE-IDE-DEVELOPMENT.md).
150
+
151
+ ## Development
152
+
153
+ ```bash
154
+ pnpm test # run the suite
155
+ pnpm test:watch # watch mode
156
+ pnpm test:coverage # coverage, enforced at 80%
157
+ pnpm typecheck
158
+ ```
159
+
160
+ Tests run in Node against the in-memory file system provider — no browser and no
161
+ WebContainer required.
162
+
163
+ ## License
164
+
165
+ MIT