@k13engineering/yajrpc 0.0.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.
@@ -0,0 +1,524 @@
1
+ /*import type { TJsonRpcError, TJsonRpcMessage, TJsonRpcNotification, TJsonRpcOptionalId, TJsonRpcParameters, TJsonRpcRequest, TJsonRpcResponse } from "./types.ts";*/
2
+
3
+ /*type TCoerceParamsResult = {
4
+ error: Error;
5
+ params: undefined;
6
+ } | {
7
+ error: undefined;
8
+ params: TJsonRpcParameters | undefined;
9
+ };*/
10
+
11
+ const coerceParams = ({ params }/*: { params: unknown }*/)/*: TCoerceParamsResult*/ => {
12
+ if (params !== undefined && (typeof params !== "object" || params === null)) {
13
+ return {
14
+ error: Error("invalid params"),
15
+ params: undefined
16
+ };
17
+ }
18
+
19
+ return {
20
+ error: undefined,
21
+ params: params /*as TJsonRpcParameters | undefined*/
22
+ };
23
+ };
24
+
25
+ /*type TCoerceErrorResult = {
26
+ error: Error;
27
+ errorField: undefined;
28
+ } | {
29
+ error: undefined;
30
+ errorField: TJsonRpcError | undefined;
31
+ };*/
32
+
33
+ // eslint-disable-next-line complexity
34
+ const coerceErrorField = ({ error }/*: { error: unknown }*/)/*: TCoerceErrorResult*/ => {
35
+ if (error === undefined) {
36
+ return {
37
+ error: undefined,
38
+ errorField: undefined
39
+ };
40
+ }
41
+
42
+ if (typeof error !== "object" || error === null) {
43
+ return {
44
+ error: Error("invalid error field"),
45
+ errorField: undefined
46
+ };
47
+ }
48
+
49
+ const errorObj = error /*as Record<string, unknown>*/;
50
+
51
+ if (typeof errorObj.code !== "number") {
52
+ return {
53
+ error: Error("invalid field type for error.code"),
54
+ errorField: undefined
55
+ };
56
+ }
57
+
58
+ if (!Number.isInteger(errorObj.code)) {
59
+ return {
60
+ error: Error("error.code is not an integer"),
61
+ errorField: undefined
62
+ };
63
+ }
64
+
65
+ if (typeof errorObj.message !== "string") {
66
+ return {
67
+ error: Error("invalid field type for error.message"),
68
+ errorField: undefined
69
+ };
70
+ }
71
+
72
+ return {
73
+ error: undefined,
74
+ errorField: {
75
+ code: errorObj.code,
76
+ message: errorObj.message,
77
+ data: errorObj.data
78
+ }
79
+ };
80
+ };
81
+
82
+ /*type TCoerceIdResult = {
83
+ error: Error;
84
+ id: undefined;
85
+ } | {
86
+ error: undefined;
87
+ id: TJsonRpcOptionalId | undefined;
88
+ };*/
89
+
90
+ // eslint-disable-next-line complexity
91
+ const coerceId = ({ id }/*: { id: unknown }*/)/*: TCoerceIdResult*/ => {
92
+ if (id === undefined) {
93
+ return {
94
+ error: undefined,
95
+ id: undefined
96
+ };
97
+ }
98
+
99
+ if (id === null) {
100
+ return {
101
+ error: undefined,
102
+ id: null
103
+ };
104
+ }
105
+
106
+ if (typeof id === "string" || typeof id === "number") {
107
+ return {
108
+ error: undefined,
109
+ id
110
+ };
111
+ }
112
+
113
+ return {
114
+ error: Error("invalid id type"),
115
+ id: undefined
116
+ };
117
+ };
118
+
119
+ const extractJrpcFields = ({ messageObj }/*: { messageObj: Record<string, unknown> }*/) => {
120
+ const { jsonrpc, id, method, params, result, error, ...otherFields } = messageObj;
121
+
122
+ return {
123
+ jrpcFields: {
124
+ jsonrpc,
125
+ id,
126
+ method,
127
+ params,
128
+ result,
129
+ error,
130
+ },
131
+ otherFields
132
+ };
133
+ };
134
+
135
+ /*type TCoerceMethodResult = {
136
+ error: Error;
137
+ method: undefined;
138
+ } | {
139
+ error: undefined;
140
+ method: string | undefined;
141
+ };*/
142
+
143
+ const coerceMethod = ({ method }/*: { method: unknown }*/)/*: TCoerceMethodResult*/ => {
144
+ if (method === undefined) {
145
+ return {
146
+ error: undefined,
147
+ method: undefined
148
+ };
149
+ }
150
+
151
+ if (typeof method !== "string") {
152
+ return {
153
+ error: Error("invalid field type for method"),
154
+ method: undefined
155
+ };
156
+ }
157
+
158
+ return {
159
+ error: undefined,
160
+ method
161
+ };
162
+ };
163
+
164
+ /*type TCoerceJrpcMessageResult = {
165
+ error: Error;
166
+ jrpcMessage: undefined;
167
+ } | {
168
+ error: undefined;
169
+ jrpcMessage: TJsonRpcMessage;
170
+ };*/
171
+
172
+ /*type TJrpcFields = {
173
+ jsonrpc: string;
174
+ id: TJsonRpcOptionalId | undefined;
175
+ method: string | undefined;
176
+ params: TJsonRpcParameters | undefined;
177
+ result: unknown | undefined;
178
+ error: TJsonRpcError | undefined;
179
+ };*/
180
+
181
+ /*type TCoerceJrpcFieldsResult = {
182
+ error: Error;
183
+ jrpcFields: undefined;
184
+ } | {
185
+ error: undefined;
186
+ jrpcFields: TJrpcFields;
187
+ };*/
188
+
189
+ const coerceJrcpFields = ({
190
+ jrpcFields
191
+ }/*: {
192
+ jrpcFields: {
193
+ jsonrpc: unknown;
194
+ id: unknown;
195
+ method: unknown;
196
+ params: unknown;
197
+ result: unknown;
198
+ error: unknown;
199
+ };
200
+ // eslint-disable-next-line complexity
201
+ }*/)/*: TCoerceJrpcFieldsResult*/ => {
202
+
203
+ if (typeof jrpcFields.jsonrpc !== "string") {
204
+ return {
205
+ error: Error("invalid field type for jsonrpc"),
206
+ jrpcFields: undefined
207
+ };
208
+ }
209
+
210
+ const { error: idError, id } = coerceId({ id: jrpcFields.id });
211
+ if (idError !== undefined) {
212
+ return {
213
+ error: idError,
214
+ jrpcFields: undefined
215
+ };
216
+ }
217
+
218
+ const { error: methodError, method } = coerceMethod({ method: jrpcFields.method });
219
+ if (methodError !== undefined) {
220
+ return {
221
+ error: methodError,
222
+ jrpcFields: undefined
223
+ };
224
+ }
225
+
226
+ const { error: paramsError, params } = coerceParams({ params: jrpcFields.params });
227
+ if (paramsError !== undefined) {
228
+ return {
229
+ error: paramsError,
230
+ jrpcFields: undefined
231
+ };
232
+ }
233
+
234
+ const { error: errorFieldError, errorField } = coerceErrorField({ error: jrpcFields.error });
235
+ if (errorFieldError !== undefined) {
236
+ return {
237
+ error: errorFieldError,
238
+ jrpcFields: undefined
239
+ };
240
+ }
241
+
242
+ return {
243
+ error: undefined,
244
+ jrpcFields: {
245
+ jsonrpc: jrpcFields.jsonrpc /*as "2.0"*/,
246
+ id,
247
+ method,
248
+ params,
249
+ result: jrpcFields.result,
250
+ error: errorField
251
+ }
252
+ };
253
+ };
254
+
255
+ /*type TCoerceJrcpNotificationResult = {
256
+ error: Error;
257
+ jrpcNotification: undefined;
258
+ } | {
259
+ error: undefined;
260
+ jrpcNotification: TJsonRpcNotification;
261
+ };*/
262
+
263
+ const coerceJrcpNotification = ({
264
+ jrpcFields
265
+ }/*: {
266
+ jrpcFields: TJrpcFields;
267
+ // eslint-disable-next-line complexity
268
+ }*/)/*: TCoerceJrcpNotificationResult*/ => {
269
+
270
+ if (jrpcFields.id !== undefined) {
271
+ return {
272
+ error: Error("notification must not have an id"),
273
+ jrpcNotification: undefined
274
+ };
275
+ }
276
+
277
+ if (jrpcFields.error !== undefined) {
278
+ return {
279
+ error: Error("notification must not have an error (maybe id is missing and this is not a notification?)"),
280
+ jrpcNotification: undefined
281
+ };
282
+ }
283
+
284
+ if (jrpcFields.result !== undefined) {
285
+ return {
286
+ error: Error("notification must not have a result (maybe id is missing and this is not a notification?)"),
287
+ jrpcNotification: undefined
288
+ };
289
+ }
290
+
291
+ if (jrpcFields.method === undefined) {
292
+ return {
293
+ error: Error("no method in notification"),
294
+ jrpcNotification: undefined
295
+ };
296
+ }
297
+
298
+ return {
299
+ error: undefined,
300
+ jrpcNotification: {
301
+ jsonrpc: "2.0",
302
+ method: jrpcFields.method,
303
+ params: jrpcFields.params,
304
+ id: undefined
305
+ }
306
+ };
307
+ };
308
+
309
+ /*type TCoerceJrpcRequestResult = {
310
+ error: Error;
311
+ jrpcRequest: undefined;
312
+ } | {
313
+ error: undefined;
314
+ jrpcRequest: TJsonRpcRequest;
315
+ };*/
316
+
317
+ const coerceJrpcRequest = ({
318
+ jrpcFields
319
+ }/*: {
320
+ jrpcFields: TJrpcFields;
321
+ // eslint-disable-next-line complexity
322
+ }*/)/*: TCoerceJrpcRequestResult*/ => {
323
+ if (jrpcFields.id === undefined) {
324
+ return {
325
+ error: Error("request must have an id"),
326
+ jrpcRequest: undefined
327
+ };
328
+ }
329
+
330
+ if (jrpcFields.method === undefined) {
331
+ return {
332
+ error: Error("no method in request"),
333
+ jrpcRequest: undefined
334
+ };
335
+ }
336
+
337
+ if (jrpcFields.error !== undefined) {
338
+ return {
339
+ error: Error("request must not have an error"),
340
+ jrpcRequest: undefined
341
+ };
342
+ }
343
+
344
+ if (jrpcFields.result !== undefined) {
345
+ return {
346
+ error: Error("request must not have a result"),
347
+ jrpcRequest: undefined
348
+ };
349
+ }
350
+
351
+ return {
352
+ error: undefined,
353
+ jrpcRequest: {
354
+ jsonrpc: "2.0",
355
+ id: jrpcFields.id,
356
+ method: jrpcFields.method,
357
+ params: jrpcFields.params
358
+ }
359
+ };
360
+ };
361
+
362
+ /*type TCoerceJrpcResponseResult = {
363
+ error: Error;
364
+ jrpcResponse: undefined;
365
+ } | {
366
+ error: undefined;
367
+ jrpcResponse: TJsonRpcResponse;
368
+ };*/
369
+
370
+ const coerceJrcpResponse = ({
371
+ jrpcFields,
372
+ }/*: {
373
+ jrpcFields: TJrpcFields;
374
+ // eslint-disable-next-line complexity
375
+ }*/)/*: TCoerceJrpcResponseResult*/ => {
376
+ if (jrpcFields.id === undefined || jrpcFields.id === null) {
377
+ return {
378
+ error: Error("response must have an id"),
379
+ jrpcResponse: undefined
380
+ };
381
+ }
382
+
383
+ const { error: errorFieldError, errorField } = coerceErrorField({ error: jrpcFields.error });
384
+ if (errorFieldError !== undefined) {
385
+ return {
386
+ error: errorFieldError,
387
+ jrpcResponse: undefined
388
+ };
389
+ }
390
+
391
+ if (errorField !== undefined) {
392
+
393
+ if (jrpcFields.result !== undefined) {
394
+ return {
395
+ error: Error("both result and error in response"),
396
+ jrpcResponse: undefined
397
+ };
398
+ }
399
+
400
+ return {
401
+ error: undefined,
402
+ jrpcResponse: {
403
+ jsonrpc: "2.0",
404
+ id: jrpcFields.id,
405
+ error: errorField
406
+ }
407
+ };
408
+ }
409
+
410
+ if (jrpcFields.result === undefined) {
411
+ return {
412
+ error: Error("no result in response"),
413
+ jrpcResponse: undefined
414
+ };
415
+ }
416
+
417
+ return {
418
+ error: undefined,
419
+ jrpcResponse: {
420
+ jsonrpc: "2.0",
421
+ id: jrpcFields.id,
422
+ result: jrpcFields.result
423
+ }
424
+ };
425
+ };
426
+
427
+ // eslint-disable-next-line max-statements, complexity
428
+ const coerceJrpcMessage = ({ message }/*: { message: unknown }*/)/*: TCoerceJrpcMessageResult*/ => {
429
+ if (typeof message !== "object" || message === null) {
430
+ return {
431
+ error: Error("invalid message"),
432
+ jrpcMessage: undefined
433
+ };
434
+ }
435
+
436
+ const messageObj = message /*as Record<string, unknown>*/;
437
+
438
+ const { jrpcFields, otherFields } = extractJrpcFields({ messageObj });
439
+ if (jrpcFields.jsonrpc !== "2.0") {
440
+ return {
441
+ error: Error("invalid jsonrpc version"),
442
+ jrpcMessage: undefined
443
+ };
444
+ }
445
+
446
+ const keysOfOtherFields = Object.keys(otherFields);
447
+
448
+ if (keysOfOtherFields.length > 0) {
449
+ return {
450
+ error: Error(`unexpected field "${keysOfOtherFields[0]}" in message`),
451
+ jrpcMessage: undefined
452
+ };
453
+ }
454
+
455
+ const { error: jrpcFieldsError, jrpcFields: coercedJrpcFields } = coerceJrcpFields({ jrpcFields });
456
+ if (jrpcFieldsError !== undefined) {
457
+ return {
458
+ error: jrpcFieldsError,
459
+ jrpcMessage: undefined
460
+ };
461
+ }
462
+
463
+ const { jsonrpc, id, method } = coercedJrpcFields;
464
+
465
+ if (jsonrpc !== "2.0") {
466
+ return {
467
+ error: Error("invalid jsonrpc version"),
468
+ jrpcMessage: undefined
469
+ };
470
+ }
471
+
472
+ if (id === undefined) {
473
+ const { error: notificationError, jrpcNotification } = coerceJrcpNotification({ jrpcFields: coercedJrpcFields });
474
+ if (notificationError !== undefined) {
475
+ return {
476
+ error: notificationError,
477
+ jrpcMessage: undefined
478
+ };
479
+ }
480
+
481
+ return {
482
+ error: undefined,
483
+ jrpcMessage: jrpcNotification
484
+ };
485
+ } else {
486
+ if (method === undefined) {
487
+ const { error: responseError, jrpcResponse } = coerceJrcpResponse({ jrpcFields: coercedJrpcFields });
488
+ if (responseError !== undefined) {
489
+ return {
490
+ error: responseError,
491
+ jrpcMessage: undefined
492
+ };
493
+ }
494
+
495
+ return {
496
+ error: undefined,
497
+ jrpcMessage: jrpcResponse
498
+ };
499
+ }
500
+
501
+ const { error: requestError, jrpcRequest } = coerceJrpcRequest({ jrpcFields: coercedJrpcFields });
502
+ if (requestError !== undefined) {
503
+ return {
504
+ error: requestError,
505
+ jrpcMessage: undefined
506
+ };
507
+ }
508
+
509
+ return {
510
+ error: undefined,
511
+ jrpcMessage: jrpcRequest
512
+ };
513
+ }
514
+ };
515
+
516
+ export {
517
+ coerceParams,
518
+ coerceErrorField,
519
+ coerceId,
520
+ coerceMethod,
521
+ coerceJrcpFields,
522
+ coerceJrcpNotification,
523
+ coerceJrpcMessage
524
+ };
@@ -0,0 +1,47 @@
1
+ import type { TJsonRpcError, TJsonRpcMessage, TJsonRpcParameters, TRequestResponse, TRequestResponseValue, TRequestResult } from "./types.js";
2
+ type TRequestHandlerResponse = {
3
+ result: TRequestResponseValue;
4
+ error: undefined;
5
+ } | {
6
+ result: undefined;
7
+ error: TJsonRpcError;
8
+ } | {
9
+ result: undefined;
10
+ error: undefined;
11
+ };
12
+ type TRequestHandler = (args: {
13
+ method: string;
14
+ params: TJsonRpcParameters | undefined;
15
+ }) => Promise<TRequestHandlerResponse>;
16
+ type TNotificationHandler = (args: {
17
+ method: string;
18
+ params: TJsonRpcParameters | undefined;
19
+ }) => void;
20
+ type TNotifyMethod = (args: {
21
+ method: string;
22
+ params: TJsonRpcParameters;
23
+ }) => void;
24
+ type TRequestMethod = (args: {
25
+ method: string;
26
+ params: TJsonRpcParameters;
27
+ timeoutMs?: number;
28
+ }) => Promise<TRequestResult>;
29
+ declare const createJrpc: ({ sendMessage, handleRequest, handleNotification }: {
30
+ sendMessage: (args: {
31
+ message: TJsonRpcMessage;
32
+ }) => void;
33
+ handleRequest: TRequestHandler;
34
+ handleNotification: TNotificationHandler;
35
+ }) => {
36
+ receivedMessage: ({ message }: {
37
+ message: unknown;
38
+ }) => {
39
+ error: Error | undefined;
40
+ };
41
+ request: TRequestMethod;
42
+ notify: TNotifyMethod;
43
+ close: () => void;
44
+ };
45
+ type TJrpc = ReturnType<typeof createJrpc>;
46
+ export { createJrpc };
47
+ export type { TJsonRpcMessage, TRequestResponse, TRequestResult, TJrpc, TRequestHandler, TNotificationHandler, TRequestMethod, TNotifyMethod };