bare-stow 0.1.8 → 0.2.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.
@@ -1,4 +1,7 @@
1
+ const harness = require('./javascript')
2
+
1
3
  exports.name = 'bare-worker'
4
+ exports.isolation = 'thread'
2
5
  exports.linked = false
3
6
  exports.offload = true
4
7
  exports.format = 'bundle'
@@ -13,76 +16,23 @@ exports.hosts = [
13
16
  'win32-x64'
14
17
  ]
15
18
 
16
- exports.generate = function generate(opts) {
17
- const { bundleSpecifier, ipc, rpc, module = 'cjs', client = null } = opts
18
-
19
- const esm = module === 'esm'
20
-
21
- const returned = client ? `{ ${ipc}, ${rpc} }` : `{ ${ipc} }`
22
-
23
- const handle = client
24
- ? `{ ${ipc}: import('bare-stow/host').IPC; ${rpc}: ${client.type} }`
25
- : `{ ${ipc}: import('bare-stow/host').IPC }`
26
-
27
- const source = esm
28
- ? `\
29
- import Worker from 'bare-worker'
30
- import stow from 'bare-stow/host'
19
+ exports.languages = {
20
+ javascript: {
21
+ generate(context) {
22
+ return harness(context, {
23
+ name: 'bare-worker',
24
+ version: '^4.3.0',
25
+ binding: 'Worker',
26
+ setup: `\
27
+ const worker = new Worker(bundle, opts)
31
28
 
32
- const bundle = import.meta.resolve(${JSON.stringify(bundleSpecifier)})
33
-
34
- export async function start({ timeout, grace, ...opts } = {}) {
35
- const worker = new Worker(bundle, opts)
36
-
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())
41
- ${client ? '\n' + indent(client.source, 2) : ''}
42
- await ${ipc}.ready
43
-
44
- return ${returned}
45
- }
46
- `
47
- : `\
48
- const Worker = require('bare-worker')
49
- const stow = require('bare-stow/host')
29
+ const ipc = stow.wrap(worker.IPC, { kill: () => worker.terminate(), timeout, grace })
50
30
 
51
- const bundle = require.resolve(${JSON.stringify(bundleSpecifier)})
31
+ worker.IPC.on('end', () => worker.IPC.destroy())
52
32
 
53
- module.exports = {
54
- async start({ timeout, grace, ...opts } = {}) {
55
- const worker = new Worker(bundle, opts)
56
-
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())
61
- ${client ? '\n' + indent(client.source, 4) : ''}
62
- await ${ipc}.ready
63
-
64
- return ${returned}
65
- }
66
- }
67
- `
68
-
69
- return [
70
- { source },
71
- {
72
- extension: '.d.ts',
73
- source: `\
74
- export function start(
75
- opts?: import('bare-worker').WorkerOptions & Omit<import('bare-stow/host').IPCOptions, 'kill'>
76
- ): Promise<${handle}>
77
- `
33
+ worker.on('exit', (code) => ipc.exited(code))`,
34
+ types: "import('bare-worker').WorkerOptions"
35
+ })
78
36
  }
79
- ]
80
- }
81
-
82
- function indent(source, spaces) {
83
- const pad = ' '.repeat(spaces)
84
- return source
85
- .split('\n')
86
- .map((line) => (line.length === 0 ? line : pad + line))
87
- .join('\n')
37
+ }
88
38
  }
@@ -0,0 +1,63 @@
1
+ module.exports = function harness(context, target) {
2
+ const { bundle, client, options } = context
3
+ const { name, version, binding, url = false, setup, types } = target
4
+
5
+ const esm = options.module === 'esm'
6
+
7
+ const body = `\
8
+ ${setup}
9
+ ${client ? '\nconst rpc = client.attach(ipc, handlers)\n' : ''}
10
+ await ipc.ready
11
+
12
+ return ${client ? '{ ipc, rpc }' : '{ ipc }'}`
13
+
14
+ const source = esm
15
+ ? `\
16
+ import ${binding} from '${name}'
17
+ import stow from 'bare-stow-host'
18
+ ${client ? `import * as client from ${JSON.stringify(client)}\n` : ''}
19
+ const bundle = ${url ? `new URL(import.meta.resolve(${JSON.stringify(bundle)}))` : `import.meta.resolve(${JSON.stringify(bundle)})`}
20
+
21
+ export async function start({ timeout, grace, ${client ? 'handlers = () => {}, ' : ''}...opts } = {}) {
22
+ ${indent(body, 2)}
23
+ }
24
+ `
25
+ : `\
26
+ const ${binding} = require('${name}')
27
+ const stow = require('bare-stow-host')
28
+ ${client ? `const client = require(${JSON.stringify(client)})\n` : ''}
29
+ const bundle = require.resolve(${JSON.stringify(bundle)})
30
+
31
+ module.exports = {
32
+ async start({ timeout, grace, ${client ? 'handlers = () => {}, ' : ''}...opts } = {}) {
33
+ ${indent(body, 4)}
34
+ }
35
+ }
36
+ `
37
+
38
+ const handle = client
39
+ ? `{ ipc: import('bare-stow-host').IPC; rpc: ReturnType<typeof import(${JSON.stringify(client)}).attach> }`
40
+ : `{ ipc: import('bare-stow-host').IPC }`
41
+
42
+ return {
43
+ source,
44
+ dependencies: {
45
+ [name]: version,
46
+ 'bare-stow-host': '^0.1.0'
47
+ },
48
+ types: `\
49
+ export function start(
50
+ opts?: ${types} & Omit<import('bare-stow-host').IPCOptions, 'kill'>${client ? ` & { handlers?: import(${JSON.stringify(client)}).Handlers }` : ''}
51
+ ): Promise<${handle}>
52
+ `
53
+ }
54
+ }
55
+
56
+ function indent(source, spaces) {
57
+ const pad = ' '.repeat(spaces)
58
+
59
+ return source
60
+ .split('\n')
61
+ .map((line) => (line.length === 0 ? line : pad + line))
62
+ .join('\n')
63
+ }
@@ -0,0 +1,57 @@
1
+ // The harness qualifies `BareStowHost.IPC` as runtimes such as Bare Kit declare
2
+ // an `IPC` of their own.
3
+ module.exports = function harness(context, target) {
4
+ const { bundle, client } = context
5
+ const { module, conformance, fields = {}, params = [], setup, dependencies } = target
6
+
7
+ const started = { ...fields, ipc: 'BareStowHost.IPC' }
8
+
9
+ if (client) started.rpc = 'Client'
10
+
11
+ const source = `\
12
+ import ${module}
13
+ import BareStowHost
14
+ import Foundation
15
+
16
+ ${conformance}
17
+
18
+ public struct Started: Sendable {
19
+ ${Object.entries(started)
20
+ .map(([name, type]) => ` public let ${name}: ${type}`)
21
+ .join('\n')}
22
+ }
23
+
24
+ public func start(
25
+ ${[
26
+ ...params,
27
+ 'timeout: TimeInterval? = nil',
28
+ 'grace: TimeInterval = BareStowHost.IPC.defaultGrace',
29
+ ...(client ? ['handlers: Handlers? = nil'] : [])
30
+ ]
31
+ .map((param) => ' ' + param)
32
+ .join(',\n')}
33
+ ) async throws -> Started {
34
+ let bundle = Bundle.module.url(forResource: ${JSON.stringify(bundle)}, withExtension: nil)!
35
+
36
+ ${setup.replace(/^(?=.)/gm, ' ')}
37
+ ${client ? '\n let rpc = attach(ipc, handlers)\n' : ''}
38
+ try await ipc.ready()
39
+
40
+ return Started(${Object.keys(started)
41
+ .map((name) => `${name}: ${name}`)
42
+ .join(', ')})
43
+ }
44
+ `
45
+
46
+ return {
47
+ source,
48
+ dependencies: {
49
+ ...dependencies,
50
+ 'bare-stow-host-swift': {
51
+ url: 'https://github.com/holepunchto/bare-stow-host-swift',
52
+ from: '0.1.0',
53
+ products: ['BareStowHost']
54
+ }
55
+ }
56
+ }
57
+ }
package/lib/target.js CHANGED
@@ -1,28 +1,59 @@
1
- const registry = {
1
+ const { registry, validateLanguages } = require('./provider')
2
+
3
+ const providers = {
4
+ 'bare-kit': () => require('./target/bare-kit'),
2
5
  'bare-sidecar': () => require('./target/bare-sidecar'),
3
6
  'bare-worker': () => require('./target/bare-worker')
4
7
  }
5
8
 
6
- module.exports = function target(name, resolve) {
7
- if (typeof name !== 'string') return validate(name)
9
+ const properties = [
10
+ 'isolation',
11
+ 'linked',
12
+ 'linking',
13
+ 'offload',
14
+ 'format',
15
+ 'encoding',
16
+ 'extension',
17
+ 'hosts'
18
+ ]
8
19
 
9
- const load = registry[name]
20
+ module.exports = registry('target', providers, function validate(target) {
21
+ validateLanguages('Target', target?.languages)
10
22
 
11
- if (load) return load()
23
+ return target
24
+ })
12
25
 
13
- if (resolve) return validate(resolve(name))
26
+ module.exports.options = function options(target, given = {}) {
27
+ if (typeof target.options === 'function') return target.options(given)
14
28
 
15
- throw new Error(`Unknown target '${name}'`)
29
+ for (const option of Object.keys(given)) {
30
+ throw new Error(`Unknown option '${option}' for target '${target.name}'`)
31
+ }
32
+
33
+ return {}
16
34
  }
17
35
 
18
- function validate(target) {
19
- if (!target || typeof target.generate !== 'function') {
20
- throw new Error('Target must provide a generate() function')
36
+ // A property may instead be a function of the language of the harness, the
37
+ // options it resolved, and the options of the target, such as hosts that differ
38
+ // between languages.
39
+ module.exports.resolve = function resolve(target, context) {
40
+ const resolved = { linking: 'package', ...target }
41
+
42
+ for (const property of properties) {
43
+ if (typeof target[property] === 'function') resolved[property] = target[property](context)
21
44
  }
22
45
 
23
- if (!Array.isArray(target.hosts)) {
46
+ if (resolved.isolation !== 'process' && resolved.isolation !== 'thread') {
47
+ throw new Error("Target must provide an isolation of 'process' or 'thread'")
48
+ }
49
+
50
+ if (resolved.linking !== 'package' && resolved.linking !== 'app') {
51
+ throw new Error("Target must provide a linking of 'package' or 'app'")
52
+ }
53
+
54
+ if (!Array.isArray(resolved.hosts)) {
24
55
  throw new Error('Target must provide a hosts array')
25
56
  }
26
57
 
27
- return target
58
+ return resolved
28
59
  }
package/package.json CHANGED
@@ -1,24 +1,12 @@
1
1
  {
2
2
  "name": "bare-stow",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Module bundling and harness generation for Bare",
5
5
  "exports": {
6
6
  "./package": "./package.json",
7
7
  ".": {
8
8
  "types": "./index.d.ts",
9
9
  "default": "./index.js"
10
- },
11
- "./protocol": {
12
- "types": "./lib/protocol.d.ts",
13
- "default": "./lib/protocol.js"
14
- },
15
- "./host": {
16
- "types": "./lib/host.d.ts",
17
- "default": "./lib/host.js"
18
- },
19
- "./runtime": {
20
- "types": "./lib/runtime.d.ts",
21
- "default": "./lib/runtime.js"
22
10
  }
23
11
  },
24
12
  "imports": {
@@ -66,22 +54,23 @@
66
54
  },
67
55
  "homepage": "https://github.com/holepunchto/bare-stow#readme",
68
56
  "dependencies": {
69
- "b4a": "^1.6.0",
70
57
  "bare-bundle": "^1.8.3",
71
58
  "bare-bundle-id": "^1.0.0",
72
59
  "bare-fs": "^4.2.1",
73
- "bare-module-lexer": "^1.6.0",
60
+ "bare-link": "^4.1.0",
74
61
  "bare-module-traverse": "^2.2.0",
75
62
  "bare-pack": "^3.0.0",
76
63
  "bare-path": "^3.0.0",
77
- "bare-signals": "^5.0.0",
78
- "bare-stream": "^2.13.1",
79
64
  "bare-type-stripper": "^0.1.2",
65
+ "bare-unpack": "^1.1.3",
80
66
  "paparam": "^1.5.0"
81
67
  },
82
68
  "devDependencies": {
69
+ "bare-os": "^3.0.0",
83
70
  "bare-rpc": "^1.3.2",
84
- "bare-sidecar": "^0.5.9",
71
+ "bare-sidecar": "^0.5.11",
72
+ "bare-stow-host": "^0.1.0",
73
+ "bare-stow-runtime": "^0.1.0",
85
74
  "bare-worker": "^4.3.0",
86
75
  "brittle": "^4.0.2",
87
76
  "lunte": "^1.7.0",
@@ -90,13 +79,9 @@
90
79
  "which-runtime": "^1.4.0"
91
80
  },
92
81
  "peerDependencies": {
93
- "bare-buffer": "^3.6.0",
94
82
  "bare-url": "^2.4.0"
95
83
  },
96
84
  "peerDependenciesMeta": {
97
- "bare-buffer": {
98
- "optional": true
99
- },
100
85
  "bare-url": {
101
86
  "optional": true
102
87
  }
package/lib/host.d.ts DELETED
@@ -1,56 +0,0 @@
1
- import { Duplex } from 'bare-stream'
2
- import { Protocol } from './protocol'
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
-
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
- */
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
- */
34
- terminate(): Promise<number | undefined>
35
- }
36
-
37
- declare class IPC {
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)
44
- }
45
-
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
55
-
56
- export { IPC, type IPCOptions, wrap }
package/lib/host.js DELETED
@@ -1,95 +0,0 @@
1
- const { Protocol, DEFAULT_GRACE } = require('./protocol')
2
-
3
- class IPC extends Protocol {
4
- constructor(stream, opts = {}) {
5
- super(stream)
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
-
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
-
26
- const onready = () => {
27
- cleanup()
28
- resolve()
29
- }
30
-
31
- const onerror = (err) => {
32
- cleanup()
33
- reject(err)
34
- }
35
-
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)
45
- })
46
-
47
- this._ready.catch(() => this.destroy())
48
- }
49
-
50
- get ready() {
51
- return this._ready
52
- }
53
-
54
- terminate() {
55
- if (this._stream.destroyed) return Promise.resolve(undefined)
56
-
57
- return new Promise((resolve) => {
58
- this.once('exit', resolve)
59
-
60
- this._terminate().then(() => {
61
- this.off('exit', resolve)
62
- resolve(undefined)
63
- })
64
- })
65
- }
66
-
67
- _destroy(err, cb) {
68
- if (this._stream.destroyed) return cb(null)
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
-
78
- this.send('terminate')
79
-
80
- this._terminating = new Promise((resolve) => {
81
- this._stream.once('close', () => {
82
- clearTimeout(timer)
83
- resolve()
84
- })
85
- })
86
-
87
- return this._terminating
88
- }
89
- }
90
-
91
- exports.IPC = IPC
92
-
93
- exports.wrap = function wrap(stream, opts) {
94
- return new IPC(stream, opts)
95
- }
package/lib/protocol.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import { Duplex, DuplexEvents } from 'bare-stream'
2
-
3
- /** The frame type marker identifying a control-channel frame. */
4
- declare const CONTROL: number
5
- /** The frame type marker identifying a user-data-channel frame. */
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
9
-
10
- /** The events emitted by a `Protocol`, in addition to the standard duplex stream events. */
11
- interface ProtocolEvents extends DuplexEvents {
12
- /** Emitted when the worker signals it is ready. */
13
- ready: []
14
- /** Emitted when a `terminate` control frame is received. */
15
- terminate: []
16
- /** Emitted when the worker signals exit, carrying its exit `code`. */
17
- exit: [code: number]
18
- }
19
-
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
- */
27
- send(type: string, payload?: object): Promise<void>
28
- }
29
-
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
- */
36
- constructor(stream: Duplex)
37
- }
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
- */
44
- declare function attach(stream: Duplex): Protocol
45
-
46
- export { type Protocol, ProtocolEvents, attach, CONTROL, USER, DEFAULT_GRACE }