@velajs/vela 1.6.0 → 1.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0 (2026-05-14)
4
+
5
+ ### New
6
+
7
+ - **`OnFirstRequest` lifecycle hook.** Fires exactly once on the first incoming HTTP request, AFTER user-supplied middleware (so runtime adapters like `@velajs/cloudflare`'s binding-init can run first) and BEFORE route handlers. Bridges module-load and request-time semantics — the canonical seam for state that depends on values only available at request time (Cloudflare D1/KV/R2 bindings, Deno Deploy env, etc.). Auto-fired by a global middleware vela installs after user middleware; non-HTTP consumers can trigger it manually via `app.callOnFirstRequest()`. Concurrency-safe: vela memoizes the promise so parallel first requests share a single fire.
8
+
9
+ ### Notes
10
+
11
+ - Edge-clean addition — no `node:*`, no `Buffer`, no `setInterval`; the hook is just `() => void | Promise<void>` wired into Hono's middleware chain.
12
+ - Backwards-compatible. Existing apps unaffected: the new middleware only fires hooks if any registered instance implements `OnFirstRequest`, and the implementation is a no-op otherwise.
13
+
3
14
  ## [1.6.0]
4
15
 
5
16
  The exception-filter chain now reaches into attached middlewares for full NestJS parity, and a new `createLazyParamDecorator` helper closes the parameter-decorator-vs-guard ordering hazard at the public-API surface.
@@ -9,6 +9,7 @@ export declare class VelaApplication {
9
9
  private readonly routeManager;
10
10
  private instances;
11
11
  private honoApp;
12
+ private firstRequestPromise;
12
13
  constructor(container: Container, routeManager: RouteManager);
13
14
  /** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */
14
15
  initRoutes(): Promise<void>;
@@ -36,5 +37,14 @@ export declare class VelaApplication {
36
37
  mountOpenApi(options: MountOpenApiOptions): this;
37
38
  callOnModuleInit(): Promise<void>;
38
39
  callOnApplicationBootstrap(): Promise<void>;
40
+ /**
41
+ * Fire `OnFirstRequest` hooks exactly once across the application's
42
+ * lifetime. Auto-invoked by a global middleware on the first HTTP request;
43
+ * also safe to call manually from non-HTTP entry points (CLI, tests).
44
+ *
45
+ * Concurrent calls share the same in-flight promise — vela guarantees
46
+ * each instance's `onFirstRequest()` runs at most once.
47
+ */
48
+ callOnFirstRequest(): Promise<void>;
39
49
  close(signal?: string): Promise<void>;
40
50
  }
@@ -1,10 +1,13 @@
1
- import { hasBeforeApplicationShutdown, hasOnApplicationBootstrap, hasOnApplicationShutdown, hasOnModuleDestroy, hasOnModuleInit } from "./lifecycle/index.js";
1
+ import { hasBeforeApplicationShutdown, hasOnApplicationBootstrap, hasOnApplicationShutdown, hasOnFirstRequest, hasOnModuleDestroy, hasOnModuleInit } from "./lifecycle/index.js";
2
2
  import { renderScalarUi } from "./openapi/scalar-ui.js";
3
3
  export class VelaApplication {
4
4
  container;
5
5
  routeManager;
6
6
  instances = [];
7
7
  honoApp = null;
8
+ // Memoizes the first-request hook fire. Concurrent first requests share
9
+ // this promise so the hook runs exactly once.
10
+ firstRequestPromise;
8
11
  constructor(container, routeManager){
9
12
  this.container = container;
10
13
  this.routeManager = routeManager;
@@ -89,6 +92,25 @@ export class VelaApplication {
89
92
  }
90
93
  }
91
94
  }
95
+ /**
96
+ * Fire `OnFirstRequest` hooks exactly once across the application's
97
+ * lifetime. Auto-invoked by a global middleware on the first HTTP request;
98
+ * also safe to call manually from non-HTTP entry points (CLI, tests).
99
+ *
100
+ * Concurrent calls share the same in-flight promise — vela guarantees
101
+ * each instance's `onFirstRequest()` runs at most once.
102
+ */ callOnFirstRequest() {
103
+ if (!this.firstRequestPromise) {
104
+ this.firstRequestPromise = (async ()=>{
105
+ for (const instance of this.instances){
106
+ if (hasOnFirstRequest(instance)) {
107
+ await instance.onFirstRequest();
108
+ }
109
+ }
110
+ })();
111
+ }
112
+ return this.firstRequestPromise;
113
+ }
92
114
  async close(signal) {
93
115
  const reversed = [
94
116
  ...this.instances
package/dist/factory.js CHANGED
@@ -6,6 +6,18 @@ export const VelaFactory = {
6
6
  const app = new VelaApplication(container, routeManager);
7
7
  const instances = await loader.resolveAllInstances();
8
8
  app.setInstances(instances);
9
+ // Register the first-request middleware AFTER user-supplied middleware
10
+ // (registered by bootstrap from `options.middleware`). Ordering matters:
11
+ // runtime adapters like `@velajs/cloudflare` install binding-init
12
+ // middleware that must run BEFORE OnFirstRequest hooks read those
13
+ // bindings. Vela's middleware is appended last → fires after all user
14
+ // middleware, before route handlers (which are registered separately).
15
+ routeManager.useGlobalMiddleware({
16
+ use: async (_c, next)=>{
17
+ await app.callOnFirstRequest();
18
+ await next();
19
+ }
20
+ });
9
21
  await app.callOnModuleInit();
10
22
  await app.callOnApplicationBootstrap();
11
23
  // Build routes (async — supports CRUD integration with dynamic imports)
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export type { Constructor, MiddlewareType, GuardType, PipeType, InterceptorType,
41
41
  export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe, } from './pipeline/index';
42
42
  export type { ParseUUIDPipeOptions, ParseArrayPipeOptions } from './pipeline/index';
43
43
  export { HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, MethodNotAllowedException, NotAcceptableException, RequestTimeoutException, ConflictException, GoneException, PayloadTooLargeException, UnsupportedMediaTypeException, UnprocessableEntityException, TooManyRequestsException, InternalServerErrorException, NotImplementedException, BadGatewayException, ServiceUnavailableException, GatewayTimeoutException, } from './errors/index';
44
- export type { OnModuleInit, OnApplicationBootstrap, OnModuleDestroy, OnApplicationShutdown, BeforeApplicationShutdown, } from './lifecycle/index';
44
+ export type { OnModuleInit, OnApplicationBootstrap, OnFirstRequest, OnModuleDestroy, OnApplicationShutdown, BeforeApplicationShutdown, } from './lifecycle/index';
45
45
  export { MetadataRegistry } from './registry/metadata.registry';
46
46
  export { createZodDto, ValidationPipe } from './validation/index';
47
47
  export type { CreateZodDtoOptions } from './validation/index';
@@ -4,6 +4,25 @@ export interface OnModuleInit {
4
4
  export interface OnApplicationBootstrap {
5
5
  onApplicationBootstrap(): void | Promise<void>;
6
6
  }
7
+ /**
8
+ * Fires exactly once, on the first incoming HTTP request, AFTER user-supplied
9
+ * middleware (so binding initializers like `@velajs/cloudflare`'s env capture
10
+ * run first) and BEFORE route handlers. The hook bridges module-load and
11
+ * request-time semantics — use it for state that depends on values only
12
+ * available at request time (Cloudflare bindings, Deno Deploy env, etc.).
13
+ *
14
+ * Concurrency: vela memoizes the call. Concurrent first requests all await
15
+ * the same promise; the hook runs exactly once.
16
+ *
17
+ * Non-HTTP consumers (CLI, tests) can trigger it manually via
18
+ * `app.callOnFirstRequest()`.
19
+ *
20
+ * Edge-safe: the hook itself is just `() => void | Promise<void>`. The
21
+ * runtime contract is async/Promise — no Node-only APIs are involved.
22
+ */
23
+ export interface OnFirstRequest {
24
+ onFirstRequest(): void | Promise<void>;
25
+ }
7
26
  export interface OnModuleDestroy {
8
27
  onModuleDestroy(): void | Promise<void>;
9
28
  }
@@ -15,6 +34,7 @@ export interface BeforeApplicationShutdown {
15
34
  }
16
35
  export declare function hasOnModuleInit(instance: unknown): instance is OnModuleInit;
17
36
  export declare function hasOnApplicationBootstrap(instance: unknown): instance is OnApplicationBootstrap;
37
+ export declare function hasOnFirstRequest(instance: unknown): instance is OnFirstRequest;
18
38
  export declare function hasOnModuleDestroy(instance: unknown): instance is OnModuleDestroy;
19
39
  export declare function hasBeforeApplicationShutdown(instance: unknown): instance is BeforeApplicationShutdown;
20
40
  export declare function hasOnApplicationShutdown(instance: unknown): instance is OnApplicationShutdown;
@@ -4,6 +4,9 @@ export function hasOnModuleInit(instance) {
4
4
  export function hasOnApplicationBootstrap(instance) {
5
5
  return instance !== null && typeof instance === 'object' && typeof instance.onApplicationBootstrap === 'function';
6
6
  }
7
+ export function hasOnFirstRequest(instance) {
8
+ return instance !== null && typeof instance === 'object' && typeof instance.onFirstRequest === 'function';
9
+ }
7
10
  export function hasOnModuleDestroy(instance) {
8
11
  return instance !== null && typeof instance === 'object' && typeof instance.onModuleDestroy === 'function';
9
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/vela",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "NestJS-compatible framework for edge runtimes, powered by Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",