swarpc 0.6.1 → 0.7.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 +44 -0
- package/dist/client.d.ts +22 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +159 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/log.d.ts +20 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +45 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +132 -0
- package/dist/types.d.ts +153 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +20 -0
- package/package.json +11 -7
- package/src/client.ts +245 -0
- package/src/index.ts +3 -0
- package/src/log.ts +62 -0
- package/src/server.ts +193 -0
- package/src/types.ts +66 -12
- package/dist/swarpc.d.ts +0 -25
- package/dist/swarpc.d.ts.map +0 -1
- package/dist/swarpc.js +0 -264
- package/src/swarpc.ts +0 -359
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Type } from "arktype";
|
|
2
2
|
/**
|
|
3
3
|
* A procedure declaration
|
|
4
4
|
*/
|
|
@@ -28,10 +28,27 @@ export type Procedure<I extends Type, P extends Type, S extends Type> = {
|
|
|
28
28
|
*/
|
|
29
29
|
autotransfer?: "always" | "never" | "output-only";
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* A promise that you can cancel by calling `.cancel(reason)` on it:
|
|
33
|
+
*
|
|
34
|
+
* ```js
|
|
35
|
+
* const { request, cancel } = client.runProcedure.cancelable(input, onProgress)
|
|
36
|
+
* setTimeout(() => cancel("Cancelled by user"), 1000)
|
|
37
|
+
* const result = await request
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export type CancelablePromise<T> = {
|
|
41
|
+
request: Promise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Abort the request.
|
|
44
|
+
* @param reason The reason for cancelling the request.
|
|
45
|
+
*/
|
|
46
|
+
cancel: (reason: string) => Promise<void>;
|
|
47
|
+
};
|
|
31
48
|
/**
|
|
32
49
|
* An implementation of a procedure
|
|
33
50
|
*/
|
|
34
|
-
export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferIn"]) => void) => Promise<S["inferIn"]>;
|
|
51
|
+
export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferIn"]) => void, abortSignal?: AbortSignal) => Promise<S["inferIn"]>;
|
|
35
52
|
/**
|
|
36
53
|
* Declarations of procedures by name.
|
|
37
54
|
*/
|
|
@@ -59,31 +76,158 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
59
76
|
*/
|
|
60
77
|
progress?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Procedures[Procedure]["progress"]["inferOut"]) => void;
|
|
61
78
|
};
|
|
79
|
+
export declare const PayloadHeaderSchema: import("arktype").Generic<[["Name", string]], {
|
|
80
|
+
readonly by: "\"sw&rpc\"";
|
|
81
|
+
readonly functionName: "Name";
|
|
82
|
+
readonly requestId: "string >= 1";
|
|
83
|
+
}, {}, {}>;
|
|
62
84
|
export type PayloadHeader<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
|
|
63
85
|
by: "sw&rpc";
|
|
64
86
|
functionName: Name & string;
|
|
65
87
|
requestId: string;
|
|
66
|
-
autotransfer: PM[Name]["autotransfer"];
|
|
67
88
|
};
|
|
89
|
+
export declare const PayloadCoreSchema: import("arktype").Generic<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
90
|
+
readonly "input?": "I";
|
|
91
|
+
readonly "progress?": "P";
|
|
92
|
+
readonly "result?": "S";
|
|
93
|
+
readonly "abort?": {
|
|
94
|
+
readonly reason: "string";
|
|
95
|
+
};
|
|
96
|
+
readonly "error?": {
|
|
97
|
+
readonly message: "string";
|
|
98
|
+
};
|
|
99
|
+
}, {}, {}>;
|
|
68
100
|
export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = {
|
|
69
101
|
input: PM[Name]["input"]["inferOut"];
|
|
70
102
|
} | {
|
|
71
103
|
progress: PM[Name]["progress"]["inferOut"];
|
|
72
104
|
} | {
|
|
73
105
|
result: PM[Name]["success"]["inferOut"];
|
|
106
|
+
} | {
|
|
107
|
+
abort: {
|
|
108
|
+
reason: string;
|
|
109
|
+
};
|
|
74
110
|
} | {
|
|
75
111
|
error: {
|
|
76
112
|
message: string;
|
|
77
113
|
};
|
|
78
114
|
};
|
|
115
|
+
export declare const PayloadSchema: import("arktype").Generic<[["Name", string], ["I", unknown], ["P", unknown], ["S", unknown]], readonly ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"], {
|
|
116
|
+
PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
117
|
+
readonly "input?": "I";
|
|
118
|
+
readonly "progress?": "P";
|
|
119
|
+
readonly "result?": "S";
|
|
120
|
+
readonly "abort?": {
|
|
121
|
+
readonly reason: "string";
|
|
122
|
+
};
|
|
123
|
+
readonly "error?": {
|
|
124
|
+
readonly message: "string";
|
|
125
|
+
};
|
|
126
|
+
}, {}, {}>, {
|
|
127
|
+
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
128
|
+
readonly "input?": "I";
|
|
129
|
+
readonly "progress?": "P";
|
|
130
|
+
readonly "result?": "S";
|
|
131
|
+
readonly "abort?": {
|
|
132
|
+
readonly reason: "string";
|
|
133
|
+
};
|
|
134
|
+
readonly "error?": {
|
|
135
|
+
readonly message: "string";
|
|
136
|
+
};
|
|
137
|
+
}, {}, {}>;
|
|
138
|
+
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
139
|
+
readonly by: "\"sw&rpc\"";
|
|
140
|
+
readonly functionName: "Name";
|
|
141
|
+
readonly requestId: "string >= 1";
|
|
142
|
+
}, {}, {}>;
|
|
143
|
+
} & {}>;
|
|
144
|
+
PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
|
|
145
|
+
readonly by: "\"sw&rpc\"";
|
|
146
|
+
readonly functionName: "Name";
|
|
147
|
+
readonly requestId: "string >= 1";
|
|
148
|
+
}, {}, {}>, {
|
|
149
|
+
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
150
|
+
readonly "input?": "I";
|
|
151
|
+
readonly "progress?": "P";
|
|
152
|
+
readonly "result?": "S";
|
|
153
|
+
readonly "abort?": {
|
|
154
|
+
readonly reason: "string";
|
|
155
|
+
};
|
|
156
|
+
readonly "error?": {
|
|
157
|
+
readonly message: "string";
|
|
158
|
+
};
|
|
159
|
+
}, {}, {}>;
|
|
160
|
+
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
161
|
+
readonly by: "\"sw&rpc\"";
|
|
162
|
+
readonly functionName: "Name";
|
|
163
|
+
readonly requestId: "string >= 1";
|
|
164
|
+
}, {}, {}>;
|
|
165
|
+
} & {}>;
|
|
166
|
+
}, {
|
|
167
|
+
PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
168
|
+
readonly "input?": "I";
|
|
169
|
+
readonly "progress?": "P";
|
|
170
|
+
readonly "result?": "S";
|
|
171
|
+
readonly "abort?": {
|
|
172
|
+
readonly reason: "string";
|
|
173
|
+
};
|
|
174
|
+
readonly "error?": {
|
|
175
|
+
readonly message: "string";
|
|
176
|
+
};
|
|
177
|
+
}, {}, {}>, {
|
|
178
|
+
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
179
|
+
readonly "input?": "I";
|
|
180
|
+
readonly "progress?": "P";
|
|
181
|
+
readonly "result?": "S";
|
|
182
|
+
readonly "abort?": {
|
|
183
|
+
readonly reason: "string";
|
|
184
|
+
};
|
|
185
|
+
readonly "error?": {
|
|
186
|
+
readonly message: "string";
|
|
187
|
+
};
|
|
188
|
+
}, {}, {}>;
|
|
189
|
+
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
190
|
+
readonly by: "\"sw&rpc\"";
|
|
191
|
+
readonly functionName: "Name";
|
|
192
|
+
readonly requestId: "string >= 1";
|
|
193
|
+
}, {}, {}>;
|
|
194
|
+
} & {}>;
|
|
195
|
+
PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
|
|
196
|
+
readonly by: "\"sw&rpc\"";
|
|
197
|
+
readonly functionName: "Name";
|
|
198
|
+
readonly requestId: "string >= 1";
|
|
199
|
+
}, {}, {}>, {
|
|
200
|
+
PayloadCoreSchema: import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
201
|
+
readonly "input?": "I";
|
|
202
|
+
readonly "progress?": "P";
|
|
203
|
+
readonly "result?": "S";
|
|
204
|
+
readonly "abort?": {
|
|
205
|
+
readonly reason: "string";
|
|
206
|
+
};
|
|
207
|
+
readonly "error?": {
|
|
208
|
+
readonly message: "string";
|
|
209
|
+
};
|
|
210
|
+
}, {}, {}>;
|
|
211
|
+
PayloadHeaderSchema: import("@ark/schema").GenericAst<[["Name", string]], {
|
|
212
|
+
readonly by: "\"sw&rpc\"";
|
|
213
|
+
readonly functionName: "Name";
|
|
214
|
+
readonly requestId: "string >= 1";
|
|
215
|
+
}, {}, {}>;
|
|
216
|
+
} & {}>;
|
|
217
|
+
}>;
|
|
79
218
|
/**
|
|
80
219
|
* The effective payload as sent by the server to the client
|
|
81
220
|
*/
|
|
82
221
|
export type Payload<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = PayloadHeader<PM, Name> & PayloadCore<PM, Name>;
|
|
83
222
|
/**
|
|
84
|
-
* A procedure's corresponding method on the client instance -- used to call the procedure
|
|
223
|
+
* A procedure's corresponding method on the client instance -- used to call the procedure. If you want to be able to cancel the request, you can use the `cancelable` method instead of running the procedure directly.
|
|
85
224
|
*/
|
|
86
|
-
export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]
|
|
225
|
+
export type ClientMethod<P extends Procedure<Type, Type, Type>> = ((input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>) & {
|
|
226
|
+
/**
|
|
227
|
+
* A method that returns a `CancelablePromise`. Cancel it by calling `.cancel(reason)` on it, and wait for the request to resolve by awaiting the `request` property on the returned object.
|
|
228
|
+
*/
|
|
229
|
+
cancelable: (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void, requestId?: string) => CancelablePromise<P["success"]["inferOut"]>;
|
|
230
|
+
};
|
|
87
231
|
/**
|
|
88
232
|
* Symbol used as the key for the procedures map on the server instance
|
|
89
233
|
*/
|
|
@@ -93,7 +237,9 @@ export declare const zImplementations: unique symbol;
|
|
|
93
237
|
*/
|
|
94
238
|
export declare const zProcedures: unique symbol;
|
|
95
239
|
/**
|
|
96
|
-
* The sw&rpc client instance, which provides methods to call procedures
|
|
240
|
+
* The sw&rpc client instance, which provides methods to call procedures.
|
|
241
|
+
* Each property of the procedures map will be a method, that accepts an input, an optional onProgress callback and an optional request ID.
|
|
242
|
+
* If you want to be able to cancel the request, you can set the request's ID yourself, and call `.abort(requestId, reason)` on the client instance to cancel it.
|
|
97
243
|
*/
|
|
98
244
|
export type SwarpcClient<Procedures extends ProceduresMap> = {
|
|
99
245
|
[zProcedures]: Procedures;
|
|
@@ -107,7 +253,7 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
|
|
|
107
253
|
export type SwarpcServer<Procedures extends ProceduresMap> = {
|
|
108
254
|
[zProcedures]: Procedures;
|
|
109
255
|
[zImplementations]: ImplementationsMap<Procedures>;
|
|
110
|
-
start(self: Window): void;
|
|
256
|
+
start(self: Window | Worker): void;
|
|
111
257
|
} & {
|
|
112
258
|
[F in keyof Procedures]: (impl: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>) => void;
|
|
113
259
|
};
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,IAAI,EAAE,MAAM,SAAS,CAAA;AAEzC;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,IAAI,IAAI;IACtE;;OAEG;IACH,KAAK,EAAE,CAAC,CAAA;IACR;;;OAGG;IACH,QAAQ,EAAE,CAAC,CAAA;IACX;;OAEG;IACH,OAAO,EAAE,CAAC,CAAA;IACV;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAA;CAClD,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI;IACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACnB;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1C,CAAA;AAED;;GAEG;AACH,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,SAAS,CAAC,KAAK,IAAI,EAC5C,WAAW,CAAC,EAAE,WAAW,KACtB,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;AAE1B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAEvE;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,UAAU,SAAS,aAAa,IAAI;KAChE,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;CACF,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,KAAK,CAAC,UAAU,SAAS,aAAa,IAAI;IACpD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,aAAa,EAC9C,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,KAC/C,IAAI,CAAA;IACT;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,aAAa,EAC5C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,KACT,IAAI,CAAA;IACT;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,SAAS,SAAS,MAAM,aAAa,EAC/C,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAChD,IAAI,CAAA;CACV,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;UAI9B,CAAA;AAEF,MAAM,MAAM,aAAa,CACvB,EAAE,SAAS,aAAa,EACxB,IAAI,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,IAC9B;IACF,EAAE,EAAE,QAAQ,CAAA;IACZ,YAAY,EAAE,IAAI,GAAG,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;UAM5B,CAAA;AAEF,MAAM,MAAM,WAAW,CACrB,EAAE,SAAS,aAAa,EACxB,IAAI,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,IAE9B;IACE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAA;CACrC,GACD;IACE,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,CAAA;CAC3C,GACD;IACE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAA;CACxC,GACD;IACE,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC1B,GACD;IACE,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3B,CAAA;AAEL,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMtB,CAAA;AAEJ;;GAEG;AACH,MAAM,MAAM,OAAO,CACjB,EAAE,SAAS,aAAa,EACxB,IAAI,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,IAC9B,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;AAEnD;;GAEG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CACjE,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,GAAG;IACxC;;OAEG;IACH,UAAU,EAAE,CACV,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,EAC1D,SAAS,CAAC,EAAE,MAAM,KACf,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;CACjD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,eAAmC,CAAA;AAChE;;GAEG;AACH,eAAO,MAAM,WAAW,eAA8B,CAAA;AAEtD;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;CAC1B,GAAG;KACD,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;CACrD,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,UAAU,SAAS,aAAa,IAAI;IAC3D,CAAC,WAAW,CAAC,EAAE,UAAU,CAAA;IACzB,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;IAClD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CACnC,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,CAAA"}
|
package/dist/types.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
import { type } from "arktype";
|
|
2
|
+
export const PayloadHeaderSchema = type("<Name extends string>", {
|
|
3
|
+
by: '"sw&rpc"',
|
|
4
|
+
functionName: "Name",
|
|
5
|
+
requestId: "string >= 1",
|
|
6
|
+
});
|
|
7
|
+
export const PayloadCoreSchema = type("<I, P, S>", {
|
|
8
|
+
"input?": "I",
|
|
9
|
+
"progress?": "P",
|
|
10
|
+
"result?": "S",
|
|
11
|
+
"abort?": { reason: "string" },
|
|
12
|
+
"error?": { message: "string" },
|
|
13
|
+
});
|
|
14
|
+
export const PayloadSchema = type
|
|
15
|
+
.scope({ PayloadCoreSchema, PayloadHeaderSchema })
|
|
16
|
+
.type("<Name extends string, I, P, S>", [
|
|
17
|
+
"PayloadHeaderSchema<Name>",
|
|
18
|
+
"&",
|
|
19
|
+
"PayloadCoreSchema<I, P, S>",
|
|
20
|
+
]);
|
|
1
21
|
/**
|
|
2
22
|
* Symbol used as the key for the procedures map on the server instance
|
|
3
23
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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,22 +24,26 @@
|
|
|
24
24
|
"dist",
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
|
-
"main": "dist/
|
|
28
|
-
"types": "dist/
|
|
27
|
+
"main": "dist/index.js",
|
|
28
|
+
"types": "dist/index.d.ts",
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc",
|
|
31
31
|
"dev": "tsc --watch",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"typedoc": "typedoc src/index.ts src/types.ts --readme README.md",
|
|
34
35
|
"version": "kacl release && prettier -w CHANGELOG.md && git add CHANGELOG.md"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"arktype": "^2.1.20"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
41
|
+
"@vitest/web-worker": "^3.2.4",
|
|
40
42
|
"kacl": "^1.1.1",
|
|
41
43
|
"prettier": "^3.6.2",
|
|
42
|
-
"typedoc": "^0.28.
|
|
43
|
-
"typescript": "^5.
|
|
44
|
+
"typedoc": "^0.28.9",
|
|
45
|
+
"typescript": "^5.9.2",
|
|
46
|
+
"vite": "^7.0.6",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
44
48
|
}
|
|
45
49
|
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { createLogger, type Logger, type LogLevel } from "./log.js"
|
|
2
|
+
import {
|
|
3
|
+
Hooks,
|
|
4
|
+
Payload,
|
|
5
|
+
PayloadCore,
|
|
6
|
+
zProcedures,
|
|
7
|
+
type ProceduresMap,
|
|
8
|
+
type SwarpcClient
|
|
9
|
+
} from "./types.js"
|
|
10
|
+
import { findTransferables } from "./utils.js"
|
|
11
|
+
|
|
12
|
+
export type { SwarpcClient } from "./types.js"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pending requests are stored in a map, where the key is the request ID.
|
|
16
|
+
* Each request has a set of handlers: resolve, reject, and onProgress.
|
|
17
|
+
* This allows having a single listener for the client, and having multiple in-flight calls to the same procedure.
|
|
18
|
+
*/
|
|
19
|
+
const pendingRequests = new Map<string, PendingRequest>()
|
|
20
|
+
type PendingRequest = {
|
|
21
|
+
functionName: string
|
|
22
|
+
reject: (err: Error) => void
|
|
23
|
+
onProgress: (progress: any) => void
|
|
24
|
+
resolve: (result: any) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Have we started the client listener?
|
|
28
|
+
let _clientListenerStarted = false
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param procedures procedures the client will be able to call
|
|
33
|
+
* @param options various options
|
|
34
|
+
* @param options.worker if provided, the client will use this worker to post messages.
|
|
35
|
+
* @param options.hooks hooks to run on messages received from the server
|
|
36
|
+
* @returns a sw&rpc client instance. Each property of the procedures map will be a method, that accepts an input and an optional onProgress callback.
|
|
37
|
+
*/
|
|
38
|
+
export function Client<Procedures extends ProceduresMap>(
|
|
39
|
+
procedures: Procedures,
|
|
40
|
+
{
|
|
41
|
+
worker,
|
|
42
|
+
loglevel = "debug",
|
|
43
|
+
hooks = {},
|
|
44
|
+
}: { worker?: Worker; hooks?: Hooks<Procedures>; loglevel?: LogLevel } = {}
|
|
45
|
+
): SwarpcClient<Procedures> {
|
|
46
|
+
const l = createLogger("client", loglevel)
|
|
47
|
+
|
|
48
|
+
// Store procedures on a symbol key, to avoid conflicts with procedure names
|
|
49
|
+
const instance = { [zProcedures]: procedures } as Partial<
|
|
50
|
+
SwarpcClient<Procedures>
|
|
51
|
+
>
|
|
52
|
+
|
|
53
|
+
for (const functionName of Object.keys(procedures) as Array<
|
|
54
|
+
keyof Procedures
|
|
55
|
+
>) {
|
|
56
|
+
if (typeof functionName !== "string") {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[SWARPC Client] Invalid function name, don't use symbols`
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const send = async (
|
|
63
|
+
requestId: string,
|
|
64
|
+
msg: PayloadCore<Procedures, typeof functionName>,
|
|
65
|
+
options?: StructuredSerializeOptions
|
|
66
|
+
) => {
|
|
67
|
+
return postMessage(
|
|
68
|
+
l,
|
|
69
|
+
worker,
|
|
70
|
+
hooks,
|
|
71
|
+
{
|
|
72
|
+
...msg,
|
|
73
|
+
by: "sw&rpc",
|
|
74
|
+
requestId,
|
|
75
|
+
functionName,
|
|
76
|
+
},
|
|
77
|
+
options
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Set the method on the instance
|
|
82
|
+
const _runProcedure = async (
|
|
83
|
+
input: unknown,
|
|
84
|
+
onProgress: (progress: unknown) => void | Promise<void> = () => {},
|
|
85
|
+
reqid?: string
|
|
86
|
+
) => {
|
|
87
|
+
// Validate the input against the procedure's input schema
|
|
88
|
+
procedures[functionName].input.assert(input)
|
|
89
|
+
|
|
90
|
+
const requestId = reqid ?? makeRequestId()
|
|
91
|
+
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
// Store promise handlers (as well as progress updates handler)
|
|
94
|
+
// so the client listener can resolve/reject the promise (and react to progress updates)
|
|
95
|
+
// when the server sends messages back
|
|
96
|
+
pendingRequests.set(requestId, {
|
|
97
|
+
functionName,
|
|
98
|
+
resolve,
|
|
99
|
+
onProgress,
|
|
100
|
+
reject,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const transfer =
|
|
104
|
+
procedures[functionName].autotransfer === "always"
|
|
105
|
+
? findTransferables(input)
|
|
106
|
+
: []
|
|
107
|
+
|
|
108
|
+
// Post the message to the server
|
|
109
|
+
l.debug(requestId, `Requesting ${functionName} with`, input)
|
|
110
|
+
send(requestId, { input }, { transfer })
|
|
111
|
+
.then(() => {})
|
|
112
|
+
.catch(reject)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// @ts-expect-error
|
|
117
|
+
instance[functionName] = _runProcedure
|
|
118
|
+
instance[functionName]!.cancelable = (input, onProgress) => {
|
|
119
|
+
const requestId = makeRequestId()
|
|
120
|
+
return {
|
|
121
|
+
request: _runProcedure(input, onProgress, requestId),
|
|
122
|
+
async cancel(reason: string) {
|
|
123
|
+
if (!pendingRequests.has(requestId)) {
|
|
124
|
+
l.warn(
|
|
125
|
+
requestId,
|
|
126
|
+
`Cannot cancel ${functionName} request, it has already been resolved or rejected`
|
|
127
|
+
)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
l.debug(requestId, `Cancelling ${functionName} with`, reason)
|
|
132
|
+
await send(requestId, { abort: { reason } })
|
|
133
|
+
pendingRequests.delete(requestId)
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return instance as SwarpcClient<Procedures>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Warms up the client by starting the listener and getting the worker, then posts a message to the worker.
|
|
144
|
+
* @returns the worker to use
|
|
145
|
+
*/
|
|
146
|
+
async function postMessage<Procedures extends ProceduresMap>(
|
|
147
|
+
l: Logger,
|
|
148
|
+
worker: Worker | undefined,
|
|
149
|
+
hooks: Hooks<Procedures>,
|
|
150
|
+
message: Payload<Procedures>,
|
|
151
|
+
options?: StructuredSerializeOptions
|
|
152
|
+
) {
|
|
153
|
+
await startClientListener(l, worker, hooks)
|
|
154
|
+
|
|
155
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
156
|
+
l.warn("", "Service Worker is not controlling the page")
|
|
157
|
+
|
|
158
|
+
// If no worker is provided, we use the service worker
|
|
159
|
+
const w =
|
|
160
|
+
worker ?? (await navigator.serviceWorker.ready.then((r) => r.active))
|
|
161
|
+
|
|
162
|
+
if (!w) {
|
|
163
|
+
throw new Error("[SWARPC Client] No active service worker found")
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
w.postMessage(message, options)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Starts the client listener, which listens for messages from the sw&rpc server.
|
|
171
|
+
* @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
|
|
172
|
+
* @returns
|
|
173
|
+
*/
|
|
174
|
+
async function startClientListener<Procedures extends ProceduresMap>(
|
|
175
|
+
l: Logger,
|
|
176
|
+
worker?: Worker,
|
|
177
|
+
hooks: Hooks<Procedures> = {}
|
|
178
|
+
) {
|
|
179
|
+
if (_clientListenerStarted) return
|
|
180
|
+
|
|
181
|
+
// Get service worker registration if no worker is provided
|
|
182
|
+
if (!worker) {
|
|
183
|
+
const sw = await navigator.serviceWorker.ready
|
|
184
|
+
if (!sw?.active) {
|
|
185
|
+
throw new Error("[SWARPC Client] Service Worker is not active")
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!navigator.serviceWorker.controller) {
|
|
189
|
+
l.warn("", "Service Worker is not controlling the page")
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const w = worker ?? navigator.serviceWorker
|
|
194
|
+
|
|
195
|
+
// Start listening for messages
|
|
196
|
+
l.debug("", "Starting client listener on", w)
|
|
197
|
+
w.addEventListener("message", (event) => {
|
|
198
|
+
// Get the data from the event
|
|
199
|
+
const eventData = (event as MessageEvent).data || {}
|
|
200
|
+
|
|
201
|
+
// Ignore other messages that aren't for us
|
|
202
|
+
if (eventData?.by !== "sw&rpc") return
|
|
203
|
+
|
|
204
|
+
// We don't use a arktype schema here, we trust the server to send valid data
|
|
205
|
+
const { requestId, ...data } = eventData as Payload<Procedures>
|
|
206
|
+
|
|
207
|
+
// Sanity check in case we somehow receive a message without requestId
|
|
208
|
+
if (!requestId) {
|
|
209
|
+
throw new Error("[SWARPC Client] Message received without requestId")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Get the associated pending request handlers
|
|
213
|
+
const handlers = pendingRequests.get(requestId)
|
|
214
|
+
if (!handlers) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`[SWARPC Client] ${requestId} has no active request handlers`
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// React to the data received: call hook, call handler,
|
|
221
|
+
// and remove the request from pendingRequests (unless it's a progress update)
|
|
222
|
+
if ("error" in data) {
|
|
223
|
+
hooks.error?.(data.functionName, new Error(data.error.message))
|
|
224
|
+
handlers.reject(new Error(data.error.message))
|
|
225
|
+
pendingRequests.delete(requestId)
|
|
226
|
+
} else if ("progress" in data) {
|
|
227
|
+
hooks.progress?.(data.functionName, data.progress)
|
|
228
|
+
handlers.onProgress(data.progress)
|
|
229
|
+
} else if ("result" in data) {
|
|
230
|
+
hooks.success?.(data.functionName, data.result)
|
|
231
|
+
handlers.resolve(data.result)
|
|
232
|
+
pendingRequests.delete(requestId)
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
_clientListenerStarted = true
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Generate a random request ID, used to identify requests between client and server.
|
|
241
|
+
* @returns a 6-character hexadecimal string
|
|
242
|
+
*/
|
|
243
|
+
export function makeRequestId(): string {
|
|
244
|
+
return Math.random().toString(16).substring(2, 8).toUpperCase()
|
|
245
|
+
}
|
package/src/index.ts
ADDED
package/src/log.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function createLogger(
|
|
2
|
+
side: "server" | "client",
|
|
3
|
+
level: LogLevel = "debug"
|
|
4
|
+
) {
|
|
5
|
+
const enabledLevels = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level))
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
debug: enabledLevels.includes("debug") ? logger("debug", side) : () => {},
|
|
9
|
+
info: enabledLevels.includes("info") ? logger("info", side) : () => {},
|
|
10
|
+
warn: enabledLevels.includes("warn") ? logger("warn", side) : () => {},
|
|
11
|
+
error: enabledLevels.includes("error") ? logger("error", side) : () => {},
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type Logger = ReturnType<typeof createLogger>
|
|
16
|
+
|
|
17
|
+
const LOG_LEVELS = ["debug", "info", "warn", "error"] as const
|
|
18
|
+
export type LogLevel = (typeof LOG_LEVELS)[number]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates partially-applied logging functions given the first 2 args
|
|
22
|
+
* @param severity
|
|
23
|
+
* @param side
|
|
24
|
+
* @returns
|
|
25
|
+
*/
|
|
26
|
+
function logger(severity: LogLevel, side: "server" | "client") {
|
|
27
|
+
return (rqid: string | null, message: string, ...args: any[]) =>
|
|
28
|
+
log(severity, side, rqid, message, ...args)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Send log messages to the console, with a helpful prefix.
|
|
33
|
+
* @param severity
|
|
34
|
+
* @param side
|
|
35
|
+
* @param rqid request ID
|
|
36
|
+
* @param message
|
|
37
|
+
* @param args passed to console methods directly
|
|
38
|
+
*/
|
|
39
|
+
export function log(
|
|
40
|
+
severity: "debug" | "info" | "warn" | "error",
|
|
41
|
+
side: "server" | "client",
|
|
42
|
+
rqid: string | null,
|
|
43
|
+
message: string,
|
|
44
|
+
...args: any[]
|
|
45
|
+
) {
|
|
46
|
+
const prefix =
|
|
47
|
+
"[" +
|
|
48
|
+
["SWARPC", side, rqid ? `%c${rqid}%c` : ""].filter(Boolean).join(" ") +
|
|
49
|
+
"]"
|
|
50
|
+
|
|
51
|
+
const prefixStyles = rqid ? ["color: cyan;", "color: inherit;"] : []
|
|
52
|
+
|
|
53
|
+
if (severity === "debug") {
|
|
54
|
+
console.debug(prefix, ...prefixStyles, message, ...args)
|
|
55
|
+
} else if (severity === "info") {
|
|
56
|
+
console.info(prefix, ...prefixStyles, message, ...args)
|
|
57
|
+
} else if (severity === "warn") {
|
|
58
|
+
console.warn(prefix, ...prefixStyles, message, ...args)
|
|
59
|
+
} else if (severity === "error") {
|
|
60
|
+
console.error(prefix, ...prefixStyles, message, ...args)
|
|
61
|
+
}
|
|
62
|
+
}
|