@gcoredev/fastedge-test 0.0.1-beta.0 → 0.1.0-beta.2
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 +6 -6
- package/bin/fastedge-debug.js +2 -0
- package/dist/fastedge-cli/METADATA.json +1 -3
- package/dist/fastedge-cli/{fastedge-run-linux-x64-unkown → fastedge-run-darwin-arm64} +0 -0
- package/dist/fastedge-cli/fastedge-run-linux-x64 +0 -0
- package/dist/fastedge-cli/fastedge-run.exe +0 -0
- package/dist/frontend/assets/index-CEFjsU8e.js +35 -0
- package/dist/frontend/assets/index-DdlINQc_.css +1 -0
- package/dist/frontend/index.html +2 -2
- package/dist/lib/index.cjs +329 -112
- package/dist/lib/index.js +331 -115
- package/dist/lib/runner/HostFunctions.d.ts +8 -0
- package/dist/lib/runner/HttpWasmRunner.d.ts +34 -14
- package/dist/lib/runner/IStateManager.d.ts +3 -2
- package/dist/lib/runner/IWasmRunner.d.ts +18 -1
- package/dist/lib/runner/NullStateManager.d.ts +1 -0
- package/dist/lib/runner/PortManager.d.ts +17 -19
- package/dist/lib/runner/ProxyWasmRunner.d.ts +7 -0
- package/dist/lib/runner/standalone.d.ts +1 -1
- package/dist/lib/schemas/api.d.ts +8 -2
- package/dist/lib/schemas/config.d.ts +4 -1
- package/dist/lib/test-framework/index.cjs +330 -114
- package/dist/lib/test-framework/index.js +332 -117
- package/dist/lib/test-framework/suite-runner.d.ts +1 -1
- package/dist/server.js +30 -30
- package/docs/API.md +758 -360
- package/docs/DEBUGGER.md +151 -0
- package/docs/INDEX.md +111 -0
- package/docs/RUNNER.md +582 -0
- package/docs/TEST_CONFIG.md +242 -0
- package/docs/TEST_FRAMEWORK.md +384 -284
- package/docs/WEBSOCKET.md +499 -0
- package/docs/quickstart.md +171 -0
- package/llms.txt +72 -14
- package/package.json +17 -6
- package/schemas/api-config.schema.json +12 -5
- package/schemas/api-load.schema.json +11 -6
- package/schemas/{test-config.schema.json → fastedge-config.test.schema.json} +12 -5
- package/dist/fastedge-cli/.gitkeep +0 -0
- package/dist/frontend/assets/index-CnXStFTd.css +0 -1
- package/dist/frontend/assets/index-FR9Oqsow.js +0 -37
- package/docs/HYBRID_LOADING.md +0 -546
- package/docs/LOCAL_SERVER.md +0 -153
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# WebSocket API
|
|
2
|
+
|
|
3
|
+
Real-time event stream from the `@gcoredev/fastedge-test` server to connected clients.
|
|
4
|
+
|
|
5
|
+
## Connection
|
|
6
|
+
|
|
7
|
+
Connect to the WebSocket server at:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ws://localhost:{port}/ws
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
where `{port}` is the port the server is running on (default `5179`).
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const ws = new WebSocket("ws://localhost:5179/ws");
|
|
17
|
+
|
|
18
|
+
ws.addEventListener("message", (event) => {
|
|
19
|
+
const msg = JSON.parse(event.data);
|
|
20
|
+
console.log(msg.type, msg.data);
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Lifecycle
|
|
25
|
+
|
|
26
|
+
1. **Connect** — the server accepts all connections and immediately sends a `connection_status` event confirming the connection and the current client count.
|
|
27
|
+
2. **Ping / pong** — the server sends WebSocket `ping` frames every 15 seconds. Clients that have not responded within 30 seconds are terminated. Standard WebSocket clients handle pong automatically.
|
|
28
|
+
3. **Disconnect** — when a client disconnects, the server broadcasts an updated `connection_status` to remaining clients.
|
|
29
|
+
|
|
30
|
+
## Event Format
|
|
31
|
+
|
|
32
|
+
Every event shares a common envelope:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
interface BaseEvent {
|
|
36
|
+
type: string; // event discriminant
|
|
37
|
+
timestamp: number; // Unix ms
|
|
38
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
39
|
+
data: object; // event-specific payload
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Field | Type | Description |
|
|
44
|
+
| ----------- | ----------------------------------------- | --------------------------------------------------- |
|
|
45
|
+
| `type` | `string` | Event discriminant — one of the values listed below |
|
|
46
|
+
| `timestamp` | `number` | Unix epoch in milliseconds |
|
|
47
|
+
| `source` | `'ui' \| 'ai_agent' \| 'api' \| 'system'` | What triggered the event |
|
|
48
|
+
| `data` | `object` | Event-specific payload |
|
|
49
|
+
|
|
50
|
+
## Event Types
|
|
51
|
+
|
|
52
|
+
### wasm_loaded
|
|
53
|
+
|
|
54
|
+
Fired when a WASM binary has been loaded and is ready to handle requests.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
interface WasmLoadedEvent {
|
|
58
|
+
type: "wasm_loaded";
|
|
59
|
+
timestamp: number;
|
|
60
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
61
|
+
data: {
|
|
62
|
+
filename: string;
|
|
63
|
+
size: number;
|
|
64
|
+
runnerPort?: number | null;
|
|
65
|
+
wasmType: "proxy-wasm" | "http-wasm";
|
|
66
|
+
resolvedPath?: string | null;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Field | Type | Description |
|
|
72
|
+
| --------------- | ----------------------------- | -------------------------------------------------------------------- |
|
|
73
|
+
| `filename` | `string` | Name of the loaded WASM file |
|
|
74
|
+
| `size` | `number` | File size in bytes |
|
|
75
|
+
| `runnerPort?` | `number \| null ` | Port the runner is listening on, if applicable. Omitted when not set |
|
|
76
|
+
| `wasmType` | `'proxy-wasm' \| 'http-wasm'` | The WASM filter type |
|
|
77
|
+
| `resolvedPath?` | `string \| null ` | Absolute filesystem path to the loaded binary. Omitted when not set |
|
|
78
|
+
|
|
79
|
+
**Example:**
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"type": "wasm_loaded",
|
|
84
|
+
"timestamp": 1742734800000,
|
|
85
|
+
"source": "api",
|
|
86
|
+
"data": {
|
|
87
|
+
"filename": "filter.wasm",
|
|
88
|
+
"size": 204800,
|
|
89
|
+
"runnerPort": 8081,
|
|
90
|
+
"wasmType": "proxy-wasm",
|
|
91
|
+
"resolvedPath": "/workspace/filter.wasm"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### request_started
|
|
99
|
+
|
|
100
|
+
Fired when the server begins processing an incoming request through the WASM filter.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface RequestStartedEvent {
|
|
104
|
+
type: "request_started";
|
|
105
|
+
timestamp: number;
|
|
106
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
107
|
+
data: {
|
|
108
|
+
url: string;
|
|
109
|
+
method: string;
|
|
110
|
+
headers: Record<string, string>;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Field | Type | Description |
|
|
116
|
+
| --------- | ------------------------ | --------------------------------- |
|
|
117
|
+
| `url` | `string` | Full request URL |
|
|
118
|
+
| `method` | `string` | HTTP method (`GET`, `POST`, etc.) |
|
|
119
|
+
| `headers` | `Record<string, string>` | Request headers |
|
|
120
|
+
|
|
121
|
+
**Example:**
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"type": "request_started",
|
|
126
|
+
"timestamp": 1742734800100,
|
|
127
|
+
"source": "api",
|
|
128
|
+
"data": {
|
|
129
|
+
"url": "https://example.com/api/resource",
|
|
130
|
+
"method": "GET",
|
|
131
|
+
"headers": {
|
|
132
|
+
"host": "example.com",
|
|
133
|
+
"user-agent": "curl/8.0"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### hook_executed
|
|
142
|
+
|
|
143
|
+
Fired after each individual proxy-wasm hook completes. Multiple `hook_executed` events are emitted per request — one per hook phase that runs.
|
|
144
|
+
|
|
145
|
+
Hook names are camelCase: `onRequestHeaders`, `onRequestBody`, `onResponseHeaders`, `onResponseBody`.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
interface HookExecutedEvent {
|
|
149
|
+
type: "hook_executed";
|
|
150
|
+
timestamp: number;
|
|
151
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
152
|
+
data: {
|
|
153
|
+
hook: string;
|
|
154
|
+
returnCode: number | null;
|
|
155
|
+
logCount: number;
|
|
156
|
+
input: {
|
|
157
|
+
request: { headers: Record<string, string>; body: string };
|
|
158
|
+
response: { headers: Record<string, string>; body: string };
|
|
159
|
+
};
|
|
160
|
+
output: {
|
|
161
|
+
request: { headers: Record<string, string>; body: string };
|
|
162
|
+
response: { headers: Record<string, string>; body: string };
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
| Field | Type | Description |
|
|
169
|
+
| ------------ | ---------------- | ---------------------------------------------------------- |
|
|
170
|
+
| `hook` | `string` | Hook name (e.g. `onRequestHeaders`) |
|
|
171
|
+
| `returnCode` | `number \| null` | Return code from the WASM filter, or `null` if unavailable |
|
|
172
|
+
| `logCount` | `number` | Number of log lines emitted during this hook |
|
|
173
|
+
| `input` | `object` | Request and response state passed into the hook |
|
|
174
|
+
| `output` | `object` | Request and response state after the hook ran |
|
|
175
|
+
|
|
176
|
+
**Example:**
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"type": "hook_executed",
|
|
181
|
+
"timestamp": 1742734800200,
|
|
182
|
+
"source": "api",
|
|
183
|
+
"data": {
|
|
184
|
+
"hook": "onRequestHeaders",
|
|
185
|
+
"returnCode": 0,
|
|
186
|
+
"logCount": 2,
|
|
187
|
+
"input": {
|
|
188
|
+
"request": {
|
|
189
|
+
"headers": { "host": "example.com" },
|
|
190
|
+
"body": ""
|
|
191
|
+
},
|
|
192
|
+
"response": {
|
|
193
|
+
"headers": {},
|
|
194
|
+
"body": ""
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"output": {
|
|
198
|
+
"request": {
|
|
199
|
+
"headers": { "host": "example.com", "x-injected": "true" },
|
|
200
|
+
"body": ""
|
|
201
|
+
},
|
|
202
|
+
"response": {
|
|
203
|
+
"headers": {},
|
|
204
|
+
"body": ""
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
### request_completed
|
|
214
|
+
|
|
215
|
+
Fired when all hook phases have completed and a final response is available.
|
|
216
|
+
|
|
217
|
+
`hookResults` keys are camelCase hook names (`onRequestHeaders`, `onRequestBody`, `onResponseHeaders`, `onResponseBody`).
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
interface RequestCompletedEvent {
|
|
221
|
+
type: "request_completed";
|
|
222
|
+
timestamp: number;
|
|
223
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
224
|
+
data: {
|
|
225
|
+
hookResults: Record<string, any>;
|
|
226
|
+
finalResponse: {
|
|
227
|
+
status: number;
|
|
228
|
+
statusText: string;
|
|
229
|
+
headers: Record<string, string>;
|
|
230
|
+
body: string;
|
|
231
|
+
contentType: string;
|
|
232
|
+
isBase64?: boolean;
|
|
233
|
+
};
|
|
234
|
+
calculatedProperties?: Record<string, unknown>;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
| Field | Type | Description |
|
|
240
|
+
| --------------------------- | -------------------------------------- | ----------------------------------------------------- |
|
|
241
|
+
| `hookResults` | `Record<string, any>` | Per-hook execution results, keyed by hook name |
|
|
242
|
+
| `finalResponse.status` | `number` | HTTP status code |
|
|
243
|
+
| `finalResponse.statusText` | `string` | HTTP status text |
|
|
244
|
+
| `finalResponse.headers` | `Record<string, string>` | Response headers |
|
|
245
|
+
| `finalResponse.body` | `string` | Response body (may be base64 if `isBase64` is `true`) |
|
|
246
|
+
| `finalResponse.contentType` | `string` | Content-Type of the response |
|
|
247
|
+
| `finalResponse.isBase64` | `boolean \| undefined` | Whether `body` is base64-encoded |
|
|
248
|
+
| `calculatedProperties` | `Record<string, unknown> \| undefined` | Properties computed during execution, if any |
|
|
249
|
+
|
|
250
|
+
**Example:**
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"type": "request_completed",
|
|
255
|
+
"timestamp": 1742734800500,
|
|
256
|
+
"source": "api",
|
|
257
|
+
"data": {
|
|
258
|
+
"hookResults": {
|
|
259
|
+
"onRequestHeaders": { "returnCode": 0 },
|
|
260
|
+
"onResponseHeaders": { "returnCode": 0 }
|
|
261
|
+
},
|
|
262
|
+
"finalResponse": {
|
|
263
|
+
"status": 200,
|
|
264
|
+
"statusText": "OK",
|
|
265
|
+
"headers": { "content-type": "application/json" },
|
|
266
|
+
"body": "{\"ok\":true}",
|
|
267
|
+
"contentType": "application/json",
|
|
268
|
+
"isBase64": false
|
|
269
|
+
},
|
|
270
|
+
"calculatedProperties": {}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### request_failed
|
|
278
|
+
|
|
279
|
+
Fired when request processing fails before a response can be produced.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
interface RequestFailedEvent {
|
|
283
|
+
type: "request_failed";
|
|
284
|
+
timestamp: number;
|
|
285
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
286
|
+
data: {
|
|
287
|
+
error: string;
|
|
288
|
+
details?: string;
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
| Field | Type | Description |
|
|
294
|
+
| --------- | --------------------- | -------------------------------------------------- |
|
|
295
|
+
| `error` | `string` | Short error message |
|
|
296
|
+
| `details` | `string \| undefined` | Extended error detail or stack trace, if available |
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"type": "request_failed",
|
|
303
|
+
"timestamp": 1742734800300,
|
|
304
|
+
"source": "api",
|
|
305
|
+
"data": {
|
|
306
|
+
"error": "WASM execution error",
|
|
307
|
+
"details": "RuntimeError: memory access out of bounds"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### properties_updated
|
|
315
|
+
|
|
316
|
+
Fired when the set of active properties changes (e.g. after a properties configuration update).
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
interface PropertiesUpdatedEvent {
|
|
320
|
+
type: "properties_updated";
|
|
321
|
+
timestamp: number;
|
|
322
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
323
|
+
data: {
|
|
324
|
+
properties: Record<string, string>;
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| Field | Type | Description |
|
|
330
|
+
| ------------ | ------------------------ | ------------------------------------------ |
|
|
331
|
+
| `properties` | `Record<string, string>` | Full current property map after the update |
|
|
332
|
+
|
|
333
|
+
**Example:**
|
|
334
|
+
|
|
335
|
+
```json
|
|
336
|
+
{
|
|
337
|
+
"type": "properties_updated",
|
|
338
|
+
"timestamp": 1742734800050,
|
|
339
|
+
"source": "ui",
|
|
340
|
+
"data": {
|
|
341
|
+
"properties": {
|
|
342
|
+
"plugin.name": "my-filter",
|
|
343
|
+
"plugin.version": "1.0.0"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### http_wasm_request_completed
|
|
352
|
+
|
|
353
|
+
Fired when an http-wasm filter finishes processing a request and a response is available.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
interface HttpWasmRequestCompletedEvent {
|
|
357
|
+
type: "http_wasm_request_completed";
|
|
358
|
+
timestamp: number;
|
|
359
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
360
|
+
data: {
|
|
361
|
+
response: {
|
|
362
|
+
status: number;
|
|
363
|
+
statusText: string;
|
|
364
|
+
headers: Record<string, string>;
|
|
365
|
+
body: string;
|
|
366
|
+
contentType: string | null;
|
|
367
|
+
isBase64?: boolean;
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
| Field | Type | Description |
|
|
374
|
+
| ---------------------- | ------------------------ | ----------------------------------------------------- |
|
|
375
|
+
| `response.status` | `number` | HTTP status code |
|
|
376
|
+
| `response.statusText` | `string` | HTTP status text |
|
|
377
|
+
| `response.headers` | `Record<string, string>` | Response headers |
|
|
378
|
+
| `response.body` | `string` | Response body (may be base64 if `isBase64` is `true`) |
|
|
379
|
+
| `response.contentType` | `string \| null` | Content-Type, or `null` if absent |
|
|
380
|
+
| `response.isBase64` | `boolean \| undefined` | Whether `body` is base64-encoded |
|
|
381
|
+
|
|
382
|
+
**Example:**
|
|
383
|
+
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"type": "http_wasm_request_completed",
|
|
387
|
+
"timestamp": 1742734800600,
|
|
388
|
+
"source": "api",
|
|
389
|
+
"data": {
|
|
390
|
+
"response": {
|
|
391
|
+
"status": 200,
|
|
392
|
+
"statusText": "OK",
|
|
393
|
+
"headers": { "content-type": "text/plain" },
|
|
394
|
+
"body": "Hello, world!",
|
|
395
|
+
"contentType": "text/plain",
|
|
396
|
+
"isBase64": false
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
### http_wasm_log
|
|
405
|
+
|
|
406
|
+
Fired in real-time as the http-wasm filter emits log lines during both execute and live modes. One event is emitted per log line.
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
interface HttpWasmLogEvent {
|
|
410
|
+
type: "http_wasm_log";
|
|
411
|
+
timestamp: number;
|
|
412
|
+
source: "ui" | "ai_agent" | "api" | "system";
|
|
413
|
+
data: {
|
|
414
|
+
level: number;
|
|
415
|
+
message: string;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
| Field | Type | Description |
|
|
421
|
+
| --------- | -------- | ---------------------------------------------------- |
|
|
422
|
+
| `level` | `number` | Log level (follows proxy-wasm log level conventions) |
|
|
423
|
+
| `message` | `string` | Log message text |
|
|
424
|
+
|
|
425
|
+
**Example:**
|
|
426
|
+
|
|
427
|
+
```json
|
|
428
|
+
{
|
|
429
|
+
"type": "http_wasm_log",
|
|
430
|
+
"timestamp": 1742734800250,
|
|
431
|
+
"source": "api",
|
|
432
|
+
"data": {
|
|
433
|
+
"level": 2,
|
|
434
|
+
"message": "processing request to /api/resource"
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### connection_status
|
|
442
|
+
|
|
443
|
+
Fired by the server in three situations:
|
|
444
|
+
|
|
445
|
+
1. Immediately after a client connects (sent only to the connecting client).
|
|
446
|
+
2. When any client connects or disconnects (broadcast to all clients).
|
|
447
|
+
3. In response to a client `ping` message.
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
interface ConnectionStatusEvent {
|
|
451
|
+
type: "connection_status";
|
|
452
|
+
timestamp: number;
|
|
453
|
+
source: "system";
|
|
454
|
+
data: {
|
|
455
|
+
connected: boolean;
|
|
456
|
+
clientCount: number;
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
| Field | Type | Description |
|
|
462
|
+
| ------------- | --------- | ---------------------------------------------------------------- |
|
|
463
|
+
| `connected` | `boolean` | Always `true` when received (indicates this client is connected) |
|
|
464
|
+
| `clientCount` | `number` | Total number of currently connected clients including this one |
|
|
465
|
+
|
|
466
|
+
**Example:**
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{
|
|
470
|
+
"type": "connection_status",
|
|
471
|
+
"timestamp": 1742734800010,
|
|
472
|
+
"source": "system",
|
|
473
|
+
"data": {
|
|
474
|
+
"connected": true,
|
|
475
|
+
"clientCount": 1
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Client Messages
|
|
481
|
+
|
|
482
|
+
The server accepts one client-to-server message type over WebSocket.
|
|
483
|
+
|
|
484
|
+
### ping
|
|
485
|
+
|
|
486
|
+
Requests a `connection_status` response from the server. Useful for verifying the connection is alive at the application level, independent of the underlying WebSocket ping/pong frames.
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{ "type": "ping" }
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The server responds by sending a `connection_status` event to the requesting client.
|
|
493
|
+
|
|
494
|
+
All other commands (loading WASM, triggering requests, updating properties) are submitted via the REST API, not over WebSocket.
|
|
495
|
+
|
|
496
|
+
## See Also
|
|
497
|
+
|
|
498
|
+
- `API.md` — REST endpoints for triggering requests, loading WASM, and updating properties
|
|
499
|
+
- `TEST_FRAMEWORK.md` — Using WebSocket events in automated tests
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Quickstart — @gcoredev/fastedge-test
|
|
2
|
+
|
|
3
|
+
A local test runner and debugger for FastEdge WASM modules.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev @gcoredev/fastedge-test
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add -D @gcoredev/fastedge-test
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Node.js >= 22.12.
|
|
16
|
+
|
|
17
|
+
## Option 1: Interactive Debugger
|
|
18
|
+
|
|
19
|
+
Launch the interactive debugger UI to inspect your WASM module's request/response behavior in a browser.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx fastedge-debug
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The server starts on port `5179` by default. Open `http://localhost:5179` in your browser to use the debugger interface.
|
|
26
|
+
|
|
27
|
+
Use the `PORT` environment variable to override the default port:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
PORT=8080 npx fastedge-debug
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
See [DEBUGGER.md](DEBUGGER.md) for full usage details.
|
|
34
|
+
|
|
35
|
+
## Option 2: Programmatic Test Suite
|
|
36
|
+
|
|
37
|
+
Use `defineTestSuite` and `runAndExit` to write test scripts that run in CI or as standalone Node.js programs.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { defineTestSuite, runAndExit, runFlow } from "@gcoredev/fastedge-test/test";
|
|
41
|
+
|
|
42
|
+
const suite = defineTestSuite({
|
|
43
|
+
wasmPath: "./dist/my-module.wasm",
|
|
44
|
+
tests: [
|
|
45
|
+
{
|
|
46
|
+
name: "redirects /old-path to /new-path",
|
|
47
|
+
async run(runner) {
|
|
48
|
+
const result = await runFlow(runner, {
|
|
49
|
+
url: "https://example.com/old-path",
|
|
50
|
+
method: "GET",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const location = result.finalResponse.headers["location"];
|
|
54
|
+
if (location !== "/new-path") {
|
|
55
|
+
throw new Error(`Expected location "/new-path", got "${location}"`);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "adds X-Custom-Header to response",
|
|
61
|
+
async run(runner) {
|
|
62
|
+
const result = await runFlow(runner, {
|
|
63
|
+
url: "https://example.com/",
|
|
64
|
+
method: "GET",
|
|
65
|
+
responseStatus: 200,
|
|
66
|
+
responseBody: "hello",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const header = result.finalResponse.headers["x-custom-header"];
|
|
70
|
+
if (!header) {
|
|
71
|
+
throw new Error("Expected x-custom-header to be present");
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await runAndExit(suite);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`runAndExit` prints a test summary and exits with code `0` (all pass) or `1` (any fail), making it suitable for CI pipelines.
|
|
82
|
+
|
|
83
|
+
See [TEST_FRAMEWORK.md](TEST_FRAMEWORK.md) for the full API reference including `runTestSuite`, `loadConfigFile`, and all `FlowOptions` fields.
|
|
84
|
+
|
|
85
|
+
## Option 3: Low-Level Runner
|
|
86
|
+
|
|
87
|
+
Use `createRunner` directly when you need full control over the runner lifecycle, or when integrating with an existing test framework such as Vitest or Jest.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { createRunner } from "@gcoredev/fastedge-test";
|
|
91
|
+
|
|
92
|
+
const runner = await createRunner("./dist/my-module.wasm");
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await runner.callFullFlow(
|
|
96
|
+
"https://example.com/", // url
|
|
97
|
+
"GET", // method
|
|
98
|
+
{ // request headers
|
|
99
|
+
":method": "GET",
|
|
100
|
+
":path": "/",
|
|
101
|
+
":authority": "example.com",
|
|
102
|
+
":scheme": "https",
|
|
103
|
+
},
|
|
104
|
+
"", // request body
|
|
105
|
+
{}, // response headers
|
|
106
|
+
"", // response body
|
|
107
|
+
200, // response status
|
|
108
|
+
"OK", // response status text
|
|
109
|
+
{}, // properties
|
|
110
|
+
true, // enforceProductionPropertyRules
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
console.log(result.finalResponse);
|
|
114
|
+
} finally {
|
|
115
|
+
await runner.cleanup();
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
You can also load from an in-memory buffer using `createRunnerFromBuffer`:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createRunnerFromBuffer } from "@gcoredev/fastedge-test";
|
|
123
|
+
import { readFile } from "fs/promises";
|
|
124
|
+
|
|
125
|
+
const buffer = await readFile("./dist/my-module.wasm");
|
|
126
|
+
const runner = await createRunnerFromBuffer(buffer);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [RUNNER.md](RUNNER.md) for the full `IWasmRunner` interface, `RunnerConfig` options, and `FullFlowResult` shape.
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
Place a `fastedge-config.test.json` file in your project root to define default request parameters, response stubs, and WASM properties for the interactive debugger.
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"$schema": "./node_modules/@gcoredev/fastedge-test/schemas/fastedge-config.test.schema.json",
|
|
138
|
+
"wasm": {
|
|
139
|
+
"path": "./dist/my-module.wasm"
|
|
140
|
+
},
|
|
141
|
+
"request": {
|
|
142
|
+
"method": "GET",
|
|
143
|
+
"url": "https://example.com/",
|
|
144
|
+
"headers": {
|
|
145
|
+
"accept": "text/html"
|
|
146
|
+
},
|
|
147
|
+
"body": ""
|
|
148
|
+
},
|
|
149
|
+
"response": {
|
|
150
|
+
"headers": {
|
|
151
|
+
"content-type": "text/html"
|
|
152
|
+
},
|
|
153
|
+
"body": "<html><body>Hello</body></html>"
|
|
154
|
+
},
|
|
155
|
+
"properties": {
|
|
156
|
+
"my-property": "value"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The `$schema` field enables editor autocompletion and inline validation.
|
|
162
|
+
|
|
163
|
+
See [TEST_CONFIG.md](TEST_CONFIG.md) for all configuration fields and their defaults.
|
|
164
|
+
|
|
165
|
+
## Next Steps
|
|
166
|
+
|
|
167
|
+
- [RUNNER.md](RUNNER.md) — `createRunner`, `createRunnerFromBuffer`, `IWasmRunner` interface, `RunnerConfig`
|
|
168
|
+
- [TEST_FRAMEWORK.md](TEST_FRAMEWORK.md) — `defineTestSuite`, `runAndExit`, `runFlow`, `runTestSuite`, `loadConfigFile`
|
|
169
|
+
- [TEST_CONFIG.md](TEST_CONFIG.md) — `fastedge-config.test.json` schema reference
|
|
170
|
+
- [DEBUGGER.md](DEBUGGER.md) — Interactive debugger usage and configuration
|
|
171
|
+
- [API.md](API.md) — REST API reference for programmatic server integration
|