@gcoredev/fastedge-test 0.2.0 → 0.2.1
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/dist/fastedge-cli/METADATA.json +1 -1
- package/dist/fastedge-cli/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/lib/index.cjs +162 -78
- package/dist/lib/index.js +162 -78
- package/dist/lib/runner/HeaderManager.d.ts +1 -0
- package/dist/lib/runner/ProxyWasmRunner.d.ts +1 -1
- package/dist/lib/test-framework/index.cjs +161 -78
- package/dist/lib/test-framework/index.js +161 -78
- package/dist/server.js +25 -25
- package/docs/API.md +4 -4
- package/docs/DEBUGGER.md +1 -0
- package/docs/RUNNER.md +32 -32
- package/docs/TEST_CONFIG.md +19 -19
- package/docs/TEST_FRAMEWORK.md +21 -23
- package/package.json +1 -1
package/dist/lib/index.js
CHANGED
|
@@ -5,13 +5,11 @@ import { WASI } from "node:wasi";
|
|
|
5
5
|
var textEncoder = new TextEncoder();
|
|
6
6
|
var textDecoder = new TextDecoder();
|
|
7
7
|
var MemoryManager = class {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.isInitializing = false;
|
|
14
|
-
}
|
|
8
|
+
memory = null;
|
|
9
|
+
instance = null;
|
|
10
|
+
hostAllocOffset = 0;
|
|
11
|
+
logCallback = null;
|
|
12
|
+
isInitializing = false;
|
|
15
13
|
setMemory(memory) {
|
|
16
14
|
this.memory = memory;
|
|
17
15
|
}
|
|
@@ -174,7 +172,7 @@ var MemoryManager = class {
|
|
|
174
172
|
|
|
175
173
|
// server/runner/HeaderManager.ts
|
|
176
174
|
var textEncoder2 = new TextEncoder();
|
|
177
|
-
var HeaderManager = class {
|
|
175
|
+
var HeaderManager = class _HeaderManager {
|
|
178
176
|
// Read a header value as a single string. For multi-valued headers (string[]) returns the first.
|
|
179
177
|
// Use when callers know the header is conventionally single-valued (content-type, host, location, etc.)
|
|
180
178
|
// and need to satisfy APIs that take a string.
|
|
@@ -197,7 +195,7 @@ var HeaderManager = class {
|
|
|
197
195
|
return flat;
|
|
198
196
|
}
|
|
199
197
|
static normalize(headers) {
|
|
200
|
-
const normalized =
|
|
198
|
+
const normalized = /* @__PURE__ */ Object.create(null);
|
|
201
199
|
for (const [key, value] of Object.entries(headers)) {
|
|
202
200
|
const k = key.toLowerCase();
|
|
203
201
|
if (Array.isArray(value)) {
|
|
@@ -208,6 +206,30 @@ var HeaderManager = class {
|
|
|
208
206
|
}
|
|
209
207
|
return normalized;
|
|
210
208
|
}
|
|
209
|
+
// Append-merge two header records: for keys present in both, values are
|
|
210
|
+
// concatenated into a string[] rather than the right-hand side overwriting
|
|
211
|
+
// the left. Keys are lowercased on the way in (consistent with `normalize`).
|
|
212
|
+
//
|
|
213
|
+
// Used by the runner to combine request-phase response-header state with
|
|
214
|
+
// the origin's response headers, mirroring how Envoy serves
|
|
215
|
+
// `add_http_response_header` calls issued during onRequestHeaders /
|
|
216
|
+
// onRequestBody against the actual upstream response — both values survive
|
|
217
|
+
// as a multi-value list (preserving the proxy-wasm cross-phase pattern).
|
|
218
|
+
static appendMerge(left, right) {
|
|
219
|
+
const result = _HeaderManager.normalize(left);
|
|
220
|
+
for (const [key, value] of Object.entries(right)) {
|
|
221
|
+
const k = key.toLowerCase();
|
|
222
|
+
const incoming = Array.isArray(value) ? value.map(String) : [String(value)];
|
|
223
|
+
if (Object.hasOwn(result, k)) {
|
|
224
|
+
const existing = result[k];
|
|
225
|
+
const existingArr = Array.isArray(existing) ? existing : [existing];
|
|
226
|
+
result[k] = [...existingArr, ...incoming];
|
|
227
|
+
} else {
|
|
228
|
+
result[k] = incoming.length === 1 ? incoming[0] : incoming;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
211
233
|
static serialize(headers) {
|
|
212
234
|
const pairs = Object.entries(headers);
|
|
213
235
|
const numPairs = pairs.length;
|
|
@@ -386,20 +408,18 @@ var HeaderManager = class {
|
|
|
386
408
|
|
|
387
409
|
// server/runner/PropertyResolver.ts
|
|
388
410
|
var PropertyResolver = class {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.responseStatusText = "OK";
|
|
402
|
-
}
|
|
411
|
+
properties = {};
|
|
412
|
+
requestHeaders = {};
|
|
413
|
+
requestMethod = "GET";
|
|
414
|
+
requestPath = "/";
|
|
415
|
+
requestScheme = "https";
|
|
416
|
+
requestUrl = "";
|
|
417
|
+
requestHost = "";
|
|
418
|
+
requestQuery = "";
|
|
419
|
+
requestExtension = "";
|
|
420
|
+
responseHeaders = {};
|
|
421
|
+
responseStatus = 200;
|
|
422
|
+
responseStatusText = "OK";
|
|
403
423
|
setProperties(properties) {
|
|
404
424
|
this.properties = properties;
|
|
405
425
|
}
|
|
@@ -591,6 +611,7 @@ var PropertyResolver = class {
|
|
|
591
611
|
|
|
592
612
|
// server/fastedge-host/SecretStore.ts
|
|
593
613
|
var SecretStore = class {
|
|
614
|
+
secrets;
|
|
594
615
|
constructor(initialSecrets) {
|
|
595
616
|
this.secrets = /* @__PURE__ */ new Map();
|
|
596
617
|
if (initialSecrets) {
|
|
@@ -673,6 +694,7 @@ var SecretStore = class {
|
|
|
673
694
|
|
|
674
695
|
// server/fastedge-host/Dictionary.ts
|
|
675
696
|
var Dictionary = class {
|
|
697
|
+
data;
|
|
676
698
|
constructor(initialData) {
|
|
677
699
|
this.data = /* @__PURE__ */ new Map();
|
|
678
700
|
if (initialData) {
|
|
@@ -840,26 +862,33 @@ function createFastEdgeHostFunctions(memory, secretStore, dictionary, logDebug)
|
|
|
840
862
|
// server/runner/HostFunctions.ts
|
|
841
863
|
var textEncoder3 = new TextEncoder();
|
|
842
864
|
var HostFunctions = class {
|
|
865
|
+
memory;
|
|
866
|
+
propertyResolver;
|
|
867
|
+
propertyAccessControl;
|
|
868
|
+
getCurrentHook;
|
|
869
|
+
logs = [];
|
|
870
|
+
requestHeaders = [];
|
|
871
|
+
responseHeaders = [];
|
|
872
|
+
requestBody = "";
|
|
873
|
+
responseBody = "";
|
|
874
|
+
vmConfig = "";
|
|
875
|
+
pluginConfig = "";
|
|
876
|
+
currentContextId = 1;
|
|
877
|
+
lastHostCall = null;
|
|
878
|
+
debug = false;
|
|
879
|
+
currentLogLevel = 0 /* Trace */;
|
|
880
|
+
// Default to show all logs
|
|
881
|
+
// http_call state
|
|
882
|
+
nextTokenId = 0;
|
|
883
|
+
pendingHttpCall = null;
|
|
884
|
+
httpCallResponse = null;
|
|
885
|
+
streamClosed = false;
|
|
886
|
+
// Local response state (from proxy_send_local_response / send_http_response)
|
|
887
|
+
localResponse = null;
|
|
888
|
+
// FastEdge extensions
|
|
889
|
+
secretStore;
|
|
890
|
+
dictionary;
|
|
843
891
|
constructor(memory, propertyResolver, propertyAccessControl, getCurrentHook, debug = false, secretStore, dictionary) {
|
|
844
|
-
this.logs = [];
|
|
845
|
-
this.requestHeaders = [];
|
|
846
|
-
this.responseHeaders = [];
|
|
847
|
-
this.requestBody = "";
|
|
848
|
-
this.responseBody = "";
|
|
849
|
-
this.vmConfig = "";
|
|
850
|
-
this.pluginConfig = "";
|
|
851
|
-
this.currentContextId = 1;
|
|
852
|
-
this.lastHostCall = null;
|
|
853
|
-
this.debug = false;
|
|
854
|
-
this.currentLogLevel = 0 /* Trace */;
|
|
855
|
-
// Default to show all logs
|
|
856
|
-
// http_call state
|
|
857
|
-
this.nextTokenId = 0;
|
|
858
|
-
this.pendingHttpCall = null;
|
|
859
|
-
this.httpCallResponse = null;
|
|
860
|
-
this.streamClosed = false;
|
|
861
|
-
// Local response state (from proxy_send_local_response / send_http_response)
|
|
862
|
-
this.localResponse = null;
|
|
863
892
|
this.memory = memory;
|
|
864
893
|
this.propertyResolver = propertyResolver;
|
|
865
894
|
this.propertyAccessControl = propertyAccessControl;
|
|
@@ -1590,6 +1619,8 @@ var BUILT_IN_PROPERTIES = [
|
|
|
1590
1619
|
}
|
|
1591
1620
|
];
|
|
1592
1621
|
var PropertyAccessControl = class {
|
|
1622
|
+
builtInProperties;
|
|
1623
|
+
customProperties;
|
|
1593
1624
|
constructor() {
|
|
1594
1625
|
this.builtInProperties = /* @__PURE__ */ new Map();
|
|
1595
1626
|
this.customProperties = /* @__PURE__ */ new Map();
|
|
@@ -1807,21 +1838,28 @@ var textEncoder4 = new TextEncoder();
|
|
|
1807
1838
|
var BUILTIN_URL = "http://fastedge-builtin.debug";
|
|
1808
1839
|
var BUILTIN_SHORTHAND = "built-in";
|
|
1809
1840
|
var ProxyWasmRunner = class {
|
|
1841
|
+
module = null;
|
|
1842
|
+
// Compiled module (reused)
|
|
1843
|
+
instance = null;
|
|
1844
|
+
// Current instance (transient per hook)
|
|
1845
|
+
memory;
|
|
1846
|
+
propertyResolver;
|
|
1847
|
+
propertyAccessControl;
|
|
1848
|
+
currentHook = null;
|
|
1849
|
+
hostFunctions;
|
|
1850
|
+
logs = [];
|
|
1851
|
+
rootContextId = 1;
|
|
1852
|
+
nextContextId = 2;
|
|
1853
|
+
currentContextId = 1;
|
|
1854
|
+
isInitializing = false;
|
|
1855
|
+
debug = process.env.PROXY_RUNNER_DEBUG === "1";
|
|
1856
|
+
stateManager = null;
|
|
1857
|
+
secretStore;
|
|
1858
|
+
dictionary;
|
|
1859
|
+
dotenvEnabled = true;
|
|
1860
|
+
// Default to enabled
|
|
1861
|
+
dotenvPath = ".";
|
|
1810
1862
|
constructor(fastEdgeConfig, dotenvEnabled = true) {
|
|
1811
|
-
this.module = null;
|
|
1812
|
-
// Compiled module (reused)
|
|
1813
|
-
this.instance = null;
|
|
1814
|
-
this.currentHook = null;
|
|
1815
|
-
this.logs = [];
|
|
1816
|
-
this.rootContextId = 1;
|
|
1817
|
-
this.nextContextId = 2;
|
|
1818
|
-
this.currentContextId = 1;
|
|
1819
|
-
this.isInitializing = false;
|
|
1820
|
-
this.debug = process.env.PROXY_RUNNER_DEBUG === "1";
|
|
1821
|
-
this.stateManager = null;
|
|
1822
|
-
this.dotenvEnabled = true;
|
|
1823
|
-
// Default to enabled
|
|
1824
|
-
this.dotenvPath = ".";
|
|
1825
1863
|
this.memory = new MemoryManager();
|
|
1826
1864
|
this.propertyResolver = new PropertyResolver();
|
|
1827
1865
|
this.propertyAccessControl = new PropertyAccessControl();
|
|
@@ -2174,6 +2212,14 @@ var ProxyWasmRunner = class {
|
|
|
2174
2212
|
`Fetch completed: ${responseStatus} ${responseStatusText}`
|
|
2175
2213
|
);
|
|
2176
2214
|
}
|
|
2215
|
+
const requestPhaseResponseHeaders = {
|
|
2216
|
+
...results.onRequestHeaders.output.response.headers ?? {},
|
|
2217
|
+
...results.onRequestBody.output.response.headers ?? {}
|
|
2218
|
+
};
|
|
2219
|
+
const mergedResponseHeaders = HeaderManager.appendMerge(
|
|
2220
|
+
requestPhaseResponseHeaders,
|
|
2221
|
+
responseHeaders
|
|
2222
|
+
);
|
|
2177
2223
|
const responseCall = {
|
|
2178
2224
|
...call,
|
|
2179
2225
|
request: {
|
|
@@ -2182,7 +2228,7 @@ var ProxyWasmRunner = class {
|
|
|
2182
2228
|
body: modifiedRequestBody
|
|
2183
2229
|
},
|
|
2184
2230
|
response: {
|
|
2185
|
-
headers:
|
|
2231
|
+
headers: mergedResponseHeaders,
|
|
2186
2232
|
body: responseBody,
|
|
2187
2233
|
status: responseStatus,
|
|
2188
2234
|
statusText: responseStatusText
|
|
@@ -2204,6 +2250,25 @@ var ProxyWasmRunner = class {
|
|
|
2204
2250
|
"system"
|
|
2205
2251
|
);
|
|
2206
2252
|
}
|
|
2253
|
+
if (results.onResponseHeaders.returnCode === 1 && this.hostFunctions.hasLocalResponse()) {
|
|
2254
|
+
const local = this.hostFunctions.getLocalResponse();
|
|
2255
|
+
const headers = results.onResponseHeaders.output.response.headers;
|
|
2256
|
+
this.hostFunctions.resetLocalResponse();
|
|
2257
|
+
const contentType2 = HeaderManager.firstValue(headers["content-type"]) || "text/plain";
|
|
2258
|
+
const { body, isBase64: isBase642 } = encodeLocalResponseBody(local.body, contentType2);
|
|
2259
|
+
return {
|
|
2260
|
+
hookResults: results,
|
|
2261
|
+
finalResponse: {
|
|
2262
|
+
status: local.statusCode,
|
|
2263
|
+
statusText: local.statusText,
|
|
2264
|
+
headers,
|
|
2265
|
+
body,
|
|
2266
|
+
contentType: contentType2,
|
|
2267
|
+
isBase64: isBase642
|
|
2268
|
+
},
|
|
2269
|
+
calculatedProperties: this.propertyResolver.getCalculatedProperties()
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2207
2272
|
const headersAfterResponseHeaders = results.onResponseHeaders.output.response.headers;
|
|
2208
2273
|
const propertiesAfterResponseHeaders = results.onResponseHeaders.properties;
|
|
2209
2274
|
this.logDebug(
|
|
@@ -2228,6 +2293,25 @@ var ProxyWasmRunner = class {
|
|
|
2228
2293
|
"system"
|
|
2229
2294
|
);
|
|
2230
2295
|
}
|
|
2296
|
+
if (results.onResponseBody.returnCode === 1 && this.hostFunctions.hasLocalResponse()) {
|
|
2297
|
+
const local = this.hostFunctions.getLocalResponse();
|
|
2298
|
+
const headers = results.onResponseBody.output.response.headers;
|
|
2299
|
+
this.hostFunctions.resetLocalResponse();
|
|
2300
|
+
const contentType2 = HeaderManager.firstValue(headers["content-type"]) || "text/plain";
|
|
2301
|
+
const { body, isBase64: isBase642 } = encodeLocalResponseBody(local.body, contentType2);
|
|
2302
|
+
return {
|
|
2303
|
+
hookResults: results,
|
|
2304
|
+
finalResponse: {
|
|
2305
|
+
status: local.statusCode,
|
|
2306
|
+
statusText: local.statusText,
|
|
2307
|
+
headers,
|
|
2308
|
+
body,
|
|
2309
|
+
contentType: contentType2,
|
|
2310
|
+
isBase64: isBase642
|
|
2311
|
+
},
|
|
2312
|
+
calculatedProperties: this.propertyResolver.getCalculatedProperties()
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2231
2315
|
const finalHeaders = results.onResponseBody.output.response.headers;
|
|
2232
2316
|
const finalBody = results.onResponseBody.output.response.body;
|
|
2233
2317
|
this.logDebug(`Final response body length: ${finalBody.length}`);
|
|
@@ -2704,7 +2788,7 @@ var ProxyWasmRunner = class {
|
|
|
2704
2788
|
/**
|
|
2705
2789
|
* Not supported for Proxy-WASM (HTTP WASM only)
|
|
2706
2790
|
*/
|
|
2707
|
-
async execute(
|
|
2791
|
+
async execute(_request) {
|
|
2708
2792
|
throw new Error(
|
|
2709
2793
|
"execute() is not supported for Proxy-WASM. Use callHook() or callFullFlow() instead."
|
|
2710
2794
|
);
|
|
@@ -2868,21 +2952,22 @@ async function isLegacySyncWasm(bufferOrPath) {
|
|
|
2868
2952
|
|
|
2869
2953
|
// server/runner/HttpWasmRunner.ts
|
|
2870
2954
|
var HttpWasmRunner = class {
|
|
2955
|
+
process = null;
|
|
2956
|
+
port = null;
|
|
2957
|
+
cliPath = null;
|
|
2958
|
+
tempWasmPath = null;
|
|
2959
|
+
currentWasmPath = null;
|
|
2960
|
+
// resolved path used when spawning
|
|
2961
|
+
logs = [];
|
|
2962
|
+
stateManager = null;
|
|
2963
|
+
portManager;
|
|
2964
|
+
dotenvEnabled = true;
|
|
2965
|
+
dotenvPath = null;
|
|
2966
|
+
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
2967
|
+
isPinnedPort = false;
|
|
2968
|
+
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
2969
|
+
isLegacySync = false;
|
|
2871
2970
|
constructor(portManager, dotenvEnabled = true) {
|
|
2872
|
-
this.process = null;
|
|
2873
|
-
this.port = null;
|
|
2874
|
-
this.cliPath = null;
|
|
2875
|
-
this.tempWasmPath = null;
|
|
2876
|
-
this.currentWasmPath = null;
|
|
2877
|
-
// resolved path used when spawning
|
|
2878
|
-
this.logs = [];
|
|
2879
|
-
this.stateManager = null;
|
|
2880
|
-
this.dotenvEnabled = true;
|
|
2881
|
-
this.dotenvPath = null;
|
|
2882
|
-
/** Pinned ports bypass PortManager allocation and must not be released back to it. */
|
|
2883
|
-
this.isPinnedPort = false;
|
|
2884
|
-
/** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
|
|
2885
|
-
this.isLegacySync = false;
|
|
2886
2971
|
this.portManager = portManager;
|
|
2887
2972
|
this.dotenvEnabled = dotenvEnabled;
|
|
2888
2973
|
}
|
|
@@ -3282,12 +3367,10 @@ function parseFetchHeaders(headers) {
|
|
|
3282
3367
|
// server/runner/PortManager.ts
|
|
3283
3368
|
import { createServer } from "net";
|
|
3284
3369
|
var PortManager = class {
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
this.lastAllocatedPort = this.minPort - 1;
|
|
3290
|
-
}
|
|
3370
|
+
minPort = 8100;
|
|
3371
|
+
maxPort = 8199;
|
|
3372
|
+
allocatedPorts = /* @__PURE__ */ new Set();
|
|
3373
|
+
lastAllocatedPort = this.minPort - 1;
|
|
3291
3374
|
/**
|
|
3292
3375
|
* Check whether a port is actually free at the OS level.
|
|
3293
3376
|
* This is necessary when multiple server processes run simultaneously —
|
|
@@ -3347,6 +3430,7 @@ var PortManager = class {
|
|
|
3347
3430
|
|
|
3348
3431
|
// server/runner/WasmRunnerFactory.ts
|
|
3349
3432
|
var WasmRunnerFactory = class {
|
|
3433
|
+
portManager;
|
|
3350
3434
|
constructor() {
|
|
3351
3435
|
this.portManager = new PortManager();
|
|
3352
3436
|
}
|
|
@@ -3,6 +3,7 @@ export declare class HeaderManager {
|
|
|
3
3
|
static firstValue(v: string | string[] | undefined): string | undefined;
|
|
4
4
|
static flattenToMap(headers: HeaderMap | HeaderRecord): HeaderMap;
|
|
5
5
|
static normalize(headers: HeaderMap | HeaderRecord): HeaderRecord;
|
|
6
|
+
static appendMerge(left: HeaderMap | HeaderRecord, right: HeaderMap | HeaderRecord): HeaderRecord;
|
|
6
7
|
static serialize(headers: HeaderMap): Uint8Array;
|
|
7
8
|
/**
|
|
8
9
|
* Deserializes a proxy-wasm binary header map format:
|
|
@@ -68,7 +68,7 @@ export declare class ProxyWasmRunner implements IWasmRunner {
|
|
|
68
68
|
/**
|
|
69
69
|
* Not supported for Proxy-WASM (HTTP WASM only)
|
|
70
70
|
*/
|
|
71
|
-
execute(
|
|
71
|
+
execute(_request: HttpRequest): Promise<HttpResponse>;
|
|
72
72
|
/**
|
|
73
73
|
* Clean up resources (no-op for Proxy-WASM, but required by interface)
|
|
74
74
|
*/
|