swarpc 0.1.2 → 0.2.1

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  <div align=center>
2
- <h1>sw&rpc</h1>
2
+ <h1>
3
+ <img src="./logo.svg" alt="sw&rpc" />
4
+ </h1>
3
5
 
4
6
  RPC for Service Workers -- move that heavy computation off of your UI thread!
5
7
 
package/dist/swarpc.d.ts CHANGED
@@ -1,19 +1,8 @@
1
- /**
2
- * @import { ProceduresMap, SwarpcClient, SwarpcServer } from './typings.js'
3
- */
4
- /**
5
- * @template {ProceduresMap} Procedures
6
- * @param {Procedures} procedures
7
- * @returns {SwarpcServer<Procedures>}
8
- */
9
- export function Server<Procedures extends ProceduresMap>(procedures: Procedures): SwarpcServer<Procedures>;
10
- /**
11
- * @template {ProceduresMap} Procedures
12
- * @param {Procedures} procedures
13
- * @returns {SwarpcClient<Procedures>}
14
- */
15
- export function Client<Procedures extends ProceduresMap>(procedures: Procedures): SwarpcClient<Procedures>;
16
- import type { ProceduresMap } from './typings.js';
17
- import type { SwarpcServer } from './typings.js';
18
- import type { SwarpcClient } from './typings.js';
1
+ import type { ProceduresMap, SwarpcClient, SwarpcServer } from "./types";
2
+ export declare function Server<Procedures extends ProceduresMap>(procedures: Procedures, { worker }?: {
3
+ worker?: Worker;
4
+ }): SwarpcServer<Procedures>;
5
+ export declare function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker }?: {
6
+ worker?: Worker;
7
+ }): SwarpcClient<Procedures>;
19
8
  //# sourceMappingURL=swarpc.d.ts.map
@@ -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,CAgFpC;AAED;;;;GAIG;AACH,uBAJ6B,UAAU,SAA1B,aAAe,cACjB,UAAU,GACR,aAAa,UAAU,CAAC,CA+BpC;mCA1H6D,cAAc;kCAAd,cAAc;kCAAd,cAAc"}
1
+ {"version":3,"file":"swarpc.d.ts","sourceRoot":"","sources":["../src/swarpc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EAEb,MAAM,SAAS,CAAA;AAEhB,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,YAAY,CAAC,UAAU,CAAC,CAyE1B;AAwDD,wBAAgB,MAAM,CAAC,UAAU,SAAS,aAAa,EACrD,UAAU,EAAE,UAAU,EACtB,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,YAAY,CAAC,UAAU,CAAC,CAiC1B"}
package/dist/swarpc.js ADDED
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __rest = (this && this.__rest) || function (s, e) {
39
+ var t = {};
40
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
41
+ t[p] = s[p];
42
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
43
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
44
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
45
+ t[p[i]] = s[p[i]];
46
+ }
47
+ return t;
48
+ };
49
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
50
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
51
+ if (ar || !(i in from)) {
52
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
53
+ ar[i] = from[i];
54
+ }
55
+ }
56
+ return to.concat(ar || Array.prototype.slice.call(from));
57
+ };
58
+ Object.defineProperty(exports, "__esModule", { value: true });
59
+ exports.Server = Server;
60
+ exports.Client = Client;
61
+ var arktype_1 = require("arktype");
62
+ function Server(procedures, _a) {
63
+ var _this = this;
64
+ var _b = _a === void 0 ? {} : _a, worker = _b.worker;
65
+ var instance = {
66
+ procedures: procedures,
67
+ implementations: {},
68
+ start: function () { },
69
+ };
70
+ var _loop_1 = function (functionName) {
71
+ instance[functionName] = (function (implementation) {
72
+ if (!instance.procedures[functionName]) {
73
+ throw new Error("No procedure found for function name: ".concat(functionName));
74
+ }
75
+ instance.implementations[functionName] = implementation;
76
+ });
77
+ };
78
+ for (var functionName in procedures) {
79
+ _loop_1(functionName);
80
+ }
81
+ var PayloadSchema = arktype_1.type.or.apply(arktype_1.type, Object.entries(procedures).map(function (_a) {
82
+ var functionName = _a[0], input = _a[1].input;
83
+ return ({
84
+ functionName: (0, arktype_1.type)("\"".concat(functionName, "\"")),
85
+ requestId: (0, arktype_1.type)("string >= 1"),
86
+ input: input,
87
+ });
88
+ }));
89
+ instance.start = function (self) {
90
+ var postMessage = function (data) { return __awaiter(_this, void 0, void 0, function () {
91
+ return __generator(this, function (_a) {
92
+ switch (_a.label) {
93
+ case 0:
94
+ if (!worker) return [3 /*break*/, 1];
95
+ self.postMessage(data);
96
+ return [3 /*break*/, 3];
97
+ case 1: return [4 /*yield*/, self.clients.matchAll().then(function (clients) {
98
+ clients.forEach(function (client) { return client.postMessage(data); });
99
+ })];
100
+ case 2:
101
+ _a.sent();
102
+ _a.label = 3;
103
+ case 3: return [2 /*return*/];
104
+ }
105
+ });
106
+ }); };
107
+ self.addEventListener("message", function (event) { return __awaiter(_this, void 0, void 0, function () {
108
+ var _a, functionName, requestId, input, postError, implementation;
109
+ var _this = this;
110
+ return __generator(this, function (_b) {
111
+ switch (_b.label) {
112
+ case 0:
113
+ _a = PayloadSchema.assert(event.data), functionName = _a.functionName, requestId = _a.requestId, input = _a.input;
114
+ postError = function (error) { return __awaiter(_this, void 0, void 0, function () {
115
+ return __generator(this, function (_a) {
116
+ return [2 /*return*/, postMessage({
117
+ functionName: functionName,
118
+ requestId: requestId,
119
+ error: {
120
+ message: "message" in error ? error.message : String(error),
121
+ },
122
+ })];
123
+ });
124
+ }); };
125
+ implementation = instance.implementations[functionName];
126
+ if (!!implementation) return [3 /*break*/, 2];
127
+ return [4 /*yield*/, postError("No implementation found")];
128
+ case 1:
129
+ _b.sent();
130
+ return [2 /*return*/];
131
+ case 2: return [4 /*yield*/, implementation(input, function (progress) { return __awaiter(_this, void 0, void 0, function () {
132
+ return __generator(this, function (_a) {
133
+ switch (_a.label) {
134
+ case 0: return [4 /*yield*/, postMessage({ functionName: functionName, requestId: requestId, progress: progress })];
135
+ case 1:
136
+ _a.sent();
137
+ return [2 /*return*/];
138
+ }
139
+ });
140
+ }); })
141
+ .catch(function (error) { return __awaiter(_this, void 0, void 0, function () {
142
+ return __generator(this, function (_a) {
143
+ switch (_a.label) {
144
+ case 0: return [4 /*yield*/, postError(error)];
145
+ case 1:
146
+ _a.sent();
147
+ return [2 /*return*/];
148
+ }
149
+ });
150
+ }); })
151
+ .then(function (result) { return __awaiter(_this, void 0, void 0, function () {
152
+ return __generator(this, function (_a) {
153
+ switch (_a.label) {
154
+ case 0: return [4 /*yield*/, postMessage({ functionName: functionName, requestId: requestId, result: result })];
155
+ case 1:
156
+ _a.sent();
157
+ return [2 /*return*/];
158
+ }
159
+ });
160
+ }); })];
161
+ case 3:
162
+ _b.sent();
163
+ return [2 /*return*/];
164
+ }
165
+ });
166
+ }); });
167
+ };
168
+ return instance;
169
+ }
170
+ function generateRequestId() {
171
+ return Math.random().toString(36).substring(2, 15);
172
+ }
173
+ var pendingRequests = new Map();
174
+ var _clientListenerStarted = false;
175
+ function startClientListener(worker) {
176
+ return __awaiter(this, void 0, void 0, function () {
177
+ var sw, w;
178
+ return __generator(this, function (_a) {
179
+ switch (_a.label) {
180
+ case 0:
181
+ if (_clientListenerStarted)
182
+ return [2 /*return*/];
183
+ if (!!worker) return [3 /*break*/, 2];
184
+ return [4 /*yield*/, navigator.serviceWorker.ready];
185
+ case 1:
186
+ sw = _a.sent();
187
+ if (!(sw === null || sw === void 0 ? void 0 : sw.active)) {
188
+ throw new Error("[SWARPC Client] Service Worker is not active");
189
+ }
190
+ if (!navigator.serviceWorker.controller) {
191
+ console.warn("[SWARPC Client] Service Worker is not controlling the page");
192
+ }
193
+ _a.label = 2;
194
+ case 2:
195
+ w = worker !== null && worker !== void 0 ? worker : navigator.serviceWorker;
196
+ w.addEventListener("message", function (event) {
197
+ var _a = event.data || {}, functionName = _a.functionName, requestId = _a.requestId, data = __rest(_a, ["functionName", "requestId"]);
198
+ if (!requestId) {
199
+ throw new Error("[SWARPC Client] Message received without requestId");
200
+ }
201
+ var handlers = pendingRequests.get(requestId);
202
+ if (!handlers) {
203
+ throw new Error("[SWARPC Client] ".concat(requestId, " has no active request handlers"));
204
+ }
205
+ if ("error" in data) {
206
+ handlers.reject(new Error(data.error.message));
207
+ pendingRequests.delete(requestId);
208
+ }
209
+ else if ("progress" in data) {
210
+ handlers.onProgress(data.progress);
211
+ }
212
+ else if ("result" in data) {
213
+ handlers.resolve(data.result);
214
+ pendingRequests.delete(requestId);
215
+ }
216
+ });
217
+ _clientListenerStarted = true;
218
+ return [2 /*return*/];
219
+ }
220
+ });
221
+ });
222
+ }
223
+ function Client(procedures, _a) {
224
+ var _this = this;
225
+ var _b = _a === void 0 ? {} : _a, worker = _b.worker;
226
+ var instance = { procedures: procedures };
227
+ var _loop_2 = function (functionName) {
228
+ instance[functionName] = (function (input_1) {
229
+ var args_1 = [];
230
+ for (var _i = 1; _i < arguments.length; _i++) {
231
+ args_1[_i - 1] = arguments[_i];
232
+ }
233
+ return __awaiter(_this, __spreadArray([input_1], args_1, true), void 0, function (input, onProgress) {
234
+ var w, _a;
235
+ if (onProgress === void 0) { onProgress = function () { }; }
236
+ return __generator(this, function (_b) {
237
+ switch (_b.label) {
238
+ case 0:
239
+ procedures[functionName].input.assert(input);
240
+ return [4 /*yield*/, startClientListener(worker)];
241
+ case 1:
242
+ _b.sent();
243
+ if (!(worker !== null && worker !== void 0)) return [3 /*break*/, 2];
244
+ _a = worker;
245
+ return [3 /*break*/, 4];
246
+ case 2: return [4 /*yield*/, navigator.serviceWorker.ready.then(function (r) { return r.active; })];
247
+ case 3:
248
+ _a = (_b.sent());
249
+ _b.label = 4;
250
+ case 4:
251
+ w = _a;
252
+ if (!w) {
253
+ throw new Error("[SWARPC Client] No active service worker found");
254
+ }
255
+ return [2 /*return*/, new Promise(function (resolve, reject) {
256
+ if (!worker && !navigator.serviceWorker.controller)
257
+ console.warn("[SWARPC Client] Service Worker is not controlling the page");
258
+ var requestId = generateRequestId();
259
+ pendingRequests.set(requestId, { resolve: resolve, onProgress: onProgress, reject: reject });
260
+ w.postMessage({ functionName: functionName, input: input, requestId: requestId });
261
+ })];
262
+ }
263
+ });
264
+ });
265
+ });
266
+ };
267
+ for (var _i = 0, _c = Object.keys(procedures); _i < _c.length; _i++) {
268
+ var functionName = _c[_i];
269
+ _loop_2(functionName);
270
+ }
271
+ return instance;
272
+ }
@@ -0,0 +1,24 @@
1
+ import type { Type } from "arktype";
2
+ export type Procedure<I extends Type, P extends Type, S extends Type> = {
3
+ input: I;
4
+ progress: P;
5
+ success: S;
6
+ };
7
+ export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferOut"]) => void) => Promise<S["inferOut"]>;
8
+ export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>;
9
+ export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>;
10
+ export type SwarpcClient<Procedures extends ProceduresMap> = {
11
+ procedures: Procedures;
12
+ } & {
13
+ [F in keyof Procedures]: ClientMethod<Procedures[F]>;
14
+ };
15
+ export type SwarpcServer<Procedures extends ProceduresMap> = {
16
+ procedures: Procedures;
17
+ implementations: {
18
+ [F in keyof Procedures]: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>;
19
+ };
20
+ start(self: Window): void;
21
+ } & {
22
+ [F in keyof Procedures]: (impl: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>) => void;
23
+ };
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,IAAI;IACtE,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,CAAC;IACZ,OAAO,EAAE,CAAC,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,uBAAuB,CACjC,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,IAAI,EACd,CAAC,SAAS,IAAI,IACZ,CACF,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,EACpB,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KAC1C,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAE5B,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAExE,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAChE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAC5B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KACvD,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAEvC,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,UAAU,EAAE,UAAU,CAAC;CACxB,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE;SACd,CAAC,IAAI,MAAM,UAAU,GAAG,uBAAuB,CAC9C,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EACtB,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EACzB,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CACzB;KACF,CAAC;IACF,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,CACvB,IAAI,EAAE,uBAAuB,CAC3B,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EACtB,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EACzB,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CACzB,KACE,IAAI;CACV,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarpc",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
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",
@@ -24,16 +24,18 @@
24
24
  "dist",
25
25
  "src"
26
26
  ],
27
- "main": "src/swarpc.js",
27
+ "main": "dist/swarpc.js",
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.ts src/types.ts --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.ts ADDED
@@ -0,0 +1,177 @@
1
+ import { type } from "arktype"
2
+ import type {
3
+ ProceduresMap,
4
+ SwarpcClient,
5
+ SwarpcServer,
6
+ ProcedureImplementation,
7
+ } from "./types"
8
+
9
+ export function Server<Procedures extends ProceduresMap>(
10
+ procedures: Procedures,
11
+ { worker }: { worker?: Worker } = {}
12
+ ): SwarpcServer<Procedures> {
13
+ const instance = {
14
+ procedures,
15
+ implementations: {} as SwarpcServer<Procedures>["implementations"],
16
+ start: () => {},
17
+ } as SwarpcServer<Procedures>
18
+
19
+ for (const functionName in procedures) {
20
+ instance[functionName] = ((implementation) => {
21
+ if (!instance.procedures[functionName]) {
22
+ throw new Error(`No procedure found for function name: ${functionName}`)
23
+ }
24
+ instance.implementations[functionName] = implementation as any
25
+ }) as SwarpcServer<Procedures>[typeof functionName]
26
+ }
27
+
28
+ const PayloadSchema = type.or(
29
+ ...Object.entries(procedures).map(([functionName, { input }]) => ({
30
+ functionName: type(`"${functionName}"`),
31
+ requestId: type("string >= 1"),
32
+ input,
33
+ }))
34
+ )
35
+
36
+ instance.start = (self: Window) => {
37
+ const postMessage = async (
38
+ data: { functionName: string; requestId: string } & Partial<{
39
+ result: any
40
+ error: any
41
+ progress: any
42
+ }>
43
+ ) => {
44
+ if (worker) {
45
+ self.postMessage(data)
46
+ } else {
47
+ await (self as any).clients.matchAll().then((clients: any[]) => {
48
+ clients.forEach((client) => client.postMessage(data))
49
+ })
50
+ }
51
+ }
52
+
53
+ self.addEventListener("message", async (event: MessageEvent) => {
54
+ const { functionName, requestId, input } = PayloadSchema.assert(
55
+ event.data
56
+ )
57
+ const postError = async (error: any) =>
58
+ postMessage({
59
+ functionName,
60
+ requestId,
61
+ error: {
62
+ message: "message" in error ? error.message : String(error),
63
+ },
64
+ })
65
+
66
+ const implementation = instance.implementations[functionName]
67
+ if (!implementation) {
68
+ await postError("No implementation found")
69
+ return
70
+ }
71
+
72
+ await implementation(input, async (progress: any) => {
73
+ await postMessage({ functionName, requestId, progress })
74
+ })
75
+ .catch(async (error: any) => {
76
+ await postError(error)
77
+ })
78
+ .then(async (result: any) => {
79
+ await postMessage({ functionName, requestId, result })
80
+ })
81
+ })
82
+ }
83
+
84
+ return instance
85
+ }
86
+
87
+ function generateRequestId(): string {
88
+ return Math.random().toString(36).substring(2, 15)
89
+ }
90
+
91
+ type PendingRequest = {
92
+ reject: (err: Error) => void
93
+ onProgress: (progress: any) => void
94
+ resolve: (result: any) => void
95
+ }
96
+
97
+ const pendingRequests = new Map<string, PendingRequest>()
98
+
99
+ let _clientListenerStarted = false
100
+ async function startClientListener(worker?: Worker) {
101
+ if (_clientListenerStarted) return
102
+
103
+ if (!worker) {
104
+ const sw = await navigator.serviceWorker.ready
105
+ if (!sw?.active) {
106
+ throw new Error("[SWARPC Client] Service Worker is not active")
107
+ }
108
+
109
+ if (!navigator.serviceWorker.controller) {
110
+ console.warn("[SWARPC Client] Service Worker is not controlling the page")
111
+ }
112
+ }
113
+
114
+ const w = worker ?? navigator.serviceWorker
115
+ w.addEventListener("message", (event: MessageEvent) => {
116
+ const { functionName, requestId, ...data } = event.data || {}
117
+ if (!requestId) {
118
+ throw new Error("[SWARPC Client] Message received without requestId")
119
+ }
120
+ const handlers = pendingRequests.get(requestId)
121
+ if (!handlers) {
122
+ throw new Error(
123
+ `[SWARPC Client] ${requestId} has no active request handlers`
124
+ )
125
+ }
126
+
127
+ if ("error" in data) {
128
+ handlers.reject(new Error(data.error.message))
129
+ pendingRequests.delete(requestId)
130
+ } else if ("progress" in data) {
131
+ handlers.onProgress(data.progress)
132
+ } else if ("result" in data) {
133
+ handlers.resolve(data.result)
134
+ pendingRequests.delete(requestId)
135
+ }
136
+ })
137
+
138
+ _clientListenerStarted = true
139
+ }
140
+
141
+ export function Client<Procedures extends ProceduresMap>(
142
+ procedures: Procedures,
143
+ { worker }: { worker?: Worker } = {}
144
+ ): SwarpcClient<Procedures> {
145
+ const instance = { procedures } as Partial<SwarpcClient<Procedures>>
146
+
147
+ for (const functionName of Object.keys(procedures) as Array<
148
+ keyof Procedures
149
+ >) {
150
+ instance[functionName] = (async (input, onProgress = () => {}) => {
151
+ procedures[functionName].input.assert(input)
152
+ await startClientListener(worker)
153
+
154
+ const w =
155
+ worker ?? (await navigator.serviceWorker.ready.then((r) => r.active))
156
+
157
+ if (!w) {
158
+ throw new Error("[SWARPC Client] No active service worker found")
159
+ }
160
+
161
+ return new Promise((resolve, reject) => {
162
+ if (!worker && !navigator.serviceWorker.controller)
163
+ console.warn(
164
+ "[SWARPC Client] Service Worker is not controlling the page"
165
+ )
166
+
167
+ const requestId = generateRequestId()
168
+
169
+ pendingRequests.set(requestId, { resolve, onProgress, reject })
170
+
171
+ w.postMessage({ functionName, input, requestId })
172
+ })
173
+ }) as SwarpcClient<Procedures>[typeof functionName]
174
+ }
175
+
176
+ return instance as SwarpcClient<Procedures>
177
+ }
package/src/types.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { Type } from "arktype";
2
+
3
+ export type Procedure<I extends Type, P extends Type, S extends Type> = {
4
+ input: I;
5
+ progress: P;
6
+ success: S;
7
+ };
8
+
9
+ export type ProcedureImplementation<
10
+ I extends Type,
11
+ P extends Type,
12
+ S extends Type
13
+ > = (
14
+ input: I["inferOut"],
15
+ onProgress: (progress: P["inferOut"]) => void
16
+ ) => Promise<S["inferOut"]>;
17
+
18
+ export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>;
19
+
20
+ export type ClientMethod<P extends Procedure<Type, Type, Type>> = (
21
+ input: P["input"]["inferIn"],
22
+ onProgress?: (progress: P["progress"]["inferOut"]) => void
23
+ ) => Promise<P["success"]["inferOut"]>;
24
+
25
+ export type SwarpcClient<Procedures extends ProceduresMap> = {
26
+ procedures: Procedures;
27
+ } & {
28
+ [F in keyof Procedures]: ClientMethod<Procedures[F]>;
29
+ };
30
+
31
+ export type SwarpcServer<Procedures extends ProceduresMap> = {
32
+ procedures: Procedures;
33
+ implementations: {
34
+ [F in keyof Procedures]: ProcedureImplementation<
35
+ Procedures[F]["input"],
36
+ Procedures[F]["progress"],
37
+ Procedures[F]["success"]
38
+ >;
39
+ };
40
+ start(self: Window): void;
41
+ } & {
42
+ [F in keyof Procedures]: (
43
+ impl: ProcedureImplementation<
44
+ Procedures[F]["input"],
45
+ Procedures[F]["progress"],
46
+ Procedures[F]["success"]
47
+ >
48
+ ) => void;
49
+ };
package/dist/typings.d.ts DELETED
@@ -1,18 +0,0 @@
1
- export type Procedure<I extends Type, P extends Type, S extends Type> = {
2
- input: I;
3
- progress: P;
4
- success: S;
5
- };
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
- export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>;
8
- export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferOut"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>;
9
- export type SwarpcClient<Procedures extends ProceduresMap> = {
10
- procedures: Procedures;
11
- } & { [F in keyof Procedures]: ClientMethod<Procedures[F]>; };
12
- export type SwarpcServer<Procedures extends ProceduresMap> = {
13
- procedures: Procedures;
14
- implementations: { [F in keyof Procedures]: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>; };
15
- start: (self: Window) => void;
16
- } & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>>) => void; };
17
- import type { Type } from 'arktype';
18
- //# sourceMappingURL=typings.d.ts.map
@@ -1 +0,0 @@
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,UAAU,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;yBAIhH,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/src/swarpc.js DELETED
@@ -1,126 +0,0 @@
1
- import { type } from "arktype"
2
-
3
- /**
4
- * @import { ProceduresMap, SwarpcClient, SwarpcServer } from './typings.js'
5
- */
6
-
7
- /**
8
- * @template {ProceduresMap} Procedures
9
- * @param {Procedures} procedures
10
- * @returns {SwarpcServer<Procedures>}
11
- */
12
- export function Server(procedures) {
13
- /** @type {SwarpcServer<Procedures>} */
14
- // @ts-expect-error
15
- const instance = {
16
- procedures,
17
- implementations: {},
18
- }
19
-
20
- for (const functionName in procedures) {
21
- instance[functionName] = (
22
- /**
23
- * @type {ProcedureImplementation<ProceduresMap[typeof functionName]['input'], ProceduresMap[typeof functionName]['progress'], ProceduresMap[typeof functionName]['success']>}
24
- */
25
- implementation
26
- ) => {
27
- if (!instance.procedures[functionName]) {
28
- throw new Error(`No procedure found for function name: ${functionName}`)
29
- }
30
-
31
- instance.implementations[functionName] = implementation
32
- }
33
- }
34
-
35
- const PayloadSchema = type.or(
36
- ...Object.entries(procedures).map(([functionName, { input }]) => ({
37
- functionName: type(`"${functionName}"`),
38
- input,
39
- }))
40
- )
41
-
42
- /**
43
- * Starts the message event handler. Needs to be called within the Service Worker context.
44
- * @param {Window} self
45
- */
46
- instance.start = (self) => {
47
- /**
48
- * @param {{functionName: string} & Partial<{ result: any; error: any; progress: any }>} data
49
- */
50
- const postMessage = async (data) => {
51
- await self.clients
52
- .matchAll()
53
- .then((clients) =>
54
- clients.forEach((client) => client.postMessage(data))
55
- )
56
- }
57
-
58
- console.log("[SWARPC Server] Starting message listener on", self)
59
-
60
- self.addEventListener("message", async (event) => {
61
- const { functionName, input } = PayloadSchema.assert(event.data)
62
- console.log("[SWARPC Server] Running", functionName, "with", input)
63
-
64
- /**
65
- * @param {*} error
66
- */
67
- const postError = async (error) =>
68
- postMessage({
69
- functionName,
70
- error: {
71
- message: "message" in error ? error.message : String(error),
72
- },
73
- })
74
-
75
- const implementation = instance.implementations[functionName]
76
- if (!implementation) {
77
- await postError("No implementation found")
78
- return
79
- }
80
-
81
- await implementation(input, async (progress) =>
82
- postMessage({ functionName, progress })
83
- )
84
- .catch(async (error) => postError(error))
85
- .then(async (result) => postMessage({ functionName, result }))
86
- })
87
- }
88
-
89
- return instance
90
- }
91
-
92
- /**
93
- * @template {ProceduresMap} Procedures
94
- * @param {Procedures} procedures
95
- * @returns {SwarpcClient<Procedures>}
96
- */
97
- export function Client(procedures) {
98
- /** @type {SwarpcClient<Procedures>} */
99
- // @ts-expect-error
100
- const instance = { procedures }
101
-
102
- for (const functionName of Object.keys(procedures)) {
103
- instance[functionName] = async (input, onProgress = () => {}) => {
104
- procedures[functionName].input.assert(input)
105
- console.log("[SWARPC Client] Calling", functionName, "with", input)
106
- navigator.serviceWorker.controller?.postMessage({ functionName, input })
107
- return new Promise((resolve, reject) => {
108
- navigator.serviceWorker.addEventListener("message", (event) => {
109
- const { functionName: fn, ...data } = event.data
110
-
111
- if (fn !== functionName) return
112
-
113
- if ("error" in data) {
114
- reject(new Error(data.error.message))
115
- } else if ("progress" in data) {
116
- onProgress(data.progress)
117
- } else if ("result" in data) {
118
- resolve(data.result)
119
- }
120
- })
121
- })
122
- }
123
- }
124
-
125
- return instance
126
- }
package/src/typings.js DELETED
@@ -1,42 +0,0 @@
1
- /**
2
- * @import { Type } from 'arktype';
3
- */
4
-
5
- /**
6
- * @template {Type} I
7
- * @template {Type} P
8
- * @template {Type} S
9
- * @typedef {Object} Procedure
10
- * @property {I} input
11
- * @property {P} progress
12
- * @property {S} success
13
- */
14
-
15
- /**
16
- * @template {Type} I
17
- * @template {Type} P
18
- * @template {Type} S
19
- * @typedef {(input: I['inferOut'], onProgress: (progress: P['inferOut']) => void) => Promise<NoInfer<S>['inferOut'] | NoInfer<S>['inferOut']>} ProcedureImplementation
20
- */
21
-
22
- /**
23
- * @typedef {Record<string, Procedure<Type, Type, Type>>} ProceduresMap
24
- */
25
-
26
- /**
27
- * @template {Procedure<Type, Type, Type>} P
28
- * @typedef {(input: P['input']['inferOut'], onProgress?: (progress: P['progress']['inferOut']) => void) => Promise<P['success']['inferOut']>} ClientMethod
29
- */
30
-
31
- /**
32
- * @template {ProceduresMap} Procedures
33
- * @typedef {{ procedures: Procedures } & { [F in keyof Procedures]: ClientMethod<Procedures[F]> }} SwarpcClient
34
- */
35
-
36
- /**
37
- * @template {ProceduresMap} Procedures
38
- * @typedef {{ procedures: Procedures, implementations: {[F in keyof Procedures]: ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success']> }, start: (self: Window) => void } & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success'] >>) => void }} SwarpcServer
39
- */
40
-
41
- // Required, otherwise nothing can be imported from this file
42
- export {}