redweb 0.8.0 → 0.10.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 +28 -0
- package/README.md +573 -307
- package/client.d.ts +42 -0
- package/client.js +55 -0
- package/docs/LIVE_HTML.md +313 -0
- package/docs/MULTIPLAYER_OPERATIONS.md +50 -0
- package/docs/PRODUCTION_READINESS.md +68 -0
- package/docs/VERIFICATION_EVIDENCE.md +20 -0
- package/examples/live-html/cards.css +36 -0
- package/examples/live-html/cards.html +11 -0
- package/examples/live-html/cards.js +91 -0
- package/examples/live-html/cards.ts +35 -0
- package/examples/live-html/chatroom.css +156 -0
- package/examples/live-html/chatroom.js +268 -0
- package/examples/live-html/chatroom.ts +217 -0
- package/examples/live-html/components.css +7 -0
- package/examples/live-html/components.js +113 -0
- package/examples/live-html/components.ts +41 -0
- package/examples/live-html/counter.css +24 -0
- package/examples/live-html/counter.html +10 -0
- package/examples/live-html/counter.js +73 -0
- package/examples/live-html/counter.ts +21 -0
- package/examples/live-html/tsconfig.json +16 -0
- package/index.d.ts +538 -114
- package/index.js +44 -12
- package/package.json +39 -15
- package/src/htmx/Html.js +133 -0
- package/src/htmx/HtmlRenderer.js +88 -0
- package/src/htmx/HtmlSyntax.js +168 -0
- package/src/htmx/LiveHtmlServer.js +91 -0
- package/src/htmx/LivePage.js +232 -0
- package/src/htmx/PageAssetLoader.js +34 -0
- package/src/htmx/PageManager.js +435 -0
- package/src/htmx/StaticExporter.js +78 -0
- package/src/htmx/StaticSite.js +182 -0
- package/src/htmx/TemplateRenderer.js +231 -0
- package/src/htmx/browserRuntime.js +97 -0
- package/src/htmx/index.js +10 -0
- package/src/htmx/metadata.js +349 -0
- package/src/htmx/sourceRoot.js +28 -0
- package/src/htmx/start.js +17 -0
- package/src/htmx/synchronous.js +9 -0
- package/src/http/BaseHttpServer.js +82 -117
- package/src/http/HttpServer.js +18 -18
- package/src/http/HttpsServer.js +20 -20
- package/src/serverLifecycle.js +46 -46
- package/src/ws/AdmissionPolicy.js +145 -0
- package/src/ws/BaseHandler.js +40 -40
- package/src/ws/BaseSocketServer.js +199 -100
- package/src/ws/DefaultHandler.js +5 -5
- package/src/ws/DefaultRoute.js +8 -8
- package/src/ws/DistributionBridge.js +271 -0
- package/src/ws/FixedStepService.js +74 -0
- package/src/ws/HeartbeatMonitor.js +75 -0
- package/src/ws/Metrics.js +34 -0
- package/src/ws/ProtocolPolicy.js +130 -0
- package/src/ws/RoomRegistry.js +117 -0
- package/src/ws/RouteRuntime.js +146 -0
- package/src/ws/SecureSocketServer.js +9 -9
- package/src/ws/SessionRegistry.js +135 -0
- package/src/ws/SocketRoute.js +523 -254
- package/src/ws/SocketServer.js +8 -8
- package/src/ws/TaskQueue.js +64 -0
- package/src/ws/TokenBucket.js +31 -0
- package/src/ws/TransportPolicy.js +68 -0
- package/src/ws/index.js +7 -2
- package/src/ws/protocol-schema.json +13 -0
- package/src/ws/protocol-validation.js +21 -0
- package/src/ws/shutdown.js +33 -33
- package/src/ws/util.js +38 -30
- package/src/htmx/HtmxRenderer.js +0 -73
- package/src/htmx/RedWebHtmxComponent.js +0 -11
package/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
declare module 'redweb' {
|
|
2
2
|
import { Application } from 'express';
|
|
3
3
|
import { CorsOptions } from 'cors';
|
|
4
|
-
import { Server as NodeHttpServer } from 'http';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { Server as NodeHttpServer } from 'http';
|
|
5
|
+
import { Server as NodeHttpsServer } from 'https';
|
|
6
|
+
import { WebSocket, ServerOptions } from 'ws';
|
|
7
|
+
import { Buffer } from 'buffer';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
8
9
|
|
|
9
10
|
/** ─────────────────── HTTP / CORE ─────────────────── */
|
|
10
11
|
|
|
@@ -13,70 +14,216 @@ declare module 'redweb' {
|
|
|
13
14
|
export interface RedWebOptions {
|
|
14
15
|
port?: number;
|
|
15
16
|
bind?: string;
|
|
16
|
-
publicPaths?: string[];
|
|
17
|
-
services?: Array<{ serviceName: string; method: string; function: Function }>;
|
|
18
|
-
listen?: boolean;
|
|
19
|
-
listenCallback?: () => void;
|
|
17
|
+
publicPaths?: string[];
|
|
18
|
+
services?: Array<{ serviceName: string; method: string; function: Function }>;
|
|
19
|
+
listen?: boolean;
|
|
20
|
+
listenCallback?: () => void;
|
|
20
21
|
encoding?: RedWebEncoding;
|
|
21
22
|
ssl?: { key: string; cert: string };
|
|
22
23
|
server?: Application;
|
|
23
|
-
corsOptions?: CorsOptions | false;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
corsOptions?: CorsOptions | false;
|
|
25
|
+
exposeErrors?: boolean;
|
|
26
|
+
logger?: RedWebLogger | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type RedWebSocket = WebSocket & {
|
|
30
|
+
clientKey: string;
|
|
31
|
+
__redwebClientKey: string;
|
|
32
|
+
remoteAddress: string;
|
|
33
|
+
isAssigned: boolean;
|
|
34
|
+
sendJson(data: unknown): boolean;
|
|
35
|
+
broadcast(data: unknown): number;
|
|
36
|
+
context?: RedWebConnectionContext;
|
|
37
|
+
joinRoom?(roomId: string): boolean;
|
|
38
|
+
leaveRoom?(roomId: string): boolean;
|
|
39
|
+
roomBroadcast?(roomId: string, data: unknown, options?: { except?: RedWebSocket }): number;
|
|
40
|
+
createSession?(sessionId: string, data: unknown): boolean;
|
|
41
|
+
resumeSession?(sessionId: string): unknown | null;
|
|
42
|
+
publishEvent?(type: string, payload: unknown): Promise<boolean>;
|
|
43
|
+
sendEvent?(type: string, payload: unknown, metadata?: ProtocolMetadata): boolean;
|
|
44
|
+
sendProtocolError?(code: string, message: string, metadata?: ProtocolMetadata): boolean;
|
|
45
|
+
sendBinaryEvent?(value: unknown): Promise<boolean>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface RedWebConnectionContext {
|
|
49
|
+
connectionId: string;
|
|
50
|
+
principal: unknown;
|
|
51
|
+
session: unknown | null;
|
|
52
|
+
metadata: Record<string, unknown>;
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
protocol?: Readonly<{ version: string }>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface AdmissionContext {
|
|
58
|
+
signal: AbortSignal;
|
|
59
|
+
networkIdentity: string;
|
|
60
|
+
route: SocketRoute;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AdmissionOptions {
|
|
64
|
+
authenticate?: (
|
|
65
|
+
request: import('http').IncomingMessage,
|
|
66
|
+
context: AdmissionContext
|
|
67
|
+
) => unknown | false | Promise<unknown | false>;
|
|
68
|
+
origins?: string[] | ((
|
|
69
|
+
origin: string | undefined,
|
|
70
|
+
request: import('http').IncomingMessage
|
|
71
|
+
) => boolean | Promise<boolean>);
|
|
72
|
+
place?: (
|
|
73
|
+
principal: unknown,
|
|
74
|
+
request: import('http').IncomingMessage,
|
|
75
|
+
context: AdmissionContext
|
|
76
|
+
) => string | false | null | undefined | Promise<string | false | null | undefined>;
|
|
77
|
+
allowedPlacementOrigins?: string[];
|
|
78
|
+
allowInsecurePlacement?: boolean;
|
|
79
|
+
timeoutMs?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface MessageRateLimit {
|
|
83
|
+
capacity: number;
|
|
84
|
+
refillPerSecond: number;
|
|
85
|
+
action?: 'drop' | 'disconnect';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface TransportLimits {
|
|
89
|
+
maxConnections?: number;
|
|
90
|
+
maxBufferedBytes?: number;
|
|
91
|
+
maxPendingMessages?: number;
|
|
92
|
+
messageRate?: MessageRateLimit;
|
|
93
|
+
slowConsumerAction?: 'drop' | 'disconnect';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface HeartbeatOptions {
|
|
97
|
+
intervalMs: number;
|
|
98
|
+
timeoutMs: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface RoomOptions {
|
|
102
|
+
maxRooms?: number;
|
|
103
|
+
maxMembersPerRoom?: number;
|
|
104
|
+
maxRoomsPerConnection?: number;
|
|
105
|
+
maxRoomIdLength?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SessionOptions {
|
|
109
|
+
ttlMs?: number;
|
|
110
|
+
maxSessions?: number;
|
|
111
|
+
maxSessionIdLength?: number;
|
|
112
|
+
sweepIntervalMs?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface MetricsSink {
|
|
116
|
+
increment?(name: string, value: number, attributes: Readonly<{ route: string }>): void | Promise<void>;
|
|
117
|
+
gauge?(name: string, value: number, attributes: Readonly<{ route: string }>): void | Promise<void>;
|
|
118
|
+
observe?(name: string, value: number, attributes: Readonly<{ route: string }>): void | Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface DistributionEvent<T = unknown> {
|
|
122
|
+
id: string;
|
|
123
|
+
source: string;
|
|
124
|
+
type: string;
|
|
125
|
+
payload: T;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface DistributionAdapter {
|
|
129
|
+
start?(signal?: AbortSignal): void | Promise<void>;
|
|
130
|
+
publish(channel: string, serializedEvent: string, signal?: AbortSignal): void | Promise<void>;
|
|
131
|
+
subscribe(
|
|
132
|
+
channel: string,
|
|
133
|
+
onEvent: (serializedEvent: string | DistributionEvent) => void,
|
|
134
|
+
signal?: AbortSignal
|
|
135
|
+
): void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;
|
|
136
|
+
unsubscribe?(channel: string, signal?: AbortSignal): void | Promise<void>;
|
|
137
|
+
close?(signal?: AbortSignal): void | Promise<void>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface DistributionOptions {
|
|
141
|
+
adapter: DistributionAdapter;
|
|
142
|
+
channel: string;
|
|
143
|
+
nodeId?: string;
|
|
144
|
+
maxEventBytes?: number;
|
|
145
|
+
maxSeenEvents?: number;
|
|
146
|
+
seenTtlMs?: number;
|
|
147
|
+
lifecycleTimeoutMs?: number;
|
|
148
|
+
publishTimeoutMs?: number;
|
|
149
|
+
maxConcurrentPublishes?: number;
|
|
150
|
+
maxConcurrentEvents?: number;
|
|
151
|
+
required?: boolean;
|
|
152
|
+
onEvent(event: DistributionEvent, route: SocketRoute): void | Promise<void>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface ProtocolMetadata {
|
|
156
|
+
requestId?: string;
|
|
157
|
+
sequence?: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface ProtocolBinaryCodec {
|
|
161
|
+
maxBytes?: number;
|
|
162
|
+
encode(value: unknown, context: RedWebConnectionContext): Buffer | Uint8Array | ArrayBuffer | Promise<Buffer | Uint8Array | ArrayBuffer>;
|
|
163
|
+
decode(buffer: Buffer, context: RedWebConnectionContext): unknown | Promise<unknown>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface ProtocolOptions {
|
|
167
|
+
versions: string[];
|
|
168
|
+
required?: boolean;
|
|
169
|
+
queryParameter?: string;
|
|
170
|
+
header?: string;
|
|
171
|
+
binary?: false | ProtocolBinaryCodec;
|
|
172
|
+
}
|
|
37
173
|
|
|
38
174
|
/** ─────────────────── SOCKET SERVER ─────────────────── */
|
|
39
175
|
|
|
40
176
|
export interface SocketServerOptions {
|
|
41
|
-
server?: NodeHttpServer;
|
|
42
|
-
port?: number;
|
|
43
|
-
bind?: string;
|
|
44
|
-
listen?: boolean;
|
|
45
|
-
routes?: Array<new () => SocketRoute>;
|
|
46
|
-
ssl?: { key: string; cert: string };
|
|
47
|
-
fallbackToRoot?: boolean;
|
|
48
|
-
closeServerOnShutdown?: boolean;
|
|
49
|
-
listenCallback?: () => void;
|
|
50
|
-
logger?: RedWebLogger | null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface RedWebLogger {
|
|
54
|
-
log?(message?: any, ...optionalParams: any[]): void;
|
|
55
|
-
warn?(message?: any, ...optionalParams: any[]): void;
|
|
56
|
-
error?(message?: any, ...optionalParams: any[]): void;
|
|
57
|
-
}
|
|
177
|
+
server?: NodeHttpServer;
|
|
178
|
+
port?: number;
|
|
179
|
+
bind?: string;
|
|
180
|
+
listen?: boolean;
|
|
181
|
+
routes?: Array<new () => SocketRoute>;
|
|
182
|
+
ssl?: { key: string; cert: string };
|
|
183
|
+
fallbackToRoot?: boolean;
|
|
184
|
+
closeServerOnShutdown?: boolean;
|
|
185
|
+
listenCallback?: () => void;
|
|
186
|
+
logger?: RedWebLogger | null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface RedWebLogger {
|
|
190
|
+
log?(message?: any, ...optionalParams: any[]): void;
|
|
191
|
+
warn?(message?: any, ...optionalParams: any[]): void;
|
|
192
|
+
error?(message?: any, ...optionalParams: any[]): void;
|
|
193
|
+
}
|
|
58
194
|
|
|
59
195
|
/** ─────────────────── ROUTES & HANDLERS ─────────────────── */
|
|
60
196
|
|
|
61
197
|
export interface SocketRouteConfig {
|
|
62
198
|
path: string;
|
|
63
|
-
handlers: Array<new () => BaseHandler>;
|
|
64
|
-
services?: Array<new () => SocketService>;
|
|
65
|
-
allowDuplicateConnections?: boolean;
|
|
66
|
-
websocketOptions?: Omit<ServerOptions, 'noServer' | 'path' | 'server' | 'port'>;
|
|
67
|
-
trustProxy?: boolean;
|
|
68
|
-
getClientKey?: (request: import('http').IncomingMessage) => string;
|
|
69
|
-
exposeErrors?: boolean;
|
|
70
|
-
logger?: RedWebLogger | null;
|
|
71
|
-
shutdownTimeoutMs?: number;
|
|
199
|
+
handlers: Array<new () => BaseHandler>;
|
|
200
|
+
services?: Array<new () => SocketService>;
|
|
201
|
+
allowDuplicateConnections?: boolean;
|
|
202
|
+
websocketOptions?: Omit<ServerOptions, 'noServer' | 'path' | 'server' | 'port'>;
|
|
203
|
+
trustProxy?: boolean;
|
|
204
|
+
getClientKey?: (request: import('http').IncomingMessage) => string;
|
|
205
|
+
exposeErrors?: boolean;
|
|
206
|
+
logger?: RedWebLogger | null;
|
|
207
|
+
shutdownTimeoutMs?: number;
|
|
208
|
+
admission?: AdmissionOptions | AdmissionOptions['authenticate'];
|
|
209
|
+
limits?: TransportLimits;
|
|
210
|
+
orderedMessages?: boolean;
|
|
211
|
+
heartbeat?: HeartbeatOptions;
|
|
212
|
+
rooms?: boolean | RoomOptions;
|
|
213
|
+
sessions?: boolean | SessionOptions;
|
|
214
|
+
metrics?: MetricsSink;
|
|
215
|
+
distribution?: false | DistributionOptions;
|
|
216
|
+
drainHandlers?: boolean;
|
|
217
|
+
protocol?: false | ProtocolOptions;
|
|
218
|
+
maxPendingUpgrades?: number;
|
|
72
219
|
}
|
|
73
220
|
|
|
74
221
|
/** Socket‑side autonomous service (game loops, timers, etc.) */
|
|
75
|
-
export abstract class SocketService {
|
|
76
|
-
name: string;
|
|
77
|
-
tickRateMs: number | null;
|
|
78
|
-
route: SocketRoute;
|
|
79
|
-
protected _tickHandle: NodeJS.Timeout | null;
|
|
222
|
+
export abstract class SocketService {
|
|
223
|
+
name: string;
|
|
224
|
+
tickRateMs: number | null;
|
|
225
|
+
route: SocketRoute;
|
|
226
|
+
protected _tickHandle: NodeJS.Timeout | null;
|
|
80
227
|
|
|
81
228
|
constructor(name: string, tickRateMs?: number);
|
|
82
229
|
|
|
@@ -84,68 +231,116 @@ declare module 'redweb' {
|
|
|
84
231
|
onInit(route: SocketRoute): void;
|
|
85
232
|
|
|
86
233
|
/** Optional recurring tick (respecting tickRateMs) */
|
|
87
|
-
onTick?():
|
|
234
|
+
onTick?(...args: any[]): unknown;
|
|
88
235
|
|
|
89
236
|
/** Called on process shutdown / route removal */
|
|
90
237
|
onShutdown(): void;
|
|
91
238
|
}
|
|
92
239
|
|
|
240
|
+
export abstract class FixedStepService extends SocketService {
|
|
241
|
+
maxCatchUpTicks: number;
|
|
242
|
+
maxRetainedLagMs: number;
|
|
243
|
+
tick: number;
|
|
244
|
+
accumulatorMs: number;
|
|
245
|
+
|
|
246
|
+
constructor(name: string, tickRateMs: number, maxCatchUpTicks?: number, maxRetainedLagMs?: number);
|
|
247
|
+
onTick?(stepMs: number, tick: number): void | Promise<void>;
|
|
248
|
+
onLagDropped?(droppedLagMs: number): void;
|
|
249
|
+
pulse(): Promise<void>;
|
|
250
|
+
onShutdown(): Promise<void>;
|
|
251
|
+
}
|
|
252
|
+
|
|
93
253
|
/** Message handler, triggered by client messages */
|
|
94
254
|
export class BaseHandler {
|
|
95
255
|
name: string;
|
|
96
256
|
constructor(name: string);
|
|
97
257
|
|
|
98
258
|
handleMessage(
|
|
99
|
-
socket: RedWebSocket,
|
|
259
|
+
socket: RedWebSocket,
|
|
100
260
|
message: any
|
|
101
|
-
): Promise<unknown>;
|
|
102
|
-
|
|
103
|
-
validateMessage(message: any, socket: RedWebSocket): boolean | Promise<boolean>;
|
|
104
|
-
onMessage(socket: RedWebSocket, message: any): unknown;
|
|
105
|
-
acceptsBinary?(socket: RedWebSocket, buffer: Buffer): boolean;
|
|
106
|
-
handleBinaryMessage(socket: RedWebSocket, buffer: Buffer): Promise<unknown>;
|
|
107
|
-
onBinaryMessage(socket: RedWebSocket, buffer: Buffer): unknown;
|
|
108
|
-
onInitialContact(socket: RedWebSocket, request?: import('http').IncomingMessage): unknown;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export class SocketRoute {
|
|
261
|
+
): Promise<unknown>;
|
|
262
|
+
|
|
263
|
+
validateMessage(message: any, socket: RedWebSocket): boolean | Promise<boolean>;
|
|
264
|
+
onMessage(socket: RedWebSocket, message: any): unknown;
|
|
265
|
+
acceptsBinary?(socket: RedWebSocket, buffer: Buffer): boolean;
|
|
266
|
+
handleBinaryMessage(socket: RedWebSocket, buffer: Buffer): Promise<unknown>;
|
|
267
|
+
onBinaryMessage(socket: RedWebSocket, buffer: Buffer): unknown;
|
|
268
|
+
onInitialContact(socket: RedWebSocket, request?: import('http').IncomingMessage): unknown;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export class SocketRoute {
|
|
112
272
|
path: string;
|
|
113
|
-
handlers: BaseHandler[];
|
|
114
|
-
services: SocketService[];
|
|
115
|
-
clients: Map<string, RedWebSocket>;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
273
|
+
handlers: BaseHandler[];
|
|
274
|
+
services: SocketService[];
|
|
275
|
+
clients: Map<string, RedWebSocket>;
|
|
276
|
+
rooms: RoomRegistry | null;
|
|
277
|
+
sessions: SessionRegistry | null;
|
|
278
|
+
distribution: unknown | null;
|
|
279
|
+
protocolPolicy: unknown | null;
|
|
280
|
+
draining: boolean;
|
|
281
|
+
allowDuplicateConnections?: boolean;
|
|
282
|
+
websocketOptions?: SocketRouteConfig['websocketOptions'];
|
|
283
|
+
|
|
284
|
+
constructor(config: SocketRouteConfig);
|
|
285
|
+
|
|
286
|
+
addHandler(handler: new () => BaseHandler): boolean;
|
|
287
|
+
resolveRemoteAddress(request: import('http').IncomingMessage): string;
|
|
288
|
+
connectionOpenCallback(socket: RedWebSocket, request?: import('http').IncomingMessage): unknown;
|
|
289
|
+
connectionCloseCallback?(socket: RedWebSocket): unknown;
|
|
290
|
+
handleMessage(sock: RedWebSocket, data: any): Promise<boolean>;
|
|
291
|
+
handleBinaryMessage(socket: RedWebSocket, buffer: Buffer): Promise<boolean>;
|
|
292
|
+
beginDrain(): boolean;
|
|
293
|
+
isReady(): boolean;
|
|
294
|
+
publish(type: string, payload: unknown): Promise<boolean>;
|
|
295
|
+
shutdown(): Promise<void>;
|
|
296
|
+
}
|
|
129
297
|
|
|
130
298
|
/** ─────────────────── SERVER BASE ─────────────────── */
|
|
131
299
|
|
|
132
300
|
export class BaseSocketServer {
|
|
133
|
-
server: NodeHttpServer;
|
|
134
|
-
routes: SocketRoute[];
|
|
135
|
-
ownsServer: boolean;
|
|
301
|
+
server: NodeHttpServer;
|
|
302
|
+
routes: SocketRoute[];
|
|
303
|
+
ownsServer: boolean;
|
|
136
304
|
|
|
137
|
-
constructor(server: NodeHttpServer, options?: SocketServerOptions);
|
|
305
|
+
constructor(server: NodeHttpServer, options?: SocketServerOptions);
|
|
138
306
|
|
|
139
|
-
addRoute(route: new () => SocketRoute): SocketRoute;
|
|
140
|
-
|
|
307
|
+
addRoute(route: new () => SocketRoute): SocketRoute;
|
|
308
|
+
isReady(): boolean;
|
|
309
|
+
beginDrain(): boolean;
|
|
310
|
+
shutdown(): Promise<void>;
|
|
141
311
|
}
|
|
142
312
|
|
|
143
|
-
/** ─────────────────── REGISTRY & UTIL TYPES ─────────────────── */
|
|
144
|
-
|
|
145
|
-
export function sendJson(socket: WebSocket, data: unknown): boolean;
|
|
313
|
+
/** ─────────────────── REGISTRY & UTIL TYPES ─────────────────── */
|
|
314
|
+
|
|
315
|
+
export function sendJson(socket: WebSocket, data: unknown): boolean;
|
|
316
|
+
|
|
317
|
+
export class RoomRegistry {
|
|
318
|
+
constructor(options?: RoomOptions);
|
|
319
|
+
join(roomId: string, socket: RedWebSocket): boolean;
|
|
320
|
+
leave(roomId: string, socket: RedWebSocket): boolean;
|
|
321
|
+
leaveAll(socket: RedWebSocket): number;
|
|
322
|
+
members(roomId: string): RedWebSocket[];
|
|
323
|
+
has(roomId: string, socket: RedWebSocket): boolean;
|
|
324
|
+
broadcast(roomId: string, data: unknown, options?: { except?: RedWebSocket }): number;
|
|
325
|
+
clear(): void;
|
|
326
|
+
close(): boolean;
|
|
327
|
+
readonly size: number;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export class SessionRegistry<T = unknown> {
|
|
331
|
+
constructor(options?: SessionOptions, logger?: RedWebLogger | null);
|
|
332
|
+
create(sessionId: string, data: T, socket?: RedWebSocket): boolean;
|
|
333
|
+
resume(sessionId: string, socket: RedWebSocket): T | null;
|
|
334
|
+
release(socket: RedWebSocket): boolean;
|
|
335
|
+
remove(sessionId: string): boolean;
|
|
336
|
+
get(sessionId: string): T | undefined;
|
|
337
|
+
sweep(): void;
|
|
338
|
+
stop(): void;
|
|
339
|
+
readonly size: number;
|
|
340
|
+
}
|
|
146
341
|
|
|
147
342
|
export interface SocketWrapper {
|
|
148
|
-
socket: RedWebSocket;
|
|
343
|
+
socket: RedWebSocket;
|
|
149
344
|
id: string;
|
|
150
345
|
send: (type: string, payload: Record<string, any>) => void;
|
|
151
346
|
getSanitized?(): Record<string, any>;
|
|
@@ -190,34 +385,252 @@ declare module 'redweb' {
|
|
|
190
385
|
constructor(options?: SocketServerOptions);
|
|
191
386
|
}
|
|
192
387
|
|
|
193
|
-
export class BaseHttpServer {
|
|
194
|
-
app: Application;
|
|
195
|
-
server?: NodeHttpServer;
|
|
196
|
-
constructor(options?: RedWebOptions);
|
|
197
|
-
shutdown?(): Promise<void>;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export class HttpServer extends BaseHttpServer {
|
|
201
|
-
constructor(options?: RedWebOptions);
|
|
202
|
-
shutdown(): Promise<void>;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export class HttpsServer extends BaseHttpServer {
|
|
206
|
-
constructor(options?: RedWebOptions);
|
|
207
|
-
shutdown(): Promise<void>;
|
|
208
|
-
}
|
|
388
|
+
export class BaseHttpServer {
|
|
389
|
+
app: Application;
|
|
390
|
+
server?: NodeHttpServer;
|
|
391
|
+
constructor(options?: RedWebOptions);
|
|
392
|
+
shutdown?(): Promise<void>;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export class HttpServer extends BaseHttpServer {
|
|
396
|
+
constructor(options?: RedWebOptions);
|
|
397
|
+
shutdown(): Promise<void>;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export class HttpsServer extends BaseHttpServer {
|
|
401
|
+
constructor(options?: RedWebOptions);
|
|
402
|
+
shutdown(): Promise<void>;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** ─────────────────── LIVE HTML ─────────────────── */
|
|
406
|
+
|
|
407
|
+
const htmlFragmentBrand: unique symbol;
|
|
408
|
+
const htmlAttributeBrand: unique symbol;
|
|
409
|
+
const htmlUrlBrand: unique symbol;
|
|
410
|
+
|
|
411
|
+
export interface HtmlFragment {
|
|
412
|
+
readonly [htmlFragmentBrand]: true;
|
|
413
|
+
toString(): string;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export interface HtmlAttribute {
|
|
417
|
+
readonly [htmlAttributeBrand]: true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export interface HtmlUrl {
|
|
421
|
+
readonly [htmlUrlBrand]: true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export interface LivePageRequest {
|
|
425
|
+
readonly path: string;
|
|
426
|
+
readonly url: string;
|
|
427
|
+
readonly method: string;
|
|
428
|
+
readonly headers: import('http').IncomingHttpHeaders;
|
|
429
|
+
readonly params: Readonly<Record<string, string>>;
|
|
430
|
+
readonly query: Readonly<Record<string, unknown>>;
|
|
431
|
+
readonly body: unknown;
|
|
432
|
+
get(name: string): string | undefined;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export interface LivePageRequestContext {
|
|
436
|
+
request: LivePageRequest;
|
|
437
|
+
params: Readonly<Record<string, string>>;
|
|
438
|
+
query: Readonly<Record<string, unknown>>;
|
|
439
|
+
body: unknown;
|
|
440
|
+
principal?: string | number | bigint | boolean;
|
|
441
|
+
signal: AbortSignal;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export interface LivePageConnectionContext {
|
|
445
|
+
socket: RedWebSocket;
|
|
446
|
+
signal?: AbortSignal;
|
|
447
|
+
principal?: string | number | bigint | boolean;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export abstract class LivePage {
|
|
451
|
+
protected readonly _connections: Set<RedWebSocket>;
|
|
452
|
+
loading?(context: LivePageRequestContext): void | Promise<void>;
|
|
453
|
+
render?(context: LivePageRequestContext): string | HtmlFragment | Promise<string | HtmlFragment>;
|
|
454
|
+
connected?(context: LivePageConnectionContext): void | Promise<void>;
|
|
455
|
+
disconnected?(context: { socket: RedWebSocket }): void | Promise<void>;
|
|
456
|
+
disposed?(): void | Promise<void>;
|
|
457
|
+
dispose(): Promise<boolean>;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export interface PageOptions {
|
|
461
|
+
template?: string;
|
|
462
|
+
css?: string | readonly string[];
|
|
463
|
+
scope?: 'connection' | 'shared';
|
|
464
|
+
shared?: boolean;
|
|
465
|
+
live?: boolean;
|
|
466
|
+
head?: PageHead;
|
|
467
|
+
cache?: PageCache;
|
|
468
|
+
layout?: PageLayout;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export type PageLayout = (content: HtmlFragment, context: LivePageRequestContext) => HtmlFragment;
|
|
472
|
+
|
|
473
|
+
export interface PageHead {
|
|
474
|
+
title?: string;
|
|
475
|
+
description?: string;
|
|
476
|
+
canonical?: string;
|
|
477
|
+
image?: string;
|
|
478
|
+
robots?: string;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export interface PageCache {
|
|
482
|
+
maxAge?: number;
|
|
483
|
+
staleWhileRevalidate?: number;
|
|
484
|
+
immutable?: boolean;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export interface StateOptions {
|
|
488
|
+
writable?: boolean;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export interface LiveStateDecorator {
|
|
492
|
+
(target: object, propertyKey: string): void;
|
|
493
|
+
<This, Value>(value: undefined, context: ClassFieldDecoratorContext<This, Value>):
|
|
494
|
+
(this: This, initialValue: Value) => Value;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export interface LiveActionDecorator {
|
|
498
|
+
(target: object, propertyKey: string, descriptor: PropertyDescriptor): void | PropertyDescriptor;
|
|
499
|
+
<This, Value extends (this: This, ...args: any[]) => any>(
|
|
500
|
+
value: Value,
|
|
501
|
+
context: ClassMethodDecoratorContext<This, Value>
|
|
502
|
+
): Value;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export interface LiveViewDecorator {
|
|
506
|
+
(target: object, propertyKey: string, descriptor: PropertyDescriptor): void | PropertyDescriptor;
|
|
507
|
+
<This, Value extends (this: This, item: any, index: number) => HtmlFragment>(
|
|
508
|
+
value: Value,
|
|
509
|
+
context: ClassMethodDecoratorContext<This, Value>
|
|
510
|
+
): Value;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function page(path: string, options?: PageOptions): ClassDecorator;
|
|
514
|
+
export function component(): ClassDecorator;
|
|
515
|
+
export function component<Props = void>(render: (properties: Props) => HtmlFragment):
|
|
516
|
+
(properties: Props) => HtmlFragment;
|
|
517
|
+
export function state(options?: StateOptions): LiveStateDecorator;
|
|
518
|
+
export function action(): LiveActionDecorator;
|
|
519
|
+
export function view(stateName: string): LiveViewDecorator;
|
|
520
|
+
export function html(strings: TemplateStringsArray, ...values: unknown[]): HtmlFragment;
|
|
521
|
+
export function attribute(value: string | number | bigint | boolean): HtmlAttribute;
|
|
522
|
+
export function url(value: string): HtmlUrl;
|
|
523
|
+
export function each<Item>(items: readonly Item[], render: (item: Item, index: number) => HtmlFragment): HtmlFragment;
|
|
524
|
+
export function codeBlock(code: unknown, options?: {
|
|
525
|
+
language?: string;
|
|
526
|
+
label?: string;
|
|
527
|
+
highlight?: (source: string, language: string) => HtmlFragment;
|
|
528
|
+
}): HtmlFragment;
|
|
529
|
+
|
|
530
|
+
export type LivePageClass = new () => object;
|
|
531
|
+
|
|
532
|
+
export interface LiveHtmlServerOptions extends Omit<RedWebOptions, 'enableHtmxRendering'> {
|
|
533
|
+
pages: readonly LivePageClass[];
|
|
534
|
+
templateRoot?: string;
|
|
535
|
+
livePaths?: {
|
|
536
|
+
socket?: string;
|
|
537
|
+
client?: string;
|
|
538
|
+
runtime?: string;
|
|
539
|
+
css?: string;
|
|
540
|
+
};
|
|
541
|
+
sessionTtlMs?: number;
|
|
542
|
+
maxSessions?: number;
|
|
543
|
+
maxConcurrentRenders?: number;
|
|
544
|
+
shutdownTimeoutMs?: number;
|
|
545
|
+
heartbeat?: HeartbeatOptions;
|
|
546
|
+
authenticate?(request: import('http').IncomingMessage | import('express').Request):
|
|
547
|
+
string | number | bigint | boolean | false | null | undefined |
|
|
548
|
+
Promise<string | number | bigint | boolean | false | null | undefined>;
|
|
549
|
+
origins?: string[] | ((origin: string | undefined, request: import('http').IncomingMessage) => boolean | Promise<boolean>);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export class LiveHtmlServer {
|
|
553
|
+
app: Application;
|
|
554
|
+
server: NodeHttpServer | NodeHttpsServer;
|
|
555
|
+
http: HttpServer | HttpsServer;
|
|
556
|
+
sockets: SocketServer | null;
|
|
557
|
+
constructor(options: LiveHtmlServerOptions);
|
|
558
|
+
shutdown(): Promise<void>;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function start(
|
|
562
|
+
pageOrPages: LivePageClass | readonly LivePageClass[],
|
|
563
|
+
options?: Omit<LiveHtmlServerOptions, 'pages'>
|
|
564
|
+
): LiveHtmlServer;
|
|
565
|
+
|
|
566
|
+
export interface StaticExportOptions {
|
|
567
|
+
outDir: string;
|
|
568
|
+
templateRoot?: string;
|
|
569
|
+
logger?: RedWebLogger | null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface StaticExportResult {
|
|
573
|
+
readonly pages: readonly string[];
|
|
574
|
+
readonly assets: readonly string[];
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export function exportStatic(
|
|
578
|
+
pageOrPages: LivePageClass | readonly LivePageClass[],
|
|
579
|
+
options: StaticExportOptions
|
|
580
|
+
): Promise<StaticExportResult>;
|
|
581
|
+
|
|
582
|
+
export interface SiteOptions {
|
|
583
|
+
origin?: string;
|
|
584
|
+
css?: string | readonly string[];
|
|
585
|
+
head?: PageHead;
|
|
586
|
+
cache?: PageCache;
|
|
587
|
+
layout?: PageLayout;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export interface SitePageOptions extends Omit<PageOptions, 'live'> {
|
|
591
|
+
live?: false;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export interface SiteExportOptions extends StaticExportOptions {
|
|
595
|
+
publicDir?: string;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export interface StaticSite {
|
|
599
|
+
page(path: string, options?: SitePageOptions): ClassDecorator;
|
|
600
|
+
export(
|
|
601
|
+
pageOrPages: LivePageClass | readonly LivePageClass[],
|
|
602
|
+
options: SiteExportOptions
|
|
603
|
+
): Promise<StaticExportResult>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function defineSite(options?: SiteOptions): StaticSite;
|
|
607
|
+
|
|
608
|
+
export class HtmlRenderer {
|
|
609
|
+
static template(filePath: string, rootDir: string): string;
|
|
610
|
+
static stylesheet(filePath: string, rootDir: string): string;
|
|
611
|
+
static render(source: string, page: object, options?: { live?: boolean }): string;
|
|
612
|
+
static collection(page: object, name: string, value: unknown): string;
|
|
613
|
+
static statePayload(name: string, value: unknown, page?: object): { name: string; value: string; html: boolean };
|
|
614
|
+
static head(metadata?: PageHead): string;
|
|
615
|
+
static document(
|
|
616
|
+
markup: string,
|
|
617
|
+
config?: (Record<string, unknown> & { runtimePath: string }) | null,
|
|
618
|
+
stylesheets?: string[],
|
|
619
|
+
metadata?: PageHead
|
|
620
|
+
): string;
|
|
621
|
+
}
|
|
209
622
|
|
|
210
623
|
/** ─────────────────── CONSTANTS ─────────────────── */
|
|
211
624
|
|
|
212
625
|
export const METHODS: {
|
|
213
626
|
GET: 'get';
|
|
214
627
|
POST: 'post';
|
|
215
|
-
PUT: 'put';
|
|
216
|
-
PATCH: 'patch';
|
|
217
|
-
DELETE: 'delete';
|
|
218
|
-
OPTIONS: 'options';
|
|
219
|
-
HEAD: 'head';
|
|
220
|
-
ALL: 'all';
|
|
628
|
+
PUT: 'put';
|
|
629
|
+
PATCH: 'patch';
|
|
630
|
+
DELETE: 'delete';
|
|
631
|
+
OPTIONS: 'options';
|
|
632
|
+
HEAD: 'head';
|
|
633
|
+
ALL: 'all';
|
|
221
634
|
};
|
|
222
635
|
|
|
223
636
|
export const ENCODINGS: {
|
|
@@ -227,4 +640,15 @@ declare module 'redweb' {
|
|
|
227
640
|
|
|
228
641
|
export const HTTP_OPTIONS: RedWebOptions;
|
|
229
642
|
export const SOCKET_OPTIONS: SocketServerOptions;
|
|
643
|
+
|
|
644
|
+
export const ERROR_CODES: Readonly<{
|
|
645
|
+
INVALID_MESSAGE: 'INVALID_MESSAGE';
|
|
646
|
+
UNKNOWN_HANDLER: 'UNKNOWN_HANDLER';
|
|
647
|
+
HANDLER_FAILED: 'HANDLER_FAILED';
|
|
648
|
+
BINARY_UNSUPPORTED: 'BINARY_UNSUPPORTED';
|
|
649
|
+
RATE_LIMITED: 'RATE_LIMITED';
|
|
650
|
+
QUEUE_FULL: 'QUEUE_FULL';
|
|
651
|
+
CAPACITY_REACHED: 'CAPACITY_REACHED';
|
|
652
|
+
INITIALIZATION_FAILED: 'INITIALIZATION_FAILED';
|
|
653
|
+
}>;
|
|
230
654
|
}
|