@cosmicdrift/kumiko-types 0.326.0 → 0.327.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.326.0",
3
+ "version": "0.327.0",
4
4
  "description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -42,6 +42,10 @@
42
42
  "types": "./src/http-route.ts",
43
43
  "default": "./src/http-route.ts"
44
44
  },
45
+ "./websocket-route": {
46
+ "types": "./src/websocket-route.ts",
47
+ "default": "./src/websocket-route.ts"
48
+ },
45
49
  "./relations": {
46
50
  "types": "./src/relations.ts",
47
51
  "default": "./src/relations.ts"
package/src/feature.ts CHANGED
@@ -77,6 +77,7 @@ import type {
77
77
  import type { EntityRelations, RelationDefinition } from "./relations";
78
78
  import type { ScreenDefinition } from "./screen";
79
79
  import type { TreeActionDef, TreeActionsHandle } from "./tree-node";
80
+ import type { WebSocketRouteDefinition } from "./websocket-route";
80
81
  import type { WorkspaceDefinition } from "./workspace";
81
82
 
82
83
  // --- Metrics (declared by features via r.metric()) ---
@@ -371,6 +372,8 @@ export type FeatureDefinition = {
371
372
  // den Hono-app (außerhalb /api/*). Pattern symmetrisch zu queryHandlers/
372
373
  // writeHandlers — Routes leben mit dem Feature, nicht im Bootstrap.
373
374
  readonly httpRoutes: Readonly<Record<string, HttpRouteDefinition>>;
375
+ // WebSocket routes declared via r.webSocketRoute(), keyed by path.
376
+ readonly webSocketRoutes: Readonly<Record<string, WebSocketRouteDefinition>>;
374
377
  // Store tables declared via r.storeTable() — bypass the event-sourcing
375
378
  // system. Keyed by feature-local short name (derived from
376
379
  // meta.tableName). The registry attaches featureName on aggregation,
@@ -859,6 +862,12 @@ export type FeatureRegistrar<TFeature extends string = string> = {
859
862
  // runProdApp.extraRoutes (declarative list with `entry` tier).
860
863
  httpRoute(definition: HttpRouteDefinition): void;
861
864
 
865
+ // Register a WebSocket endpoint under /api/ws/*. Rides the /api/* auth
866
+ // chain; the framework adds a cookie-session origin check, a per-message
867
+ // size cap and a heartbeat with session re-validation. Duplicate paths are
868
+ // rejected per feature here and across features in buildServer.
869
+ webSocketRoute(definition: WebSocketRouteDefinition): void;
870
+
862
871
  // Declare an "unmanaged" framework-native table that bypasses the
863
872
  // event-sourcing system. Reserved for legacy-import, read-only caches,
864
873
  // write-only webhook payload buffers, or read-side projections of
@@ -0,0 +1,68 @@
1
+ // WebSocket route definition — feature-declared WebSocket endpoints under
2
+ // /api/ws/*. Auth (session + origin) runs before the upgrade; `connect`
3
+ // then returns the per-connection handlers or a Response that rejects it.
4
+
5
+ import type { Context } from "hono";
6
+ import type { SessionUser, WriteResult } from "./handlers";
7
+
8
+ export const WEBSOCKET_MAX_PAYLOAD_BYTES = 1024 * 1024;
9
+ export const WEBSOCKET_DEFAULT_MAX_MESSAGE_BYTES = 64 * 1024;
10
+ export const WEBSOCKET_ROUTE_PATH_PREFIX = "/api/ws/";
11
+ // Bun closes a socket whose unsent outbound queue exceeds this (slow or stalled reader).
12
+ export const WEBSOCKET_BACKPRESSURE_LIMIT_BYTES = 4 * 1024 * 1024;
13
+ export const WEBSOCKET_DEFAULT_MAX_CONNECTIONS_PER_USER = 5;
14
+ export const WEBSOCKET_MAX_CONNECTIONS_PER_USER_LIMIT = 100;
15
+
16
+ export type WebSocketMessageData = string | Uint8Array;
17
+
18
+ export type WebSocketConnection = {
19
+ readonly send: (data: WebSocketMessageData) => void;
20
+ readonly close: (code?: number, reason?: string) => void;
21
+ /**
22
+ * Aborts when the socket closes. onClose can run while onOpen/onMessage is
23
+ * still awaiting, so after any await that allocates (e.g. an upstream
24
+ * session), check `signal.aborted` and release it yourself.
25
+ */
26
+ readonly signal: AbortSignal;
27
+ };
28
+
29
+ export type WebSocketSessionHandlers = {
30
+ /** May still be awaiting when onClose runs; see `WebSocketConnection.signal`. */
31
+ readonly onOpen?: (connection: WebSocketConnection) => void | Promise<void>;
32
+ readonly onMessage?: (
33
+ data: WebSocketMessageData,
34
+ connection: WebSocketConnection,
35
+ ) => void | Promise<void>;
36
+ /** Runs immediately on close, not queued behind a slow onMessage; pending messages never start after it. */
37
+ readonly onClose?: (
38
+ code: number,
39
+ reason: string,
40
+ connection: WebSocketConnection,
41
+ ) => void | Promise<void>;
42
+ };
43
+
44
+ export type WebSocketRouteConnectDeps = {
45
+ readonly user: SessionUser;
46
+ readonly query: (type: string, payload: unknown) => Promise<unknown>;
47
+ readonly write: (type: string, payload: unknown) => Promise<WriteResult>;
48
+ /** Resolved via the server's trustedProxyHops-aware resolver, like HttpRouteHandlerDeps.clientIp. */
49
+ readonly clientIp: string;
50
+ };
51
+
52
+ export type WebSocketRouteDefinition = {
53
+ /** Must start with "/api/ws/"; `:param` segments allowed, no "*". */
54
+ readonly path: string;
55
+ /** Per-message cap; oversized messages close the socket with 1009. Default 64 KiB, max 1 MiB. */
56
+ readonly maxMessageBytes?: number;
57
+ /** Concurrent sockets per user and tenant on this route (per server process). Default 5, max 100; above it the upgrade gets 429. */
58
+ readonly maxConnectionsPerUser?: number;
59
+ /**
60
+ * Runs after auth + origin checks, before the upgrade. Returning a Response rejects the upgrade with it.
61
+ * Allocate per-connection resources in `onOpen`, not here: if the upgrade then fails, no `onClose` runs.
62
+ */
63
+ readonly connect: (
64
+ // biome-ignore lint/suspicious/noExplicitAny: Hono context generics are invisible at the framework boundary
65
+ c: Context<any, any>,
66
+ deps: WebSocketRouteConnectDeps,
67
+ ) => WebSocketSessionHandlers | Response | Promise<WebSocketSessionHandlers | Response>;
68
+ };