@arrirpc/server 0.45.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/dist/index.cjs ADDED
@@ -0,0 +1,1223 @@
1
+ 'use strict';
2
+
3
+ const codegenUtils = require('@arrirpc/codegen-utils');
4
+ const arri = require('arri');
5
+ const h3 = require('h3');
6
+ const schema = require('@arrirpc/schema');
7
+ const h3Sse = require('h3-sse');
8
+ const scule = require('scule');
9
+
10
+ var __defProp$4 = Object.defineProperty;
11
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __publicField$4 = (obj, key, value) => {
13
+ __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
14
+ return value;
15
+ };
16
+ class ArriServerError extends Error {
17
+ constructor(err) {
18
+ super(err.message);
19
+ __publicField$4(this, "code");
20
+ __publicField$4(this, "data");
21
+ this.code = err.code;
22
+ this.data = err.data;
23
+ if (err.stack) {
24
+ this.stack = err.stack;
25
+ }
26
+ }
27
+ }
28
+ const ArriServerErrorResponse = schema.a.object({
29
+ code: schema.a.int16(),
30
+ message: schema.a.string(),
31
+ stack: schema.a.optional(schema.a.array(schema.a.string())),
32
+ data: schema.a.optional(schema.a.any())
33
+ });
34
+ function defineError(statusCode, input = {}) {
35
+ const defaultVals = errorResponseDefaults[statusCode];
36
+ return new ArriServerError({
37
+ code: statusCode,
38
+ message: input.message ?? defaultVals?.message ?? "An unknown error occurred",
39
+ data: input.data
40
+ });
41
+ }
42
+ function errorResponseFromValidationErrors(errors, prefixText = `Missing or invalid parameters`) {
43
+ const errorParts = [];
44
+ for (const err of errors) {
45
+ const propName = err.instancePath?.split("/") ?? [];
46
+ if (propName.length > 0) {
47
+ propName.shift();
48
+ }
49
+ if (!errorParts.includes(propName.join("."))) {
50
+ errorParts.push(propName.join("."));
51
+ }
52
+ }
53
+ throw defineError(400, {
54
+ message: `${prefixText}: [${errorParts.join(",")}]`,
55
+ data: errors
56
+ });
57
+ }
58
+ const errorResponseDefaults = {
59
+ 400: {
60
+ name: "BAD_REQUEST",
61
+ message: "Bad Request"
62
+ },
63
+ 401: {
64
+ name: "UNAUTHORIZED",
65
+ message: "Unauthorized"
66
+ },
67
+ 402: {
68
+ name: "PAYMENT_REQUIRED",
69
+ message: "Payment Required"
70
+ },
71
+ 403: {
72
+ name: "FORBIDDEN",
73
+ message: "Forbidden"
74
+ },
75
+ 404: {
76
+ name: "NOT_FOUND",
77
+ message: "Not Found"
78
+ },
79
+ 405: {
80
+ name: "METHOD_NOT_ALLOWED",
81
+ message: "Method Not Allowed"
82
+ },
83
+ 406: {
84
+ name: "NOT_ACCEPTABLE",
85
+ message: "Not Acceptable"
86
+ },
87
+ 407: {
88
+ name: "PROXY_AUTHENTICATION_REQUIRED",
89
+ message: "Proxy Authentication Required"
90
+ },
91
+ 408: {
92
+ name: "REQUEST_TIMEOUT",
93
+ message: "Request Timeout"
94
+ },
95
+ 409: {
96
+ name: "CONFLICT",
97
+ message: "Conflict"
98
+ },
99
+ 410: {
100
+ name: "GONE",
101
+ message: "Gone"
102
+ },
103
+ 411: {
104
+ name: "LENGTH_REQUIRED",
105
+ message: "Length Required"
106
+ },
107
+ 412: {
108
+ name: "PRECONDITION_FAILED",
109
+ message: "Precondition Failed"
110
+ },
111
+ 413: {
112
+ name: "REQUEST_TOO_LONG",
113
+ message: "Request Entity Too Large"
114
+ },
115
+ 414: {
116
+ name: "REQUEST_URI_TOO_LONG",
117
+ message: "Request-URI Too Long"
118
+ },
119
+ 415: {
120
+ name: "UNSUPPORTED_MEDIA_TYPE",
121
+ message: "Unsupported Media Type"
122
+ },
123
+ 416: {
124
+ name: "REQUESTED_RANGE_NOT_SATISFIABLE",
125
+ message: "Requested Range Not Satisfiable"
126
+ },
127
+ 417: {
128
+ name: "EXPECTATION_FAILED",
129
+ message: "Expectation Failed"
130
+ },
131
+ 418: {
132
+ name: "IM_A_TEAPOT",
133
+ message: "I'm a teapot"
134
+ },
135
+ 419: {
136
+ name: "INSUFFICIENT_SPACE_ON_RESOURCE",
137
+ message: "Insufficient Space on Resource"
138
+ },
139
+ 420: {
140
+ name: "METHOD_FAILURE",
141
+ message: "Method Failure"
142
+ },
143
+ 421: {
144
+ name: "MISDIRECTED_REQUEST",
145
+ message: "Misdirected Request"
146
+ },
147
+ 422: {
148
+ name: "UNPROCESSABLE_ENTITY",
149
+ message: "Unprocessable Entity"
150
+ },
151
+ 423: {
152
+ name: "LOCKED",
153
+ message: "Locked"
154
+ },
155
+ 424: {
156
+ name: "FAILED_DEPENDENCY",
157
+ message: "Failed Dependency"
158
+ },
159
+ 428: {
160
+ name: "PRECONDITION_REQUIRED",
161
+ message: "Precondition Required"
162
+ },
163
+ 429: {
164
+ name: "TOO_MANY_REQUESTS",
165
+ message: "Too Many Requests"
166
+ },
167
+ 431: {
168
+ name: "REQUEST_HEADER_FIELDS_TOO_LARGE",
169
+ message: "Request Header Fields Too Large"
170
+ },
171
+ 451: {
172
+ name: "UNAVAILABLE_FOR_LEGAL_REASONS",
173
+ message: "Unavailable For Legal Reasons"
174
+ },
175
+ 500: {
176
+ name: "INTERNAL_SERVER_ERROR",
177
+ message: "Internal Server Error"
178
+ },
179
+ 501: {
180
+ name: "NOT_IMPLEMENTED",
181
+ message: "Not Implemented"
182
+ },
183
+ 502: {
184
+ name: "BAD_GATEWAY",
185
+ message: "Bad Gateway"
186
+ },
187
+ 503: {
188
+ name: "SERVICE_UNAVAILABLE",
189
+ message: "Service Unavailable"
190
+ },
191
+ 504: {
192
+ name: "GATEWAY_TIMEOUT",
193
+ message: "Gateway Timeout"
194
+ },
195
+ 505: {
196
+ name: "HTTP_VERSION_NOT_SUPPORTED",
197
+ message: "HTTP Version Not Supported"
198
+ },
199
+ 507: {
200
+ name: "INSUFFICIENT_STORAGE",
201
+ message: "Insufficient Storage"
202
+ },
203
+ 511: {
204
+ name: "NETWORK_AUTHENTICATION_REQUIRED",
205
+ message: "Network Authentication Required"
206
+ }
207
+ };
208
+ async function handleH3Error(err, event, onError, debug) {
209
+ let arriErr;
210
+ if (err instanceof ArriServerError) {
211
+ arriErr = err;
212
+ } else if (h3.isError(err)) {
213
+ arriErr = new ArriServerError({
214
+ code: err.statusCode,
215
+ message: err.message,
216
+ stack: err.stack,
217
+ data: err.data
218
+ });
219
+ } else if (err instanceof Error) {
220
+ arriErr = new ArriServerError({
221
+ code: 500,
222
+ message: err.message,
223
+ data: err,
224
+ stack: err.stack
225
+ });
226
+ } else {
227
+ arriErr = new ArriServerError({
228
+ code: 500,
229
+ message: `An unknown error occurred`,
230
+ data: err
231
+ });
232
+ }
233
+ if (!arriErr) {
234
+ return;
235
+ }
236
+ h3.setResponseStatus(event, arriErr.code);
237
+ if (onError) {
238
+ await onError(arriErr, event);
239
+ }
240
+ if (event.handled) {
241
+ return;
242
+ }
243
+ return sendArriError(event, arriErr, debug);
244
+ }
245
+ async function sendArriError(event, error, debug) {
246
+ const payload = {
247
+ code: error.code,
248
+ message: error.message,
249
+ data: error.data
250
+ };
251
+ if (debug) {
252
+ payload.stack = error.stack?.split("\n").map((l) => l.trim());
253
+ }
254
+ h3.setResponseHeader(event, "Content-Type", "application/json");
255
+ h3.setResponseStatus(event, error.code);
256
+ return h3.send(event, JSON.stringify(payload));
257
+ }
258
+
259
+ function isRpcParamSchema(input) {
260
+ return schema.isAObjectSchema(input) || schema.isADiscriminatorSchema(input);
261
+ }
262
+ function isRpc(input) {
263
+ return typeof input === "object" && input !== null && "method" in input && codegenUtils.isRpcHttpMethod(input.method) && "handler" in input && typeof input.handler === "function";
264
+ }
265
+ function defineRpc(config) {
266
+ config.transport = "http";
267
+ return config;
268
+ }
269
+ function createHttpRpcDefinition(rpcName, httpPath, procedure) {
270
+ let method;
271
+ if (procedure.isEventStream === true) {
272
+ method = procedure.method ?? "get";
273
+ } else {
274
+ method = procedure.method ?? "post";
275
+ }
276
+ return {
277
+ transport: "http",
278
+ description: procedure.description,
279
+ path: httpPath,
280
+ method,
281
+ params: getRpcParamName(rpcName, procedure),
282
+ response: getRpcResponseDefinition(rpcName, procedure),
283
+ isDeprecated: procedure.isDeprecated,
284
+ isEventStream: procedure.isEventStream === true ? true : void 0
285
+ };
286
+ }
287
+ function getRpcPath(rpcName, prefix = "") {
288
+ const path = rpcName.split(".").map(
289
+ (part) => codegenUtils.removeDisallowedChars(
290
+ scule.kebabCase(part),
291
+ `!@#$%^&*()+=[]{}|\\;:'"<>,./?`
292
+ )
293
+ ).join("/");
294
+ const finalPath = prefix ? `/${prefix}/${path}` : `/${path}`;
295
+ return finalPath;
296
+ }
297
+ function getRpcParamName(rpcName, procedure) {
298
+ if (!isRpcParamSchema(procedure.params)) {
299
+ return void 0;
300
+ }
301
+ const nameParts = rpcName.split(".").map(
302
+ (part) => codegenUtils.removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
303
+ );
304
+ const paramName = procedure.params.metadata.id ?? scule.pascalCase(`${nameParts.join(`_`)}_params`);
305
+ return paramName;
306
+ }
307
+ function getRpcResponseName(rpcName, procedure) {
308
+ if (!isRpcParamSchema(procedure.response)) {
309
+ return void 0;
310
+ }
311
+ const nameParts = rpcName.split(".").map(
312
+ (part) => codegenUtils.removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
313
+ );
314
+ const responseName = procedure.response.metadata.id ?? scule.pascalCase(`${nameParts.join("_")}_response`);
315
+ return responseName;
316
+ }
317
+ function getRpcResponseDefinition(rpcName, procedure) {
318
+ if (!isRpcParamSchema(procedure.response)) {
319
+ return void 0;
320
+ }
321
+ const name = getRpcResponseName(rpcName, procedure);
322
+ if (!name) {
323
+ return void 0;
324
+ }
325
+ return name;
326
+ }
327
+ function registerRpc(router, path, procedure, opts) {
328
+ let responseValidator;
329
+ try {
330
+ responseValidator = procedure.response ? schema.a.compile(procedure.response) : void 0;
331
+ } catch (err) {
332
+ console.error("ERROR COMPILING VALIDATOR", err);
333
+ }
334
+ const httpMethod = procedure.method ?? "post";
335
+ const handler = h3.eventHandler(async (event) => {
336
+ event.context.rpcName = procedure.name;
337
+ if (h3.isPreflightRequest(event)) {
338
+ return "ok";
339
+ }
340
+ try {
341
+ if (opts.onRequest) {
342
+ await opts.onRequest(event);
343
+ }
344
+ if (opts.middleware.length) {
345
+ for (const m of opts.middleware) {
346
+ await m(event);
347
+ }
348
+ }
349
+ if (isRpcParamSchema(procedure.params)) {
350
+ await validateRpcRequestInput(
351
+ event,
352
+ httpMethod,
353
+ procedure.params
354
+ );
355
+ }
356
+ const response = await procedure.handler(
357
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
358
+ event.context,
359
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
360
+ event
361
+ );
362
+ event.context.response = response;
363
+ if (opts.onBeforeResponse) {
364
+ await opts.onBeforeResponse(event);
365
+ }
366
+ if (typeof response === "object") {
367
+ if (!responseValidator?.validate(response)) {
368
+ const errors = schema.a.errors(procedure.response, response);
369
+ throw defineError(500, {
370
+ message: "Failed to serialize response. Response does not match specified schema",
371
+ data: errors
372
+ });
373
+ }
374
+ h3.setResponseHeader(event, "Content-Type", "application/json");
375
+ await h3.send(
376
+ event,
377
+ responseValidator?.serialize(response) ?? JSON.stringify(response)
378
+ );
379
+ } else {
380
+ h3.setResponseHeader(event, "Content-Type", "application/json");
381
+ await h3.send(event, `{}`);
382
+ }
383
+ if (opts.onAfterResponse) {
384
+ await opts.onAfterResponse(event);
385
+ }
386
+ if (procedure.postHandler) {
387
+ await procedure.postHandler(
388
+ event.context,
389
+ event
390
+ );
391
+ }
392
+ } catch (err) {
393
+ await handleH3Error(err, event, opts.onError, opts.debug ?? false);
394
+ }
395
+ return "";
396
+ });
397
+ switch (httpMethod) {
398
+ case "get":
399
+ router.get(path, handler);
400
+ break;
401
+ case "delete":
402
+ router.delete(path, handler);
403
+ break;
404
+ case "patch":
405
+ router.patch(path, handler);
406
+ break;
407
+ case "put":
408
+ router.put(path, handler);
409
+ break;
410
+ case "post":
411
+ default:
412
+ router.post(path, handler);
413
+ break;
414
+ }
415
+ }
416
+ async function validateRpcRequestInput(event, httpMethod, schema$1) {
417
+ switch (httpMethod) {
418
+ case "get": {
419
+ const parsedParams = await h3.getValidatedQuery(
420
+ event,
421
+ (input) => schema.a.safeCoerce(schema$1, input)
422
+ );
423
+ if (parsedParams.success) {
424
+ event.context.params = parsedParams.value;
425
+ } else {
426
+ const errParts = [];
427
+ for (const err of parsedParams.error.errors) {
428
+ const errPath = err.instancePath.split("/");
429
+ errPath.shift();
430
+ const propName = errPath.join(".");
431
+ if (!errParts.includes(propName)) {
432
+ errParts.push(propName);
433
+ }
434
+ }
435
+ const message = `Missing or invalid url query parameters: [${errParts.join(
436
+ ", "
437
+ )}]`;
438
+ throw defineError(400, {
439
+ message,
440
+ data: parsedParams.error
441
+ });
442
+ }
443
+ break;
444
+ }
445
+ case "delete":
446
+ case "patch":
447
+ case "post":
448
+ case "put": {
449
+ const body = await h3.readRawBody(event);
450
+ if (!body) {
451
+ throw defineError(400, {
452
+ message: `Invalid request body. Expected object. Got undefined.`
453
+ });
454
+ }
455
+ const parsedParams = schema.a.safeParse(schema$1, body);
456
+ if (!parsedParams.success) {
457
+ const errorParts = [];
458
+ for (const err of parsedParams.error.errors) {
459
+ const errPath = err.instancePath.split("/");
460
+ errPath.shift();
461
+ if (!errorParts.includes(errPath.join("."))) {
462
+ errorParts.push(errPath.join("."));
463
+ }
464
+ }
465
+ throw defineError(400, {
466
+ message: `Invalid request body. Affected properties [${errorParts.join(
467
+ ", "
468
+ )}]`,
469
+ data: parsedParams.error
470
+ });
471
+ }
472
+ event.context.params = parsedParams.value;
473
+ break;
474
+ }
475
+ }
476
+ }
477
+
478
+ var __defProp$3 = Object.defineProperty;
479
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
480
+ var __publicField$3 = (obj, key, value) => {
481
+ __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
482
+ return value;
483
+ };
484
+ function defineEventStreamRpc(config) {
485
+ return {
486
+ ...config,
487
+ method: config.method ?? "get",
488
+ isEventStream: true,
489
+ transport: "http"
490
+ };
491
+ }
492
+ function isEventStreamRpc(input) {
493
+ return isRpc(input) && "isEventStream" in input && input.isEventStream === true;
494
+ }
495
+ class EventStreamConnection {
496
+ constructor(event, opts) {
497
+ __publicField$3(this, "lastEventId");
498
+ __publicField$3(this, "validationErrors");
499
+ __publicField$3(this, "validator");
500
+ __publicField$3(this, "serializer");
501
+ // for some reason Rollup cannot output DTS when this is set to NodeJS.Timeout
502
+ __publicField$3(this, "pingInterval");
503
+ __publicField$3(this, "pingIntervalMs");
504
+ __publicField$3(this, "eventStream");
505
+ this.eventStream = h3Sse.createEventStream(event);
506
+ this.lastEventId = this.eventStream.lastEventId;
507
+ this.pingIntervalMs = opts.pingInterval ?? 6e4;
508
+ this.serializer = opts.serializer;
509
+ this.validator = opts.validator;
510
+ this.validationErrors = opts.validationErrors;
511
+ this.eventStream.onClose(() => {
512
+ this.cleanup();
513
+ });
514
+ }
515
+ /**
516
+ * Send the stream to the client. This must be called before sending any events.
517
+ */
518
+ send() {
519
+ void this.eventStream.send();
520
+ this.pingInterval = setInterval(async () => {
521
+ await this.eventStream.push({
522
+ event: "ping",
523
+ data: ""
524
+ });
525
+ }, this.pingIntervalMs);
526
+ }
527
+ async push(data, eventId) {
528
+ if (Array.isArray(data)) {
529
+ const events = [];
530
+ for (const item of data) {
531
+ if (this.validator(item)) {
532
+ events.push({
533
+ id: eventId,
534
+ event: "message",
535
+ data: this.serializer(item)
536
+ });
537
+ continue;
538
+ }
539
+ const errors2 = this.validationErrors(item);
540
+ const errorResponse2 = defineError(500, {
541
+ message: "Failed to serialize response. Response does not match specified schema.",
542
+ data: errors2
543
+ });
544
+ events.push({
545
+ id: eventId,
546
+ event: "error",
547
+ data: JSON.stringify(errorResponse2)
548
+ });
549
+ }
550
+ await this.eventStream.push(events);
551
+ return;
552
+ }
553
+ if (this.validator(data)) {
554
+ await this.eventStream.push({
555
+ id: eventId,
556
+ event: "message",
557
+ data: this.serializer(data)
558
+ });
559
+ return;
560
+ }
561
+ const errors = this.validationErrors(data);
562
+ const errorResponse = defineError(500, {
563
+ message: "Failed to serialize response. Response does not match specified schema.",
564
+ data: errors
565
+ });
566
+ await this.eventStream.push({
567
+ id: eventId,
568
+ event: "error",
569
+ data: JSON.stringify(errorResponse)
570
+ });
571
+ }
572
+ // /**
573
+ // * Push a custom event. These events will need to be parsed manually using the `onEvent` hooks of any generated clients.
574
+ // * Note events with the name "error" or "message" cannot be used for custom events.
575
+ // */
576
+ // async pushCustomEvent(event: SseEvent): Promise<void> {
577
+ // if (event.event === "message") {
578
+ // throw new Error(
579
+ // `Event type "message" is the default event type. Therefore it cannot be used when pushing custom events.`,
580
+ // );
581
+ // }
582
+ // if (event.event === "error") {
583
+ // throw new Error(
584
+ // `Event type "error" is reserved for the pushError() method. Therefore it cannot be used when pushing custom events.`,
585
+ // );
586
+ // }
587
+ // }
588
+ /**
589
+ * Publish an error event. This will trigger the `onError` hooks of any connected clients.
590
+ */
591
+ async pushError(error, eventId) {
592
+ await this.eventStream.push({
593
+ id: eventId,
594
+ event: "error",
595
+ data: JSON.stringify(error)
596
+ });
597
+ }
598
+ cleanup() {
599
+ if (this.pingInterval) {
600
+ clearInterval(this.pingInterval);
601
+ }
602
+ }
603
+ /**
604
+ * Tell clients that the stream has ended and close the connection.
605
+ */
606
+ async close() {
607
+ await this.eventStream.push({
608
+ event: "done",
609
+ data: "this stream has ended"
610
+ }).catch();
611
+ await this.eventStream.close();
612
+ }
613
+ onClose(cb) {
614
+ this.eventStream.onClose(cb);
615
+ }
616
+ }
617
+ function registerEventStreamRpc(router, path, procedure, opts) {
618
+ let responseValidator;
619
+ try {
620
+ responseValidator = procedure.response ? schema.a.compile(procedure.response) : void 0;
621
+ } catch (err) {
622
+ console.error("ERROR COMPILING VALIDATOR", err);
623
+ }
624
+ const httpMethod = procedure.method ?? "get";
625
+ const handler = h3.eventHandler(async (event) => {
626
+ event.context.rpcName = procedure.name;
627
+ if (h3.isPreflightRequest(event)) {
628
+ return "ok";
629
+ }
630
+ try {
631
+ if (opts.onRequest) {
632
+ await opts.onRequest(event);
633
+ }
634
+ if (opts.middleware.length) {
635
+ for (const m of opts.middleware) {
636
+ await m(event);
637
+ }
638
+ }
639
+ if (isRpcParamSchema(procedure.params)) {
640
+ await validateRpcRequestInput(
641
+ event,
642
+ httpMethod,
643
+ procedure.params
644
+ );
645
+ }
646
+ const stream = new EventStreamConnection(event, {
647
+ pingInterval: procedure.pingInterval,
648
+ validator: responseValidator?.validate ?? function() {
649
+ return true;
650
+ },
651
+ serializer: (
652
+ // eslint-disable-next-line @typescript-eslint/unbound-method
653
+ responseValidator?.serialize ?? function(_) {
654
+ return "";
655
+ }
656
+ ),
657
+ validationErrors(input) {
658
+ if (procedure.response) {
659
+ return schema.a.errors(procedure.response, input);
660
+ }
661
+ return [];
662
+ }
663
+ });
664
+ event.context.stream = stream;
665
+ await procedure.handler(
666
+ event.context,
667
+ event
668
+ );
669
+ if (!event.handled && !stream.eventStream._handled) {
670
+ stream.send();
671
+ }
672
+ } catch (err) {
673
+ await handleH3Error(err, event, opts.onError, opts.debug ?? false);
674
+ }
675
+ return "";
676
+ });
677
+ switch (httpMethod) {
678
+ case "get":
679
+ router.get(path, handler);
680
+ break;
681
+ case "delete":
682
+ router.delete(path, handler);
683
+ break;
684
+ case "patch":
685
+ router.patch(path, handler);
686
+ break;
687
+ case "put":
688
+ router.put(path, handler);
689
+ break;
690
+ case "post":
691
+ default:
692
+ router.post(path, handler);
693
+ break;
694
+ }
695
+ }
696
+
697
+ function defineRoute(route) {
698
+ return route;
699
+ }
700
+ function registerRoute(router, route, opts) {
701
+ if (typeof route.method === "string") {
702
+ handleRoute(router, route.method, route, opts);
703
+ return;
704
+ }
705
+ for (const method of route.method) {
706
+ handleRoute(router, method, route, opts);
707
+ }
708
+ }
709
+ function handleRoute(router, method, route, opts) {
710
+ const handler = h3.defineEventHandler(async (event) => {
711
+ if (h3.isPreflightRequest(event)) {
712
+ return "ok";
713
+ }
714
+ try {
715
+ if (opts.onRequest) {
716
+ await opts.onRequest(event);
717
+ }
718
+ if (opts.middleware.length) {
719
+ for (const m of opts.middleware) {
720
+ await m(event);
721
+ }
722
+ }
723
+ if (route.query) {
724
+ const query = h3.getQuery(event);
725
+ const parsedQuery = schema.a.safeCoerce(route.query, query);
726
+ if (!parsedQuery.success) {
727
+ const errParts = [];
728
+ for (const err of parsedQuery.error.errors) {
729
+ const errPath = err.instancePath.split("/");
730
+ errPath.shift();
731
+ const propName = errPath.join(".");
732
+ if (!errParts.includes(propName)) {
733
+ errParts.push(propName);
734
+ }
735
+ }
736
+ const message = `Missing or invalid url query parameters: [${errParts.join(
737
+ ", "
738
+ )}]`;
739
+ throw defineError(400, {
740
+ message
741
+ });
742
+ }
743
+ event.context.query = parsedQuery.value;
744
+ }
745
+ const notAllowedBodyMethods = [
746
+ "GET",
747
+ "HEAD",
748
+ "CONNECT",
749
+ "OPTIONS"
750
+ ];
751
+ if (route.body && !notAllowedBodyMethods.includes(event.method)) {
752
+ const body = await h3.readRawBody(event);
753
+ const parsedBody = schema.a.safeParse(route.body, body);
754
+ if (!parsedBody.success) {
755
+ const errorParts = [];
756
+ for (const err of parsedBody.error.errors) {
757
+ const errPath = err.instancePath.split("/");
758
+ errPath.shift();
759
+ if (!errorParts.includes(errPath.join("."))) {
760
+ errorParts.push(errPath.join("."));
761
+ }
762
+ }
763
+ throw defineError(400, {
764
+ message: `Invalid request body. Affected properties [${errorParts.join(
765
+ ", "
766
+ )}]`,
767
+ data: parsedBody.error
768
+ });
769
+ }
770
+ event.context.body = parsedBody.value;
771
+ }
772
+ const response = await route.handler(event);
773
+ event.context.response = response;
774
+ if (!event.handled) {
775
+ if (opts.onBeforeResponse) {
776
+ await opts.onBeforeResponse(event);
777
+ }
778
+ if (typeof response === "object" && response) {
779
+ h3.setHeader(event, "Content-Type", "application/json");
780
+ await h3.send(event, JSON.stringify(response));
781
+ } else {
782
+ await h3.send(event, response);
783
+ }
784
+ }
785
+ if (opts.onAfterResponse) {
786
+ await opts.onAfterResponse(event);
787
+ }
788
+ if (route.postHandler) {
789
+ await route.postHandler(event);
790
+ }
791
+ } catch (err) {
792
+ await handleH3Error(err, event, opts.onError, opts.debug ?? false);
793
+ }
794
+ return "";
795
+ });
796
+ switch (method) {
797
+ case "head":
798
+ router.head(route.path, handler);
799
+ break;
800
+ case "get":
801
+ router.get(route.path, handler);
802
+ break;
803
+ case "delete":
804
+ router.delete(route.path, handler);
805
+ break;
806
+ case "patch":
807
+ router.patch(route.path, handler);
808
+ break;
809
+ case "post":
810
+ router.post(route.path, handler);
811
+ break;
812
+ case "put":
813
+ router.put(route.path, handler);
814
+ break;
815
+ }
816
+ }
817
+
818
+ var __defProp$2 = Object.defineProperty;
819
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
820
+ var __publicField$2 = (obj, key, value) => {
821
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
822
+ return value;
823
+ };
824
+ class ArriRouter {
825
+ constructor() {
826
+ __publicField$2(this, "procedures", []);
827
+ __publicField$2(this, "routes", []);
828
+ __publicField$2(this, "definitions", {});
829
+ }
830
+ rpc(procedure) {
831
+ procedure.transport = "http";
832
+ this.procedures.push(procedure);
833
+ }
834
+ wsRpc(procedure) {
835
+ procedure.transport = "ws";
836
+ this.procedures.push(procedure);
837
+ }
838
+ route(route) {
839
+ this.routes.push(route);
840
+ }
841
+ registerDefinitions(models) {
842
+ for (const key of Object.keys(models)) {
843
+ this.definitions[key] = models[key];
844
+ }
845
+ }
846
+ getProcedures() {
847
+ return this.procedures;
848
+ }
849
+ getRoutes() {
850
+ return this.routes;
851
+ }
852
+ getDefinitions() {
853
+ return this.definitions;
854
+ }
855
+ }
856
+
857
+ var __defProp$1 = Object.defineProperty;
858
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
859
+ var __publicField$1 = (obj, key, value) => {
860
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
861
+ return value;
862
+ };
863
+ class WsPeer {
864
+ constructor(peer, opts) {
865
+ __publicField$1(this, "_peer");
866
+ __publicField$1(this, "_validator");
867
+ __publicField$1(this, "context");
868
+ __publicField$1(this, "url");
869
+ this._peer = peer;
870
+ this._validator = opts.validator;
871
+ this.url = peer.url;
872
+ this.context = opts.context;
873
+ }
874
+ send(data) {
875
+ if (!this._validator) {
876
+ return;
877
+ }
878
+ if (!this._validator.validate(data)) {
879
+ return;
880
+ }
881
+ const payload = this._validator.serialize(data);
882
+ return this._peer.send(`event: message
883
+ data: ${payload}`);
884
+ }
885
+ sendError(err) {
886
+ return this._peer.send(`event: error
887
+ data: ${JSON.stringify(err)}`);
888
+ }
889
+ close() {
890
+ throw new Error("close() is not yet implemented on WsPeer");
891
+ }
892
+ subscribe(channel) {
893
+ this._peer.subscribe(channel);
894
+ }
895
+ unsubscribe(channel) {
896
+ this._peer.unsubscribe(channel);
897
+ }
898
+ publish(channel, message) {
899
+ if (!this._validator) {
900
+ return;
901
+ }
902
+ if (!this._validator.validate(message)) {
903
+ const err = {
904
+ code: 500,
905
+ message: `Error serializing message on server. The payload doesn't match the specified schema.`,
906
+ data: {
907
+ payload: message
908
+ }
909
+ };
910
+ this.sendError(err);
911
+ return;
912
+ }
913
+ const payload = this._validator.serialize(message);
914
+ this._peer.publish(channel, payload);
915
+ }
916
+ readyState() {
917
+ return this._peer.readyState;
918
+ }
919
+ }
920
+ function defineWebsocketRpc(def) {
921
+ def.transport = "ws";
922
+ return def;
923
+ }
924
+ function registerWebsocketRpc(router, path, rpc) {
925
+ let responseValidator;
926
+ let paramValidator;
927
+ try {
928
+ responseValidator = rpc.response ? schema.a.compile(rpc.response) : void 0;
929
+ } catch (err) {
930
+ console.error("ERROR COMPILING VALIDATOR", err);
931
+ }
932
+ try {
933
+ paramValidator = rpc.params ? schema.a.compile(rpc.params) : void 0;
934
+ } catch (err) {
935
+ console.error("ERROR COMPILING PARAMS", err);
936
+ }
937
+ const rpcHandler = typeof rpc.handler === "function" ? rpc.handler() : rpc.handler;
938
+ const handler = h3.defineWebSocketHandler({
939
+ upgrade(req) {
940
+ },
941
+ open(peer) {
942
+ const urlParts = peer.url.split("?");
943
+ const context = {
944
+ rpcName: rpc.name,
945
+ clientAddress: peer.addr
946
+ };
947
+ if (urlParts.length > 1) {
948
+ urlParts.shift();
949
+ const queryStr = new URLSearchParams(urlParts.join("?"));
950
+ const query = {};
951
+ for (const [key, val] of queryStr.entries()) {
952
+ query[key] = val;
953
+ }
954
+ context.queryParams = query;
955
+ }
956
+ const wsPeer = new WsPeer(peer, {
957
+ validator: responseValidator,
958
+ context
959
+ });
960
+ peer.ctx.__wsPeer = wsPeer;
961
+ rpcHandler.onOpen(peer.ctx.__wsPeer);
962
+ },
963
+ message(peer, message) {
964
+ if (!paramValidator) {
965
+ return;
966
+ }
967
+ const data = paramValidator.safeParse(message.text());
968
+ if (!data.success) {
969
+ const errorResponse = {
970
+ code: 400,
971
+ message: data.error.message,
972
+ data: data.error.errors,
973
+ stack: data.error.stack?.split("\n")
974
+ };
975
+ peer.ctx.__wsPeer.sendError(errorResponse);
976
+ return;
977
+ }
978
+ rpcHandler.onMessage(peer.ctx.__wsPeer, data.value);
979
+ },
980
+ close(peer, details) {
981
+ rpcHandler.onClose(peer.ctx.__wsPeer, details);
982
+ }
983
+ });
984
+ router.use(path, handler);
985
+ }
986
+ function createWsRpcDefinition(rpcName, path, rpc) {
987
+ return {
988
+ transport: "ws",
989
+ path,
990
+ params: getRpcParamName(rpcName, rpc),
991
+ response: getRpcResponseName(rpcName, rpc),
992
+ isDeprecated: rpc.isDeprecated,
993
+ description: rpc.description
994
+ };
995
+ }
996
+
997
+ var __defProp = Object.defineProperty;
998
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
999
+ var __publicField = (obj, key, value) => {
1000
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1001
+ return value;
1002
+ };
1003
+ const createAppDefinition = (def) => def;
1004
+ class ArriApp {
1005
+ constructor(opts = {}) {
1006
+ __publicField(this, "__isArri__", true);
1007
+ __publicField(this, "h3App");
1008
+ __publicField(this, "h3Router", h3.createRouter());
1009
+ __publicField(this, "_rpcDefinitionPath");
1010
+ __publicField(this, "_rpcRoutePrefix");
1011
+ __publicField(this, "appInfo");
1012
+ __publicField(this, "_procedures", {});
1013
+ __publicField(this, "_definitions", {});
1014
+ __publicField(this, "_middlewares", []);
1015
+ __publicField(this, "_onRequest");
1016
+ __publicField(this, "_onAfterResponse");
1017
+ __publicField(this, "_onBeforeResponse");
1018
+ __publicField(this, "_onError");
1019
+ __publicField(this, "_debug");
1020
+ __publicField(this, "definitionPath");
1021
+ this.appInfo = opts?.appInfo;
1022
+ this.h3App = h3.createApp({
1023
+ debug: opts?.debug
1024
+ });
1025
+ this._debug = opts.debug ?? false;
1026
+ this._onRequest = opts.onRequest;
1027
+ this._onError = opts.onError;
1028
+ this._onAfterResponse = opts.onAfterResponse;
1029
+ this._onBeforeResponse = opts.onBeforeResponse;
1030
+ this._rpcRoutePrefix = opts?.rpcRoutePrefix ?? "";
1031
+ this._rpcDefinitionPath = opts?.rpcDefinitionPath ?? "__definition";
1032
+ this.h3App.use(this.h3Router);
1033
+ this.definitionPath = this._rpcRoutePrefix ? `/${this._rpcRoutePrefix}/${this._rpcDefinitionPath}`.split("//").join("/") : `/${this._rpcDefinitionPath}`;
1034
+ this.h3Router.get(
1035
+ this.definitionPath,
1036
+ h3.eventHandler(() => this.getAppDefinition())
1037
+ );
1038
+ if (!opts.disableDefaultRoute) {
1039
+ this.route({
1040
+ method: ["get", "head"],
1041
+ path: "/",
1042
+ handler: (_) => {
1043
+ let schemaPath;
1044
+ if (this._rpcRoutePrefix) {
1045
+ schemaPath = `/${this._rpcRoutePrefix}/${this._rpcDefinitionPath}`;
1046
+ } else {
1047
+ schemaPath = `/${this._rpcDefinitionPath}`;
1048
+ }
1049
+ return {
1050
+ title: this.appInfo?.title ?? "Arri-RPC Server",
1051
+ description: this.appInfo?.description ?? "This server utilizes Arri-RPC. Visit the schema path to see all of the available procedures.",
1052
+ schemaPath,
1053
+ ...this.appInfo
1054
+ };
1055
+ }
1056
+ });
1057
+ }
1058
+ if (process.env.ARRI_DEV_MODE === "true") {
1059
+ this.h3Router.get(
1060
+ arri.DEV_DEFINITION_ENDPOINT,
1061
+ h3.eventHandler(() => this.getAppDefinition())
1062
+ );
1063
+ }
1064
+ this.h3Router.use(
1065
+ "/**",
1066
+ h3.eventHandler(async (event) => {
1067
+ h3.setResponseStatus(event, 404);
1068
+ const error = defineError(404);
1069
+ try {
1070
+ if (this._onRequest) {
1071
+ await this._onRequest(event);
1072
+ }
1073
+ } catch (err) {
1074
+ await handleH3Error(err, event, this._onError, this._debug);
1075
+ }
1076
+ if (event.handled) {
1077
+ return;
1078
+ }
1079
+ return handleH3Error(error, event, this._onError, this._debug);
1080
+ })
1081
+ );
1082
+ }
1083
+ use(input) {
1084
+ if (typeof input === "object" && input instanceof ArriRouter) {
1085
+ for (const route of input.getRoutes()) {
1086
+ this.route(route);
1087
+ }
1088
+ for (const rpc of input.getProcedures()) {
1089
+ if (rpc.transport === "http") {
1090
+ this.rpc(rpc);
1091
+ } else {
1092
+ this.wsRpc(rpc);
1093
+ }
1094
+ }
1095
+ this.registerDefinitions(input.getDefinitions());
1096
+ return;
1097
+ }
1098
+ this._middlewares.push(input);
1099
+ }
1100
+ rpc(procedure) {
1101
+ procedure.transport = "http";
1102
+ const p = procedure;
1103
+ const path = p.path ?? getRpcPath(p.name, this._rpcRoutePrefix);
1104
+ this._procedures[p.name] = createHttpRpcDefinition(p.name, path, p);
1105
+ if (isRpcParamSchema(p.params)) {
1106
+ const paramName = getRpcParamName(p.name, p);
1107
+ if (paramName) {
1108
+ this._definitions[paramName] = p.params;
1109
+ }
1110
+ }
1111
+ if (isRpcParamSchema(p.response)) {
1112
+ const responseName = getRpcResponseName(p.name, p);
1113
+ if (responseName) {
1114
+ this._definitions[responseName] = p.response;
1115
+ }
1116
+ }
1117
+ if (isEventStreamRpc(p)) {
1118
+ registerEventStreamRpc(this.h3Router, path, p, {
1119
+ middleware: this._middlewares,
1120
+ onRequest: this._onRequest,
1121
+ onError: this._onError,
1122
+ onAfterResponse: this._onAfterResponse,
1123
+ onBeforeResponse: this._onBeforeResponse,
1124
+ debug: this._debug
1125
+ });
1126
+ return;
1127
+ }
1128
+ registerRpc(this.h3Router, path, p, {
1129
+ middleware: this._middlewares,
1130
+ onRequest: this._onRequest,
1131
+ onError: this._onError,
1132
+ onAfterResponse: this._onAfterResponse,
1133
+ onBeforeResponse: this._onBeforeResponse,
1134
+ debug: this._debug
1135
+ });
1136
+ }
1137
+ wsRpc(procedure) {
1138
+ procedure.transport = "ws";
1139
+ const p = procedure;
1140
+ const path = procedure.path ?? getRpcPath(procedure.name, this._rpcRoutePrefix);
1141
+ this._procedures[procedure.name] = createWsRpcDefinition(
1142
+ procedure.name,
1143
+ path,
1144
+ p
1145
+ );
1146
+ if (isRpcParamSchema(procedure.params)) {
1147
+ const paramName = getRpcParamName(procedure.name, p);
1148
+ if (paramName) {
1149
+ this._definitions[paramName] = procedure.params;
1150
+ }
1151
+ }
1152
+ if (isRpcParamSchema(procedure.response)) {
1153
+ const responseName = getRpcResponseName(procedure.name, p);
1154
+ if (responseName) {
1155
+ this._definitions[responseName] = procedure.response;
1156
+ }
1157
+ }
1158
+ registerWebsocketRpc(this.h3Router, path, p);
1159
+ }
1160
+ route(route) {
1161
+ registerRoute(this.h3Router, route, {
1162
+ middleware: this._middlewares,
1163
+ onRequest: this._onRequest,
1164
+ onError: this._onError,
1165
+ onAfterResponse: this._onAfterResponse,
1166
+ onBeforeResponse: this._onBeforeResponse,
1167
+ debug: this._debug
1168
+ });
1169
+ }
1170
+ registerDefinitions(definitions) {
1171
+ for (const key of Object.keys(definitions)) {
1172
+ this._definitions[key] = definitions[key];
1173
+ }
1174
+ }
1175
+ getAppDefinition() {
1176
+ const appDef = {
1177
+ arriSchemaVersion: codegenUtils.SCHEMA_VERSION,
1178
+ info: this.appInfo,
1179
+ procedures: {},
1180
+ definitions: this._definitions
1181
+ };
1182
+ for (const key of Object.keys(this._procedures)) {
1183
+ const rpc = this._procedures[key];
1184
+ appDef.procedures[key] = rpc;
1185
+ }
1186
+ return appDef;
1187
+ }
1188
+ }
1189
+
1190
+ const defineMiddleware = (middleware) => middleware;
1191
+
1192
+ exports.ArriApp = ArriApp;
1193
+ exports.ArriRouter = ArriRouter;
1194
+ exports.ArriServerError = ArriServerError;
1195
+ exports.ArriServerErrorResponse = ArriServerErrorResponse;
1196
+ exports.EventStreamConnection = EventStreamConnection;
1197
+ exports.WsPeer = WsPeer;
1198
+ exports.createAppDefinition = createAppDefinition;
1199
+ exports.createHttpRpcDefinition = createHttpRpcDefinition;
1200
+ exports.createWsRpcDefinition = createWsRpcDefinition;
1201
+ exports.defineError = defineError;
1202
+ exports.defineEventStreamRpc = defineEventStreamRpc;
1203
+ exports.defineMiddleware = defineMiddleware;
1204
+ exports.defineRoute = defineRoute;
1205
+ exports.defineRpc = defineRpc;
1206
+ exports.defineWebsocketRpc = defineWebsocketRpc;
1207
+ exports.errorResponseFromValidationErrors = errorResponseFromValidationErrors;
1208
+ exports.getRpcParamName = getRpcParamName;
1209
+ exports.getRpcPath = getRpcPath;
1210
+ exports.getRpcResponseName = getRpcResponseName;
1211
+ exports.handleH3Error = handleH3Error;
1212
+ exports.handleRoute = handleRoute;
1213
+ exports.isEventStreamRpc = isEventStreamRpc;
1214
+ exports.isRpc = isRpc;
1215
+ exports.isRpcParamSchema = isRpcParamSchema;
1216
+ exports.registerEventStreamRpc = registerEventStreamRpc;
1217
+ exports.registerRoute = registerRoute;
1218
+ exports.registerRpc = registerRpc;
1219
+ exports.registerWebsocketRpc = registerWebsocketRpc;
1220
+ exports.validateRpcRequestInput = validateRpcRequestInput;
1221
+ Object.keys(h3).forEach(function (k) {
1222
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = h3[k];
1223
+ });