@arrirpc/server 0.84.1 → 0.85.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
@@ -385,6 +385,74 @@ declare module '@arrirpc/server' {
385
385
  }
386
386
  ```
387
387
 
388
+ ### Background Workers & Lifecycle Hooks
389
+
390
+ You can register background workers and lifecycle hooks on `ArriApp`. Workers and lifecycle hooks do not run when the app is imported during codegen (`arri build`); they only start when the web server begins listening.
391
+
392
+ ```ts
393
+ import { ArriApp, defineWorker } from '@arrirpc/server';
394
+
395
+ const app = new ArriApp({
396
+ onStart() {
397
+ console.log('Server is starting up');
398
+ },
399
+ onStop() {
400
+ console.log('Server is shutting down');
401
+ },
402
+ onWorkerError(err, worker) {
403
+ logger.error({ err, worker: worker.name }, 'Worker error');
404
+ },
405
+ });
406
+
407
+ // Registering a worker with a continuous loop and AbortSignal
408
+ app.registerWorker(
409
+ defineWorker({
410
+ name: 'queue-consumer',
411
+ async start(signal) {
412
+ while (!signal.aborted) {
413
+ const job = await queue.pop({ signal });
414
+ if (job) await processJob(job);
415
+ }
416
+ },
417
+ async stop() {
418
+ // Optional: cleanup or drain
419
+ },
420
+ }),
421
+ );
422
+
423
+ // Registering a third-party worker (e.g. BullMQ)
424
+ app.registerWorker(
425
+ defineWorker({
426
+ name: 'bullmq-emails',
427
+ async start() {
428
+ worker = new Worker('emails', async (job) => {
429
+ await sendEmail(job.data);
430
+ });
431
+ },
432
+ async stop() {
433
+ await worker.close();
434
+ },
435
+ }),
436
+ );
437
+
438
+ // Function shorthand
439
+ app.registerWorker(async (signal) => {
440
+ while (!signal.aborted) {
441
+ await heartbeat();
442
+ await delay(5000, signal);
443
+ }
444
+ });
445
+ ```
446
+
447
+ Services can also register workers:
448
+
449
+ ```ts
450
+ const authService = new ArriService('auth');
451
+ authService.registerWorker(tokenCleanupWorker);
452
+
453
+ app.use(authService); // Workers will be registered on the app
454
+ ```
455
+
388
456
  ### Adding Client Generators
389
457
 
390
458
  ```ts
package/dist/index.cjs CHANGED
@@ -804,6 +804,18 @@ class ArriRouter {
804
804
  }
805
805
  }
806
806
 
807
+ function defineWorker(worker) {
808
+ if (typeof worker === "function") {
809
+ return {
810
+ start: worker
811
+ };
812
+ }
813
+ return worker;
814
+ }
815
+ function isWorker(input) {
816
+ return typeof input === "object" && input !== null && "start" in input && typeof input.start === "function";
817
+ }
818
+
807
819
  class ArriService {
808
820
  name;
809
821
  constructor(name) {
@@ -811,10 +823,14 @@ class ArriService {
811
823
  }
812
824
  procedures = [];
813
825
  definitions = {};
826
+ workers = [];
814
827
  rpc(name, procedure) {
815
828
  procedure.name = `${this.name}.${name}`;
816
829
  this.procedures.push(procedure);
817
830
  }
831
+ registerWorker(worker) {
832
+ this.workers.push(defineWorker(worker));
833
+ }
818
834
  registerDefinitions(models) {
819
835
  for (const key of Object.keys(models)) {
820
836
  this.definitions[key] = models[key];
@@ -826,6 +842,9 @@ class ArriService {
826
842
  getDefinitions() {
827
843
  return this.definitions;
828
844
  }
845
+ getWorkers() {
846
+ return this.workers;
847
+ }
829
848
  }
830
849
  function defineService(name, procedures) {
831
850
  const service = new ArriService(name);
@@ -846,6 +865,12 @@ class ArriApp {
846
865
  _procedures = {};
847
866
  _definitions = {};
848
867
  _middlewares = [];
868
+ _workers = [];
869
+ _onStartHooks = [];
870
+ _onStopHooks = [];
871
+ _workerErrorHooks = [];
872
+ _abortController = null;
873
+ _isRunning = false;
849
874
  _onRequest;
850
875
  _onAfterResponse;
851
876
  _onBeforeResponse;
@@ -863,6 +888,15 @@ class ArriApp {
863
888
  this._onError = opts.onError;
864
889
  this._onAfterResponse = opts.onAfterResponse;
865
890
  this._onBeforeResponse = opts.onBeforeResponse;
891
+ if (opts.onStart) {
892
+ this.onStart(opts.onStart);
893
+ }
894
+ if (opts.onStop) {
895
+ this.onStop(opts.onStop);
896
+ }
897
+ if (opts.onWorkerError) {
898
+ this.onWorkerError(opts.onWorkerError);
899
+ }
866
900
  this._heartbeatMs = opts.heartbeatMs ?? 2e4;
867
901
  this._rpcRoutePrefix = opts?.rpcRoutePrefix ?? "";
868
902
  this._rpcDefinitionPath = opts?.rpcDefinitionPath ?? "__definition";
@@ -937,6 +971,9 @@ class ArriApp {
937
971
  this.rpc(rpc.name, rpc);
938
972
  }
939
973
  this.registerDefinitions(input.getDefinitions());
974
+ for (const worker of input.getWorkers()) {
975
+ this.registerWorker(worker);
976
+ }
940
977
  return;
941
978
  }
942
979
  this._middlewares.push(input);
@@ -1012,6 +1049,118 @@ class ArriApp {
1012
1049
  }
1013
1050
  return appDef;
1014
1051
  }
1052
+ registerWorker(worker) {
1053
+ const w = defineWorker(worker);
1054
+ this._workers.push(w);
1055
+ if (this._isRunning && this._abortController) {
1056
+ void (async () => {
1057
+ try {
1058
+ await w.start(this._abortController.signal);
1059
+ } catch (err) {
1060
+ await this._handleWorkerError(w, err);
1061
+ }
1062
+ })();
1063
+ }
1064
+ }
1065
+ onStart(hook) {
1066
+ this._onStartHooks.push(hook);
1067
+ }
1068
+ onStop(hook) {
1069
+ this._onStopHooks.push(hook);
1070
+ }
1071
+ onWorkerError(hook) {
1072
+ this._workerErrorHooks.push(hook);
1073
+ }
1074
+ async _handleWorkerError(worker, error) {
1075
+ let handled = false;
1076
+ if (worker.onError) {
1077
+ try {
1078
+ await worker.onError(error);
1079
+ handled = true;
1080
+ } catch (hookErr) {
1081
+ console.error(
1082
+ `[Arri] Error in worker.onError hook for worker ${worker.name ?? "unnamed"}:`,
1083
+ hookErr
1084
+ );
1085
+ }
1086
+ }
1087
+ for (const hook of this._workerErrorHooks) {
1088
+ try {
1089
+ await hook(error, worker);
1090
+ handled = true;
1091
+ } catch (hookErr) {
1092
+ console.error(
1093
+ `[Arri] Error in onWorkerError hook for worker ${worker.name ?? "unnamed"}:`,
1094
+ hookErr
1095
+ );
1096
+ }
1097
+ }
1098
+ if (!handled) {
1099
+ console.error(
1100
+ `[Arri] Unhandled error in worker ${worker.name ?? "unnamed"}:`,
1101
+ error
1102
+ );
1103
+ }
1104
+ }
1105
+ async start() {
1106
+ if (this._isRunning) {
1107
+ return;
1108
+ }
1109
+ this._isRunning = true;
1110
+ const controller = new AbortController();
1111
+ this._abortController = controller;
1112
+ try {
1113
+ for (const hook of this._onStartHooks) {
1114
+ await hook();
1115
+ }
1116
+ } catch (err) {
1117
+ this._isRunning = false;
1118
+ controller.abort();
1119
+ this._abortController = null;
1120
+ throw err;
1121
+ }
1122
+ for (const worker of this._workers) {
1123
+ void (async () => {
1124
+ try {
1125
+ await worker.start(controller.signal);
1126
+ } catch (err) {
1127
+ await this._handleWorkerError(worker, err);
1128
+ }
1129
+ })();
1130
+ }
1131
+ }
1132
+ async stop() {
1133
+ if (!this._isRunning) {
1134
+ return;
1135
+ }
1136
+ this._isRunning = false;
1137
+ this._abortController?.abort();
1138
+ const stopPromises = this._workers.map(async (worker) => {
1139
+ try {
1140
+ if (worker.stop) {
1141
+ await worker.stop();
1142
+ }
1143
+ } catch (err) {
1144
+ await this._handleWorkerError(worker, err);
1145
+ }
1146
+ });
1147
+ const hookPromises = this._onStopHooks.map(async (hook) => {
1148
+ try {
1149
+ await hook();
1150
+ } catch (err) {
1151
+ if (this._debug) {
1152
+ console.error("Error in onStop hook:", err);
1153
+ }
1154
+ }
1155
+ });
1156
+ await Promise.allSettled([...stopPromises, ...hookPromises]);
1157
+ }
1158
+ get workers() {
1159
+ return [...this._workers];
1160
+ }
1161
+ get isRunning() {
1162
+ return this._isRunning;
1163
+ }
1015
1164
  }
1016
1165
 
1017
1166
  const defineMiddleware = (middleware) => middleware;
@@ -1030,6 +1179,7 @@ exports.defineMiddleware = defineMiddleware;
1030
1179
  exports.defineRoute = defineRoute;
1031
1180
  exports.defineRpc = defineRpc;
1032
1181
  exports.defineService = defineService;
1182
+ exports.defineWorker = defineWorker;
1033
1183
  exports.errorResponseFromValidationErrors = errorResponseFromValidationErrors;
1034
1184
  exports.getRpcParamName = getRpcParamName;
1035
1185
  exports.getRpcPath = getRpcPath;
@@ -1041,6 +1191,7 @@ exports.handleRoute = handleRoute;
1041
1191
  exports.isEventStreamRpc = isEventStreamRpc;
1042
1192
  exports.isRpc = isRpc;
1043
1193
  exports.isRpcParamSchema = isRpcParamSchema;
1194
+ exports.isWorker = isWorker;
1044
1195
  exports.registerEventStreamRpc = registerEventStreamRpc;
1045
1196
  exports.registerRoute = registerRoute;
1046
1197
  exports.registerRpc = registerRpc;
package/dist/index.d.cts CHANGED
@@ -186,15 +186,29 @@ type RequestValidator<T = any> = {
186
186
  declare function getSchemaValidator<T extends Record<string, any> = any>(rpcName: string, type: 'params' | 'response', schema: RpcParamSchema<T>): RequestValidator<T> | undefined;
187
187
  declare function getSchemaName(rpcName: string, type: 'params' | 'response', schema: RpcParamSchema<any>): string;
188
188
 
189
+ type WorkerHandler = (signal: AbortSignal) => Promise<void> | void;
190
+ type WorkerErrorHandler = (error: unknown, worker: ArriWorker) => void | Promise<void>;
191
+ interface ArriWorker {
192
+ name?: string;
193
+ start: (signal: AbortSignal) => Promise<void> | void;
194
+ stop?: () => Promise<void> | void;
195
+ onError?: (error: unknown) => void | Promise<void>;
196
+ }
197
+ declare function defineWorker(worker: ArriWorker | WorkerHandler): ArriWorker;
198
+ declare function isWorker(input: unknown): input is ArriWorker;
199
+
189
200
  declare class ArriService {
190
201
  name: string;
191
202
  constructor(name: string);
192
203
  private readonly procedures;
193
204
  private readonly definitions;
205
+ private readonly workers;
194
206
  rpc(name: string, procedure: Rpc<any, any, any>): void;
207
+ registerWorker(worker: ArriWorker | WorkerHandler): void;
195
208
  registerDefinitions(models: DefinitionMap): void;
196
209
  getProcedures(): NamedHttpRpc<any, any, any>[];
197
210
  getDefinitions(): DefinitionMap;
211
+ getWorkers(): ArriWorker[];
198
212
  }
199
213
  declare function defineService(name: string, procedures?: Record<string, HttpRpc<any, any, any>>): ArriService;
200
214
 
@@ -207,9 +221,15 @@ declare class ArriApp {
207
221
  private readonly _rpcDefinitionPath;
208
222
  private readonly _rpcRoutePrefix;
209
223
  appInfo: AppDefinition['info'];
210
- private _procedures;
224
+ private readonly _procedures;
211
225
  private _definitions;
212
226
  private readonly _middlewares;
227
+ private readonly _workers;
228
+ private readonly _onStartHooks;
229
+ private readonly _onStopHooks;
230
+ private readonly _workerErrorHooks;
231
+ private _abortController;
232
+ private _isRunning;
213
233
  private readonly _onRequest;
214
234
  private readonly _onAfterResponse;
215
235
  private readonly _onBeforeResponse;
@@ -225,6 +245,15 @@ declare class ArriApp {
225
245
  route<TPath extends string, TQuery extends AObjectSchema<any, any>, TBody extends ASchema<any>, TResponse = any>(route: ArriRoute<TPath, TQuery, TBody, TResponse>): void;
226
246
  registerDefinitions(definitions: DefinitionMap): void;
227
247
  getAppDefinition(): AppDefinition;
248
+ registerWorker(worker: ArriWorker | WorkerHandler): void;
249
+ onStart(hook: () => Promise<void> | void): void;
250
+ onStop(hook: () => Promise<void> | void): void;
251
+ onWorkerError(hook: WorkerErrorHandler): void;
252
+ private _handleWorkerError;
253
+ start(): Promise<void>;
254
+ stop(): Promise<void>;
255
+ get workers(): ArriWorker[];
256
+ get isRunning(): boolean;
228
257
  }
229
258
  interface ArriOptions {
230
259
  debug?: boolean;
@@ -245,10 +274,13 @@ interface ArriOptions {
245
274
  onAfterResponse?: (event: RequestHookEvent) => void | Promise<void>;
246
275
  onBeforeResponse?: (event: RequestHookEvent) => void | Promise<void>;
247
276
  onError?: (error: arriError, event: RequestHookEvent) => void | Promise<void>;
277
+ onStart?: () => Promise<void> | void;
278
+ onStop?: () => Promise<void> | void;
279
+ onWorkerError?: WorkerErrorHandler;
248
280
  }
249
281
  interface RequestHookEvent extends Omit<H3Event, 'context'> {
250
282
  context: RequestHookContext;
251
283
  }
252
284
 
253
- export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
254
- export type { ArriEventContext, ArriOptions, ArriRoute, DefinitionMap, EventStreamConnectionOptions, EventStreamRpc, EventStreamRpcHandler, EventStreamRpcHandlerContext, ExtractParam, ExtractParams, HttpRpc, Middleware, MiddlewareEvent, MiddlewareEventContext, NamedHttpRpc, PostRouteEvent, RequestHookContext, RequestHookEvent, RequestValidator, RouteEvent, RouteEventContext, RouteOptions, RoutePostEventContext, Rpc, RpcEvent, RpcEventContext, RpcHandler, RpcParamSchema, RpcPostEvent, RpcPostEventContext, RpcPostHandler, SsePushResult };
285
+ export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, defineWorker, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, isWorker, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
286
+ export type { ArriEventContext, ArriOptions, ArriRoute, ArriWorker, DefinitionMap, EventStreamConnectionOptions, EventStreamRpc, EventStreamRpcHandler, EventStreamRpcHandlerContext, ExtractParam, ExtractParams, HttpRpc, Middleware, MiddlewareEvent, MiddlewareEventContext, NamedHttpRpc, PostRouteEvent, RequestHookContext, RequestHookEvent, RequestValidator, RouteEvent, RouteEventContext, RouteOptions, RoutePostEventContext, Rpc, RpcEvent, RpcEventContext, RpcHandler, RpcParamSchema, RpcPostEvent, RpcPostEventContext, RpcPostHandler, SsePushResult, WorkerErrorHandler, WorkerHandler };
package/dist/index.d.mts CHANGED
@@ -186,15 +186,29 @@ type RequestValidator<T = any> = {
186
186
  declare function getSchemaValidator<T extends Record<string, any> = any>(rpcName: string, type: 'params' | 'response', schema: RpcParamSchema<T>): RequestValidator<T> | undefined;
187
187
  declare function getSchemaName(rpcName: string, type: 'params' | 'response', schema: RpcParamSchema<any>): string;
188
188
 
189
+ type WorkerHandler = (signal: AbortSignal) => Promise<void> | void;
190
+ type WorkerErrorHandler = (error: unknown, worker: ArriWorker) => void | Promise<void>;
191
+ interface ArriWorker {
192
+ name?: string;
193
+ start: (signal: AbortSignal) => Promise<void> | void;
194
+ stop?: () => Promise<void> | void;
195
+ onError?: (error: unknown) => void | Promise<void>;
196
+ }
197
+ declare function defineWorker(worker: ArriWorker | WorkerHandler): ArriWorker;
198
+ declare function isWorker(input: unknown): input is ArriWorker;
199
+
189
200
  declare class ArriService {
190
201
  name: string;
191
202
  constructor(name: string);
192
203
  private readonly procedures;
193
204
  private readonly definitions;
205
+ private readonly workers;
194
206
  rpc(name: string, procedure: Rpc<any, any, any>): void;
207
+ registerWorker(worker: ArriWorker | WorkerHandler): void;
195
208
  registerDefinitions(models: DefinitionMap): void;
196
209
  getProcedures(): NamedHttpRpc<any, any, any>[];
197
210
  getDefinitions(): DefinitionMap;
211
+ getWorkers(): ArriWorker[];
198
212
  }
199
213
  declare function defineService(name: string, procedures?: Record<string, HttpRpc<any, any, any>>): ArriService;
200
214
 
@@ -207,9 +221,15 @@ declare class ArriApp {
207
221
  private readonly _rpcDefinitionPath;
208
222
  private readonly _rpcRoutePrefix;
209
223
  appInfo: AppDefinition['info'];
210
- private _procedures;
224
+ private readonly _procedures;
211
225
  private _definitions;
212
226
  private readonly _middlewares;
227
+ private readonly _workers;
228
+ private readonly _onStartHooks;
229
+ private readonly _onStopHooks;
230
+ private readonly _workerErrorHooks;
231
+ private _abortController;
232
+ private _isRunning;
213
233
  private readonly _onRequest;
214
234
  private readonly _onAfterResponse;
215
235
  private readonly _onBeforeResponse;
@@ -225,6 +245,15 @@ declare class ArriApp {
225
245
  route<TPath extends string, TQuery extends AObjectSchema<any, any>, TBody extends ASchema<any>, TResponse = any>(route: ArriRoute<TPath, TQuery, TBody, TResponse>): void;
226
246
  registerDefinitions(definitions: DefinitionMap): void;
227
247
  getAppDefinition(): AppDefinition;
248
+ registerWorker(worker: ArriWorker | WorkerHandler): void;
249
+ onStart(hook: () => Promise<void> | void): void;
250
+ onStop(hook: () => Promise<void> | void): void;
251
+ onWorkerError(hook: WorkerErrorHandler): void;
252
+ private _handleWorkerError;
253
+ start(): Promise<void>;
254
+ stop(): Promise<void>;
255
+ get workers(): ArriWorker[];
256
+ get isRunning(): boolean;
228
257
  }
229
258
  interface ArriOptions {
230
259
  debug?: boolean;
@@ -245,10 +274,13 @@ interface ArriOptions {
245
274
  onAfterResponse?: (event: RequestHookEvent) => void | Promise<void>;
246
275
  onBeforeResponse?: (event: RequestHookEvent) => void | Promise<void>;
247
276
  onError?: (error: arriError, event: RequestHookEvent) => void | Promise<void>;
277
+ onStart?: () => Promise<void> | void;
278
+ onStop?: () => Promise<void> | void;
279
+ onWorkerError?: WorkerErrorHandler;
248
280
  }
249
281
  interface RequestHookEvent extends Omit<H3Event, 'context'> {
250
282
  context: RequestHookContext;
251
283
  }
252
284
 
253
- export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
254
- export type { ArriEventContext, ArriOptions, ArriRoute, DefinitionMap, EventStreamConnectionOptions, EventStreamRpc, EventStreamRpcHandler, EventStreamRpcHandlerContext, ExtractParam, ExtractParams, HttpRpc, Middleware, MiddlewareEvent, MiddlewareEventContext, NamedHttpRpc, PostRouteEvent, RequestHookContext, RequestHookEvent, RequestValidator, RouteEvent, RouteEventContext, RouteOptions, RoutePostEventContext, Rpc, RpcEvent, RpcEventContext, RpcHandler, RpcParamSchema, RpcPostEvent, RpcPostEventContext, RpcPostHandler, SsePushResult };
285
+ export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, defineWorker, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, isWorker, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
286
+ export type { ArriEventContext, ArriOptions, ArriRoute, ArriWorker, DefinitionMap, EventStreamConnectionOptions, EventStreamRpc, EventStreamRpcHandler, EventStreamRpcHandlerContext, ExtractParam, ExtractParams, HttpRpc, Middleware, MiddlewareEvent, MiddlewareEventContext, NamedHttpRpc, PostRouteEvent, RequestHookContext, RequestHookEvent, RequestValidator, RouteEvent, RouteEventContext, RouteOptions, RoutePostEventContext, Rpc, RpcEvent, RpcEventContext, RpcHandler, RpcParamSchema, RpcPostEvent, RpcPostEventContext, RpcPostHandler, SsePushResult, WorkerErrorHandler, WorkerHandler };
package/dist/index.d.ts CHANGED
@@ -186,15 +186,29 @@ type RequestValidator<T = any> = {
186
186
  declare function getSchemaValidator<T extends Record<string, any> = any>(rpcName: string, type: 'params' | 'response', schema: RpcParamSchema<T>): RequestValidator<T> | undefined;
187
187
  declare function getSchemaName(rpcName: string, type: 'params' | 'response', schema: RpcParamSchema<any>): string;
188
188
 
189
+ type WorkerHandler = (signal: AbortSignal) => Promise<void> | void;
190
+ type WorkerErrorHandler = (error: unknown, worker: ArriWorker) => void | Promise<void>;
191
+ interface ArriWorker {
192
+ name?: string;
193
+ start: (signal: AbortSignal) => Promise<void> | void;
194
+ stop?: () => Promise<void> | void;
195
+ onError?: (error: unknown) => void | Promise<void>;
196
+ }
197
+ declare function defineWorker(worker: ArriWorker | WorkerHandler): ArriWorker;
198
+ declare function isWorker(input: unknown): input is ArriWorker;
199
+
189
200
  declare class ArriService {
190
201
  name: string;
191
202
  constructor(name: string);
192
203
  private readonly procedures;
193
204
  private readonly definitions;
205
+ private readonly workers;
194
206
  rpc(name: string, procedure: Rpc<any, any, any>): void;
207
+ registerWorker(worker: ArriWorker | WorkerHandler): void;
195
208
  registerDefinitions(models: DefinitionMap): void;
196
209
  getProcedures(): NamedHttpRpc<any, any, any>[];
197
210
  getDefinitions(): DefinitionMap;
211
+ getWorkers(): ArriWorker[];
198
212
  }
199
213
  declare function defineService(name: string, procedures?: Record<string, HttpRpc<any, any, any>>): ArriService;
200
214
 
@@ -207,9 +221,15 @@ declare class ArriApp {
207
221
  private readonly _rpcDefinitionPath;
208
222
  private readonly _rpcRoutePrefix;
209
223
  appInfo: AppDefinition['info'];
210
- private _procedures;
224
+ private readonly _procedures;
211
225
  private _definitions;
212
226
  private readonly _middlewares;
227
+ private readonly _workers;
228
+ private readonly _onStartHooks;
229
+ private readonly _onStopHooks;
230
+ private readonly _workerErrorHooks;
231
+ private _abortController;
232
+ private _isRunning;
213
233
  private readonly _onRequest;
214
234
  private readonly _onAfterResponse;
215
235
  private readonly _onBeforeResponse;
@@ -225,6 +245,15 @@ declare class ArriApp {
225
245
  route<TPath extends string, TQuery extends AObjectSchema<any, any>, TBody extends ASchema<any>, TResponse = any>(route: ArriRoute<TPath, TQuery, TBody, TResponse>): void;
226
246
  registerDefinitions(definitions: DefinitionMap): void;
227
247
  getAppDefinition(): AppDefinition;
248
+ registerWorker(worker: ArriWorker | WorkerHandler): void;
249
+ onStart(hook: () => Promise<void> | void): void;
250
+ onStop(hook: () => Promise<void> | void): void;
251
+ onWorkerError(hook: WorkerErrorHandler): void;
252
+ private _handleWorkerError;
253
+ start(): Promise<void>;
254
+ stop(): Promise<void>;
255
+ get workers(): ArriWorker[];
256
+ get isRunning(): boolean;
228
257
  }
229
258
  interface ArriOptions {
230
259
  debug?: boolean;
@@ -245,10 +274,13 @@ interface ArriOptions {
245
274
  onAfterResponse?: (event: RequestHookEvent) => void | Promise<void>;
246
275
  onBeforeResponse?: (event: RequestHookEvent) => void | Promise<void>;
247
276
  onError?: (error: arriError, event: RequestHookEvent) => void | Promise<void>;
277
+ onStart?: () => Promise<void> | void;
278
+ onStop?: () => Promise<void> | void;
279
+ onWorkerError?: WorkerErrorHandler;
248
280
  }
249
281
  interface RequestHookEvent extends Omit<H3Event, 'context'> {
250
282
  context: RequestHookContext;
251
283
  }
252
284
 
253
- export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
254
- export type { ArriEventContext, ArriOptions, ArriRoute, DefinitionMap, EventStreamConnectionOptions, EventStreamRpc, EventStreamRpcHandler, EventStreamRpcHandlerContext, ExtractParam, ExtractParams, HttpRpc, Middleware, MiddlewareEvent, MiddlewareEventContext, NamedHttpRpc, PostRouteEvent, RequestHookContext, RequestHookEvent, RequestValidator, RouteEvent, RouteEventContext, RouteOptions, RoutePostEventContext, Rpc, RpcEvent, RpcEventContext, RpcHandler, RpcParamSchema, RpcPostEvent, RpcPostEventContext, RpcPostHandler, SsePushResult };
285
+ export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, defineWorker, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, isWorker, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
286
+ export type { ArriEventContext, ArriOptions, ArriRoute, ArriWorker, DefinitionMap, EventStreamConnectionOptions, EventStreamRpc, EventStreamRpcHandler, EventStreamRpcHandlerContext, ExtractParam, ExtractParams, HttpRpc, Middleware, MiddlewareEvent, MiddlewareEventContext, NamedHttpRpc, PostRouteEvent, RequestHookContext, RequestHookEvent, RequestValidator, RouteEvent, RouteEventContext, RouteOptions, RoutePostEventContext, Rpc, RpcEvent, RpcEventContext, RpcHandler, RpcParamSchema, RpcPostEvent, RpcPostEventContext, RpcPostHandler, SsePushResult, WorkerErrorHandler, WorkerHandler };
package/dist/index.mjs CHANGED
@@ -803,6 +803,18 @@ class ArriRouter {
803
803
  }
804
804
  }
805
805
 
806
+ function defineWorker(worker) {
807
+ if (typeof worker === "function") {
808
+ return {
809
+ start: worker
810
+ };
811
+ }
812
+ return worker;
813
+ }
814
+ function isWorker(input) {
815
+ return typeof input === "object" && input !== null && "start" in input && typeof input.start === "function";
816
+ }
817
+
806
818
  class ArriService {
807
819
  name;
808
820
  constructor(name) {
@@ -810,10 +822,14 @@ class ArriService {
810
822
  }
811
823
  procedures = [];
812
824
  definitions = {};
825
+ workers = [];
813
826
  rpc(name, procedure) {
814
827
  procedure.name = `${this.name}.${name}`;
815
828
  this.procedures.push(procedure);
816
829
  }
830
+ registerWorker(worker) {
831
+ this.workers.push(defineWorker(worker));
832
+ }
817
833
  registerDefinitions(models) {
818
834
  for (const key of Object.keys(models)) {
819
835
  this.definitions[key] = models[key];
@@ -825,6 +841,9 @@ class ArriService {
825
841
  getDefinitions() {
826
842
  return this.definitions;
827
843
  }
844
+ getWorkers() {
845
+ return this.workers;
846
+ }
828
847
  }
829
848
  function defineService(name, procedures) {
830
849
  const service = new ArriService(name);
@@ -845,6 +864,12 @@ class ArriApp {
845
864
  _procedures = {};
846
865
  _definitions = {};
847
866
  _middlewares = [];
867
+ _workers = [];
868
+ _onStartHooks = [];
869
+ _onStopHooks = [];
870
+ _workerErrorHooks = [];
871
+ _abortController = null;
872
+ _isRunning = false;
848
873
  _onRequest;
849
874
  _onAfterResponse;
850
875
  _onBeforeResponse;
@@ -862,6 +887,15 @@ class ArriApp {
862
887
  this._onError = opts.onError;
863
888
  this._onAfterResponse = opts.onAfterResponse;
864
889
  this._onBeforeResponse = opts.onBeforeResponse;
890
+ if (opts.onStart) {
891
+ this.onStart(opts.onStart);
892
+ }
893
+ if (opts.onStop) {
894
+ this.onStop(opts.onStop);
895
+ }
896
+ if (opts.onWorkerError) {
897
+ this.onWorkerError(opts.onWorkerError);
898
+ }
865
899
  this._heartbeatMs = opts.heartbeatMs ?? 2e4;
866
900
  this._rpcRoutePrefix = opts?.rpcRoutePrefix ?? "";
867
901
  this._rpcDefinitionPath = opts?.rpcDefinitionPath ?? "__definition";
@@ -936,6 +970,9 @@ class ArriApp {
936
970
  this.rpc(rpc.name, rpc);
937
971
  }
938
972
  this.registerDefinitions(input.getDefinitions());
973
+ for (const worker of input.getWorkers()) {
974
+ this.registerWorker(worker);
975
+ }
939
976
  return;
940
977
  }
941
978
  this._middlewares.push(input);
@@ -1011,8 +1048,120 @@ class ArriApp {
1011
1048
  }
1012
1049
  return appDef;
1013
1050
  }
1051
+ registerWorker(worker) {
1052
+ const w = defineWorker(worker);
1053
+ this._workers.push(w);
1054
+ if (this._isRunning && this._abortController) {
1055
+ void (async () => {
1056
+ try {
1057
+ await w.start(this._abortController.signal);
1058
+ } catch (err) {
1059
+ await this._handleWorkerError(w, err);
1060
+ }
1061
+ })();
1062
+ }
1063
+ }
1064
+ onStart(hook) {
1065
+ this._onStartHooks.push(hook);
1066
+ }
1067
+ onStop(hook) {
1068
+ this._onStopHooks.push(hook);
1069
+ }
1070
+ onWorkerError(hook) {
1071
+ this._workerErrorHooks.push(hook);
1072
+ }
1073
+ async _handleWorkerError(worker, error) {
1074
+ let handled = false;
1075
+ if (worker.onError) {
1076
+ try {
1077
+ await worker.onError(error);
1078
+ handled = true;
1079
+ } catch (hookErr) {
1080
+ console.error(
1081
+ `[Arri] Error in worker.onError hook for worker ${worker.name ?? "unnamed"}:`,
1082
+ hookErr
1083
+ );
1084
+ }
1085
+ }
1086
+ for (const hook of this._workerErrorHooks) {
1087
+ try {
1088
+ await hook(error, worker);
1089
+ handled = true;
1090
+ } catch (hookErr) {
1091
+ console.error(
1092
+ `[Arri] Error in onWorkerError hook for worker ${worker.name ?? "unnamed"}:`,
1093
+ hookErr
1094
+ );
1095
+ }
1096
+ }
1097
+ if (!handled) {
1098
+ console.error(
1099
+ `[Arri] Unhandled error in worker ${worker.name ?? "unnamed"}:`,
1100
+ error
1101
+ );
1102
+ }
1103
+ }
1104
+ async start() {
1105
+ if (this._isRunning) {
1106
+ return;
1107
+ }
1108
+ this._isRunning = true;
1109
+ const controller = new AbortController();
1110
+ this._abortController = controller;
1111
+ try {
1112
+ for (const hook of this._onStartHooks) {
1113
+ await hook();
1114
+ }
1115
+ } catch (err) {
1116
+ this._isRunning = false;
1117
+ controller.abort();
1118
+ this._abortController = null;
1119
+ throw err;
1120
+ }
1121
+ for (const worker of this._workers) {
1122
+ void (async () => {
1123
+ try {
1124
+ await worker.start(controller.signal);
1125
+ } catch (err) {
1126
+ await this._handleWorkerError(worker, err);
1127
+ }
1128
+ })();
1129
+ }
1130
+ }
1131
+ async stop() {
1132
+ if (!this._isRunning) {
1133
+ return;
1134
+ }
1135
+ this._isRunning = false;
1136
+ this._abortController?.abort();
1137
+ const stopPromises = this._workers.map(async (worker) => {
1138
+ try {
1139
+ if (worker.stop) {
1140
+ await worker.stop();
1141
+ }
1142
+ } catch (err) {
1143
+ await this._handleWorkerError(worker, err);
1144
+ }
1145
+ });
1146
+ const hookPromises = this._onStopHooks.map(async (hook) => {
1147
+ try {
1148
+ await hook();
1149
+ } catch (err) {
1150
+ if (this._debug) {
1151
+ console.error("Error in onStop hook:", err);
1152
+ }
1153
+ }
1154
+ });
1155
+ await Promise.allSettled([...stopPromises, ...hookPromises]);
1156
+ }
1157
+ get workers() {
1158
+ return [...this._workers];
1159
+ }
1160
+ get isRunning() {
1161
+ return this._isRunning;
1162
+ }
1014
1163
  }
1015
1164
 
1016
1165
  const defineMiddleware = (middleware) => middleware;
1017
1166
 
1018
- export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
1167
+ export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, defineWorker, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, isWorker, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrirpc/server",
3
- "version": "0.84.1",
3
+ "version": "0.85.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -28,9 +28,9 @@
28
28
  "scule": "^1.3.0",
29
29
  "source-map-support": "^0.5.21",
30
30
  "uncrypto": "^0.1.3",
31
- "@arrirpc/codegen-utils": "0.84.1",
32
- "@arrirpc/schema": "0.84.1",
33
- "arri": "0.84.1"
31
+ "@arrirpc/codegen-utils": "0.85.0",
32
+ "@arrirpc/schema": "0.85.0",
33
+ "arri": "0.85.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "bun-types": "^1.3.8"