bare-stow 0.1.6 → 0.1.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.
- package/README.md +6 -38
- package/index.d.ts +95 -0
- package/index.js +60 -8
- package/lib/fs.js +4 -0
- package/lib/host.d.ts +44 -7
- package/lib/host.js +57 -24
- package/lib/protocol.d.ts +25 -1
- package/lib/protocol.js +10 -1
- package/lib/runtime.d.ts +29 -0
- package/lib/runtime.js +92 -0
- package/lib/shim.js +5 -51
- package/lib/target/bare-sidecar.js +9 -9
- package/lib/target/bare-worker.js +15 -9
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -66,8 +66,12 @@ ipc.on('exit', (code) => {
|
|
|
66
66
|
ipc.destroy()
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
The harness imports its runtime by name, `bare-stow/host` along with `bare-sidecar` or `bare-worker` depending on the target, so the package that the harness is written into must depend on both. Relying on another package to bring them in only works while a package manager happens to hoist them, and not at all under an isolated layout such as that of pnpm. `stow()` resolves every import of the harness from `out` before writing anything and throws if one of them can't be found.
|
|
70
|
+
|
|
69
71
|
For the `bare-sidecar` target, `start()` accepts the options of `bare-sidecar`. The sidecar inherits the standard streams of the host unless `stdio` says otherwise.
|
|
70
72
|
|
|
73
|
+
`start()` also accepts `timeout`, the number of milliseconds to wait for the worker to signal ready before rejecting and terminating it, and `grace`, the number of milliseconds a terminating worker gets to run `stop()` and exit before it is killed. `timeout` defaults to waiting indefinitely and `grace` to 5 seconds. The worker holds itself to the same `grace` whenever it shuts down, including when it receives `SIGTERM` or the host goes away without terminating it. On Windows, a sidecar is killed along with its host instead, so `stop()` only runs when the host terminates it.
|
|
74
|
+
|
|
71
75
|
## Protocol
|
|
72
76
|
|
|
73
77
|
The harness multiplexes a control channel and a user data channel over the underlying binary duplex stream. The `ipc` returned from `start()` is itself a duplex stream carrying the user data; control frames ride alongside it on the same handle. RPC libraries (such as `bare-rpc`) bind to `ipc` just like a raw stream.
|
|
@@ -114,43 +118,7 @@ const ipc = wrap(stream) // Any duplex byte stream
|
|
|
114
118
|
await ipc.ready
|
|
115
119
|
```
|
|
116
120
|
|
|
117
|
-
##
|
|
118
|
-
|
|
119
|
-
#### `const artifacts = stow(entry, target, out[, opts])`
|
|
120
|
-
|
|
121
|
-
Bundle the module graph rooted at `entry` for a `target` runtime and write the resulting artifacts to disk at and alongside `out`. Returns an async generator that yields `{ url }` objects as each artifact is written, allowing callers to observe progress.
|
|
122
|
-
|
|
123
|
-
`entry` is a `URL` (or `URL`-coercible string) pointing at the entry module. `target` selects the target runtime, given either as a built-in name (`'bare-sidecar'` or `'bare-worker'`) or as a [target provider](#targets-and-rpc-providers) object. Other targets ship as their own packages; require one and pass it as the provider. The target determines the harness format, bundle extension, host triples, and whether assets and addons are linked into the bundle or offloaded as sibling files. `out` is the output `URL` of the harness; the bundle is written next to it with the target's extension.
|
|
124
|
-
|
|
125
|
-
The harness artifacts are yielded first: The harness itself at `out`, followed by a TypeScript declaration (`.d.ts`) alongside it so hosts importing the harness are typed. The bundle is yielded next, then any offloaded assets and native addons when the target supports offloading.
|
|
126
|
-
|
|
127
|
-
Options include:
|
|
128
|
-
|
|
129
|
-
```js
|
|
130
|
-
opts = {
|
|
131
|
-
client,
|
|
132
|
-
server,
|
|
133
|
-
base,
|
|
134
|
-
hosts,
|
|
135
|
-
resolveTarget,
|
|
136
|
-
resolveRPC
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
- `client`: The RPC library to wire into the harness as a client, given either as a built-in name (`'bare-rpc'`) or as an [RPC provider](#targets-and-rpc-providers) object.
|
|
141
|
-
- `server`: The RPC library to wire into the bundle entry shim as a server, given either as a built-in name (`'bare-rpc'`) or as an [RPC provider](#targets-and-rpc-providers) object.
|
|
142
|
-
- `base`: The base `URL` of the module graph, which must contain every module in the bundle. Defaults to the closest directory that does.
|
|
143
|
-
- `hosts`: An array of host triples to build for. Must be a subset of the host triples supported by the target; passing a host the target does not support throws. Defaults to all host triples supported by the target.
|
|
144
|
-
- `resolveTarget`: A function mapping a target name to a [target provider](#targets-and-rpc-providers) object, called when `target` is a name not in the built-in registry (which knows `'bare-sidecar'` and `'bare-worker'`).
|
|
145
|
-
- `resolveRPC`: A function mapping an RPC library name to an [RPC provider](#targets-and-rpc-providers) object, called when `client` or `server` is a name not in the built-in registry (which only knows `'bare-rpc'`).
|
|
146
|
-
|
|
147
|
-
Any additional options are forwarded to `bare-pack`. See <https://github.com/holepunchto/bare-pack> for the full set, including `builtins`, `imports`, `defer`, and `resolve`.
|
|
148
|
-
|
|
149
|
-
#### TypeScript
|
|
150
|
-
|
|
151
|
-
TypeScript modules are supported out of the box: `.ts`, `.mts`, and `.cts` sources are stripped of their type syntax with [`bare-type-stripper`](https://github.com/holepunchto/bare-type-stripper) as they are read, and their extensions are aliased to `.js`, `.mjs`, and `.cjs` respectively for module-type detection. The stripper only erases types, so the module system follows the aliased JavaScript extension. Because stripping is purely lexical, non-erasable constructs (`enum`, `namespace` with a body, parameter properties) throw, and JSX (`.tsx`) is not supported.
|
|
152
|
-
|
|
153
|
-
### Targets and RPC providers
|
|
121
|
+
## Targets and RPC providers
|
|
154
122
|
|
|
155
123
|
The harness and RPC code generators are decoupled from the bundler: `target`, `client`, and `server` each accept a provider object directly, so generators can live outside `bare-stow`. Passing a name instead resolves it through `resolveTarget` / `resolveRPC`, which default to the built-in registries.
|
|
156
124
|
|
|
@@ -169,7 +137,7 @@ A target provider describes the runtime profile it owns and generates the harnes
|
|
|
169
137
|
target = {
|
|
170
138
|
name, // The target's name, used in diagnostics.
|
|
171
139
|
linked, // Whether assets and addons are linked into the bundle.
|
|
172
|
-
offload, // Whether (and which) assets and addons are offloaded
|
|
140
|
+
offload, // Whether (and which) assets and addons are offloaded to a `<bundle>.d` directory next to the bundle.
|
|
173
141
|
format, // The bundle encoding: 'bundle', 'bundle.cjs', 'bundle.mjs', or 'bundle.json'.
|
|
174
142
|
encoding, // The string encoding for encoded formats, or null for raw bytes.
|
|
175
143
|
extension, // The bundle file extension, e.g. '.bundle'.
|
package/index.d.ts
CHANGED
|
@@ -1,63 +1,158 @@
|
|
|
1
1
|
import URL from 'bare-url'
|
|
2
2
|
import { PackOptions } from 'bare-pack'
|
|
3
3
|
|
|
4
|
+
/** A single generated source artifact, such as a harness or a type declaration. */
|
|
4
5
|
interface Artifact {
|
|
6
|
+
/**
|
|
7
|
+
* The file extension the artifact should be written with alongside the primary output, or unset
|
|
8
|
+
* to write it to the primary output path itself.
|
|
9
|
+
*/
|
|
5
10
|
extension?: string
|
|
11
|
+
/** The artifact's source text. */
|
|
6
12
|
source: string
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
type TargetName = 'bare-sidecar' | 'bare-worker'
|
|
10
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The context passed to a target's `generate()`, describing the bundle to embed and the RPC wiring
|
|
19
|
+
* to splice in.
|
|
20
|
+
*/
|
|
11
21
|
interface TargetContext {
|
|
22
|
+
/** The module specifier the harness uses to load the bundle, relative to the harness itself. */
|
|
12
23
|
bundleSpecifier: string
|
|
24
|
+
/** The identifier bound to the IPC stream in generated harness code. */
|
|
13
25
|
ipc: string
|
|
26
|
+
/** The identifier bound to the RPC client/server in generated harness code. */
|
|
14
27
|
rpc: string
|
|
28
|
+
/** The module system the harness is generated in, `'esm'` or `'cjs'`. */
|
|
15
29
|
module: 'esm' | 'cjs'
|
|
30
|
+
/**
|
|
31
|
+
* The resolved client RPC wiring to splice into the harness, or `null` when no client RPC was
|
|
32
|
+
* requested.
|
|
33
|
+
*/
|
|
16
34
|
client: RPCClient | null
|
|
17
35
|
}
|
|
18
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A bundling target, such as `bare-sidecar` or `bare-worker`, describing how to package and boot a
|
|
39
|
+
* bundle on a given host runtime.
|
|
40
|
+
*/
|
|
19
41
|
interface Target {
|
|
42
|
+
/** The target's name. */
|
|
20
43
|
name: string
|
|
44
|
+
/** Whether the bundle links against its dependencies rather than inlining them. */
|
|
21
45
|
linked: boolean
|
|
46
|
+
/**
|
|
47
|
+
* Whether native addons and/or assets are written out as sibling files instead of being inlined
|
|
48
|
+
* into the bundle.
|
|
49
|
+
*/
|
|
22
50
|
offload: boolean | { addons?: boolean; assets?: boolean }
|
|
51
|
+
/** The encoding format of the written bundle artifact. */
|
|
23
52
|
format: 'bundle' | 'bundle.cjs' | 'bundle.mjs' | 'bundle.json'
|
|
53
|
+
/**
|
|
54
|
+
* The text encoding used when the bundle format wraps the bundle as a string, or `null` when the
|
|
55
|
+
* bundle is written as raw bytes.
|
|
56
|
+
*/
|
|
24
57
|
encoding: string | null
|
|
58
|
+
/** The file extension used for the target's harness artifact. */
|
|
25
59
|
extension: string
|
|
60
|
+
/**
|
|
61
|
+
* The module system the harness is generated in, `'esm'` or `'cjs'`. When unset, it follows the
|
|
62
|
+
* output path's extension or nearest `package.json` `type`.
|
|
63
|
+
*/
|
|
26
64
|
module?: 'esm' | 'cjs'
|
|
65
|
+
/** The host triples the target supports. */
|
|
27
66
|
hosts: string[]
|
|
67
|
+
/**
|
|
68
|
+
* Generate the harness artifacts for `context`.
|
|
69
|
+
* @param context - The target context describing the bundle to embed and the RPC wiring to splice
|
|
70
|
+
* in.
|
|
71
|
+
*/
|
|
28
72
|
generate(context: TargetContext): Artifact[]
|
|
29
73
|
}
|
|
30
74
|
|
|
31
75
|
type RPCName = 'bare-rpc'
|
|
32
76
|
|
|
77
|
+
/** The context passed to an RPC adapter's `generate()`. */
|
|
33
78
|
interface RPCContext {
|
|
79
|
+
/** The identifier bound to the IPC stream in generated code. */
|
|
34
80
|
ipc: string
|
|
81
|
+
/** The identifier bound to the RPC client/server in generated code. */
|
|
35
82
|
rpc: string
|
|
83
|
+
/** The module system the generated code targets, `'esm'` or `'cjs'`. */
|
|
36
84
|
module: 'esm' | 'cjs'
|
|
85
|
+
/**
|
|
86
|
+
* Whether the generated wiring is for the `'client'` (harness side) or `'server'` (bundle entry
|
|
87
|
+
* shim side).
|
|
88
|
+
*/
|
|
37
89
|
role: 'client' | 'server'
|
|
38
90
|
}
|
|
39
91
|
|
|
92
|
+
/**
|
|
93
|
+
* An RPC library adapter that generates the wiring code spliced into a stowed bundle's harness or
|
|
94
|
+
* entry shim.
|
|
95
|
+
*/
|
|
40
96
|
interface RPC {
|
|
97
|
+
/** The RPC adapter's name. */
|
|
41
98
|
name: string
|
|
99
|
+
/**
|
|
100
|
+
* Generate the RPC wiring artifacts for `context`.
|
|
101
|
+
* @param context - The RPC context describing the identifiers, module system, and role to
|
|
102
|
+
* generate wiring for.
|
|
103
|
+
*/
|
|
42
104
|
generate(context: RPCContext): Artifact[]
|
|
43
105
|
}
|
|
44
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The resolved client RPC wiring, carrying both the runtime source to splice into the harness and
|
|
109
|
+
* the type declaration to splice into the harness's `.d.ts`.
|
|
110
|
+
*/
|
|
45
111
|
interface RPCClient {
|
|
112
|
+
/** The generated runtime source for the client RPC wiring. */
|
|
46
113
|
source: string
|
|
114
|
+
/** The generated type declaration for the client RPC wiring. */
|
|
47
115
|
type: string
|
|
48
116
|
}
|
|
49
117
|
|
|
118
|
+
/** Options for `stow()`. */
|
|
50
119
|
interface StowOptions extends Omit<PackOptions, 'offload' | 'linked' | 'resolve' | 'aliases'> {
|
|
120
|
+
/** The RPC library (or its name) to wire into the harness. */
|
|
51
121
|
client?: RPC | RPCName
|
|
122
|
+
/** The RPC library (or its name) to wire into the bundle's entry shim. */
|
|
52
123
|
server?: RPC | RPCName
|
|
124
|
+
/**
|
|
125
|
+
* Resolve a target name to a `Target`, overriding the default target resolution.
|
|
126
|
+
* @param name - The target name to resolve to a `Target`.
|
|
127
|
+
*/
|
|
53
128
|
resolveTarget?(name: string): Target
|
|
129
|
+
/**
|
|
130
|
+
* Resolve an RPC name to an `RPC`, overriding the default RPC resolution.
|
|
131
|
+
* @param name - The RPC name to resolve to an `RPC`.
|
|
132
|
+
*/
|
|
54
133
|
resolveRPC?(name: string): RPC
|
|
55
134
|
}
|
|
56
135
|
|
|
136
|
+
/** An artifact written by `stow()`, yielded once its file has been written. */
|
|
57
137
|
interface StowArtifact {
|
|
138
|
+
/** The file URL the artifact was written to. */
|
|
58
139
|
url: URL
|
|
59
140
|
}
|
|
60
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Bundle the module graph rooted at `entry` for `target`, writing a harness plus bundle to `out`.
|
|
144
|
+
* @param entry - The entry module to bundle, as a `file:` URL or path string.
|
|
145
|
+
* @param target - The bundling target: a `Target` object, or a target name resolved to one (the
|
|
146
|
+
* built-in `bare-sidecar` and `bare-worker`, or a `bare-stow-target-<name>` package).
|
|
147
|
+
* @param out - The path to write the harness to, as a `file:` URL or path string; the bundle is
|
|
148
|
+
* written alongside it.
|
|
149
|
+
* @param opts - Options; see [`StowOptions`](#stowoptions).
|
|
150
|
+
* @returns An async generator that yields each written artifact's `url` as it is produced, starting
|
|
151
|
+
* with the harness, then the bundle, then any offloaded addon or asset files.
|
|
152
|
+
* @throws The `target` argument is missing.
|
|
153
|
+
* @throws The `out` argument is missing.
|
|
154
|
+
* @throws A host in `opts.hosts` is not supported by the resolved target.
|
|
155
|
+
*/
|
|
61
156
|
declare function stow(
|
|
62
157
|
entry: URL | string,
|
|
63
158
|
target: Target | TargetName,
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const pack = require('bare-pack')
|
|
|
4
4
|
const id = require('bare-bundle-id')
|
|
5
5
|
const strip = require('bare-type-stripper')
|
|
6
6
|
const { resolve } = require('bare-module-traverse')
|
|
7
|
+
const lex = require('bare-module-lexer')
|
|
7
8
|
const fs = require('./lib/fs')
|
|
8
9
|
const shim = require('./lib/shim')
|
|
9
10
|
const harness = require('./lib/target')
|
|
@@ -19,7 +20,7 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
|
|
|
19
20
|
|
|
20
21
|
const targetName = typeof target === 'string' ? target : t.name
|
|
21
22
|
|
|
22
|
-
entry = new URL(entry)
|
|
23
|
+
entry = await fs.resolveModule(new URL(entry))
|
|
23
24
|
out = new URL(out)
|
|
24
25
|
|
|
25
26
|
// The target may pin a module system; otherwise it follows the output path.
|
|
@@ -82,12 +83,13 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
|
|
|
82
83
|
},
|
|
83
84
|
readModule,
|
|
84
85
|
fs.listPrefix,
|
|
86
|
+
fs.resolveModule,
|
|
85
87
|
writeFile
|
|
86
88
|
)
|
|
87
89
|
|
|
88
90
|
base = resolveBase(base, bundle)
|
|
89
91
|
|
|
90
|
-
bundle = rerootOffloaded(bundle.unmount(base), base,
|
|
92
|
+
bundle = rerootOffloaded(bundle.unmount(base), base, bundleURL, offloaded)
|
|
91
93
|
|
|
92
94
|
bundle.id = id(bundle).toString('hex')
|
|
93
95
|
|
|
@@ -118,6 +120,10 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
|
|
|
118
120
|
client: clientSetup
|
|
119
121
|
})
|
|
120
122
|
|
|
123
|
+
for (const artifact of artifacts) {
|
|
124
|
+
if (!artifact.extension) await assertImportsResolve(artifact.source, out)
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
// The first artifact is the harness written to `out`; any further artifacts
|
|
122
128
|
// are siblings named after `out` with the artifact's own extension.
|
|
123
129
|
for (const artifact of artifacts) {
|
|
@@ -130,6 +136,10 @@ module.exports = async function* stow(entry, target, out, opts = {}) {
|
|
|
130
136
|
await fs.writeFile(bundleURL, encodeBundle(bundle, t.format, t.encoding ?? 'utf8'))
|
|
131
137
|
yield { url: bundleURL }
|
|
132
138
|
|
|
139
|
+
// Offloaded files are collected in the order their reads complete, so they
|
|
140
|
+
// are sorted to keep the output stable.
|
|
141
|
+
offloaded.sort((a, b) => (a.url.href < b.url.href ? -1 : a.url.href > b.url.href ? 1 : 0))
|
|
142
|
+
|
|
133
143
|
for (const artifact of offloaded) {
|
|
134
144
|
await fs.writeFile(artifact.url, artifact.source)
|
|
135
145
|
yield { url: artifact.url }
|
|
@@ -203,20 +213,22 @@ function collectOffloaded(sink) {
|
|
|
203
213
|
}
|
|
204
214
|
}
|
|
205
215
|
|
|
206
|
-
// Offloaded files are written
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
216
|
+
// Offloaded files are written to a directory named after the bundle rather than
|
|
217
|
+
// next to the harness, as a mirrored `node_modules` beside the harness would
|
|
218
|
+
// shadow the packages it imports. Their resolutions are rewritten to point into
|
|
219
|
+
// that directory, along with the directories that hold them.
|
|
220
|
+
function rerootOffloaded(bundle, base, bundleURL, offloaded) {
|
|
210
221
|
if (offloaded.length === 0) return bundle
|
|
211
222
|
|
|
212
|
-
const
|
|
223
|
+
const name = path.basename(bundleURL.pathname) + '.d'
|
|
224
|
+
const dir = new URL(name + '/', bundleURL)
|
|
213
225
|
const rewrites = new Map()
|
|
214
226
|
|
|
215
227
|
for (const file of offloaded) {
|
|
216
228
|
const relative = offloadedPath(file.url, base)
|
|
217
229
|
|
|
218
230
|
let key = '/' + path.posix.relative(base.pathname, file.url.pathname)
|
|
219
|
-
let value = '/../' + relative
|
|
231
|
+
let value = '/../' + name + '/' + relative
|
|
220
232
|
|
|
221
233
|
rewrites.set(key, value)
|
|
222
234
|
|
|
@@ -242,6 +254,8 @@ function rerootOffloaded(bundle, base, out, offloaded) {
|
|
|
242
254
|
}
|
|
243
255
|
|
|
244
256
|
bundle.resolutions = resolutions
|
|
257
|
+
bundle.addons = bundle.addons.map((href) => rewrites.get(href) || href)
|
|
258
|
+
bundle.assets = bundle.assets.map((href) => rewrites.get(href) || href)
|
|
245
259
|
|
|
246
260
|
return bundle
|
|
247
261
|
}
|
|
@@ -274,6 +288,44 @@ function isTerminator(input) {
|
|
|
274
288
|
return input === '' || input.endsWith('/') || input.endsWith(':')
|
|
275
289
|
}
|
|
276
290
|
|
|
291
|
+
async function assertImportsResolve(source, out) {
|
|
292
|
+
for (const entry of lex(source).imports) {
|
|
293
|
+
const { specifier } = entry
|
|
294
|
+
|
|
295
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) continue
|
|
296
|
+
|
|
297
|
+
if (await resolves(entry, out)) continue
|
|
298
|
+
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Harness '${fileURLToPath(out)}' imports '${specifier}', which the package containing it does not depend on`
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function resolves(entry, parentURL) {
|
|
306
|
+
const resolver = resolve.bare(entry, parentURL)
|
|
307
|
+
|
|
308
|
+
let next = resolver.next()
|
|
309
|
+
|
|
310
|
+
while (next.done !== true) {
|
|
311
|
+
const value = next.value
|
|
312
|
+
|
|
313
|
+
if (value.package) {
|
|
314
|
+
const source = await fs.readModule(value.package)
|
|
315
|
+
|
|
316
|
+
next = resolver.next(source === null ? null : JSON.parse(source))
|
|
317
|
+
} else {
|
|
318
|
+
const exists = await fs.isFile(value.resolution)
|
|
319
|
+
|
|
320
|
+
if (exists) return true
|
|
321
|
+
|
|
322
|
+
next = resolver.next(false)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return false
|
|
327
|
+
}
|
|
328
|
+
|
|
277
329
|
async function resolveModule(out) {
|
|
278
330
|
switch (path.extname(out.pathname)) {
|
|
279
331
|
case '.mjs':
|
package/lib/fs.js
CHANGED
package/lib/host.d.ts
CHANGED
|
@@ -1,19 +1,56 @@
|
|
|
1
1
|
import { Duplex } from 'bare-stream'
|
|
2
2
|
import { Protocol } from './protocol'
|
|
3
3
|
|
|
4
|
+
/** Options for `wrap()`. */
|
|
5
|
+
interface IPCOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Stop the worker once it has ignored a `terminate` control frame for `grace` milliseconds.
|
|
8
|
+
* Defaults to destroying `stream`.
|
|
9
|
+
*/
|
|
10
|
+
kill?: () => unknown
|
|
11
|
+
/**
|
|
12
|
+
* The number of milliseconds to wait for the worker to signal ready before rejecting `ready` and
|
|
13
|
+
* terminating the worker, or `null` to wait indefinitely. Defaults to `null`.
|
|
14
|
+
*/
|
|
15
|
+
timeout?: number | null
|
|
16
|
+
/**
|
|
17
|
+
* The number of milliseconds to wait for the worker to exit after a `terminate` control frame
|
|
18
|
+
* before calling `kill`. Defaults to `5000`.
|
|
19
|
+
*/
|
|
20
|
+
grace?: number
|
|
21
|
+
}
|
|
22
|
+
|
|
4
23
|
interface IPC extends Protocol {
|
|
24
|
+
/**
|
|
25
|
+
* A promise that resolves once the worker has signaled ready, and rejects if the worker errors,
|
|
26
|
+
* exits, or times out before then. The worker is terminated when it rejects.
|
|
27
|
+
*/
|
|
5
28
|
readonly ready: Promise<void>
|
|
29
|
+
/**
|
|
30
|
+
* Send a `terminate` control frame to the worker and resolve once it exits, killing it if it has
|
|
31
|
+
* not exited within the grace period.
|
|
32
|
+
* @returns The worker's exit `code`, or `undefined` if it closed without signaling one.
|
|
33
|
+
*/
|
|
6
34
|
terminate(): Promise<number | undefined>
|
|
7
35
|
}
|
|
8
36
|
|
|
9
37
|
declare class IPC {
|
|
10
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Wrap the host side of a stowed bundle's transport `stream`.
|
|
40
|
+
* @param stream - The host side of a stowed bundle's transport (any duplex byte stream).
|
|
41
|
+
* @param opts - Options; see [`IPCOptions`](#ipcoptions).
|
|
42
|
+
*/
|
|
43
|
+
constructor(stream: Duplex, opts?: IPCOptions)
|
|
11
44
|
}
|
|
12
45
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Wrap the host side of a stowed bundle's transport `stream`, returning an `IPC` handle with
|
|
48
|
+
* lifecycle helpers layered on top of the `Protocol`.
|
|
49
|
+
* @param stream - The host side of a stowed bundle's transport (any duplex byte stream).
|
|
50
|
+
* @param opts - Options; see [`IPCOptions`](#ipcoptions).
|
|
51
|
+
* @returns An `IPC` handle wrapping `stream`, with the `ready` promise and `terminate()` layered on
|
|
52
|
+
* top of `Protocol`.
|
|
53
|
+
*/
|
|
54
|
+
declare function wrap(stream: Duplex, opts?: IPCOptions): IPC
|
|
18
55
|
|
|
19
|
-
export
|
|
56
|
+
export { IPC, type IPCOptions, wrap }
|
package/lib/host.js
CHANGED
|
@@ -1,24 +1,50 @@
|
|
|
1
|
-
const { Protocol } = require('./protocol')
|
|
1
|
+
const { Protocol, DEFAULT_GRACE } = require('./protocol')
|
|
2
2
|
|
|
3
3
|
class IPC extends Protocol {
|
|
4
|
-
constructor(stream) {
|
|
4
|
+
constructor(stream, opts = {}) {
|
|
5
5
|
super(stream)
|
|
6
6
|
|
|
7
|
+
const { kill = () => stream.destroy(), timeout = null, grace = DEFAULT_GRACE } = opts
|
|
8
|
+
|
|
9
|
+
this._kill = kill
|
|
10
|
+
this._grace = grace
|
|
11
|
+
this._terminating = null
|
|
12
|
+
|
|
13
|
+
this.send('configure', { grace })
|
|
14
|
+
|
|
7
15
|
this._ready = new Promise((resolve, reject) => {
|
|
16
|
+
const timer =
|
|
17
|
+
timeout === null
|
|
18
|
+
? null
|
|
19
|
+
: setTimeout(() => onerror(new Error('Worker timed out before ready')), timeout)
|
|
20
|
+
|
|
21
|
+
const cleanup = () => {
|
|
22
|
+
clearTimeout(timer)
|
|
23
|
+
this.off('ready', onready).off('error', onerror).off('exit', onexit).off('close', onclose)
|
|
24
|
+
}
|
|
25
|
+
|
|
8
26
|
const onready = () => {
|
|
9
|
-
|
|
27
|
+
cleanup()
|
|
10
28
|
resolve()
|
|
11
29
|
}
|
|
12
30
|
|
|
13
31
|
const onerror = (err) => {
|
|
14
|
-
|
|
32
|
+
cleanup()
|
|
15
33
|
reject(err)
|
|
16
34
|
}
|
|
17
35
|
|
|
18
|
-
|
|
36
|
+
const onexit = (code) => {
|
|
37
|
+
// An error frame precedes the exit frame and is emitted on destroy
|
|
38
|
+
if (this.destroying) return
|
|
39
|
+
onerror(new Error(`Worker exited with code ${code} before ready`))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const onclose = () => onerror(new Error('Worker closed before ready'))
|
|
43
|
+
|
|
44
|
+
this.on('ready', onready).on('error', onerror).on('exit', onexit).on('close', onclose)
|
|
19
45
|
})
|
|
20
46
|
|
|
21
|
-
this._ready.catch(() =>
|
|
47
|
+
this._ready.catch(() => this.destroy())
|
|
22
48
|
}
|
|
23
49
|
|
|
24
50
|
get ready() {
|
|
@@ -26,37 +52,44 @@ class IPC extends Protocol {
|
|
|
26
52
|
}
|
|
27
53
|
|
|
28
54
|
terminate() {
|
|
29
|
-
return
|
|
30
|
-
const cleanup = () => {
|
|
31
|
-
this.off('exit', onexit)
|
|
32
|
-
this._stream.off('close', onclose)
|
|
33
|
-
}
|
|
55
|
+
if (this._stream.destroyed) return Promise.resolve(undefined)
|
|
34
56
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
resolve(code)
|
|
38
|
-
}
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
this.once('exit', resolve)
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
|
|
60
|
+
this._terminate().then(() => {
|
|
61
|
+
this.off('exit', resolve)
|
|
42
62
|
resolve(undefined)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
this.once('exit', onexit).send('terminate')
|
|
46
|
-
this._stream.once('close', onclose)
|
|
63
|
+
})
|
|
47
64
|
})
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
_destroy(err, cb) {
|
|
51
68
|
if (this._stream.destroyed) return cb(null)
|
|
52
69
|
|
|
70
|
+
this._terminate().then(() => cb(null))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_terminate() {
|
|
74
|
+
if (this._terminating !== null) return this._terminating
|
|
75
|
+
|
|
76
|
+
const timer = setTimeout(() => this._kill(), this._grace)
|
|
77
|
+
|
|
53
78
|
this.send('terminate')
|
|
54
|
-
|
|
79
|
+
|
|
80
|
+
this._terminating = new Promise((resolve) => {
|
|
81
|
+
this._stream.once('close', () => {
|
|
82
|
+
clearTimeout(timer)
|
|
83
|
+
resolve()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return this._terminating
|
|
55
88
|
}
|
|
56
89
|
}
|
|
57
90
|
|
|
58
91
|
exports.IPC = IPC
|
|
59
92
|
|
|
60
|
-
exports.wrap = function wrap(stream) {
|
|
61
|
-
return new IPC(stream)
|
|
93
|
+
exports.wrap = function wrap(stream, opts) {
|
|
94
|
+
return new IPC(stream, opts)
|
|
62
95
|
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,22 +1,46 @@
|
|
|
1
1
|
import { Duplex, DuplexEvents } from 'bare-stream'
|
|
2
2
|
|
|
3
|
+
/** The frame type marker identifying a control-channel frame. */
|
|
3
4
|
declare const CONTROL: number
|
|
5
|
+
/** The frame type marker identifying a user-data-channel frame. */
|
|
4
6
|
declare const USER: number
|
|
7
|
+
/** The default number of milliseconds a terminating worker gets to stop and exit. */
|
|
8
|
+
declare const DEFAULT_GRACE: number
|
|
5
9
|
|
|
10
|
+
/** The events emitted by a `Protocol`, in addition to the standard duplex stream events. */
|
|
6
11
|
interface ProtocolEvents extends DuplexEvents {
|
|
12
|
+
/** Emitted when the worker signals it is ready. */
|
|
7
13
|
ready: []
|
|
14
|
+
/** Emitted when a `terminate` control frame is received. */
|
|
8
15
|
terminate: []
|
|
16
|
+
/** Emitted when the worker signals exit, carrying its exit `code`. */
|
|
9
17
|
exit: [code: number]
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
interface Protocol<E extends ProtocolEvents = ProtocolEvents> extends Duplex<E> {
|
|
21
|
+
/**
|
|
22
|
+
* Send a control frame of `type` with an optional JSON-serializable `payload`.
|
|
23
|
+
* @param type - The control frame type, for example `'ready'`, `'exit'`, `'error'`, or
|
|
24
|
+
* `'terminate'`.
|
|
25
|
+
* @param payload - An optional JSON-serializable payload carried with the frame.
|
|
26
|
+
*/
|
|
13
27
|
send(type: string, payload?: object): Promise<void>
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
declare class Protocol {
|
|
31
|
+
/**
|
|
32
|
+
* Attach a `Protocol` to the given underlying duplex byte `stream`.
|
|
33
|
+
* @param stream - The underlying duplex byte stream to multiplex the control and user-data
|
|
34
|
+
* channels over.
|
|
35
|
+
*/
|
|
17
36
|
constructor(stream: Duplex)
|
|
18
37
|
}
|
|
19
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Attach a `Protocol` to `stream`, an underlying duplex byte stream.
|
|
41
|
+
* @param stream - Any duplex byte stream to attach the protocol to.
|
|
42
|
+
* @returns A `Protocol` multiplexing control and user-data frames over `stream`.
|
|
43
|
+
*/
|
|
20
44
|
declare function attach(stream: Duplex): Protocol
|
|
21
45
|
|
|
22
|
-
export { type Protocol, ProtocolEvents, attach, CONTROL, USER }
|
|
46
|
+
export { type Protocol, ProtocolEvents, attach, CONTROL, USER, DEFAULT_GRACE }
|
package/lib/protocol.js
CHANGED
|
@@ -7,6 +7,8 @@ const USER = 0x01
|
|
|
7
7
|
exports.CONTROL = CONTROL
|
|
8
8
|
exports.USER = USER
|
|
9
9
|
|
|
10
|
+
exports.DEFAULT_GRACE = 5000
|
|
11
|
+
|
|
10
12
|
const HEADER_LENGTH = 5
|
|
11
13
|
|
|
12
14
|
class Protocol extends Duplex {
|
|
@@ -22,7 +24,7 @@ class Protocol extends Duplex {
|
|
|
22
24
|
.on('data', (chunk) => this._ondata(chunk))
|
|
23
25
|
.on('end', () => this.push(null))
|
|
24
26
|
.on('close', () => this.destroy())
|
|
25
|
-
.on('error', (err) => this.
|
|
27
|
+
.on('error', (err) => this._onerror(err))
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
send(type, payload) {
|
|
@@ -48,6 +50,13 @@ class Protocol extends Duplex {
|
|
|
48
50
|
cb(null)
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
_onerror(err) {
|
|
54
|
+
// On Linux, a peer that goes away with data still unread resets the
|
|
55
|
+
// connection rather than ending it, which is no different from it closing.
|
|
56
|
+
if (err.code === 'ECONNRESET') this.destroy()
|
|
57
|
+
else this.destroy(err)
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
_ondata(chunk) {
|
|
52
61
|
this._buffer.push(chunk)
|
|
53
62
|
this._buffered += chunk.byteLength
|
package/lib/runtime.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Duplex } from 'bare-stream'
|
|
2
|
+
import { Protocol } from './protocol'
|
|
3
|
+
|
|
4
|
+
interface Runtime extends Protocol {
|
|
5
|
+
/**
|
|
6
|
+
* Load the bundle entry and start it with `arg`, signaling ready once its `start()` resolves.
|
|
7
|
+
* @param load - A function importing the bundle entry module.
|
|
8
|
+
* @param arg - The value passed to the entry's `start()`.
|
|
9
|
+
*/
|
|
10
|
+
run(load: () => Promise<{ default: Function }>, arg: unknown): Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
declare class Runtime {
|
|
14
|
+
/**
|
|
15
|
+
* Attach the worker side of a stowed bundle's lifecycle to `stream`.
|
|
16
|
+
* @param stream - The worker side of a stowed bundle's transport, typically `Bare.IPC`.
|
|
17
|
+
*/
|
|
18
|
+
constructor(stream: Duplex)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Attach the worker side of a stowed bundle's lifecycle to `stream`, shutting the worker down on
|
|
23
|
+
* `terminate`, `SIGTERM`, runtime exit, uncaught errors, or loss of the host.
|
|
24
|
+
* @param stream - The worker side of a stowed bundle's transport, typically `Bare.IPC`.
|
|
25
|
+
* @returns A `Runtime` multiplexing control and user-data frames over `stream`.
|
|
26
|
+
*/
|
|
27
|
+
declare function attach(stream: Duplex): Runtime
|
|
28
|
+
|
|
29
|
+
export { Runtime, attach }
|
package/lib/runtime.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const { Protocol, DEFAULT_GRACE } = require('./protocol')
|
|
2
|
+
|
|
3
|
+
class Runtime extends Protocol {
|
|
4
|
+
constructor(stream) {
|
|
5
|
+
super(stream)
|
|
6
|
+
|
|
7
|
+
this._stop = null
|
|
8
|
+
this._grace = DEFAULT_GRACE
|
|
9
|
+
this._readied = false
|
|
10
|
+
this._exitCode = null
|
|
11
|
+
this._connected = true
|
|
12
|
+
|
|
13
|
+
const ondisconnect = () => this._ondisconnect()
|
|
14
|
+
|
|
15
|
+
this.on('configure', ({ grace }) => {
|
|
16
|
+
this._grace = grace
|
|
17
|
+
}).on('terminate', () => this._shutdown(0))
|
|
18
|
+
|
|
19
|
+
stream.on('end', ondisconnect).on('close', ondisconnect)
|
|
20
|
+
|
|
21
|
+
Bare.on('beforeExit', (code) => this._shutdown(code))
|
|
22
|
+
.on('uncaughtException', (err) => this._shutdown(1, err))
|
|
23
|
+
.on('unhandledRejection', (err) => this._shutdown(1, err))
|
|
24
|
+
|
|
25
|
+
// Signals are delivered to the whole process, so a worker thread must leave
|
|
26
|
+
// them to the host.
|
|
27
|
+
if (Bare.Thread.isMainThread) {
|
|
28
|
+
const Signal = require('bare-signals')
|
|
29
|
+
|
|
30
|
+
new Signal('SIGTERM')
|
|
31
|
+
.on('signal', () => this._onsigterm())
|
|
32
|
+
.start()
|
|
33
|
+
.unref()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async run(load, arg) {
|
|
38
|
+
try {
|
|
39
|
+
const entry = await load()
|
|
40
|
+
|
|
41
|
+
this._stop = await entry.default(arg, () => this._ready())
|
|
42
|
+
|
|
43
|
+
this._ready()
|
|
44
|
+
} catch (err) {
|
|
45
|
+
this._shutdown(1, err)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_ready() {
|
|
50
|
+
if (this._readied) return
|
|
51
|
+
this._readied = true
|
|
52
|
+
|
|
53
|
+
this.send('ready')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async _shutdown(code, err = null) {
|
|
57
|
+
if (this._exitCode !== null) return
|
|
58
|
+
this._exitCode = code
|
|
59
|
+
|
|
60
|
+
setTimeout(() => Bare.exit(code), this._grace).unref()
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
if (this._stop) await this._stop()
|
|
64
|
+
} catch (e) {
|
|
65
|
+
err = err || e
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this._connected) {
|
|
69
|
+
if (err) this.send('error', { message: err.message, stack: err.stack })
|
|
70
|
+
|
|
71
|
+
await this.send('exit', { code })
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Bare.exit(code)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_ondisconnect() {
|
|
78
|
+
this._connected = false
|
|
79
|
+
this._shutdown(0)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_onsigterm() {
|
|
83
|
+
if (this._exitCode !== null) Bare.exit(this._exitCode)
|
|
84
|
+
else this._shutdown(0)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.Runtime = Runtime
|
|
89
|
+
|
|
90
|
+
exports.attach = function attach(stream) {
|
|
91
|
+
return new Runtime(stream)
|
|
92
|
+
}
|
package/lib/shim.js
CHANGED
|
@@ -9,60 +9,14 @@ module.exports = exports = function shim(entry, shimURL, opts = {}) {
|
|
|
9
9
|
|
|
10
10
|
const specifier = relativeSpecifier(shimURL, entry)
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const arg = setup ? '{ rpc, ipc }' : 'ipc'
|
|
12
|
+
const arg = server ? '{ rpc, ipc }' : 'ipc'
|
|
15
13
|
|
|
16
14
|
return `\
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
const ipc = protocol.attach(Bare.IPC)
|
|
20
|
-
${setup ? '\n' + setup : ''}
|
|
21
|
-
let stop = null
|
|
22
|
-
let readied = false
|
|
23
|
-
let exited = false
|
|
24
|
-
|
|
25
|
-
function ready() {
|
|
26
|
-
if (readied) return
|
|
27
|
-
readied = true
|
|
28
|
-
|
|
29
|
-
ipc.send('ready')
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function shutdown(code, err = null) {
|
|
33
|
-
if (exited) return
|
|
34
|
-
exited = true
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
if (stop) await stop()
|
|
38
|
-
} catch (e) {
|
|
39
|
-
err = err || e
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (err) ipc.send('error', { message: err.message, stack: err.stack })
|
|
15
|
+
import runtime from 'bare-stow/runtime'
|
|
43
16
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
ipc.on('terminate', () => shutdown(0))
|
|
50
|
-
|
|
51
|
-
Bare
|
|
52
|
-
.on('beforeExit', (exitCode) => shutdown(exitCode))
|
|
53
|
-
.on('uncaughtException', (err) => shutdown(1, err))
|
|
54
|
-
.on('unhandledRejection', (err) => shutdown(1, err))
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const entry = await import(${JSON.stringify(specifier)})
|
|
58
|
-
const start = entry.default
|
|
59
|
-
|
|
60
|
-
stop = await start(${arg}, ready) || null
|
|
61
|
-
|
|
62
|
-
ready()
|
|
63
|
-
} catch (err) {
|
|
64
|
-
shutdown(1, err)
|
|
65
|
-
}
|
|
17
|
+
const ipc = runtime.attach(Bare.IPC)
|
|
18
|
+
${server ? '\n' + server : ''}
|
|
19
|
+
ipc.run(() => import(${JSON.stringify(specifier)}), ${arg})
|
|
66
20
|
`
|
|
67
21
|
}
|
|
68
22
|
|
|
@@ -28,14 +28,13 @@ exports.generate = function generate(opts) {
|
|
|
28
28
|
? `\
|
|
29
29
|
import Sidecar from 'bare-sidecar'
|
|
30
30
|
import stow from 'bare-stow/host'
|
|
31
|
-
import { fileURLToPath } from 'url'
|
|
32
31
|
|
|
33
|
-
const bundle =
|
|
32
|
+
const bundle = new URL(import.meta.resolve(${JSON.stringify(bundleSpecifier)}))
|
|
34
33
|
|
|
35
|
-
export async function start(opts = {}) {
|
|
34
|
+
export async function start({ timeout, grace, ...opts } = {}) {
|
|
36
35
|
const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
|
|
37
36
|
|
|
38
|
-
const ${ipc} = stow.wrap(sidecar)
|
|
37
|
+
const ${ipc} = stow.wrap(sidecar, { timeout, grace })
|
|
39
38
|
${client ? '\n' + indent(client.source, 2) : ''}
|
|
40
39
|
await ${ipc}.ready
|
|
41
40
|
|
|
@@ -43,17 +42,16 @@ ${client ? '\n' + indent(client.source, 2) : ''}
|
|
|
43
42
|
}
|
|
44
43
|
`
|
|
45
44
|
: `\
|
|
46
|
-
const path = require('path')
|
|
47
45
|
const Sidecar = require('bare-sidecar')
|
|
48
46
|
const stow = require('bare-stow/host')
|
|
49
47
|
|
|
50
|
-
const bundle =
|
|
48
|
+
const bundle = require.resolve(${JSON.stringify(bundleSpecifier)})
|
|
51
49
|
|
|
52
50
|
module.exports = {
|
|
53
|
-
async start(opts = {}) {
|
|
51
|
+
async start({ timeout, grace, ...opts } = {}) {
|
|
54
52
|
const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
|
|
55
53
|
|
|
56
|
-
const ${ipc} = stow.wrap(sidecar)
|
|
54
|
+
const ${ipc} = stow.wrap(sidecar, { timeout, grace })
|
|
57
55
|
${client ? '\n' + indent(client.source, 4) : ''}
|
|
58
56
|
await ${ipc}.ready
|
|
59
57
|
|
|
@@ -67,7 +65,9 @@ ${client ? '\n' + indent(client.source, 4) : ''}
|
|
|
67
65
|
{
|
|
68
66
|
extension: '.d.ts',
|
|
69
67
|
source: `\
|
|
70
|
-
export function start(
|
|
68
|
+
export function start(
|
|
69
|
+
opts?: import('bare-sidecar').SidecarOptions & Omit<import('bare-stow/host').IPCOptions, 'kill'>
|
|
70
|
+
): Promise<${handle}>
|
|
71
71
|
`
|
|
72
72
|
}
|
|
73
73
|
]
|
|
@@ -28,14 +28,16 @@ exports.generate = function generate(opts) {
|
|
|
28
28
|
? `\
|
|
29
29
|
import Worker from 'bare-worker'
|
|
30
30
|
import stow from 'bare-stow/host'
|
|
31
|
-
import { fileURLToPath } from 'bare-url'
|
|
32
31
|
|
|
33
|
-
const bundle =
|
|
32
|
+
const bundle = import.meta.resolve(${JSON.stringify(bundleSpecifier)})
|
|
34
33
|
|
|
35
|
-
export async function start(opts = {}) {
|
|
34
|
+
export async function start({ timeout, grace, ...opts } = {}) {
|
|
36
35
|
const worker = new Worker(bundle, opts)
|
|
37
36
|
|
|
38
|
-
const ${ipc} = stow.wrap(worker.IPC)
|
|
37
|
+
const ${ipc} = stow.wrap(worker.IPC, { kill: () => worker.terminate(), timeout, grace })
|
|
38
|
+
|
|
39
|
+
// The IPC stream of a worker ends, but never closes, when its thread exits.
|
|
40
|
+
worker.IPC.on('end', () => worker.IPC.destroy())
|
|
39
41
|
${client ? '\n' + indent(client.source, 2) : ''}
|
|
40
42
|
await ${ipc}.ready
|
|
41
43
|
|
|
@@ -43,17 +45,19 @@ ${client ? '\n' + indent(client.source, 2) : ''}
|
|
|
43
45
|
}
|
|
44
46
|
`
|
|
45
47
|
: `\
|
|
46
|
-
const path = require('bare-path')
|
|
47
48
|
const Worker = require('bare-worker')
|
|
48
49
|
const stow = require('bare-stow/host')
|
|
49
50
|
|
|
50
|
-
const bundle =
|
|
51
|
+
const bundle = require.resolve(${JSON.stringify(bundleSpecifier)})
|
|
51
52
|
|
|
52
53
|
module.exports = {
|
|
53
|
-
async start(opts = {}) {
|
|
54
|
+
async start({ timeout, grace, ...opts } = {}) {
|
|
54
55
|
const worker = new Worker(bundle, opts)
|
|
55
56
|
|
|
56
|
-
const ${ipc} = stow.wrap(worker.IPC)
|
|
57
|
+
const ${ipc} = stow.wrap(worker.IPC, { kill: () => worker.terminate(), timeout, grace })
|
|
58
|
+
|
|
59
|
+
// The IPC stream of a worker ends, but never closes, when its thread exits.
|
|
60
|
+
worker.IPC.on('end', () => worker.IPC.destroy())
|
|
57
61
|
${client ? '\n' + indent(client.source, 4) : ''}
|
|
58
62
|
await ${ipc}.ready
|
|
59
63
|
|
|
@@ -67,7 +71,9 @@ ${client ? '\n' + indent(client.source, 4) : ''}
|
|
|
67
71
|
{
|
|
68
72
|
extension: '.d.ts',
|
|
69
73
|
source: `\
|
|
70
|
-
export function start(
|
|
74
|
+
export function start(
|
|
75
|
+
opts?: import('bare-worker').WorkerOptions & Omit<import('bare-stow/host').IPCOptions, 'kill'>
|
|
76
|
+
): Promise<${handle}>
|
|
71
77
|
`
|
|
72
78
|
}
|
|
73
79
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-stow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Module bundling and harness generation for Bare",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./package": "./package.json",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"./host": {
|
|
16
16
|
"types": "./lib/host.d.ts",
|
|
17
17
|
"default": "./lib/host.js"
|
|
18
|
+
},
|
|
19
|
+
"./runtime": {
|
|
20
|
+
"types": "./lib/runtime.d.ts",
|
|
21
|
+
"default": "./lib/runtime.js"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"imports": {
|
|
@@ -68,15 +72,16 @@
|
|
|
68
72
|
"bare-fs": "^4.2.1",
|
|
69
73
|
"bare-module-lexer": "^1.6.0",
|
|
70
74
|
"bare-module-traverse": "^2.2.0",
|
|
71
|
-
"bare-pack": "^
|
|
75
|
+
"bare-pack": "^3.0.0",
|
|
72
76
|
"bare-path": "^3.0.0",
|
|
77
|
+
"bare-signals": "^5.0.0",
|
|
73
78
|
"bare-stream": "^2.13.1",
|
|
74
79
|
"bare-type-stripper": "^0.1.2",
|
|
75
80
|
"paparam": "^1.5.0"
|
|
76
81
|
},
|
|
77
82
|
"devDependencies": {
|
|
78
83
|
"bare-rpc": "^1.3.2",
|
|
79
|
-
"bare-sidecar": "^0.5.
|
|
84
|
+
"bare-sidecar": "^0.5.9",
|
|
80
85
|
"bare-worker": "^4.3.0",
|
|
81
86
|
"brittle": "^4.0.2",
|
|
82
87
|
"lunte": "^1.7.0",
|