@jeffusion/bungee-core 5.15.2 → 5.17.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.
@@ -9,7 +9,7 @@ export interface ConfigWorkerLifecycle<ServingHandle> {
9
9
  readonly plugin_status: PluginRuntimeOrchestratorStatusReport;
10
10
  }>;
11
11
  stop(handle: ServingHandle): Promise<void>;
12
- stopAccepting(handle: ServingHandle): Promise<void>;
12
+ stopAccepting(handle: ServingHandle, drainTimeoutMs?: number): Promise<void>;
13
13
  drain(handle: ServingHandle): Promise<void>;
14
14
  forceStop?(handle: ServingHandle): Promise<void>;
15
15
  }
@@ -1,3 +1,4 @@
1
+ import { type WebSocketBridge, type WebSocketBridgeData } from '../websocket';
1
2
  import type { AppConfig } from '@jeffusion/bungee-types';
2
3
  import type { Server } from 'bun';
3
4
  import { type ConfigWorkerLifecycle } from '../config-publication/worker-runtime-contract';
@@ -8,6 +9,9 @@ import type { PluginServiceHost } from '../plugin-services';
8
9
  type LifecycleServer = Pick<Server<unknown>, 'port' | 'stop'>;
9
10
  export type ConfigWorkerServingHandle = {
10
11
  readonly server: LifecycleServer;
12
+ readonly websocket?: WebSocketBridge;
13
+ readonly sessions?: Set<Promise<void>>;
14
+ closeTimer?: ReturnType<typeof setTimeout>;
11
15
  readonly cleanup: () => Promise<void>;
12
16
  drainPromise: Promise<void> | null;
13
17
  drainComplete: boolean;
@@ -16,6 +20,7 @@ export type ConfigWorkerServingHandle = {
16
20
  stopPromise: Promise<void> | null;
17
21
  };
18
22
  type LifecycleFetch = (request: Request) => Response | Promise<Response>;
23
+ type WebSocketFetch = (request: Request, server?: Bun.Server<any>) => Response | undefined | Promise<Response | undefined>;
19
24
  export type ServingRequestContext = {
20
25
  readonly servingRevision: number;
21
26
  readonly logging?: RequestLoggerDependencies;
@@ -34,6 +39,7 @@ export type ProductionResources = {
34
39
  cleanupPluginRuntime(): Promise<void>;
35
40
  handleRequest(request: Request, config: AppConfig, context: ServingRequestContext): Promise<Response>;
36
41
  serve(fetch: LifecycleFetch): LifecycleServer;
42
+ serveWebSocket?(fetch: WebSocketFetch, websocket: Bun.WebSocketHandler<WebSocketBridgeData>): LifecycleServer;
37
43
  closeAccessLog(): Promise<void>;
38
44
  closeFileLog(): Promise<void>;
39
45
  };
@@ -42,6 +42,7 @@ export declare class WorkerRequestAdmission {
42
42
  url: string;
43
43
  model: string | null;
44
44
  body: unknown;
45
+ transport?: 'websocket';
45
46
  }, signal: AbortSignal, loadBody?: () => Promise<unknown>): Promise<PreparedAdmissionAttempt[]>;
46
47
  release(): Promise<void>;
47
48
  }
@@ -107,4 +107,4 @@ export interface GatewayLogResult {
107
107
  readonly logger?: RequestLogger;
108
108
  }
109
109
  /** Provider stages are fixed, required, and registered exactly once at startup. */
110
- export declare const GATEWAY_PROVIDER_STAGES: readonly ["onGatewayBody", "onGatewayRequest", "onGatewayRoute", "onGatewayAdmission", "onGatewayAdmissionSession", "onGatewayAdmissionPrepare", "onGatewaySelect", "onGatewayFailover", "onGatewayRetry", "onGatewayForward", "onGatewayHeaderRules", "onGatewayCors", "onGatewayBodyRules", "onGatewayQueryRules", "onGatewayResponseRules", "onGatewayLog"];
110
+ export declare const GATEWAY_PROVIDER_STAGES: readonly ["onGatewayWebSocket", "onGatewayBody", "onGatewayRequest", "onGatewayRoute", "onGatewayAdmission", "onGatewayAdmissionSession", "onGatewayAdmissionPrepare", "onGatewaySelect", "onGatewayFailover", "onGatewayRetry", "onGatewayForward", "onGatewayHeaderRules", "onGatewayCors", "onGatewayBodyRules", "onGatewayQueryRules", "onGatewayResponseRules", "onGatewayLog"];
@@ -6,6 +6,7 @@ export type * from '../plugin-extensions';
6
6
  export type * from '../plugin-services';
7
7
  export type * from './contracts';
8
8
  export type * from './body-contracts';
9
+ export type * from './websocket-contracts';
9
10
  export { protocolSSEOutput } from '../plugins/sse-envelope';
10
11
  export { logger } from '../plugin-logger';
11
12
  export { ANONYMOUS_PRINCIPAL } from '../plugin-extensions';
@@ -319,6 +319,9 @@ var init_impl = __esm(() => {
319
319
  // src/hooks/plugin-hooks.ts
320
320
  function createPluginHooks() {
321
321
  return {
322
+ onGatewayWebSocket: new AsyncSeriesBailHook("onGatewayWebSocket"),
323
+ onWebSocketHandshake: new AsyncSeriesWaterfallHook("onWebSocketHandshake"),
324
+ onWebSocketObservation: new WebSocketObservationHook("onWebSocketObservation"),
322
325
  onGatewayBody: new SyncBailHook("onGatewayBody"),
323
326
  onGatewayRequest: new AsyncSeriesBailHook("onGatewayRequest"),
324
327
  onGatewayRoute: new AsyncSeriesBailHook("onGatewayRoute"),
@@ -358,7 +361,7 @@ function clearHooks(hooks) {
358
361
  for (const hook of Object.values(hooks))
359
362
  hook.clear();
360
363
  }
361
- var AttemptObservationHook;
364
+ var AttemptObservationHook, WebSocketObservationHook;
362
365
  var init_plugin_hooks = __esm(() => {
363
366
  init_impl();
364
367
  AttemptObservationHook = class AttemptObservationHook extends AsyncParallelHook {
@@ -374,6 +377,17 @@ var init_plugin_hooks = __esm(() => {
374
377
  }
375
378
  }
376
379
  };
380
+ WebSocketObservationHook = class WebSocketObservationHook extends AsyncParallelHook {
381
+ async promise(...args) {
382
+ this.callCount++;
383
+ const started = performance.now();
384
+ try {
385
+ await Promise.all(this.taps.map((tap) => this.executeTap(tap, args)));
386
+ } finally {
387
+ this.totalTimeMs += performance.now() - started;
388
+ }
389
+ }
390
+ };
377
391
  });
378
392
 
379
393
  // src/hooks/index.ts
@@ -1,6 +1,7 @@
1
1
  import { type PluginHooks } from '../hooks';
2
2
  import type { Plugin } from '../plugin.types';
3
3
  import { type executeHttpRequest } from './request-plugin';
4
+ import type { GatewayWebSocketInput, GatewayWebSocketResult } from './websocket-contracts';
4
5
  export declare function gatewayBuiltins(): Plugin[];
5
6
  /** The host only owns registration; providers use the existing Plugin contract and Hook executor. */
6
7
  export declare function registerGatewayBuiltins(hooks: PluginHooks, plugins?: readonly Plugin[]): void;
@@ -10,3 +11,4 @@ export declare function gatewayHooks(): PluginHooks;
10
11
  export declare function initializeGateway(): void;
11
12
  export declare function requireGatewayResult<T>(value: T | undefined, stage: string): T;
12
13
  export declare const runGatewayRequest: typeof executeHttpRequest;
14
+ export declare function runGatewayWebSocket(input: GatewayWebSocketInput): Promise<GatewayWebSocketResult>;
@@ -0,0 +1,62 @@
1
+ /** Read-only WebSocket SDK. HTTP bodies and WebSocket messages have separate owners. */
2
+ export interface WebSocketMessageView {
3
+ readonly kind: 'text' | 'binary';
4
+ readonly byteLength: number;
5
+ /** Demand-driven, shared, deeply frozen JSON; undefined for binary/invalid JSON. */
6
+ json(): unknown;
7
+ }
8
+ export interface WebSocketSessionContext {
9
+ readonly connectionId: string;
10
+ readonly keyId?: string | null;
11
+ readonly routeId: string;
12
+ readonly upstreamId: string;
13
+ readonly upstreamUrl: string;
14
+ readonly servingRevision?: number;
15
+ }
16
+ export interface WebSocketSessionMetrics {
17
+ readonly durationMs: number;
18
+ readonly clientMessages: number;
19
+ readonly upstreamMessages: number;
20
+ readonly clientBytes: number;
21
+ readonly upstreamBytes: number;
22
+ }
23
+ export type WebSocketObservationEvent = Readonly<WebSocketSessionContext & {
24
+ /** Delivery lease expires when the observer returns or exceeds its deadline. */
25
+ readonly isActive: () => boolean;
26
+ } & ({
27
+ readonly phase: 'open';
28
+ } | {
29
+ readonly phase: 'message';
30
+ readonly direction: 'client' | 'upstream';
31
+ readonly message: WebSocketMessageView;
32
+ } | {
33
+ readonly phase: 'incomplete';
34
+ readonly reason: 'observer-timeout' | 'observer-error' | 'buffer-limit' | 'message-limit';
35
+ } | {
36
+ readonly phase: 'close';
37
+ readonly code: number;
38
+ readonly reason: string;
39
+ readonly metrics: WebSocketSessionMetrics;
40
+ })>;
41
+ /** Handshake-only transforms; message data is never mutable through this hook. */
42
+ export interface WebSocketHandshakeContext {
43
+ readonly connectionId: string;
44
+ readonly routeId: string;
45
+ readonly upstreamId: string;
46
+ readonly signal: AbortSignal;
47
+ url: URL;
48
+ headers: Headers;
49
+ }
50
+ export interface GatewayWebSocketInput {
51
+ readonly request: Request;
52
+ readonly nativeRequest: Request;
53
+ readonly server: Bun.Server<any>;
54
+ readonly bridge: import('../websocket').WebSocketBridge;
55
+ readonly config: import('@jeffusion/bungee-types').AppConfig;
56
+ readonly servingRevision: number;
57
+ /** Host shutdown barrier; retains observer/credential owners until close cleanup. */
58
+ readonly retain: (completion: Promise<void>) => void;
59
+ }
60
+ export interface GatewayWebSocketResult {
61
+ readonly response?: Response;
62
+ }
@@ -0,0 +1,3 @@
1
+ import type { RuntimeUpstream } from '../worker/types';
2
+ /** Uses the same exact endpoint/binding/origin credential policy as HTTP attempts. */
3
+ export declare function websocketCredential(upstream: RuntimeUpstream, url: URL, sourceHeaders: Headers, revision: number, attemptId: string, signal: AbortSignal): Promise<Headers>;
@@ -0,0 +1,9 @@
1
+ import type { Plugin, PluginHooks } from '@jeffusion/bungee-core/plugin';
2
+ /** WebSocket is a registered gateway provider, with handshake and read-only message hooks. */
3
+ export declare class WebSocketGatewayPlugin implements Plugin {
4
+ bodyRequirements(): {
5
+ request: "none";
6
+ };
7
+ register(hooks: PluginHooks): void;
8
+ private upgrade;
9
+ }
@@ -1,3 +1,4 @@
1
+ import type { WebSocketObservationEvent, WebSocketHandshakeContext, GatewayWebSocketInput, GatewayWebSocketResult } from '../gateway/websocket-contracts';
1
2
  /**
2
3
  * Bungee Plugin Hooks 定义
3
4
  *
@@ -109,6 +110,10 @@ export type AttemptObservationEvent = Readonly<{
109
110
  declare class AttemptObservationHook extends AsyncParallelHook<[AttemptObservationEvent]> {
110
111
  promise(...args: [AttemptObservationEvent]): Promise<void>;
111
112
  }
113
+ /** Strict, isolated WebSocket observers use the same plugin registration contract. */
114
+ declare class WebSocketObservationHook extends AsyncParallelHook<[WebSocketObservationEvent]> {
115
+ promise(...args: [WebSocketObservationEvent]): Promise<void>;
116
+ }
112
117
  /**
113
118
  * 错误上下文
114
119
  */
@@ -220,6 +225,9 @@ export declare function createPluginHooks(): PluginHooks;
220
225
  * Plugin Hooks 类型
221
226
  */
222
227
  export interface PluginHooks {
228
+ onGatewayWebSocket: AsyncSeriesBailHook<[GatewayWebSocketInput], GatewayWebSocketResult>;
229
+ onWebSocketHandshake: AsyncSeriesWaterfallHook<WebSocketHandshakeContext>;
230
+ onWebSocketObservation: WebSocketObservationHook;
223
231
  onGatewayBody: SyncBailHook<GatewayBodyArguments, BodySource>;
224
232
  onGatewayHeaderRules: AsyncSeriesBailHook<GatewayHeaderRuleArguments, true>;
225
233
  onGatewayCors: AsyncSeriesBailHook<GatewayCorsArguments, Response>;
@@ -100,7 +100,7 @@ export declare const accessLogWriter: {
100
100
  getDatabase: () => Database;
101
101
  updateResponseBodyId: (requestId: string, respBodyId: string) => void;
102
102
  updateBodyId: (requestId: string, type: "request" | "response" | "original-request", bodyId: string) => void;
103
- updateProtocolOutcome: (requestId: string, outcome: "cancelled" | "failed" | "completed" | "incomplete", success: boolean, code?: string | undefined, diagnosticMessage?: string | undefined) => void;
103
+ updateProtocolOutcome: (requestId: string, outcome: "incomplete" | "cancelled" | "failed" | "completed", success: boolean, code?: string | undefined, diagnosticMessage?: string | undefined) => void;
104
104
  updateTransportOutcome: (requestId: string, outcome: "cancelled" | "unknown" | "failed" | "pending" | "completed", code?: string | undefined) => void;
105
105
  flush: () => Promise<void>;
106
106
  close: () => Promise<void>;