swarpc 0.1.2 → 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.
- package/README.md +3 -1
- package/dist/swarpc.d.ts +5 -1
- package/dist/swarpc.d.ts.map +1 -1
- package/dist/typings.d.ts +1 -1
- package/dist/typings.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/swarpc.js +117 -28
- package/src/typings.js +1 -1
package/README.md
CHANGED
package/dist/swarpc.d.ts
CHANGED
|
@@ -10,9 +10,13 @@ export function Server<Procedures extends ProceduresMap>(procedures: Procedures)
|
|
|
10
10
|
/**
|
|
11
11
|
* @template {ProceduresMap} Procedures
|
|
12
12
|
* @param {Procedures} procedures
|
|
13
|
+
* @param {object} [options]
|
|
14
|
+
* @param {Worker} [options.worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
13
15
|
* @returns {SwarpcClient<Procedures>}
|
|
14
16
|
*/
|
|
15
|
-
export function Client<Procedures extends ProceduresMap>(procedures: Procedures
|
|
17
|
+
export function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker }?: {
|
|
18
|
+
worker?: Worker;
|
|
19
|
+
}): SwarpcClient<Procedures>;
|
|
16
20
|
import type { ProceduresMap } from './typings.js';
|
|
17
21
|
import type { SwarpcServer } from './typings.js';
|
|
18
22
|
import type { SwarpcClient } from './typings.js';
|
package/dist/swarpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swarpc.d.ts","sourceRoot":"","sources":["../src/swarpc.js"],"names":[],"mappings":"AAEA;;GAEG;AAEH;;;;GAIG;AACH,uBAJ6B,UAAU,SAA1B,aAAe,cACjB,UAAU,GACR,aAAa,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"swarpc.d.ts","sourceRoot":"","sources":["../src/swarpc.js"],"names":[],"mappings":"AAEA;;GAEG;AAEH;;;;GAIG;AACH,uBAJ6B,UAAU,SAA1B,aAAe,cACjB,UAAU,GACR,aAAa,UAAU,CAAC,CAsGpC;AA0DD;;;;;;GAMG;AACH,uBAN6B,UAAU,SAA1B,aAAe,cACjB,UAAU,eAElB;IAAyB,MAAM,GAAvB,MAAM;CACd,GAAU,aAAa,UAAU,CAAC,CAiCpC;mCA5M6D,cAAc;kCAAd,cAAc;kCAAd,cAAc"}
|
package/dist/typings.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type Procedure<I extends Type, P extends Type, S extends Type> = {
|
|
|
5
5
|
};
|
|
6
6
|
export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferOut"]) => void) => Promise<NoInfer<S>["inferOut"] | NoInfer<S>["inferOut"]>;
|
|
7
7
|
export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>;
|
|
8
|
-
export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["
|
|
8
|
+
export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>;
|
|
9
9
|
export type SwarpcClient<Procedures extends ProceduresMap> = {
|
|
10
10
|
procedures: Procedures;
|
|
11
11
|
} & { [F in keyof Procedures]: ClientMethod<Procedures[F]>; };
|
package/dist/typings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typings.d.ts","sourceRoot":"","sources":["../src/typings.js"],"names":[],"mappings":"sBAKoB,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM;WAEL,CAAC;cACD,CAAC;aACD,CAAC;;oCAIK,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,IACN,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;4BAIjI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;yBAIb,CAAC,SAA9B,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAE,IAC7B,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"typings.d.ts","sourceRoot":"","sources":["../src/typings.js"],"names":[],"mappings":"sBAKoB,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM;WAEL,CAAC;cACD,CAAC;aACD,CAAC;;oCAIK,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,IACN,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;4BAIjI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;yBAIb,CAAC,SAA9B,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAE,IAC7B,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC;yBAI/G,UAAU,SAAzB,aAAc,IACf;IAAE,UAAU,EAAE,UAAU,CAAA;CAAE,GAAG,GAAG,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAE;yBAIrE,UAAU,SAAzB,aAAc,IACf;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE,GAAE,CAAC,IAAI,MAAM,UAAU,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAE,CAAC;IAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAG,GAAG,GAAG,CAAC,IAAI,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAE,CAAC,KAAK,IAAI,GAAE;0BApC/V,SAAS"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Full type-safe RPC library for service worker -- move things off of the UI thread with ease!",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"service-workers",
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
"types": "dist/swarpc.d.ts",
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc",
|
|
31
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
31
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
32
|
+
"typedoc": "typedoc src/swarpc.js src/typings.js --readme README.md"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"arktype": "^2.1.20"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
38
|
+
"typedoc": "^0.28.7",
|
|
37
39
|
"typescript": "^5.8.3"
|
|
38
40
|
}
|
|
39
41
|
}
|
package/src/swarpc.js
CHANGED
|
@@ -7,9 +7,11 @@ import { type } from "arktype"
|
|
|
7
7
|
/**
|
|
8
8
|
* @template {ProceduresMap} Procedures
|
|
9
9
|
* @param {Procedures} procedures
|
|
10
|
+
* @param {object} [options]
|
|
11
|
+
* @param {Worker} [options.worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
10
12
|
* @returns {SwarpcServer<Procedures>}
|
|
11
13
|
*/
|
|
12
|
-
export function Server(procedures) {
|
|
14
|
+
export function Server(procedures, { worker } = { worker: undefined }) {
|
|
13
15
|
/** @type {SwarpcServer<Procedures>} */
|
|
14
16
|
// @ts-expect-error
|
|
15
17
|
const instance = {
|
|
@@ -35,6 +37,7 @@ export function Server(procedures) {
|
|
|
35
37
|
const PayloadSchema = type.or(
|
|
36
38
|
...Object.entries(procedures).map(([functionName, { input }]) => ({
|
|
37
39
|
functionName: type(`"${functionName}"`),
|
|
40
|
+
requestId: type("string >= 1"),
|
|
38
41
|
input,
|
|
39
42
|
}))
|
|
40
43
|
)
|
|
@@ -48,18 +51,26 @@ export function Server(procedures) {
|
|
|
48
51
|
* @param {{functionName: string} & Partial<{ result: any; error: any; progress: any }>} data
|
|
49
52
|
*/
|
|
50
53
|
const postMessage = async (data) => {
|
|
51
|
-
|
|
52
|
-
.
|
|
53
|
-
|
|
54
|
+
if (worker) {
|
|
55
|
+
self.postMessage(data)
|
|
56
|
+
} else {
|
|
57
|
+
await self.clients.matchAll().then((clients) => {
|
|
58
|
+
console.debug(`[SWARPC Server] Posting message to clients`, clients)
|
|
54
59
|
clients.forEach((client) => client.postMessage(data))
|
|
55
|
-
)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
console.log("[SWARPC Server] Starting message listener on", self)
|
|
59
65
|
|
|
60
66
|
self.addEventListener("message", async (event) => {
|
|
61
|
-
const { functionName, input } = PayloadSchema.assert(
|
|
62
|
-
|
|
67
|
+
const { functionName, requestId, input } = PayloadSchema.assert(
|
|
68
|
+
event.data
|
|
69
|
+
)
|
|
70
|
+
console.log(
|
|
71
|
+
`[SWARPC Server] ${requestId} Running ${functionName} with`,
|
|
72
|
+
input
|
|
73
|
+
)
|
|
63
74
|
|
|
64
75
|
/**
|
|
65
76
|
* @param {*} error
|
|
@@ -67,6 +78,7 @@ export function Server(procedures) {
|
|
|
67
78
|
const postError = async (error) =>
|
|
68
79
|
postMessage({
|
|
69
80
|
functionName,
|
|
81
|
+
requestId,
|
|
70
82
|
error: {
|
|
71
83
|
message: "message" in error ? error.message : String(error),
|
|
72
84
|
},
|
|
@@ -78,23 +90,97 @@ export function Server(procedures) {
|
|
|
78
90
|
return
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
await implementation(input, async (progress) =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
await implementation(input, async (progress) => {
|
|
94
|
+
console.debug(
|
|
95
|
+
`[SWARPC Server] ${requestId} Progress for ${functionName}:`,
|
|
96
|
+
progress
|
|
97
|
+
)
|
|
98
|
+
await postMessage({ functionName, requestId, progress })
|
|
99
|
+
})
|
|
100
|
+
.catch(async (error) => {
|
|
101
|
+
console.debug(
|
|
102
|
+
`[SWARPC Server] ${requestId} Error in ${functionName}:`,
|
|
103
|
+
error
|
|
104
|
+
)
|
|
105
|
+
await postError(error)
|
|
106
|
+
})
|
|
107
|
+
.then(async (result) => {
|
|
108
|
+
console.debug(
|
|
109
|
+
`[SWARPC Server] ${requestId} Result for ${functionName}:`,
|
|
110
|
+
result
|
|
111
|
+
)
|
|
112
|
+
await postMessage({ functionName, requestId, result })
|
|
113
|
+
})
|
|
86
114
|
})
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
return instance
|
|
90
118
|
}
|
|
91
119
|
|
|
120
|
+
function generateRequestId() {
|
|
121
|
+
return Math.random().toString(36).substring(2, 15)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @type {Map<string, { reject: (err: Error) => void; onProgress: (progress: any) => void; resolve: (result: any) => void }>}
|
|
126
|
+
*/
|
|
127
|
+
const pendingRequests = new Map()
|
|
128
|
+
|
|
129
|
+
let _clientListenerStarted = false
|
|
130
|
+
/**
|
|
131
|
+
* @param {Worker} [worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
132
|
+
*/
|
|
133
|
+
async function startClientListener(worker) {
|
|
134
|
+
if (_clientListenerStarted) return
|
|
135
|
+
|
|
136
|
+
if (!worker) {
|
|
137
|
+
const sw = await navigator.serviceWorker.ready
|
|
138
|
+
if (!sw?.active) {
|
|
139
|
+
throw new Error("[SWARPC Client] Service Worker is not active")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!navigator.serviceWorker.controller) {
|
|
143
|
+
console.warn("[SWARPC Client] Service Worker is not controlling the page")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const w = worker ?? navigator.serviceWorker
|
|
148
|
+
console.debug("[SWARPC Client] Registering message listener for client", w)
|
|
149
|
+
|
|
150
|
+
w.addEventListener("message", (event) => {
|
|
151
|
+
const { functionName, requestId, ...data } = event.data || {}
|
|
152
|
+
if (!requestId) {
|
|
153
|
+
throw new Error("[SWARPC Client] Message received without requestId")
|
|
154
|
+
}
|
|
155
|
+
const handlers = pendingRequests.get(requestId)
|
|
156
|
+
if (!handlers) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`[SWARPC Client] ${requestId} has no active request handlers`
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if ("error" in data) {
|
|
163
|
+
handlers.reject(new Error(data.error.message))
|
|
164
|
+
pendingRequests.delete(requestId)
|
|
165
|
+
} else if ("progress" in data) {
|
|
166
|
+
handlers.onProgress(data.progress)
|
|
167
|
+
} else if ("result" in data) {
|
|
168
|
+
handlers.resolve(data.result)
|
|
169
|
+
pendingRequests.delete(requestId)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
_clientListenerStarted = true
|
|
174
|
+
}
|
|
175
|
+
|
|
92
176
|
/**
|
|
93
177
|
* @template {ProceduresMap} Procedures
|
|
94
178
|
* @param {Procedures} procedures
|
|
179
|
+
* @param {object} [options]
|
|
180
|
+
* @param {Worker} [options.worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
95
181
|
* @returns {SwarpcClient<Procedures>}
|
|
96
182
|
*/
|
|
97
|
-
export function Client(procedures) {
|
|
183
|
+
export function Client(procedures, { worker } = { worker: undefined }) {
|
|
98
184
|
/** @type {SwarpcClient<Procedures>} */
|
|
99
185
|
// @ts-expect-error
|
|
100
186
|
const instance = { procedures }
|
|
@@ -102,22 +188,25 @@ export function Client(procedures) {
|
|
|
102
188
|
for (const functionName of Object.keys(procedures)) {
|
|
103
189
|
instance[functionName] = async (input, onProgress = () => {}) => {
|
|
104
190
|
procedures[functionName].input.assert(input)
|
|
105
|
-
|
|
106
|
-
|
|
191
|
+
await startClientListener(worker)
|
|
192
|
+
|
|
193
|
+
const w =
|
|
194
|
+
worker ?? (await navigator.serviceWorker.ready.then((r) => r.active))
|
|
107
195
|
return new Promise((resolve, reject) => {
|
|
108
|
-
navigator.serviceWorker.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
196
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
197
|
+
console.warn(
|
|
198
|
+
"[SWARPC Client] Service Worker is not controlling the page"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const requestId = generateRequestId()
|
|
202
|
+
|
|
203
|
+
pendingRequests.set(requestId, { resolve, onProgress, reject })
|
|
204
|
+
|
|
205
|
+
console.log(
|
|
206
|
+
`[SWARPC Client] ${requestId} Requesting ${functionName} with`,
|
|
207
|
+
input
|
|
208
|
+
)
|
|
209
|
+
w.postMessage({ functionName, input, requestId })
|
|
121
210
|
})
|
|
122
211
|
}
|
|
123
212
|
}
|
package/src/typings.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* @template {Procedure<Type, Type, Type>} P
|
|
28
|
-
* @typedef {(input: P['input']['
|
|
28
|
+
* @typedef {(input: P['input']['inferIn'], onProgress?: (progress: P['progress']['inferOut']) => void) => Promise<P['success']['inferOut']>} ClientMethod
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
/**
|