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,24 +1,229 @@
1
1
  exports.name = 'bare-rpc'
2
2
 
3
- exports.generate = function generate(opts) {
4
- const { ipc, rpc } = opts
3
+ exports.languages = {
4
+ javascript: { generate: javascript },
5
+ kotlin: { generate: kotlin },
6
+ python: { generate: python },
7
+ swift: { generate: swift }
8
+ }
9
+
10
+ function kotlin({ options }) {
11
+ const source = `\
12
+ package ${options.package}
13
+
14
+ import kotlinx.coroutines.CoroutineScope
15
+ import kotlinx.coroutines.Dispatchers
16
+ import to.holepunch.bare.rpc.CommandRouter
17
+ import to.holepunch.bare.rpc.Rpc
18
+ import to.holepunch.bare.stow.host.Ipc
19
+
20
+ typealias Client = Rpc
21
+
22
+ typealias Handlers = CommandRouter.() -> Unit
23
+
24
+ internal fun attach(ipc: Ipc, handlers: Handlers): Client {
25
+ val router = CommandRouter().apply(handlers)
26
+
27
+ return Rpc(
28
+ ipc.data,
29
+ ipc::write,
30
+ CoroutineScope(Dispatchers.Default),
31
+ onEvent = router::dispatch,
32
+ onRequest = router::dispatch,
33
+ )
34
+ }
35
+ `
36
+
37
+ return {
38
+ source,
39
+ dependencies: {
40
+ 'com.github.holepunchto.bare-rpc-kotlin:bare-rpc-kotlin': 'v0.4.0',
41
+ 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.1.1'
42
+ }
43
+ }
44
+ }
45
+
46
+ function swift() {
47
+ const source = `\
48
+ import BareRPC
49
+ import BareStowHost
50
+ import Foundation
51
+
52
+ public final class Client: @unchecked Sendable {
53
+ public let rpc: RPC
54
+
55
+ private let delegate: Delegate
56
+
57
+ fileprivate init(rpc: RPC, delegate: Delegate) {
58
+ self.rpc = rpc
59
+ self.delegate = delegate
60
+ }
61
+
62
+ public func request(_ command: UInt, data: Data? = nil) async throws -> Data? {
63
+ try await rpc.request(command, data: data)
64
+ }
5
65
 
6
- const esm = opts.module === 'esm'
66
+ public func event(_ command: UInt, data: Data? = nil) async {
67
+ await rpc.event(command, data: data)
68
+ }
69
+ }
70
+
71
+ public typealias Handlers = (CommandRouter) -> Void
72
+
73
+ func attach(_ ipc: IPC, _ handlers: Handlers?) -> Client {
74
+ let router = CommandRouter()
7
75
 
8
- return [
9
- {
10
- source: `\
11
- ${esm ? "const { default: RPC } = await import('bare-rpc')" : "const RPC = require('bare-rpc')"}
76
+ handlers?(router)
12
77
 
13
- const router = new RPC.CommandRouter()
14
- const ${rpc} = new RPC(${ipc}, router)
78
+ let delegate = Delegate(router: router)
79
+ let rpc = RPC(delegate: delegate)
80
+
81
+ Task {
82
+ for await frame in delegate.frames {
83
+ try? await ipc.write(frame)
84
+ }
85
+ }
86
+
87
+ Task {
88
+ do {
89
+ for try await data in ipc {
90
+ await rpc.receive(data)
91
+ }
92
+ } catch {}
93
+
94
+ delegate.finish()
95
+ }
96
+
97
+ return Client(rpc: rpc, delegate: delegate)
98
+ }
15
99
 
16
- ${rpc}.respond = router.respond.bind(router)
100
+ private final class Delegate: RPCDelegate, @unchecked Sendable {
101
+ let router: CommandRouter
102
+ let frames: AsyncStream<Data>
103
+
104
+ private let continuation: AsyncStream<Data>.Continuation
105
+
106
+ init(router: CommandRouter) {
107
+ self.router = router
108
+ (frames, continuation) = AsyncStream.makeStream()
109
+ }
110
+
111
+ func rpc(_ rpc: RPC, send data: Data) {
112
+ continuation.yield(data)
113
+ }
114
+
115
+ func rpc(_ rpc: RPC, didReceiveRequest request: IncomingRequest) async throws {
116
+ await router.dispatch(request)
117
+ }
118
+
119
+ func rpc(_ rpc: RPC, didReceiveEvent event: IncomingEvent) async {
120
+ await router.dispatch(event)
121
+ }
122
+
123
+ func finish() {
124
+ continuation.finish()
125
+ }
126
+ }
17
127
  `
18
- },
19
- {
20
- extension: '.d.ts',
21
- source: "import('bare-rpc')"
128
+
129
+ return {
130
+ source,
131
+ dependencies: {
132
+ 'bare-rpc-swift': {
133
+ url: 'https://github.com/holepunchto/bare-rpc-swift',
134
+ from: '1.1.0',
135
+ products: ['BareRPC']
136
+ },
137
+ 'bare-stow-host-swift': {
138
+ url: 'https://github.com/holepunchto/bare-stow-host-swift',
139
+ from: '0.1.0',
140
+ products: ['BareStowHost']
141
+ }
22
142
  }
23
- ]
143
+ }
144
+ }
145
+
146
+ function python() {
147
+ const source = `\
148
+ from collections.abc import Callable
149
+
150
+ from bare_rpc import RPC, CommandRouter
151
+ from bare_stow_host import IPC
152
+
153
+ Client = RPC
154
+
155
+ Handlers = Callable[[CommandRouter], None]
156
+
157
+
158
+ def attach(ipc: IPC, handlers: Handlers) -> Client:
159
+ router = CommandRouter()
160
+
161
+ handlers(router)
162
+
163
+ return RPC(
164
+ ipc,
165
+ on_request=router.dispatch_request,
166
+ on_event=router.dispatch_event,
167
+ )
168
+ `
169
+
170
+ return {
171
+ source,
172
+ dependencies: {
173
+ 'bare-rpc': '~=0.1.0',
174
+ 'bare-stow-host': '~=0.1.0'
175
+ }
176
+ }
177
+ }
178
+
179
+ function javascript({ role, options }) {
180
+ const esm = options.module === 'esm'
181
+
182
+ // The bundle registers its handlers in `start()`, before it signals ready.
183
+ const server = role === 'server'
184
+
185
+ const params = server ? 'ipc' : 'ipc, handlers'
186
+
187
+ const body = server
188
+ ? `\
189
+ const router = new RPC.CommandRouter()
190
+ const rpc = new RPC(ipc, router)
191
+
192
+ rpc.respond = router.respond.bind(router)
193
+
194
+ return rpc
195
+ }
196
+ `
197
+ : `\
198
+ const router = new RPC.CommandRouter()
199
+
200
+ handlers(router)
201
+
202
+ return new RPC(ipc, router)
203
+ }
204
+ `
205
+
206
+ const source = esm
207
+ ? `\
208
+ import RPC from 'bare-rpc'
209
+
210
+ export function attach(${params}) {
211
+ ${body}`
212
+ : `\
213
+ const RPC = require('bare-rpc')
214
+
215
+ exports.attach = function attach(${params}) {
216
+ ${body}`
217
+
218
+ const types = server
219
+ ? `\
220
+ export function attach(ipc: import('bare-stow-host').IPC): import('bare-rpc')
221
+ `
222
+ : `\
223
+ export type Handlers = (router: import('bare-rpc').CommandRouter) => void
224
+
225
+ export function attach(ipc: import('bare-stow-host').IPC, handlers: Handlers): import('bare-rpc')
226
+ `
227
+
228
+ return { source, types, dependencies: { 'bare-rpc': '^1.3.9' } }
24
229
  }
package/lib/rpc.js CHANGED
@@ -1,23 +1,11 @@
1
- const registry = {
2
- 'bare-rpc': () => require('./rpc/bare-rpc')
3
- }
4
-
5
- module.exports = function rpc(name, resolve) {
6
- if (typeof name !== 'string') return validate(name)
7
-
8
- const load = registry[name]
1
+ const { registry, validateLanguages } = require('./provider')
9
2
 
10
- if (load) return load()
11
-
12
- if (resolve) return validate(resolve(name))
13
-
14
- throw new Error(`Unknown RPC library '${name}'`)
3
+ const providers = {
4
+ 'bare-rpc': () => require('./rpc/bare-rpc')
15
5
  }
16
6
 
17
- function validate(rpc) {
18
- if (!rpc || typeof rpc.generate !== 'function') {
19
- throw new Error('RPC library must provide a generate() function')
20
- }
7
+ module.exports = registry('RPC library', providers, function validate(rpc) {
8
+ validateLanguages('RPC library', rpc?.languages)
21
9
 
22
10
  return rpc
23
- }
11
+ })
package/lib/shim.js CHANGED
@@ -1,40 +1,33 @@
1
- const path = require('path')
2
- const { fileURLToPath } = require('url')
1
+ const javascript = require('./language/javascript')
2
+ const { select } = require('./provider')
3
3
 
4
- module.exports = exports = function shim(entry, shimURL, opts = {}) {
5
- // `server` is a pre-rendered RPC server setup fragment (binding `rpc`), or
6
- // null. The bundler resolves the RPC library and renders it; the shim only
7
- // decides where the fragment goes.
8
- const { server = null } = opts
4
+ // The bundle runs in Bare, so its side of every provider is JavaScript.
5
+ module.exports = function shim(entry, isolation, server = null) {
6
+ if (server) server = select('RPC library', server, javascript.name)
9
7
 
10
- const specifier = relativeSpecifier(shimURL, entry)
8
+ const dir = new URL('./', entry)
9
+ const url = new URL('__main__.mjs', dir)
10
+ const serverURL = server ? new URL('__server__.mjs', dir) : null
11
11
 
12
- const arg = server ? '{ rpc, ipc }' : 'ipc'
12
+ const modules = new Map()
13
13
 
14
- return `\
15
- import runtime from 'bare-stow/runtime'
14
+ if (server) {
15
+ modules.set(
16
+ serverURL.href,
17
+ server.generate({ role: 'server', options: { module: 'esm' } }).source
18
+ )
19
+ }
16
20
 
21
+ modules.set(
22
+ url.href,
23
+ `\
24
+ import runtime from '${isolation === 'process' ? 'bare-stow-runtime/process' : 'bare-stow-runtime'}'
25
+ ${server ? `import * as server from ${JSON.stringify(javascript.reference(url, serverURL))}\n` : ''}
17
26
  const ipc = runtime.attach(Bare.IPC)
18
- ${server ? '\n' + server : ''}
19
- ipc.run(() => import(${JSON.stringify(specifier)}), ${arg})
27
+ ${server ? '\nconst rpc = server.attach(ipc)\n' : ''}
28
+ ipc.run(() => import(${JSON.stringify(javascript.reference(url, entry))}), ${server ? '{ rpc, ipc }' : 'ipc'})
20
29
  `
21
- }
22
-
23
- exports.url = function url(base) {
24
- return new URL('__main__.mjs', base)
25
- }
26
-
27
- function relativeSpecifier(from, to) {
28
- if (from.protocol === 'file:' && to.protocol === 'file:') {
29
- const fromPath = path.dirname(fileURLToPath(from))
30
- const toPath = fileURLToPath(to)
31
-
32
- let relative = path.relative(fromPath, toPath).split(path.sep).join('/')
33
-
34
- if (!relative.startsWith('.')) relative = './' + relative
35
-
36
- return relative
37
- }
30
+ )
38
31
 
39
- return to.href
32
+ return { url, modules }
40
33
  }
@@ -0,0 +1,144 @@
1
+ const swift = require('./swift')
2
+
3
+ exports.name = 'bare-kit'
4
+ exports.isolation = 'thread'
5
+ exports.linked = true
6
+ exports.linking = ({ targetOptions }) => targetOptions.linking
7
+ exports.offload = false
8
+ exports.format = 'bundle'
9
+ exports.encoding = null
10
+ exports.extension = '.bundle'
11
+ exports.hosts = ({ language }) => hosts[language]
12
+
13
+ // Applications with several bundles link their addons once with `link()`.
14
+ exports.options = function options(given = {}) {
15
+ const { linking = 'package', ...unknown } = given
16
+
17
+ for (const option of Object.keys(unknown)) {
18
+ throw new Error(`Unknown option '${option}' for target 'bare-kit'`)
19
+ }
20
+
21
+ if (linking !== 'package' && linking !== 'app') {
22
+ throw new Error(`Option 'linking' must be 'package' or 'app', got '${linking}'`)
23
+ }
24
+
25
+ return { linking }
26
+ }
27
+
28
+ const bareKitSwift = {
29
+ url: 'https://github.com/holepunchto/bare-kit-swift',
30
+ from: '2.0.0',
31
+ products: ['BareKit']
32
+ }
33
+
34
+ const bareKitKotlin = {
35
+ 'com.github.holepunchto.bare-kit-kotlin:bare-kit-kotlin': 'v0.1.1'
36
+ }
37
+
38
+ const hosts = {
39
+ kotlin: ['android-arm64', 'android-arm', 'android-x64', 'android-ia32'],
40
+ swift: ['darwin-arm64', 'darwin-x64', 'ios-arm64', 'ios-arm64-simulator', 'ios-x64-simulator']
41
+ }
42
+
43
+ exports.languages = {
44
+ kotlin: {
45
+ generate: kotlin,
46
+ link() {
47
+ return { source: null, dependencies: bareKitKotlin }
48
+ }
49
+ },
50
+ swift: {
51
+ // Re-exports Bare Kit, so that code linking the module can run worklets.
52
+ link() {
53
+ return {
54
+ source: '@_exported import BareKit\n',
55
+ dependencies: { 'bare-kit-swift': bareKitSwift }
56
+ }
57
+ },
58
+ generate(context) {
59
+ const start = context.resources
60
+ ? `try worklet.start(${JSON.stringify(context.resources + '.bundle')}, source: Data(contentsOf: bundle))`
61
+ : 'try worklet.start(bundle)'
62
+
63
+ return swift(context, {
64
+ module: 'BareKit',
65
+ conformance: 'extension BareKit.IPC: @retroactive Transport {}',
66
+ fields: { worklet: 'Worklet' },
67
+ params: ['options: Worklet.Options = Worklet.Options()'],
68
+ setup: `\
69
+ let worklet = try Worklet(options: options)
70
+
71
+ let transport = try BareKit.IPC(worklet: worklet)
72
+
73
+ ${start}
74
+
75
+ let ipc = BareStowHost.IPC(
76
+ transport, kill: { worklet.terminate() }, timeout: timeout, grace: grace)
77
+
78
+ Task { ipc.exited(code: await worklet.wait()) }`,
79
+ dependencies: { 'bare-kit-swift': bareKitSwift }
80
+ })
81
+ }
82
+ }
83
+ }
84
+
85
+ function kotlin({ bundle, client, resources, options }) {
86
+ const start = resources
87
+ ? `worklet.start(${JSON.stringify(resources + '.bundle')}, context.assets.open(${JSON.stringify(bundle)}).use { it.readBytes() })`
88
+ : `worklet.start(${JSON.stringify(bundle)})`
89
+
90
+ const source = `\
91
+ package ${options.package}
92
+
93
+ import android.content.Context
94
+ import kotlin.time.Duration
95
+ import kotlinx.coroutines.CoroutineScope
96
+ import kotlinx.coroutines.Dispatchers
97
+ import kotlinx.coroutines.launch
98
+ import to.holepunch.bare.kit.Ipc as WorkletIpc
99
+ import to.holepunch.bare.kit.Worklet
100
+ import to.holepunch.bare.stow.host.Ipc
101
+
102
+ class Started(val worklet: Worklet, val ipc: Ipc${client ? ', val rpc: Client' : ''})
103
+
104
+ suspend fun start(
105
+ context: Context,
106
+ options: Worklet.Options = Worklet.Options(),
107
+ timeout: Duration? = null,
108
+ grace: Duration = Ipc.DEFAULT_GRACE,${client ? '\n handlers: Handlers = {},' : ''}
109
+ ): Started {
110
+ val worklet = Worklet(context, options)
111
+
112
+ val transport = WorkletIpc(worklet)
113
+
114
+ ${start}
115
+
116
+ val ipc =
117
+ Ipc(
118
+ transport.data,
119
+ transport::write,
120
+ kill = { worklet.terminate() },
121
+ timeout = timeout,
122
+ grace = grace,
123
+ )
124
+
125
+ CoroutineScope(Dispatchers.Default).launch {
126
+ ipc.exited(worklet.wait())
127
+
128
+ transport.close()
129
+ }
130
+ ${client ? '\n val rpc = attach(ipc, handlers)\n' : ''}
131
+ ipc.ready()
132
+
133
+ return Started(worklet, ipc${client ? ', rpc' : ''})
134
+ }
135
+ `
136
+
137
+ return {
138
+ source,
139
+ dependencies: {
140
+ ...bareKitKotlin,
141
+ 'com.github.holepunchto.bare-stow-host-kotlin:bare-stow-host-kotlin': 'v0.1.1'
142
+ }
143
+ }
144
+ }
@@ -1,4 +1,8 @@
1
+ const harness = require('./javascript')
2
+ const swift = require('./swift')
3
+
1
4
  exports.name = 'bare-sidecar'
5
+ exports.isolation = 'process'
2
6
  exports.linked = false
3
7
  exports.offload = true
4
8
  exports.format = 'bundle'
@@ -13,70 +17,103 @@ exports.hosts = [
13
17
  'win32-x64'
14
18
  ]
15
19
 
16
- exports.generate = function generate(opts) {
17
- const { bundleSpecifier, ipc, rpc, module = 'cjs', client = null } = opts
20
+ exports.languages = {
21
+ javascript: {
22
+ generate(context) {
23
+ return harness(context, {
24
+ name: 'bare-sidecar',
25
+ version: '^0.5.12',
26
+ binding: 'Sidecar',
27
+ url: true,
28
+ setup: `\
29
+ const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
18
30
 
19
- const esm = module === 'esm'
31
+ const ipc = stow.wrap(sidecar, { kill: () => sidecar.kill('SIGKILL'), timeout, grace })
20
32
 
21
- const returned = client ? `{ ${ipc}, ${rpc} }` : `{ ${ipc} }`
33
+ sidecar.on('exit', (code, signal) => ipc.exited(code, signal))`,
34
+ types: "import('bare-sidecar').SidecarOptions"
35
+ })
36
+ }
37
+ },
38
+ python: { generate: python },
39
+ swift: {
40
+ generate(context) {
41
+ return swift(context, {
42
+ module: 'BareSidecar',
43
+ conformance: 'extension Sidecar: @retroactive Transport {}',
44
+ setup: `\
45
+ let sidecar = try await Sidecar.spawn(bundle)
22
46
 
23
- const handle = client
24
- ? `{ ${ipc}: import('bare-stow/host').IPC; ${rpc}: ${client.type} }`
25
- : `{ ${ipc}: import('bare-stow/host').IPC }`
47
+ let ipc = BareStowHost.IPC(sidecar, kill: { sidecar.kill() }, timeout: timeout, grace: grace)
26
48
 
27
- const source = esm
28
- ? `\
29
- import Sidecar from 'bare-sidecar'
30
- import stow from 'bare-stow/host'
49
+ Task {
50
+ switch await sidecar.wait() {
51
+ case .exit(let code): ipc.exited(code: code)
52
+ case .signal(let signal): ipc.exited(code: nil, signal: signal)
53
+ }
54
+ }`,
55
+ dependencies: {
56
+ 'bare-sidecar-swift': {
57
+ url: 'https://github.com/holepunchto/bare-sidecar-swift',
58
+ from: '0.1.0',
59
+ products: ['BareSidecar']
60
+ }
61
+ }
62
+ })
63
+ }
64
+ }
65
+ }
31
66
 
32
- const bundle = new URL(import.meta.resolve(${JSON.stringify(bundleSpecifier)}))
67
+ function python({ bundle, client }) {
68
+ const source = `\
69
+ import asyncio
70
+ from pathlib import Path
71
+ from typing import NamedTuple
33
72
 
34
- export async function start({ timeout, grace, ...opts } = {}) {
35
- const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
73
+ from bare_sidecar import spawn
74
+ from bare_stow_host import DEFAULT_GRACE, IPC, wrap
75
+ ${client ? `\nfrom ${client} import Client, Handlers, attach\n` : ''}
76
+ bundle = Path(__file__).parent / ${JSON.stringify(bundle)}
36
77
 
37
- const ${ipc} = stow.wrap(sidecar, { timeout, grace })
38
- ${client ? '\n' + indent(client.source, 2) : ''}
39
- await ${ipc}.ready
78
+ _watching = set()
40
79
 
41
- return ${returned}
42
- }
43
- `
44
- : `\
45
- const Sidecar = require('bare-sidecar')
46
- const stow = require('bare-stow/host')
47
80
 
48
- const bundle = require.resolve(${JSON.stringify(bundleSpecifier)})
81
+ class Started(NamedTuple):
82
+ ipc: IPC${client ? '\n rpc: Client' : ''}
49
83
 
50
- module.exports = {
51
- async start({ timeout, grace, ...opts } = {}) {
52
- const sidecar = new Sidecar(bundle, { stdio: 'inherit', ...opts })
53
84
 
54
- const ${ipc} = stow.wrap(sidecar, { timeout, grace })
55
- ${client ? '\n' + indent(client.source, 4) : ''}
56
- await ${ipc}.ready
85
+ async def start(
86
+ *,
87
+ timeout=None,
88
+ grace=DEFAULT_GRACE,${client ? '\n handlers: Handlers = lambda router: None,' : ''}
89
+ **opts,
90
+ ) -> Started:
91
+ sidecar = await spawn(bundle, **opts)
57
92
 
58
- return ${returned}
59
- }
60
- }
61
- `
93
+ ipc = wrap(sidecar, kill=sidecar.kill, timeout=timeout, grace=grace)
94
+
95
+ watching = asyncio.ensure_future(_watch(sidecar, ipc))
96
+ _watching.add(watching)
97
+ watching.add_done_callback(_watching.discard)
98
+ ${client ? '\n rpc = attach(ipc, handlers)\n' : ''}
99
+ await ipc.ready
100
+
101
+ return Started(ipc${client ? ', rpc' : ''})
102
+
103
+
104
+ async def _watch(sidecar, ipc):
105
+ await sidecar.wait()
62
106
 
63
- return [
64
- { source },
65
- {
66
- extension: '.d.ts',
67
- source: `\
68
- export function start(
69
- opts?: import('bare-sidecar').SidecarOptions & Omit<import('bare-stow/host').IPCOptions, 'kill'>
70
- ): Promise<${handle}>
107
+ ipc.exited(sidecar.exit_code, sidecar.signal_code)
108
+
109
+ await sidecar.close()
71
110
  `
72
- }
73
- ]
74
- }
75
111
 
76
- function indent(source, spaces) {
77
- const pad = ' '.repeat(spaces)
78
- return source
79
- .split('\n')
80
- .map((line) => (line.length === 0 ? line : pad + line))
81
- .join('\n')
112
+ return {
113
+ source,
114
+ dependencies: {
115
+ 'bare-sidecar': '~=0.1.0',
116
+ 'bare-stow-host': '~=0.1.0'
117
+ }
118
+ }
82
119
  }