arri 0.13.0 → 0.15.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.
@@ -210,28 +210,25 @@ function isRpcParamSchema(input) {
210
210
  return arriValidate.isAObjectSchema(input) || arriValidate.isARecordSchema(input) || arriValidate.isADiscriminatorSchema(input);
211
211
  }
212
212
  function isRpc(input) {
213
- if (typeof input !== "object" || input === null) {
214
- return false;
215
- }
216
- const anyInput = input;
217
- if (!arriCodegenUtils.isRpcHttpMethod(anyInput.method)) {
218
- return false;
219
- }
220
- if (typeof anyInput.handler !== "function") {
221
- return false;
222
- }
223
- return true;
213
+ return typeof input === "object" && input !== null && "method" in input && arriCodegenUtils.isRpcHttpMethod(input.method) && "handler" in input && typeof input.handler === "function";
224
214
  }
225
215
  function defineRpc(config) {
226
216
  return config;
227
217
  }
228
218
  function createRpcDefinition(rpcName, httpPath, procedure) {
219
+ let method;
220
+ if (procedure.isEventStream === true) {
221
+ method = procedure.method ?? "get";
222
+ } else {
223
+ method = procedure.method ?? "post";
224
+ }
229
225
  return {
230
226
  description: procedure.description,
231
227
  path: httpPath,
232
- method: procedure.method ?? "post",
228
+ method,
233
229
  params: getRpcParamName(rpcName, procedure),
234
- response: getRpcResponseDefinition(rpcName, procedure)
230
+ response: getRpcResponseDefinition(rpcName, procedure),
231
+ isEventStream: procedure.isEventStream === true ? true : void 0
235
232
  };
236
233
  }
237
234
  function getRpcPath(rpcName, prefix = "") {
@@ -297,70 +294,11 @@ function registerRpc(router, path, procedure, opts) {
297
294
  }
298
295
  }
299
296
  if (isRpcParamSchema(procedure.params)) {
300
- switch (httpMethod) {
301
- case "get": {
302
- const parsedParams = await h3.getValidatedQuery(
303
- event,
304
- (input) => arriValidate.a.safeCoerce(procedure.params, input)
305
- );
306
- if (parsedParams.success) {
307
- event.context.params = parsedParams.value;
308
- } else {
309
- const errParts = [];
310
- for (const err of parsedParams.error.errors) {
311
- const errPath = err.instancePath.split("/");
312
- errPath.shift();
313
- const propName = errPath.join(".");
314
- if (!errParts.includes(propName)) {
315
- errParts.push(propName);
316
- }
317
- }
318
- const message = `Missing or invalid url query parameters: [${errParts.join(
319
- ", "
320
- )}]`;
321
- throw defineError(400, {
322
- statusMessage: message,
323
- data: parsedParams.error
324
- });
325
- }
326
- break;
327
- }
328
- case "delete":
329
- case "patch":
330
- case "post":
331
- case "put": {
332
- const body = await h3.readRawBody(event);
333
- if (!body) {
334
- throw defineError(400, {
335
- statusMessage: `Invalid request body. Expected object. Got undefined.`
336
- });
337
- }
338
- const parsedParams = arriValidate.a.safeParse(
339
- procedure.params,
340
- body
341
- );
342
- if (!parsedParams.success) {
343
- const errorParts = [];
344
- for (const err of parsedParams.error.errors) {
345
- const errPath = err.instancePath.split("/");
346
- errPath.shift();
347
- if (!errorParts.includes(errPath.join("."))) {
348
- errorParts.push(errPath.join("."));
349
- }
350
- }
351
- throw defineError(400, {
352
- statusMessage: `Invalid request body. Affected properties [${errorParts.join(
353
- ", "
354
- )}]`,
355
- data: parsedParams.error
356
- });
357
- }
358
- event.context.params = parsedParams.value;
359
- break;
360
- }
361
- default:
362
- break;
363
- }
297
+ await validateRpcRequestInput(
298
+ event,
299
+ httpMethod,
300
+ procedure.params
301
+ );
364
302
  }
365
303
  const response = await procedure.handler(
366
304
  event.context,
@@ -410,29 +348,297 @@ function registerRpc(router, path, procedure, opts) {
410
348
  break;
411
349
  }
412
350
  }
351
+ async function validateRpcRequestInput(event, httpMethod, schema) {
352
+ switch (httpMethod) {
353
+ case "get": {
354
+ const parsedParams = await h3.getValidatedQuery(
355
+ event,
356
+ (input) => arriValidate.a.safeCoerce(schema, input)
357
+ );
358
+ if (parsedParams.success) {
359
+ event.context.params = parsedParams.value;
360
+ } else {
361
+ const errParts = [];
362
+ for (const err of parsedParams.error.errors) {
363
+ const errPath = err.instancePath.split("/");
364
+ errPath.shift();
365
+ const propName = errPath.join(".");
366
+ if (!errParts.includes(propName)) {
367
+ errParts.push(propName);
368
+ }
369
+ }
370
+ const message = `Missing or invalid url query parameters: [${errParts.join(
371
+ ", "
372
+ )}]`;
373
+ throw defineError(400, {
374
+ statusMessage: message,
375
+ data: parsedParams.error
376
+ });
377
+ }
378
+ break;
379
+ }
380
+ case "delete":
381
+ case "patch":
382
+ case "post":
383
+ case "put": {
384
+ const body = await h3.readRawBody(event);
385
+ if (!body) {
386
+ throw defineError(400, {
387
+ statusMessage: `Invalid request body. Expected object. Got undefined.`
388
+ });
389
+ }
390
+ const parsedParams = arriValidate.a.safeParse(schema, body);
391
+ if (!parsedParams.success) {
392
+ const errorParts = [];
393
+ for (const err of parsedParams.error.errors) {
394
+ const errPath = err.instancePath.split("/");
395
+ errPath.shift();
396
+ if (!errorParts.includes(errPath.join("."))) {
397
+ errorParts.push(errPath.join("."));
398
+ }
399
+ }
400
+ throw defineError(400, {
401
+ statusMessage: `Invalid request body. Affected properties [${errorParts.join(
402
+ ", "
403
+ )}]`,
404
+ data: parsedParams.error
405
+ });
406
+ }
407
+ event.context.params = parsedParams.value;
408
+ break;
409
+ }
410
+ }
411
+ }
413
412
 
414
- var __defProp$1 = Object.defineProperty;
415
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
416
- var __publicField$1 = (obj, key, value) => {
417
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
413
+ var __defProp$2 = Object.defineProperty;
414
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
415
+ var __publicField$2 = (obj, key, value) => {
416
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
418
417
  return value;
419
418
  };
420
- class ArriRouter {
421
- constructor() {
422
- __publicField$1(this, "procedures", []);
423
- __publicField$1(this, "routes", []);
419
+ function setSseHeaders(event) {
420
+ h3.setHeaders(event, {
421
+ "Transfer-Encoding": "chunked",
422
+ "Content-Type": "text/event-stream",
423
+ Connection: "keep-alive",
424
+ "Cache-Control": "no-cache"
425
+ });
426
+ }
427
+ function defineEventStreamRpc(config) {
428
+ return {
429
+ ...config,
430
+ method: config.method ?? "get",
431
+ isEventStream: true
432
+ };
433
+ }
434
+ function isEventStreamRpc(input) {
435
+ return isRpc(input) && "isEventStream" in input && input.isEventStream === true;
436
+ }
437
+ function formatSse({ id, data, event }) {
438
+ const parts = [];
439
+ if (id) {
440
+ parts.push(`id: ${id}`);
424
441
  }
425
- rpc(procedure) {
426
- this.procedures.push(procedure);
442
+ if (event) {
443
+ parts.push(`event: ${event}`);
427
444
  }
428
- route(route) {
429
- this.routes.push(route);
445
+ parts.push(`data: ${data}`);
446
+ const payload = `${parts.join("\n")}
447
+
448
+ `;
449
+ return payload;
450
+ }
451
+ function formatSseList(events) {
452
+ let output = "";
453
+ for (const event of events) {
454
+ output += formatSse(event);
430
455
  }
431
- getProcedures() {
432
- return this.procedures;
456
+ return output;
457
+ }
458
+ class EventStreamConnection {
459
+ constructor(event, opts) {
460
+ __publicField$2(this, "lastEventId");
461
+ __publicField$2(this, "writable");
462
+ __publicField$2(this, "readable");
463
+ __publicField$2(this, "writer");
464
+ __publicField$2(this, "encoder");
465
+ __publicField$2(this, "serializer");
466
+ __publicField$2(this, "h3Event");
467
+ __publicField$2(this, "pingInterval");
468
+ __publicField$2(this, "pingIntervalMs");
469
+ this.h3Event = event;
470
+ setSseHeaders(this.h3Event);
471
+ h3.setResponseStatus(this.h3Event, 200);
472
+ const id = h3.getHeader(event, "Last-Event-ID");
473
+ this.lastEventId = id;
474
+ const { readable, writable } = new TransformStream();
475
+ this.writable = writable;
476
+ this.readable = readable;
477
+ this.writer = writable.getWriter();
478
+ this.encoder = new TextEncoder();
479
+ this.pingIntervalMs = opts.pingInterval ?? 6e4;
480
+ this.serializer = opts.serializer;
433
481
  }
434
- getRoutes() {
435
- return this.routes;
482
+ /**
483
+ * Start sending the event stream to the client
484
+ */
485
+ start() {
486
+ this.h3Event._handled = true;
487
+ void h3.sendStream(this.h3Event, this.readable);
488
+ this.pingInterval = setInterval(async () => {
489
+ await this.publishEvent({
490
+ id: this.lastEventId,
491
+ event: "ping",
492
+ data: ""
493
+ });
494
+ }, this.pingIntervalMs);
495
+ }
496
+ async push(data, eventId) {
497
+ if (Array.isArray(data)) {
498
+ const events = [];
499
+ for (const item of data) {
500
+ events.push({
501
+ id: eventId,
502
+ event: "message",
503
+ data: this.serializer(item)
504
+ });
505
+ }
506
+ await this.publishEvents(events);
507
+ return;
508
+ }
509
+ await this.publishEvent({
510
+ id: eventId,
511
+ event: "message",
512
+ data: this.serializer(data)
513
+ });
514
+ }
515
+ // /**
516
+ // * Push a custom event. These events will need to be parsed manually using the `onEvent` hooks of any generated clients.
517
+ // * Note events with the name "error" or "message" cannot be used for custom events.
518
+ // */
519
+ // async pushCustomEvent(event: SseEvent): Promise<void> {
520
+ // if (event.event === "message") {
521
+ // throw new Error(
522
+ // `Event type "message" is the default event type. Therefore it cannot be used when pushing custom events.`,
523
+ // );
524
+ // }
525
+ // if (event.event === "error") {
526
+ // throw new Error(
527
+ // `Event type "error" is reserved for the pushError() method. Therefore it cannot be used when pushing custom events.`,
528
+ // );
529
+ // }
530
+ // }
531
+ /**
532
+ * Publish an error event. This will trigger the `onError` hooks of any connected clients.
533
+ */
534
+ async pushError(error, eventId) {
535
+ await this.publishEvent({
536
+ id: eventId,
537
+ event: "error",
538
+ data: JSON.stringify(error)
539
+ });
540
+ }
541
+ async publishEvents(events) {
542
+ const payload = formatSseList(events);
543
+ await this.writer.write(this.encoder.encode(payload));
544
+ }
545
+ async publishEvent(event) {
546
+ const payload = formatSse(event);
547
+ await this.writer.write(this.encoder.encode(payload));
548
+ }
549
+ async cleanup() {
550
+ if (this.pingInterval) {
551
+ clearInterval(this.pingInterval);
552
+ }
553
+ try {
554
+ await this.writer.close();
555
+ } catch (_) {
556
+ }
557
+ }
558
+ /**
559
+ * Tell clients that the stream has ended and close the connection.
560
+ */
561
+ async end() {
562
+ await this.publishEvent({
563
+ event: "done",
564
+ data: "this stream has ended"
565
+ }).catch();
566
+ this.h3Event.node.res.end();
567
+ await this.cleanup();
568
+ }
569
+ on(event, callback) {
570
+ switch (event) {
571
+ case "disconnect":
572
+ this.h3Event.node.req.on("close", async () => {
573
+ await callback();
574
+ await this.cleanup();
575
+ });
576
+ break;
577
+ case "end":
578
+ this.h3Event.node.req.on("end", callback);
579
+ break;
580
+ }
581
+ }
582
+ }
583
+ function registerEventStreamRpc(router, path, procedure, opts) {
584
+ let responseValidator;
585
+ try {
586
+ responseValidator = procedure.response ? arriValidate.a.compile(procedure.response) : void 0;
587
+ } catch (err) {
588
+ console.error("ERROR COMPILING VALIDATOR", err);
589
+ }
590
+ const httpMethod = procedure.method ?? "get";
591
+ const handler = h3.eventHandler(async (event) => {
592
+ event.context.rpcName = procedure.name;
593
+ if (h3.isPreflightRequest(event)) {
594
+ return "ok";
595
+ }
596
+ try {
597
+ if (opts.onRequest) {
598
+ await opts.onRequest(event);
599
+ }
600
+ if (opts.middleware.length) {
601
+ for (const m of opts.middleware) {
602
+ await m(event);
603
+ }
604
+ }
605
+ if (isRpcParamSchema(procedure.params)) {
606
+ await validateRpcRequestInput(
607
+ event,
608
+ httpMethod,
609
+ procedure.params
610
+ );
611
+ }
612
+ const connection = new EventStreamConnection(event, {
613
+ pingInterval: procedure.pingInterval,
614
+ serializer: responseValidator?.serialize ?? function(_) {
615
+ return "";
616
+ }
617
+ });
618
+ event.context.connection = connection;
619
+ await procedure.handler(event.context, event);
620
+ } catch (err) {
621
+ await handleH3Error(err, event, opts.onError);
622
+ }
623
+ return "";
624
+ });
625
+ switch (httpMethod) {
626
+ case "get":
627
+ router.get(path, handler);
628
+ break;
629
+ case "delete":
630
+ router.delete(path, handler);
631
+ break;
632
+ case "patch":
633
+ router.patch(path, handler);
634
+ break;
635
+ case "put":
636
+ router.put(path, handler);
637
+ break;
638
+ case "post":
639
+ default:
640
+ router.post(path, handler);
641
+ break;
436
642
  }
437
643
  }
438
644
 
@@ -557,6 +763,40 @@ function handleRoute(router, method, route, opts) {
557
763
  }
558
764
  }
559
765
 
766
+ var __defProp$1 = Object.defineProperty;
767
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
768
+ var __publicField$1 = (obj, key, value) => {
769
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
770
+ return value;
771
+ };
772
+ class ArriRouter {
773
+ constructor() {
774
+ __publicField$1(this, "procedures", []);
775
+ __publicField$1(this, "routes", []);
776
+ __publicField$1(this, "models", {});
777
+ }
778
+ rpc(procedure) {
779
+ this.procedures.push(procedure);
780
+ }
781
+ route(route) {
782
+ this.routes.push(route);
783
+ }
784
+ registerModels(models) {
785
+ for (const key of Object.keys(models)) {
786
+ this.models[key] = models[key];
787
+ }
788
+ }
789
+ getProcedures() {
790
+ return this.procedures;
791
+ }
792
+ getRoutes() {
793
+ return this.routes;
794
+ }
795
+ getModels() {
796
+ return this.models;
797
+ }
798
+ }
799
+
560
800
  var __defProp = Object.defineProperty;
561
801
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
562
802
  var __publicField = (obj, key, value) => {
@@ -610,11 +850,6 @@ class ArriApp {
610
850
  if (this.onRequest) {
611
851
  await this.onRequest(event);
612
852
  }
613
- if (this.middlewares.length) {
614
- for (const m of this.middlewares) {
615
- await m(event);
616
- }
617
- }
618
853
  } catch (err) {
619
854
  await handleH3Error(err, event, this.onError);
620
855
  }
@@ -639,6 +874,7 @@ class ArriApp {
639
874
  for (const rpc of input.getProcedures()) {
640
875
  this.rpc(rpc);
641
876
  }
877
+ this.registerModels(input.getModels());
642
878
  return;
643
879
  }
644
880
  this.middlewares.push(input);
@@ -662,6 +898,16 @@ class ArriApp {
662
898
  this.models[responseName] = procedure.response;
663
899
  }
664
900
  }
901
+ if (isEventStreamRpc(procedure)) {
902
+ registerEventStreamRpc(this.h3Router, path, procedure, {
903
+ middleware: this.middlewares,
904
+ onRequest: this.onRequest,
905
+ onError: this.onError,
906
+ onAfterResponse: this.onAfterResponse,
907
+ onBeforeResponse: this.onBeforeResponse
908
+ });
909
+ return;
910
+ }
665
911
  registerRpc(this.h3Router, path, procedure, {
666
912
  middleware: this.middlewares,
667
913
  onRequest: this.onRequest,
@@ -679,6 +925,11 @@ class ArriApp {
679
925
  onBeforeResponse: this.onBeforeResponse
680
926
  });
681
927
  }
928
+ registerModels(models) {
929
+ for (const key of Object.keys(models)) {
930
+ this.models[key] = models[key];
931
+ }
932
+ }
682
933
  getAppDefinition() {
683
934
  const appDef = {
684
935
  arriSchemaVersion: "0.0.2",
@@ -738,21 +989,29 @@ exports.ArriRouter = ArriRouter;
738
989
  exports.DEV_DEFINITION_ENDPOINT = DEV_DEFINITION_ENDPOINT;
739
990
  exports.DEV_ENDPOINT_ROOT = DEV_ENDPOINT_ROOT;
740
991
  exports.ErrorResponse = ErrorResponse;
992
+ exports.EventStreamConnection = EventStreamConnection;
741
993
  exports.createRpcDefinition = createRpcDefinition;
742
994
  exports.defaultConfig = defaultConfig;
743
995
  exports.defineConfig = defineConfig;
744
996
  exports.defineError = defineError;
997
+ exports.defineEventStreamRpc = defineEventStreamRpc;
745
998
  exports.defineRoute = defineRoute;
746
999
  exports.defineRpc = defineRpc;
747
1000
  exports.errorResponseFromValidationErrors = errorResponseFromValidationErrors;
1001
+ exports.formatSse = formatSse;
1002
+ exports.formatSseList = formatSseList;
748
1003
  exports.getRpcParamName = getRpcParamName;
749
1004
  exports.getRpcPath = getRpcPath;
750
1005
  exports.getRpcResponseName = getRpcResponseName;
751
1006
  exports.handleH3Error = handleH3Error;
752
1007
  exports.handleRoute = handleRoute;
753
1008
  exports.isArriConfig = isArriConfig;
1009
+ exports.isEventStreamRpc = isEventStreamRpc;
754
1010
  exports.isResolvedArriConfig = isResolvedArriConfig;
755
1011
  exports.isRpc = isRpc;
756
1012
  exports.isRpcParamSchema = isRpcParamSchema;
1013
+ exports.registerEventStreamRpc = registerEventStreamRpc;
757
1014
  exports.registerRoute = registerRoute;
758
1015
  exports.registerRpc = registerRpc;
1016
+ exports.setSseHeaders = setSseHeaders;
1017
+ exports.validateRpcRequestInput = validateRpcRequestInput;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arri",
3
- "version": "0.13.0",
3
+ "version": "0.15.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -30,7 +30,7 @@
30
30
  "citty": "^0.1.5",
31
31
  "consola": "^3.2.3",
32
32
  "degit": "^2.8.4",
33
- "esbuild": "^0.19.7",
33
+ "esbuild": "^0.19.8",
34
34
  "esbuild-plugin-replace": "^1.4.0",
35
35
  "globby": "^14.0.0",
36
36
  "h3": "^1.9.0",
@@ -39,16 +39,17 @@
39
39
  "pathe": "^1.1.1",
40
40
  "prettier": "^3.1.0",
41
41
  "scule": "^1.1.0",
42
- "arri-codegen-dart": "0.13.0",
43
- "arri-codegen-utils": "0.13.0",
44
- "arri-codegen-ts": "0.13.0",
45
- "arri-validate": "0.13.0"
42
+ "uncrypto": "^0.1.3",
43
+ "arri-codegen-dart": "0.15.1",
44
+ "arri-codegen-ts": "0.15.1",
45
+ "arri-codegen-utils": "0.15.1",
46
+ "arri-validate": "0.15.1"
46
47
  },
47
48
  "peerDependencies": {
48
49
  "listhen": "1.x"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@types/degit": "^2.8.6",
52
- "bun-types": "^1.0.13"
53
+ "bun-types": "^1.0.14"
53
54
  }
54
55
  }