arri 0.24.1 → 0.26.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/README.md CHANGED
@@ -259,22 +259,24 @@ export default defineEventStreamRpc({
259
259
  createdAt: a.timestamp(),
260
260
  updatedAt: a.timestamp(),
261
261
  }),
262
- handler({ params, connection }) {
262
+ handler({ params, stream }) {
263
+ // initialize the stream and send it to the client
264
+ stream.init();
265
+
263
266
  // send a message every second
264
267
  const interval = setInterval(async () => {
265
- await connection.push({
268
+ await stream.push({
266
269
  id: "1",
267
270
  name: "John Doe",
268
271
  createdAt: new Date(),
269
272
  updatedAt: new Date(),
270
273
  });
271
274
  }, 1000);
275
+
272
276
  // cleanup when the client disconnects
273
- connection.on("disconnect", () => {
277
+ stream.on("close", () => {
274
278
  clearInterval(interval);
275
279
  });
276
- // start streaming events to the client
277
- connection.start();
278
280
  },
279
281
  });
280
282
  ```
@@ -282,11 +284,11 @@ export default defineEventStreamRpc({
282
284
  #### EventStreamConnection methods
283
285
 
284
286
  ```ts
285
- connection.push(data: Data, eventId?: string)
286
- connection.pushError(error: ArriRequestError, eventId?: string)
287
- connection.start()
288
- connection.end()
289
- connection.on(e: 'disconnect' | 'end', callback: () => any)
287
+ stream.push(data: Data, eventId?: string)
288
+ stream.pushError(error: ArriRequestError, eventId?: string)
289
+ stream.init()
290
+ stream.end()
291
+ stream.on(e: 'request:close' | 'close', callback: () => any)
290
292
  ```
291
293
 
292
294
  ### Adding Non-RPC Routes
@@ -491,7 +493,7 @@ arri build [flags]
491
493
  arri init [dir]
492
494
  ```
493
495
 
494
- ## Developing
496
+ ## Development
495
497
 
496
498
  ### Building
497
499
 
package/dist/cli.cjs CHANGED
@@ -12,7 +12,7 @@ const esbuild = require('esbuild');
12
12
  const esbuildPluginReplace = require('esbuild-plugin-replace');
13
13
  const path = require('pathe');
14
14
  const prettier = require('prettier');
15
- const config = require('./shared/arri.f4b47a61.cjs');
15
+ const config = require('./shared/arri.593bcad9.cjs');
16
16
  const globby = require('globby');
17
17
  const scule = require('scule');
18
18
  const arriCodegenDart = require('arri-codegen-dart');
@@ -22,7 +22,7 @@ const chokidar = require('chokidar');
22
22
  const Degit = require('degit');
23
23
  require('h3');
24
24
  require('arri-validate');
25
- require('node:process');
25
+ require('h3-sse');
26
26
 
27
27
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
28
28
 
@@ -49,9 +49,9 @@ const Degit__default = /*#__PURE__*/_interopDefaultCompat(Degit);
49
49
 
50
50
  const GEN_APP_FILE = "__arri_app.js";
51
51
  const GEN_SERVER_ENTRY_FILE = "__arri_server.js";
52
- const OUT_APP_FILE = "app.js";
53
- const OUT_SERVER_ENTRY = "server.js";
54
- const OUT_CODEGEN = "codegen.js";
52
+ const OUT_APP_FILE = "app.mjs";
53
+ const OUT_SERVER_ENTRY = "server.mjs";
54
+ const OUT_CODEGEN = "codegen.mjs";
55
55
  async function setupWorkingDir(config) {
56
56
  const arriDir = path__default.resolve(config.rootDir, config.buildDir);
57
57
  const outDir = path__default.resolve(config.rootDir, ".output");
@@ -294,7 +294,7 @@ async function createServerEntryFile(config) {
294
294
  js: `import { createRequire as topLevelCreateRequire } from 'module';
295
295
  const require = topLevelCreateRequire(import.meta.url);`
296
296
  },
297
- plugins: [esbuildPluginReplace.replace({ "virtual:arri/app": "./app.js" })],
297
+ plugins: [esbuildPluginReplace.replace({ "virtual:arri/app": "./app.mjs" })],
298
298
  allowOverwrite: true,
299
299
  outfile: path__default.resolve(config.rootDir, ".output", OUT_SERVER_ENTRY)
300
300
  });
package/dist/cli.mjs CHANGED
@@ -11,7 +11,7 @@ import { build as build$1 } from 'esbuild';
11
11
  import { replace } from 'esbuild-plugin-replace';
12
12
  import path from 'pathe';
13
13
  import prettier from 'prettier';
14
- import { n as defaultConfig, m as isResolvedArriConfig, a as DEV_DEFINITION_ENDPOINT } from './shared/arri.ebc63bd6.mjs';
14
+ import { n as defaultConfig, m as isResolvedArriConfig, a as DEV_DEFINITION_ENDPOINT } from './shared/arri.21567b92.mjs';
15
15
  import { globby } from 'globby';
16
16
  import { camelCase, kebabCase } from 'scule';
17
17
  import { dartClientGenerator } from 'arri-codegen-dart';
@@ -21,13 +21,13 @@ import chokidar from 'chokidar';
21
21
  import Degit from 'degit';
22
22
  import 'h3';
23
23
  import 'arri-validate';
24
- import 'node:process';
24
+ import 'h3-sse';
25
25
 
26
26
  const GEN_APP_FILE = "__arri_app.js";
27
27
  const GEN_SERVER_ENTRY_FILE = "__arri_server.js";
28
- const OUT_APP_FILE = "app.js";
29
- const OUT_SERVER_ENTRY = "server.js";
30
- const OUT_CODEGEN = "codegen.js";
28
+ const OUT_APP_FILE = "app.mjs";
29
+ const OUT_SERVER_ENTRY = "server.mjs";
30
+ const OUT_CODEGEN = "codegen.mjs";
31
31
  async function setupWorkingDir(config) {
32
32
  const arriDir = path.resolve(config.rootDir, config.buildDir);
33
33
  const outDir = path.resolve(config.rootDir, ".output");
@@ -270,7 +270,7 @@ async function createServerEntryFile(config) {
270
270
  js: `import { createRequire as topLevelCreateRequire } from 'module';
271
271
  const require = topLevelCreateRequire(import.meta.url);`
272
272
  },
273
- plugins: [replace({ "virtual:arri/app": "./app.js" })],
273
+ plugins: [replace({ "virtual:arri/app": "./app.mjs" })],
274
274
  allowOverwrite: true,
275
275
  outfile: path.resolve(config.rootDir, ".output", OUT_SERVER_ENTRY)
276
276
  });
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const config = require('./shared/arri.f4b47a61.cjs');
3
+ const config = require('./shared/arri.593bcad9.cjs');
4
4
  const h3 = require('h3');
5
+ const h3Sse = require('h3-sse');
5
6
  require('arri-validate');
6
- require('node:process');
7
7
  require('arri-codegen-utils');
8
8
  require('scule');
9
9
 
@@ -23,8 +23,6 @@ exports.defineEventStreamRpc = config.defineEventStreamRpc;
23
23
  exports.defineRoute = config.defineRoute;
24
24
  exports.defineRpc = config.defineRpc;
25
25
  exports.errorResponseFromValidationErrors = config.errorResponseFromValidationErrors;
26
- exports.formatSse = config.formatSse;
27
- exports.formatSseList = config.formatSseList;
28
26
  exports.getRpcParamName = config.getRpcParamName;
29
27
  exports.getRpcPath = config.getRpcPath;
30
28
  exports.getRpcResponseName = config.getRpcResponseName;
@@ -38,9 +36,11 @@ exports.isRpcParamSchema = config.isRpcParamSchema;
38
36
  exports.registerEventStreamRpc = config.registerEventStreamRpc;
39
37
  exports.registerRoute = config.registerRoute;
40
38
  exports.registerRpc = config.registerRpc;
41
- exports.setSseHeaders = config.setSseHeaders;
42
39
  exports.validateRpcRequestInput = config.validateRpcRequestInput;
43
40
  exports.defineMiddleware = defineMiddleware;
44
41
  Object.keys(h3).forEach(function (k) {
45
42
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = h3[k];
46
43
  });
44
+ Object.keys(h3Sse).forEach(function (k) {
45
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = h3Sse[k];
46
+ });
package/dist/index.d.cts CHANGED
@@ -2,6 +2,8 @@ import { HttpMethod, RpcHttpMethod, RpcDefinition, SchemaFormProperties, SchemaF
2
2
  import { AObjectSchema, ASchema, InferType, a, ValidationError, ValueError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
3
3
  import { H3EventContext, H3Event, Router, H3Error, App } from 'h3';
4
4
  export * from 'h3';
5
+ import { EventStream } from 'h3-sse';
6
+ export * from 'h3-sse';
5
7
  import { BuildOptions } from 'esbuild';
6
8
 
7
9
  interface MiddlewareEventContext extends H3EventContext {
@@ -58,19 +60,15 @@ declare function errorResponseFromValidationErrors(errors: ValidationError["erro
58
60
  type StatusCode = number | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 507 | 511;
59
61
  declare function handleH3Error(err: unknown, event: H3Event, onError: ArriOptions["onError"]): Promise<void>;
60
62
 
61
- declare function setSseHeaders(event: H3Event): void;
62
63
  declare function defineEventStreamRpc<TParams extends RpcParamSchema | undefined = undefined, TResponse extends RpcParamSchema | undefined | never = undefined>(config: Omit<EventStreamRpc<TParams, TResponse>, "isEventStream">): EventStreamRpc<TParams, TResponse>;
63
64
  declare function isEventStreamRpc(input: unknown): input is EventStreamRpc<any, any>;
64
65
  interface EventStreamRpc<TParams extends RpcParamSchema | undefined = undefined, TResponse extends RpcParamSchema | undefined = undefined> extends Omit<Rpc<true, TParams, TResponse>, "handler" | "postHandler"> {
65
66
  isEventStream: true;
66
67
  handler: EventStreamRpcHandler<TParams extends RpcParamSchema ? InferType<TParams> : undefined, TResponse extends RpcParamSchema ? InferType<TResponse> : undefined>;
67
68
  }
68
- type EventStreamRpcHandler<TParams, TResponse> = (context: EventStreamRpcHandlerContext<TParams, TResponse>, event: EventStreamRpcEvent<TParams, TResponse>) => void | Promise<void>;
69
- interface EventStreamRpcEvent<TParams, TResponse> extends RpcEvent<TParams> {
70
- connection: EventStreamConnection<TResponse>;
71
- }
69
+ type EventStreamRpcHandler<TParams, TResponse> = (context: EventStreamRpcHandlerContext<TParams, TResponse>, event: RpcEvent<TResponse>) => void | Promise<void>;
72
70
  interface EventStreamRpcHandlerContext<TParams = any, TResponse = any> extends RpcHandlerContext<TParams> {
73
- connection: EventStreamConnection<TResponse>;
71
+ stream: EventStreamConnection<TResponse>;
74
72
  }
75
73
  interface EventStreamConnectionOptions<TData> {
76
74
  validator: (input: unknown) => input is TData;
@@ -78,34 +76,20 @@ interface EventStreamConnectionOptions<TData> {
78
76
  serializer: (input: TData) => string;
79
77
  pingInterval?: number;
80
78
  }
81
- /**
82
- * A server sent event
83
- */
84
- interface Sse {
85
- id?: string;
86
- event?: string;
87
- data: string;
88
- }
89
- declare function formatSse({ id, data, event }: Sse): string;
90
- declare function formatSseList(events: Sse[]): string;
91
79
  declare class EventStreamConnection<TData> {
92
- lastEventId: string | undefined;
93
- private readonly writable;
94
- private readonly readable;
95
- private readonly writer;
96
- private writerIsClosed;
97
- private readonly encoder;
80
+ readonly lastEventId?: string;
81
+ private readonly h3Event;
98
82
  private readonly validationErrors;
99
83
  private readonly validator;
100
84
  private readonly serializer;
101
- private readonly h3Event;
102
85
  private pingInterval;
103
86
  private readonly pingIntervalMs;
87
+ readonly eventStream: EventStream;
104
88
  constructor(event: H3Event, opts: EventStreamConnectionOptions<TData>);
105
89
  /**
106
- * Start sending the event stream to the client
90
+ * Initialize the stream. This must be called before sending any events.
107
91
  */
108
- start(): void;
92
+ init(): void;
109
93
  /**
110
94
  * Publish a new event. Events published with this hook will trigger the `onData()` hooks of any connected clients.
111
95
  */
@@ -115,15 +99,13 @@ declare class EventStreamConnection<TData> {
115
99
  * Publish an error event. This will trigger the `onError` hooks of any connected clients.
116
100
  */
117
101
  pushError(error: ErrorResponse, eventId?: string): Promise<void>;
118
- private publishEvents;
119
- private publishEvent;
120
102
  private cleanup;
121
103
  /**
122
104
  * Tell clients that the stream has ended and close the connection.
123
105
  */
124
- end(): Promise<void>;
125
- on(event: "disconnect", callback: () => any): void;
126
- on(event: "end", callback: () => any): void;
106
+ close(): Promise<void>;
107
+ on(event: "close", callback: () => any): void;
108
+ on(event: "request:close", callback: () => any): void;
127
109
  }
128
110
  declare function registerEventStreamRpc(router: Router, path: string, procedure: EventStreamRpc<any, any> & {
129
111
  name: string;
@@ -257,4 +239,4 @@ type ResolvedArriConfig = Required<ArriConfig>;
257
239
  declare const defaultConfig: Required<ArriConfig>;
258
240
  declare function defineConfig(config: ArriConfig): ResolvedArriConfig;
259
241
 
260
- export { ArriApp, type ArriConfig, type ArriOptions, type ArriRoute, ArriRouter, type ArriRouterBase, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, EventStreamConnection, type EventStreamConnectionOptions, type EventStreamRpc, type EventStreamRpcEvent, type EventStreamRpcHandler, type EventStreamRpcHandlerContext, type ExtractParam, type ExtractParams, type HandlerContext, type HttpRpc, type Middleware, type MiddlewareEvent, type MiddlewareEventContext, type ModelMap, type NamedRpc, type PostRouteEvent, type PostRouteEventContext, type ResolvedArriConfig, type RouteEvent, type RouteEventContext, type RouteOptions, type Rpc, type RpcEvent, type RpcHandler, type RpcHandlerContext, type RpcParamSchema, type RpcPostEvent, type RpcPostHandler, type RpcPostHandlerContext, type Sse, type StatusCode, createRpcDefinition, defaultConfig, defineConfig, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, formatSse, formatSseList, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, handleRoute, isArriConfig, isEventStreamRpc, isResolvedArriConfig, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, setSseHeaders, validateRpcRequestInput };
242
+ export { ArriApp, type ArriConfig, type ArriOptions, type ArriRoute, ArriRouter, type ArriRouterBase, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, EventStreamConnection, type EventStreamConnectionOptions, type EventStreamRpc, type EventStreamRpcHandler, type EventStreamRpcHandlerContext, type ExtractParam, type ExtractParams, type HandlerContext, type HttpRpc, type Middleware, type MiddlewareEvent, type MiddlewareEventContext, type ModelMap, type NamedRpc, type PostRouteEvent, type PostRouteEventContext, type ResolvedArriConfig, type RouteEvent, type RouteEventContext, type RouteOptions, type Rpc, type RpcEvent, type RpcHandler, type RpcHandlerContext, type RpcParamSchema, type RpcPostEvent, type RpcPostHandler, type RpcPostHandlerContext, type StatusCode, createRpcDefinition, defaultConfig, defineConfig, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, handleRoute, isArriConfig, isEventStreamRpc, isResolvedArriConfig, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
package/dist/index.d.mts CHANGED
@@ -2,6 +2,8 @@ import { HttpMethod, RpcHttpMethod, RpcDefinition, SchemaFormProperties, SchemaF
2
2
  import { AObjectSchema, ASchema, InferType, a, ValidationError, ValueError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
3
3
  import { H3EventContext, H3Event, Router, H3Error, App } from 'h3';
4
4
  export * from 'h3';
5
+ import { EventStream } from 'h3-sse';
6
+ export * from 'h3-sse';
5
7
  import { BuildOptions } from 'esbuild';
6
8
 
7
9
  interface MiddlewareEventContext extends H3EventContext {
@@ -58,19 +60,15 @@ declare function errorResponseFromValidationErrors(errors: ValidationError["erro
58
60
  type StatusCode = number | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 507 | 511;
59
61
  declare function handleH3Error(err: unknown, event: H3Event, onError: ArriOptions["onError"]): Promise<void>;
60
62
 
61
- declare function setSseHeaders(event: H3Event): void;
62
63
  declare function defineEventStreamRpc<TParams extends RpcParamSchema | undefined = undefined, TResponse extends RpcParamSchema | undefined | never = undefined>(config: Omit<EventStreamRpc<TParams, TResponse>, "isEventStream">): EventStreamRpc<TParams, TResponse>;
63
64
  declare function isEventStreamRpc(input: unknown): input is EventStreamRpc<any, any>;
64
65
  interface EventStreamRpc<TParams extends RpcParamSchema | undefined = undefined, TResponse extends RpcParamSchema | undefined = undefined> extends Omit<Rpc<true, TParams, TResponse>, "handler" | "postHandler"> {
65
66
  isEventStream: true;
66
67
  handler: EventStreamRpcHandler<TParams extends RpcParamSchema ? InferType<TParams> : undefined, TResponse extends RpcParamSchema ? InferType<TResponse> : undefined>;
67
68
  }
68
- type EventStreamRpcHandler<TParams, TResponse> = (context: EventStreamRpcHandlerContext<TParams, TResponse>, event: EventStreamRpcEvent<TParams, TResponse>) => void | Promise<void>;
69
- interface EventStreamRpcEvent<TParams, TResponse> extends RpcEvent<TParams> {
70
- connection: EventStreamConnection<TResponse>;
71
- }
69
+ type EventStreamRpcHandler<TParams, TResponse> = (context: EventStreamRpcHandlerContext<TParams, TResponse>, event: RpcEvent<TResponse>) => void | Promise<void>;
72
70
  interface EventStreamRpcHandlerContext<TParams = any, TResponse = any> extends RpcHandlerContext<TParams> {
73
- connection: EventStreamConnection<TResponse>;
71
+ stream: EventStreamConnection<TResponse>;
74
72
  }
75
73
  interface EventStreamConnectionOptions<TData> {
76
74
  validator: (input: unknown) => input is TData;
@@ -78,34 +76,20 @@ interface EventStreamConnectionOptions<TData> {
78
76
  serializer: (input: TData) => string;
79
77
  pingInterval?: number;
80
78
  }
81
- /**
82
- * A server sent event
83
- */
84
- interface Sse {
85
- id?: string;
86
- event?: string;
87
- data: string;
88
- }
89
- declare function formatSse({ id, data, event }: Sse): string;
90
- declare function formatSseList(events: Sse[]): string;
91
79
  declare class EventStreamConnection<TData> {
92
- lastEventId: string | undefined;
93
- private readonly writable;
94
- private readonly readable;
95
- private readonly writer;
96
- private writerIsClosed;
97
- private readonly encoder;
80
+ readonly lastEventId?: string;
81
+ private readonly h3Event;
98
82
  private readonly validationErrors;
99
83
  private readonly validator;
100
84
  private readonly serializer;
101
- private readonly h3Event;
102
85
  private pingInterval;
103
86
  private readonly pingIntervalMs;
87
+ readonly eventStream: EventStream;
104
88
  constructor(event: H3Event, opts: EventStreamConnectionOptions<TData>);
105
89
  /**
106
- * Start sending the event stream to the client
90
+ * Initialize the stream. This must be called before sending any events.
107
91
  */
108
- start(): void;
92
+ init(): void;
109
93
  /**
110
94
  * Publish a new event. Events published with this hook will trigger the `onData()` hooks of any connected clients.
111
95
  */
@@ -115,15 +99,13 @@ declare class EventStreamConnection<TData> {
115
99
  * Publish an error event. This will trigger the `onError` hooks of any connected clients.
116
100
  */
117
101
  pushError(error: ErrorResponse, eventId?: string): Promise<void>;
118
- private publishEvents;
119
- private publishEvent;
120
102
  private cleanup;
121
103
  /**
122
104
  * Tell clients that the stream has ended and close the connection.
123
105
  */
124
- end(): Promise<void>;
125
- on(event: "disconnect", callback: () => any): void;
126
- on(event: "end", callback: () => any): void;
106
+ close(): Promise<void>;
107
+ on(event: "close", callback: () => any): void;
108
+ on(event: "request:close", callback: () => any): void;
127
109
  }
128
110
  declare function registerEventStreamRpc(router: Router, path: string, procedure: EventStreamRpc<any, any> & {
129
111
  name: string;
@@ -257,4 +239,4 @@ type ResolvedArriConfig = Required<ArriConfig>;
257
239
  declare const defaultConfig: Required<ArriConfig>;
258
240
  declare function defineConfig(config: ArriConfig): ResolvedArriConfig;
259
241
 
260
- export { ArriApp, type ArriConfig, type ArriOptions, type ArriRoute, ArriRouter, type ArriRouterBase, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, EventStreamConnection, type EventStreamConnectionOptions, type EventStreamRpc, type EventStreamRpcEvent, type EventStreamRpcHandler, type EventStreamRpcHandlerContext, type ExtractParam, type ExtractParams, type HandlerContext, type HttpRpc, type Middleware, type MiddlewareEvent, type MiddlewareEventContext, type ModelMap, type NamedRpc, type PostRouteEvent, type PostRouteEventContext, type ResolvedArriConfig, type RouteEvent, type RouteEventContext, type RouteOptions, type Rpc, type RpcEvent, type RpcHandler, type RpcHandlerContext, type RpcParamSchema, type RpcPostEvent, type RpcPostHandler, type RpcPostHandlerContext, type Sse, type StatusCode, createRpcDefinition, defaultConfig, defineConfig, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, formatSse, formatSseList, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, handleRoute, isArriConfig, isEventStreamRpc, isResolvedArriConfig, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, setSseHeaders, validateRpcRequestInput };
242
+ export { ArriApp, type ArriConfig, type ArriOptions, type ArriRoute, ArriRouter, type ArriRouterBase, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, EventStreamConnection, type EventStreamConnectionOptions, type EventStreamRpc, type EventStreamRpcHandler, type EventStreamRpcHandlerContext, type ExtractParam, type ExtractParams, type HandlerContext, type HttpRpc, type Middleware, type MiddlewareEvent, type MiddlewareEventContext, type ModelMap, type NamedRpc, type PostRouteEvent, type PostRouteEventContext, type ResolvedArriConfig, type RouteEvent, type RouteEventContext, type RouteOptions, type Rpc, type RpcEvent, type RpcHandler, type RpcHandlerContext, type RpcParamSchema, type RpcPostEvent, type RpcPostHandler, type RpcPostHandlerContext, type StatusCode, createRpcDefinition, defaultConfig, defineConfig, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, handleRoute, isArriConfig, isEventStreamRpc, isResolvedArriConfig, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ import { HttpMethod, RpcHttpMethod, RpcDefinition, SchemaFormProperties, SchemaF
2
2
  import { AObjectSchema, ASchema, InferType, a, ValidationError, ValueError, ARecordSchema, ADiscriminatorSchema } from 'arri-validate';
3
3
  import { H3EventContext, H3Event, Router, H3Error, App } from 'h3';
4
4
  export * from 'h3';
5
+ import { EventStream } from 'h3-sse';
6
+ export * from 'h3-sse';
5
7
  import { BuildOptions } from 'esbuild';
6
8
 
7
9
  interface MiddlewareEventContext extends H3EventContext {
@@ -58,19 +60,15 @@ declare function errorResponseFromValidationErrors(errors: ValidationError["erro
58
60
  type StatusCode = number | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 507 | 511;
59
61
  declare function handleH3Error(err: unknown, event: H3Event, onError: ArriOptions["onError"]): Promise<void>;
60
62
 
61
- declare function setSseHeaders(event: H3Event): void;
62
63
  declare function defineEventStreamRpc<TParams extends RpcParamSchema | undefined = undefined, TResponse extends RpcParamSchema | undefined | never = undefined>(config: Omit<EventStreamRpc<TParams, TResponse>, "isEventStream">): EventStreamRpc<TParams, TResponse>;
63
64
  declare function isEventStreamRpc(input: unknown): input is EventStreamRpc<any, any>;
64
65
  interface EventStreamRpc<TParams extends RpcParamSchema | undefined = undefined, TResponse extends RpcParamSchema | undefined = undefined> extends Omit<Rpc<true, TParams, TResponse>, "handler" | "postHandler"> {
65
66
  isEventStream: true;
66
67
  handler: EventStreamRpcHandler<TParams extends RpcParamSchema ? InferType<TParams> : undefined, TResponse extends RpcParamSchema ? InferType<TResponse> : undefined>;
67
68
  }
68
- type EventStreamRpcHandler<TParams, TResponse> = (context: EventStreamRpcHandlerContext<TParams, TResponse>, event: EventStreamRpcEvent<TParams, TResponse>) => void | Promise<void>;
69
- interface EventStreamRpcEvent<TParams, TResponse> extends RpcEvent<TParams> {
70
- connection: EventStreamConnection<TResponse>;
71
- }
69
+ type EventStreamRpcHandler<TParams, TResponse> = (context: EventStreamRpcHandlerContext<TParams, TResponse>, event: RpcEvent<TResponse>) => void | Promise<void>;
72
70
  interface EventStreamRpcHandlerContext<TParams = any, TResponse = any> extends RpcHandlerContext<TParams> {
73
- connection: EventStreamConnection<TResponse>;
71
+ stream: EventStreamConnection<TResponse>;
74
72
  }
75
73
  interface EventStreamConnectionOptions<TData> {
76
74
  validator: (input: unknown) => input is TData;
@@ -78,34 +76,20 @@ interface EventStreamConnectionOptions<TData> {
78
76
  serializer: (input: TData) => string;
79
77
  pingInterval?: number;
80
78
  }
81
- /**
82
- * A server sent event
83
- */
84
- interface Sse {
85
- id?: string;
86
- event?: string;
87
- data: string;
88
- }
89
- declare function formatSse({ id, data, event }: Sse): string;
90
- declare function formatSseList(events: Sse[]): string;
91
79
  declare class EventStreamConnection<TData> {
92
- lastEventId: string | undefined;
93
- private readonly writable;
94
- private readonly readable;
95
- private readonly writer;
96
- private writerIsClosed;
97
- private readonly encoder;
80
+ readonly lastEventId?: string;
81
+ private readonly h3Event;
98
82
  private readonly validationErrors;
99
83
  private readonly validator;
100
84
  private readonly serializer;
101
- private readonly h3Event;
102
85
  private pingInterval;
103
86
  private readonly pingIntervalMs;
87
+ readonly eventStream: EventStream;
104
88
  constructor(event: H3Event, opts: EventStreamConnectionOptions<TData>);
105
89
  /**
106
- * Start sending the event stream to the client
90
+ * Initialize the stream. This must be called before sending any events.
107
91
  */
108
- start(): void;
92
+ init(): void;
109
93
  /**
110
94
  * Publish a new event. Events published with this hook will trigger the `onData()` hooks of any connected clients.
111
95
  */
@@ -115,15 +99,13 @@ declare class EventStreamConnection<TData> {
115
99
  * Publish an error event. This will trigger the `onError` hooks of any connected clients.
116
100
  */
117
101
  pushError(error: ErrorResponse, eventId?: string): Promise<void>;
118
- private publishEvents;
119
- private publishEvent;
120
102
  private cleanup;
121
103
  /**
122
104
  * Tell clients that the stream has ended and close the connection.
123
105
  */
124
- end(): Promise<void>;
125
- on(event: "disconnect", callback: () => any): void;
126
- on(event: "end", callback: () => any): void;
106
+ close(): Promise<void>;
107
+ on(event: "close", callback: () => any): void;
108
+ on(event: "request:close", callback: () => any): void;
127
109
  }
128
110
  declare function registerEventStreamRpc(router: Router, path: string, procedure: EventStreamRpc<any, any> & {
129
111
  name: string;
@@ -257,4 +239,4 @@ type ResolvedArriConfig = Required<ArriConfig>;
257
239
  declare const defaultConfig: Required<ArriConfig>;
258
240
  declare function defineConfig(config: ArriConfig): ResolvedArriConfig;
259
241
 
260
- export { ArriApp, type ArriConfig, type ArriOptions, type ArriRoute, ArriRouter, type ArriRouterBase, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, EventStreamConnection, type EventStreamConnectionOptions, type EventStreamRpc, type EventStreamRpcEvent, type EventStreamRpcHandler, type EventStreamRpcHandlerContext, type ExtractParam, type ExtractParams, type HandlerContext, type HttpRpc, type Middleware, type MiddlewareEvent, type MiddlewareEventContext, type ModelMap, type NamedRpc, type PostRouteEvent, type PostRouteEventContext, type ResolvedArriConfig, type RouteEvent, type RouteEventContext, type RouteOptions, type Rpc, type RpcEvent, type RpcHandler, type RpcHandlerContext, type RpcParamSchema, type RpcPostEvent, type RpcPostHandler, type RpcPostHandlerContext, type Sse, type StatusCode, createRpcDefinition, defaultConfig, defineConfig, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, formatSse, formatSseList, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, handleRoute, isArriConfig, isEventStreamRpc, isResolvedArriConfig, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, setSseHeaders, validateRpcRequestInput };
242
+ export { ArriApp, type ArriConfig, type ArriOptions, type ArriRoute, ArriRouter, type ArriRouterBase, DEV_DEFINITION_ENDPOINT, DEV_ENDPOINT_ROOT, ErrorResponse, EventStreamConnection, type EventStreamConnectionOptions, type EventStreamRpc, type EventStreamRpcHandler, type EventStreamRpcHandlerContext, type ExtractParam, type ExtractParams, type HandlerContext, type HttpRpc, type Middleware, type MiddlewareEvent, type MiddlewareEventContext, type ModelMap, type NamedRpc, type PostRouteEvent, type PostRouteEventContext, type ResolvedArriConfig, type RouteEvent, type RouteEventContext, type RouteOptions, type Rpc, type RpcEvent, type RpcHandler, type RpcHandlerContext, type RpcParamSchema, type RpcPostEvent, type RpcPostHandler, type RpcPostHandlerContext, type StatusCode, createRpcDefinition, defaultConfig, defineConfig, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, handleH3Error, handleRoute, isArriConfig, isEventStreamRpc, isResolvedArriConfig, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- export { A as ArriApp, p as ArriRouter, a as DEV_DEFINITION_ENDPOINT, D as DEV_ENDPOINT_ROOT, E as ErrorResponse, B as EventStreamConnection, c as createRpcDefinition, n as defaultConfig, o as defineConfig, h as defineError, w as defineEventStreamRpc, q as defineRoute, d as defineRpc, j as errorResponseFromValidationErrors, y as formatSse, z as formatSseList, e as getRpcParamName, g as getRpcPath, f as getRpcResponseName, k as handleH3Error, t as handleRoute, l as isArriConfig, x as isEventStreamRpc, m as isResolvedArriConfig, b as isRpc, i as isRpcParamSchema, C as registerEventStreamRpc, s as registerRoute, r as registerRpc, u as setSseHeaders, v as validateRpcRequestInput } from './shared/arri.ebc63bd6.mjs';
1
+ export { A as ArriApp, p as ArriRouter, a as DEV_DEFINITION_ENDPOINT, D as DEV_ENDPOINT_ROOT, E as ErrorResponse, x as EventStreamConnection, c as createRpcDefinition, n as defaultConfig, o as defineConfig, h as defineError, u as defineEventStreamRpc, q as defineRoute, d as defineRpc, j as errorResponseFromValidationErrors, e as getRpcParamName, g as getRpcPath, f as getRpcResponseName, k as handleH3Error, t as handleRoute, l as isArriConfig, w as isEventStreamRpc, m as isResolvedArriConfig, b as isRpc, i as isRpcParamSchema, y as registerEventStreamRpc, s as registerRoute, r as registerRpc, v as validateRpcRequestInput } from './shared/arri.21567b92.mjs';
2
2
  export * from 'h3';
3
+ export * from 'h3-sse';
3
4
  import 'arri-validate';
4
- import 'node:process';
5
5
  import 'arri-codegen-utils';
6
6
  import 'scule';
7
7
 
@@ -1,6 +1,6 @@
1
- import { createError, isError, setResponseStatus, send, eventHandler, isPreflightRequest, setResponseHeader, readRawBody, getValidatedQuery, getHeader, setResponseHeaders, sendStream, defineEventHandler, getQuery, setHeader, createRouter, createApp, sendError } from 'h3';
1
+ import { createError, isError, setResponseStatus, send, eventHandler, isPreflightRequest, setResponseHeader, readRawBody, getValidatedQuery, defineEventHandler, getQuery, setHeader, createRouter, createApp, sendError } from 'h3';
2
2
  import { a, isAObjectSchema, isARecordSchema, isADiscriminatorSchema } from 'arri-validate';
3
- import { nextTick } from 'node:process';
3
+ import { createEventStream, sendEventStream } from 'h3-sse';
4
4
  import { isRpcHttpMethod, removeDisallowedChars } from 'arri-codegen-utils';
5
5
  import { kebabCase, pascalCase } from 'scule';
6
6
 
@@ -431,19 +431,6 @@ var __publicField$2 = (obj, key, value) => {
431
431
  __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
432
432
  return value;
433
433
  };
434
- function setSseHeaders(event) {
435
- const isHttp2 = getHeader(event, ":path") !== void 0 && getHeader(event, ":method") !== void 0;
436
- const input = {
437
- "Content-Type": "text/event-stream",
438
- "Cache-Control": "private, no-cache, no-store, no-transform, must-revalidate, max-age=0",
439
- Pragma: "no-cache",
440
- "X-Accel-Buffering": "no"
441
- };
442
- if (!isHttp2) {
443
- input.Connection = "keep-alive";
444
- }
445
- setResponseHeaders(event, input);
446
- }
447
434
  function defineEventStreamRpc(config) {
448
435
  return {
449
436
  ...config,
@@ -454,69 +441,35 @@ function defineEventStreamRpc(config) {
454
441
  function isEventStreamRpc(input) {
455
442
  return isRpc(input) && "isEventStream" in input && input.isEventStream === true;
456
443
  }
457
- function formatSse({ id, data, event }) {
458
- const parts = [];
459
- if (id) {
460
- parts.push(`id: ${id}`);
461
- }
462
- if (event) {
463
- parts.push(`event: ${event}`);
464
- }
465
- parts.push(`data: ${data}`);
466
- const payload = `${parts.join("\n")}
467
-
468
- `;
469
- return payload;
470
- }
471
- function formatSseList(events) {
472
- let output = "";
473
- for (const event of events) {
474
- output += formatSse(event);
475
- }
476
- return output;
477
- }
478
444
  class EventStreamConnection {
479
445
  constructor(event, opts) {
480
446
  __publicField$2(this, "lastEventId");
481
- __publicField$2(this, "writable");
482
- __publicField$2(this, "readable");
483
- __publicField$2(this, "writer");
484
- __publicField$2(this, "writerIsClosed", false);
485
- __publicField$2(this, "encoder");
447
+ __publicField$2(this, "h3Event");
486
448
  __publicField$2(this, "validationErrors");
487
449
  __publicField$2(this, "validator");
488
450
  __publicField$2(this, "serializer");
489
- __publicField$2(this, "h3Event");
490
451
  // for some reason Rollup cannot output DTS when this is set to NodeJS.Timeout
491
452
  __publicField$2(this, "pingInterval");
492
453
  __publicField$2(this, "pingIntervalMs");
454
+ __publicField$2(this, "eventStream");
493
455
  this.h3Event = event;
494
- const id = getHeader(event, "Last-Event-ID");
495
- this.lastEventId = id;
496
- const { readable, writable } = new TransformStream();
497
- this.writable = writable;
498
- this.readable = readable;
499
- this.writer = writable.getWriter();
500
- this.encoder = new TextEncoder();
456
+ this.eventStream = createEventStream(event, true);
457
+ this.lastEventId = this.eventStream.lastEventId;
501
458
  this.pingIntervalMs = opts.pingInterval ?? 6e4;
502
459
  this.serializer = opts.serializer;
503
460
  this.validator = opts.validator;
504
461
  this.validationErrors = opts.validationErrors;
505
- void this.writer.closed.then(() => {
506
- this.writerIsClosed = true;
462
+ this.eventStream.on("close", () => {
463
+ this.cleanup();
507
464
  });
508
465
  }
509
466
  /**
510
- * Start sending the event stream to the client
467
+ * Initialize the stream. This must be called before sending any events.
511
468
  */
512
- start() {
513
- setSseHeaders(this.h3Event);
514
- setResponseStatus(this.h3Event, 200);
515
- this.h3Event._handled = true;
516
- void sendStream(this.h3Event, this.readable);
469
+ init() {
470
+ void sendEventStream(this.h3Event, this.eventStream);
517
471
  this.pingInterval = setInterval(async () => {
518
- await this.publishEvent({
519
- id: this.lastEventId,
472
+ await this.eventStream.push({
520
473
  event: "ping",
521
474
  data: ""
522
475
  });
@@ -546,11 +499,11 @@ class EventStreamConnection {
546
499
  data: JSON.stringify(errorResponse2)
547
500
  });
548
501
  }
549
- await this.publishEvents(events);
502
+ await this.eventStream.push(events);
550
503
  return;
551
504
  }
552
505
  if (this.validator(data)) {
553
- await this.publishEvent({
506
+ await this.eventStream.push({
554
507
  id: eventId,
555
508
  event: "message",
556
509
  data: this.serializer(data)
@@ -563,7 +516,7 @@ class EventStreamConnection {
563
516
  statusMessage: "Failed to serialize response. Response does not match specified schema.",
564
517
  data: errors
565
518
  };
566
- await this.publishEvent({
519
+ await this.eventStream.push({
567
520
  id: eventId,
568
521
  event: "error",
569
522
  data: JSON.stringify(errorResponse)
@@ -589,79 +542,34 @@ class EventStreamConnection {
589
542
  * Publish an error event. This will trigger the `onError` hooks of any connected clients.
590
543
  */
591
544
  async pushError(error, eventId) {
592
- await this.publishEvent({
545
+ await this.eventStream.push({
593
546
  id: eventId,
594
547
  event: "error",
595
548
  data: JSON.stringify(error)
596
549
  });
597
550
  }
598
- async publishEvents(events) {
599
- const payload = formatSseList(events);
600
- if (this.writerIsClosed) {
601
- try {
602
- this.h3Event.node.res.end();
603
- } catch (_) {
604
- await this.cleanup();
605
- }
606
- return;
607
- }
608
- await this.writer.write(this.encoder.encode(payload));
609
- }
610
- async publishEvent(event) {
611
- const payload = formatSse(event);
612
- if (this.writerIsClosed) {
613
- try {
614
- this.h3Event.node.res.end();
615
- } catch (_) {
616
- await this.cleanup();
617
- }
618
- return;
619
- }
620
- await this.writer.write(this.encoder.encode(payload));
621
- }
622
- async cleanup() {
551
+ cleanup() {
623
552
  if (this.pingInterval) {
624
553
  clearInterval(this.pingInterval);
625
554
  }
626
- if (!this.writerIsClosed) {
627
- try {
628
- await this.writer.close();
629
- } catch (_) {
630
- }
631
- }
632
555
  }
633
556
  /**
634
557
  * Tell clients that the stream has ended and close the connection.
635
558
  */
636
- async end() {
637
- await this.publishEvent({
559
+ async close() {
560
+ await this.eventStream.push({
638
561
  event: "done",
639
562
  data: "this stream has ended"
640
563
  }).catch();
641
- await new Promise((resolve, reject) => {
642
- nextTick(() => {
643
- try {
644
- this.h3Event.node.res.end();
645
- resolve(true);
646
- } catch (err) {
647
- reject(err);
648
- }
649
- });
650
- });
564
+ await this.eventStream.close();
651
565
  }
652
566
  on(event, callback) {
653
567
  switch (event) {
654
- case "disconnect":
655
- this.h3Event.node.req.on("close", async () => {
656
- await callback();
657
- await this.cleanup();
658
- });
568
+ case "close":
569
+ this.eventStream.on("close", callback);
659
570
  break;
660
- case "end":
661
- this.h3Event.node.req.on("end", async () => {
662
- await callback();
663
- await this.cleanup();
664
- });
571
+ case "request:close":
572
+ this.eventStream.on("request:close", callback);
665
573
  break;
666
574
  }
667
575
  }
@@ -695,7 +603,7 @@ function registerEventStreamRpc(router, path, procedure, opts) {
695
603
  procedure.params
696
604
  );
697
605
  }
698
- const connection = new EventStreamConnection(event, {
606
+ const stream = new EventStreamConnection(event, {
699
607
  pingInterval: procedure.pingInterval,
700
608
  validator: responseValidator?.validate ?? function() {
701
609
  return true;
@@ -710,11 +618,14 @@ function registerEventStreamRpc(router, path, procedure, opts) {
710
618
  return [];
711
619
  }
712
620
  });
713
- event.context.connection = connection;
621
+ event.context.stream = stream;
714
622
  await procedure.handler(
715
623
  event.context,
716
624
  event
717
625
  );
626
+ if (!event.handled && !stream.eventStream._handled) {
627
+ await sendEventStream(event, stream.eventStream);
628
+ }
718
629
  } catch (err) {
719
630
  await handleH3Error(err, event, opts.onError);
720
631
  }
@@ -1086,4 +997,4 @@ function defineConfig(config) {
1086
997
  };
1087
998
  }
1088
999
 
1089
- export { ArriApp as A, EventStreamConnection as B, registerEventStreamRpc as C, DEV_ENDPOINT_ROOT as D, ErrorResponse as E, DEV_DEFINITION_ENDPOINT as a, isRpc as b, createRpcDefinition as c, defineRpc as d, getRpcParamName as e, getRpcResponseName as f, getRpcPath as g, defineError as h, isRpcParamSchema as i, errorResponseFromValidationErrors as j, handleH3Error as k, isArriConfig as l, isResolvedArriConfig as m, defaultConfig as n, defineConfig as o, ArriRouter as p, defineRoute as q, registerRpc as r, registerRoute as s, handleRoute as t, setSseHeaders as u, validateRpcRequestInput as v, defineEventStreamRpc as w, isEventStreamRpc as x, formatSse as y, formatSseList as z };
1000
+ export { ArriApp as A, DEV_ENDPOINT_ROOT as D, ErrorResponse as E, DEV_DEFINITION_ENDPOINT as a, isRpc as b, createRpcDefinition as c, defineRpc as d, getRpcParamName as e, getRpcResponseName as f, getRpcPath as g, defineError as h, isRpcParamSchema as i, errorResponseFromValidationErrors as j, handleH3Error as k, isArriConfig as l, isResolvedArriConfig as m, defaultConfig as n, defineConfig as o, ArriRouter as p, defineRoute as q, registerRpc as r, registerRoute as s, handleRoute as t, defineEventStreamRpc as u, validateRpcRequestInput as v, isEventStreamRpc as w, EventStreamConnection as x, registerEventStreamRpc as y };
@@ -2,7 +2,7 @@
2
2
 
3
3
  const h3 = require('h3');
4
4
  const arriValidate = require('arri-validate');
5
- const node_process = require('node:process');
5
+ const h3Sse = require('h3-sse');
6
6
  const arriCodegenUtils = require('arri-codegen-utils');
7
7
  const scule = require('scule');
8
8
 
@@ -433,19 +433,6 @@ var __publicField$2 = (obj, key, value) => {
433
433
  __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
434
434
  return value;
435
435
  };
436
- function setSseHeaders(event) {
437
- const isHttp2 = h3.getHeader(event, ":path") !== void 0 && h3.getHeader(event, ":method") !== void 0;
438
- const input = {
439
- "Content-Type": "text/event-stream",
440
- "Cache-Control": "private, no-cache, no-store, no-transform, must-revalidate, max-age=0",
441
- Pragma: "no-cache",
442
- "X-Accel-Buffering": "no"
443
- };
444
- if (!isHttp2) {
445
- input.Connection = "keep-alive";
446
- }
447
- h3.setResponseHeaders(event, input);
448
- }
449
436
  function defineEventStreamRpc(config) {
450
437
  return {
451
438
  ...config,
@@ -456,69 +443,35 @@ function defineEventStreamRpc(config) {
456
443
  function isEventStreamRpc(input) {
457
444
  return isRpc(input) && "isEventStream" in input && input.isEventStream === true;
458
445
  }
459
- function formatSse({ id, data, event }) {
460
- const parts = [];
461
- if (id) {
462
- parts.push(`id: ${id}`);
463
- }
464
- if (event) {
465
- parts.push(`event: ${event}`);
466
- }
467
- parts.push(`data: ${data}`);
468
- const payload = `${parts.join("\n")}
469
-
470
- `;
471
- return payload;
472
- }
473
- function formatSseList(events) {
474
- let output = "";
475
- for (const event of events) {
476
- output += formatSse(event);
477
- }
478
- return output;
479
- }
480
446
  class EventStreamConnection {
481
447
  constructor(event, opts) {
482
448
  __publicField$2(this, "lastEventId");
483
- __publicField$2(this, "writable");
484
- __publicField$2(this, "readable");
485
- __publicField$2(this, "writer");
486
- __publicField$2(this, "writerIsClosed", false);
487
- __publicField$2(this, "encoder");
449
+ __publicField$2(this, "h3Event");
488
450
  __publicField$2(this, "validationErrors");
489
451
  __publicField$2(this, "validator");
490
452
  __publicField$2(this, "serializer");
491
- __publicField$2(this, "h3Event");
492
453
  // for some reason Rollup cannot output DTS when this is set to NodeJS.Timeout
493
454
  __publicField$2(this, "pingInterval");
494
455
  __publicField$2(this, "pingIntervalMs");
456
+ __publicField$2(this, "eventStream");
495
457
  this.h3Event = event;
496
- const id = h3.getHeader(event, "Last-Event-ID");
497
- this.lastEventId = id;
498
- const { readable, writable } = new TransformStream();
499
- this.writable = writable;
500
- this.readable = readable;
501
- this.writer = writable.getWriter();
502
- this.encoder = new TextEncoder();
458
+ this.eventStream = h3Sse.createEventStream(event, true);
459
+ this.lastEventId = this.eventStream.lastEventId;
503
460
  this.pingIntervalMs = opts.pingInterval ?? 6e4;
504
461
  this.serializer = opts.serializer;
505
462
  this.validator = opts.validator;
506
463
  this.validationErrors = opts.validationErrors;
507
- void this.writer.closed.then(() => {
508
- this.writerIsClosed = true;
464
+ this.eventStream.on("close", () => {
465
+ this.cleanup();
509
466
  });
510
467
  }
511
468
  /**
512
- * Start sending the event stream to the client
469
+ * Initialize the stream. This must be called before sending any events.
513
470
  */
514
- start() {
515
- setSseHeaders(this.h3Event);
516
- h3.setResponseStatus(this.h3Event, 200);
517
- this.h3Event._handled = true;
518
- void h3.sendStream(this.h3Event, this.readable);
471
+ init() {
472
+ void h3Sse.sendEventStream(this.h3Event, this.eventStream);
519
473
  this.pingInterval = setInterval(async () => {
520
- await this.publishEvent({
521
- id: this.lastEventId,
474
+ await this.eventStream.push({
522
475
  event: "ping",
523
476
  data: ""
524
477
  });
@@ -548,11 +501,11 @@ class EventStreamConnection {
548
501
  data: JSON.stringify(errorResponse2)
549
502
  });
550
503
  }
551
- await this.publishEvents(events);
504
+ await this.eventStream.push(events);
552
505
  return;
553
506
  }
554
507
  if (this.validator(data)) {
555
- await this.publishEvent({
508
+ await this.eventStream.push({
556
509
  id: eventId,
557
510
  event: "message",
558
511
  data: this.serializer(data)
@@ -565,7 +518,7 @@ class EventStreamConnection {
565
518
  statusMessage: "Failed to serialize response. Response does not match specified schema.",
566
519
  data: errors
567
520
  };
568
- await this.publishEvent({
521
+ await this.eventStream.push({
569
522
  id: eventId,
570
523
  event: "error",
571
524
  data: JSON.stringify(errorResponse)
@@ -591,79 +544,34 @@ class EventStreamConnection {
591
544
  * Publish an error event. This will trigger the `onError` hooks of any connected clients.
592
545
  */
593
546
  async pushError(error, eventId) {
594
- await this.publishEvent({
547
+ await this.eventStream.push({
595
548
  id: eventId,
596
549
  event: "error",
597
550
  data: JSON.stringify(error)
598
551
  });
599
552
  }
600
- async publishEvents(events) {
601
- const payload = formatSseList(events);
602
- if (this.writerIsClosed) {
603
- try {
604
- this.h3Event.node.res.end();
605
- } catch (_) {
606
- await this.cleanup();
607
- }
608
- return;
609
- }
610
- await this.writer.write(this.encoder.encode(payload));
611
- }
612
- async publishEvent(event) {
613
- const payload = formatSse(event);
614
- if (this.writerIsClosed) {
615
- try {
616
- this.h3Event.node.res.end();
617
- } catch (_) {
618
- await this.cleanup();
619
- }
620
- return;
621
- }
622
- await this.writer.write(this.encoder.encode(payload));
623
- }
624
- async cleanup() {
553
+ cleanup() {
625
554
  if (this.pingInterval) {
626
555
  clearInterval(this.pingInterval);
627
556
  }
628
- if (!this.writerIsClosed) {
629
- try {
630
- await this.writer.close();
631
- } catch (_) {
632
- }
633
- }
634
557
  }
635
558
  /**
636
559
  * Tell clients that the stream has ended and close the connection.
637
560
  */
638
- async end() {
639
- await this.publishEvent({
561
+ async close() {
562
+ await this.eventStream.push({
640
563
  event: "done",
641
564
  data: "this stream has ended"
642
565
  }).catch();
643
- await new Promise((resolve, reject) => {
644
- node_process.nextTick(() => {
645
- try {
646
- this.h3Event.node.res.end();
647
- resolve(true);
648
- } catch (err) {
649
- reject(err);
650
- }
651
- });
652
- });
566
+ await this.eventStream.close();
653
567
  }
654
568
  on(event, callback) {
655
569
  switch (event) {
656
- case "disconnect":
657
- this.h3Event.node.req.on("close", async () => {
658
- await callback();
659
- await this.cleanup();
660
- });
570
+ case "close":
571
+ this.eventStream.on("close", callback);
661
572
  break;
662
- case "end":
663
- this.h3Event.node.req.on("end", async () => {
664
- await callback();
665
- await this.cleanup();
666
- });
573
+ case "request:close":
574
+ this.eventStream.on("request:close", callback);
667
575
  break;
668
576
  }
669
577
  }
@@ -697,7 +605,7 @@ function registerEventStreamRpc(router, path, procedure, opts) {
697
605
  procedure.params
698
606
  );
699
607
  }
700
- const connection = new EventStreamConnection(event, {
608
+ const stream = new EventStreamConnection(event, {
701
609
  pingInterval: procedure.pingInterval,
702
610
  validator: responseValidator?.validate ?? function() {
703
611
  return true;
@@ -712,11 +620,14 @@ function registerEventStreamRpc(router, path, procedure, opts) {
712
620
  return [];
713
621
  }
714
622
  });
715
- event.context.connection = connection;
623
+ event.context.stream = stream;
716
624
  await procedure.handler(
717
625
  event.context,
718
626
  event
719
627
  );
628
+ if (!event.handled && !stream.eventStream._handled) {
629
+ await h3Sse.sendEventStream(event, stream.eventStream);
630
+ }
720
631
  } catch (err) {
721
632
  await handleH3Error(err, event, opts.onError);
722
633
  }
@@ -1102,8 +1013,6 @@ exports.defineEventStreamRpc = defineEventStreamRpc;
1102
1013
  exports.defineRoute = defineRoute;
1103
1014
  exports.defineRpc = defineRpc;
1104
1015
  exports.errorResponseFromValidationErrors = errorResponseFromValidationErrors;
1105
- exports.formatSse = formatSse;
1106
- exports.formatSseList = formatSseList;
1107
1016
  exports.getRpcParamName = getRpcParamName;
1108
1017
  exports.getRpcPath = getRpcPath;
1109
1018
  exports.getRpcResponseName = getRpcResponseName;
@@ -1117,5 +1026,4 @@ exports.isRpcParamSchema = isRpcParamSchema;
1117
1026
  exports.registerEventStreamRpc = registerEventStreamRpc;
1118
1027
  exports.registerRoute = registerRoute;
1119
1028
  exports.registerRpc = registerRpc;
1120
- exports.setSseHeaders = setSseHeaders;
1121
1029
  exports.validateRpcRequestInput = validateRpcRequestInput;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arri",
3
- "version": "0.24.1",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -26,28 +26,29 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@joshmossas/listhen": "1.6.0",
29
- "c12": "^1.8.0",
29
+ "c12": "^1.9.0",
30
30
  "chokidar": "^3.6.0",
31
31
  "citty": "^0.1.6",
32
32
  "consola": "^3.2.3",
33
33
  "degit": "^2.8.4",
34
- "esbuild": "^0.20.0",
34
+ "esbuild": "^0.20.1",
35
35
  "esbuild-plugin-replace": "^1.4.0",
36
36
  "globby": "^14.0.1",
37
- "h3": "^1.10.1",
37
+ "h3": "^1.10.2",
38
+ "h3-sse": "^0.0.9",
38
39
  "jiti": "^1.21.0",
39
40
  "ofetch": "^1.3.3",
40
41
  "pathe": "^1.1.2",
41
42
  "prettier": "^3.2.5",
42
43
  "scule": "^1.3.0",
43
44
  "uncrypto": "^0.1.3",
44
- "arri-codegen-dart": "0.24.1",
45
- "arri-codegen-ts": "0.24.1",
46
- "arri-codegen-utils": "0.24.1",
47
- "arri-validate": "0.24.1"
45
+ "arri-codegen-dart": "0.26.0",
46
+ "arri-validate": "0.26.0",
47
+ "arri-codegen-ts": "0.26.0",
48
+ "arri-codegen-utils": "0.26.0"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@types/degit": "^2.8.6",
51
- "bun-types": "^1.0.27"
52
+ "bun-types": "^1.0.28"
52
53
  }
53
54
  }