@gcoredev/fastedge-test 0.2.0 → 0.2.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/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
- constructor() {
9
- this.memory = null;
10
- this.instance = null;
11
- this.hostAllocOffset = 0;
12
- this.logCallback = null;
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
- constructor() {
390
- this.properties = {};
391
- this.requestHeaders = {};
392
- this.requestMethod = "GET";
393
- this.requestPath = "/";
394
- this.requestScheme = "https";
395
- this.requestUrl = "";
396
- this.requestHost = "";
397
- this.requestQuery = "";
398
- this.requestExtension = "";
399
- this.responseHeaders = {};
400
- this.responseStatus = 200;
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,39 @@ 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
+ // `headers` carries the additional headers passed as the 4th argument of
888
+ // `send_http_response`; ProxyWasmRunner merges these into finalResponse at
889
+ // the short-circuit points so they reach the caller alongside any headers
890
+ // accumulated via stream_context.headers.response.add() in the same hook.
891
+ // Stored as tuples to preserve order and duplicate-name semantics (e.g. for
892
+ // multi-value Set-Cookie additions).
893
+ localResponse = null;
894
+ // FastEdge extensions
895
+ secretStore;
896
+ dictionary;
843
897
  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
898
  this.memory = memory;
864
899
  this.propertyResolver = propertyResolver;
865
900
  this.propertyAccessControl = propertyAccessControl;
@@ -1204,12 +1239,13 @@ var HostFunctions = class {
1204
1239
  );
1205
1240
  const statusText = this.memory.readString(statusCodePtr, statusCodeLen);
1206
1241
  const body = this.memory.readBytes(bodyPtr, bodyLen);
1242
+ let headers = [];
1207
1243
  if (headerPairsLen > 0) {
1208
1244
  const headerBytes = this.memory.readBytes(headerPairsPtr, headerPairsLen);
1209
- const headers = HeaderManager.deserializeBinary(headerBytes);
1210
- this.logDebug(`send_local_response headers (not merged): ${JSON.stringify(headers)}`);
1245
+ headers = HeaderManager.deserializeBinaryToTuples(headerBytes);
1246
+ this.logDebug(`send_local_response headers: ${JSON.stringify(headers)}`);
1211
1247
  }
1212
- this.localResponse = { statusCode, statusText, body };
1248
+ this.localResponse = { statusCode, statusText, body, headers };
1213
1249
  this.logs.push({
1214
1250
  level: 1,
1215
1251
  message: `local_response status=${statusCode} ${statusText} bodyLen=${body.byteLength} grpc=${grpcStatus}`
@@ -1590,6 +1626,8 @@ var BUILT_IN_PROPERTIES = [
1590
1626
  }
1591
1627
  ];
1592
1628
  var PropertyAccessControl = class {
1629
+ builtInProperties;
1630
+ customProperties;
1593
1631
  constructor() {
1594
1632
  this.builtInProperties = /* @__PURE__ */ new Map();
1595
1633
  this.customProperties = /* @__PURE__ */ new Map();
@@ -1807,21 +1845,28 @@ var textEncoder4 = new TextEncoder();
1807
1845
  var BUILTIN_URL = "http://fastedge-builtin.debug";
1808
1846
  var BUILTIN_SHORTHAND = "built-in";
1809
1847
  var ProxyWasmRunner = class {
1848
+ module = null;
1849
+ // Compiled module (reused)
1850
+ instance = null;
1851
+ // Current instance (transient per hook)
1852
+ memory;
1853
+ propertyResolver;
1854
+ propertyAccessControl;
1855
+ currentHook = null;
1856
+ hostFunctions;
1857
+ logs = [];
1858
+ rootContextId = 1;
1859
+ nextContextId = 2;
1860
+ currentContextId = 1;
1861
+ isInitializing = false;
1862
+ debug = process.env.PROXY_RUNNER_DEBUG === "1";
1863
+ stateManager = null;
1864
+ secretStore;
1865
+ dictionary;
1866
+ dotenvEnabled = true;
1867
+ // Default to enabled
1868
+ dotenvPath = ".";
1810
1869
  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
1870
  this.memory = new MemoryManager();
1826
1871
  this.propertyResolver = new PropertyResolver();
1827
1872
  this.propertyAccessControl = new PropertyAccessControl();
@@ -1994,14 +2039,15 @@ var ProxyWasmRunner = class {
1994
2039
  const local = this.hostFunctions.getLocalResponse();
1995
2040
  const responseHeaders2 = results.onRequestHeaders.output.response.headers;
1996
2041
  this.hostFunctions.resetLocalResponse();
1997
- const contentType2 = HeaderManager.firstValue(responseHeaders2["content-type"]) || "text/plain";
2042
+ const mergedHeaders = local.headers.length > 0 ? HeaderManager.appendMerge(responseHeaders2, HeaderManager.tuplesToRecord(local.headers)) : responseHeaders2;
2043
+ const contentType2 = HeaderManager.firstValue(mergedHeaders["content-type"]) || "text/plain";
1998
2044
  const { body, isBase64: isBase642 } = encodeLocalResponseBody(local.body, contentType2);
1999
2045
  return {
2000
2046
  hookResults: results,
2001
2047
  finalResponse: {
2002
2048
  status: local.statusCode,
2003
2049
  statusText: local.statusText,
2004
- headers: responseHeaders2,
2050
+ headers: mergedHeaders,
2005
2051
  body,
2006
2052
  contentType: contentType2,
2007
2053
  isBase64: isBase642
@@ -2037,14 +2083,15 @@ var ProxyWasmRunner = class {
2037
2083
  const local = this.hostFunctions.getLocalResponse();
2038
2084
  const responseHeaders2 = results.onRequestBody.output.response.headers;
2039
2085
  this.hostFunctions.resetLocalResponse();
2040
- const contentType2 = HeaderManager.firstValue(responseHeaders2["content-type"]) || "text/plain";
2086
+ const mergedHeaders = local.headers.length > 0 ? HeaderManager.appendMerge(responseHeaders2, HeaderManager.tuplesToRecord(local.headers)) : responseHeaders2;
2087
+ const contentType2 = HeaderManager.firstValue(mergedHeaders["content-type"]) || "text/plain";
2041
2088
  const { body, isBase64: isBase642 } = encodeLocalResponseBody(local.body, contentType2);
2042
2089
  return {
2043
2090
  hookResults: results,
2044
2091
  finalResponse: {
2045
2092
  status: local.statusCode,
2046
2093
  statusText: local.statusText,
2047
- headers: responseHeaders2,
2094
+ headers: mergedHeaders,
2048
2095
  body,
2049
2096
  contentType: contentType2,
2050
2097
  isBase64: isBase642
@@ -2174,6 +2221,14 @@ var ProxyWasmRunner = class {
2174
2221
  `Fetch completed: ${responseStatus} ${responseStatusText}`
2175
2222
  );
2176
2223
  }
2224
+ const requestPhaseResponseHeaders = {
2225
+ ...results.onRequestHeaders.output.response.headers ?? {},
2226
+ ...results.onRequestBody.output.response.headers ?? {}
2227
+ };
2228
+ const mergedResponseHeaders = HeaderManager.appendMerge(
2229
+ requestPhaseResponseHeaders,
2230
+ responseHeaders
2231
+ );
2177
2232
  const responseCall = {
2178
2233
  ...call,
2179
2234
  request: {
@@ -2182,7 +2237,7 @@ var ProxyWasmRunner = class {
2182
2237
  body: modifiedRequestBody
2183
2238
  },
2184
2239
  response: {
2185
- headers: responseHeaders,
2240
+ headers: mergedResponseHeaders,
2186
2241
  body: responseBody,
2187
2242
  status: responseStatus,
2188
2243
  statusText: responseStatusText
@@ -2204,6 +2259,26 @@ var ProxyWasmRunner = class {
2204
2259
  "system"
2205
2260
  );
2206
2261
  }
2262
+ if (results.onResponseHeaders.returnCode === 1 && this.hostFunctions.hasLocalResponse()) {
2263
+ const local = this.hostFunctions.getLocalResponse();
2264
+ const responseHeaders2 = results.onResponseHeaders.output.response.headers;
2265
+ this.hostFunctions.resetLocalResponse();
2266
+ const mergedHeaders = local.headers.length > 0 ? HeaderManager.appendMerge(responseHeaders2, HeaderManager.tuplesToRecord(local.headers)) : responseHeaders2;
2267
+ const contentType2 = HeaderManager.firstValue(mergedHeaders["content-type"]) || "text/plain";
2268
+ const { body, isBase64: isBase642 } = encodeLocalResponseBody(local.body, contentType2);
2269
+ return {
2270
+ hookResults: results,
2271
+ finalResponse: {
2272
+ status: local.statusCode,
2273
+ statusText: local.statusText,
2274
+ headers: mergedHeaders,
2275
+ body,
2276
+ contentType: contentType2,
2277
+ isBase64: isBase642
2278
+ },
2279
+ calculatedProperties: this.propertyResolver.getCalculatedProperties()
2280
+ };
2281
+ }
2207
2282
  const headersAfterResponseHeaders = results.onResponseHeaders.output.response.headers;
2208
2283
  const propertiesAfterResponseHeaders = results.onResponseHeaders.properties;
2209
2284
  this.logDebug(
@@ -2228,6 +2303,26 @@ var ProxyWasmRunner = class {
2228
2303
  "system"
2229
2304
  );
2230
2305
  }
2306
+ if (results.onResponseBody.returnCode === 1 && this.hostFunctions.hasLocalResponse()) {
2307
+ const local = this.hostFunctions.getLocalResponse();
2308
+ const responseHeaders2 = results.onResponseBody.output.response.headers;
2309
+ this.hostFunctions.resetLocalResponse();
2310
+ const mergedHeaders = local.headers.length > 0 ? HeaderManager.appendMerge(responseHeaders2, HeaderManager.tuplesToRecord(local.headers)) : responseHeaders2;
2311
+ const contentType2 = HeaderManager.firstValue(mergedHeaders["content-type"]) || "text/plain";
2312
+ const { body, isBase64: isBase642 } = encodeLocalResponseBody(local.body, contentType2);
2313
+ return {
2314
+ hookResults: results,
2315
+ finalResponse: {
2316
+ status: local.statusCode,
2317
+ statusText: local.statusText,
2318
+ headers: mergedHeaders,
2319
+ body,
2320
+ contentType: contentType2,
2321
+ isBase64: isBase642
2322
+ },
2323
+ calculatedProperties: this.propertyResolver.getCalculatedProperties()
2324
+ };
2325
+ }
2231
2326
  const finalHeaders = results.onResponseBody.output.response.headers;
2232
2327
  const finalBody = results.onResponseBody.output.response.body;
2233
2328
  this.logDebug(`Final response body length: ${finalBody.length}`);
@@ -2595,7 +2690,9 @@ var ProxyWasmRunner = class {
2595
2690
  }
2596
2691
  }
2597
2692
  createImports() {
2598
- const wasi = new WASI({ version: "preview1", env: this.dictionary.getAll() });
2693
+ const dictEnv = this.dictionary.getAll();
2694
+ const wasiEnv = Object.keys(dictEnv).length === 0 ? { __FASTEDGE_RUNNER__: "1" } : dictEnv;
2695
+ const wasi = new WASI({ version: "preview1", env: wasiEnv });
2599
2696
  const wasiImport = wasi.wasiImport;
2600
2697
  return {
2601
2698
  env: this.hostFunctions.createImports(),
@@ -2704,7 +2801,7 @@ var ProxyWasmRunner = class {
2704
2801
  /**
2705
2802
  * Not supported for Proxy-WASM (HTTP WASM only)
2706
2803
  */
2707
- async execute(request) {
2804
+ async execute(_request) {
2708
2805
  throw new Error(
2709
2806
  "execute() is not supported for Proxy-WASM. Use callHook() or callFullFlow() instead."
2710
2807
  );
@@ -2868,21 +2965,22 @@ async function isLegacySyncWasm(bufferOrPath) {
2868
2965
 
2869
2966
  // server/runner/HttpWasmRunner.ts
2870
2967
  var HttpWasmRunner = class {
2968
+ process = null;
2969
+ port = null;
2970
+ cliPath = null;
2971
+ tempWasmPath = null;
2972
+ currentWasmPath = null;
2973
+ // resolved path used when spawning
2974
+ logs = [];
2975
+ stateManager = null;
2976
+ portManager;
2977
+ dotenvEnabled = true;
2978
+ dotenvPath = null;
2979
+ /** Pinned ports bypass PortManager allocation and must not be released back to it. */
2980
+ isPinnedPort = false;
2981
+ /** @deprecated Legacy sync support — remove when #[fastedge::http] is retired */
2982
+ isLegacySync = false;
2871
2983
  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
2984
  this.portManager = portManager;
2887
2985
  this.dotenvEnabled = dotenvEnabled;
2888
2986
  }
@@ -3282,12 +3380,10 @@ function parseFetchHeaders(headers) {
3282
3380
  // server/runner/PortManager.ts
3283
3381
  import { createServer } from "net";
3284
3382
  var PortManager = class {
3285
- constructor() {
3286
- this.minPort = 8100;
3287
- this.maxPort = 8199;
3288
- this.allocatedPorts = /* @__PURE__ */ new Set();
3289
- this.lastAllocatedPort = this.minPort - 1;
3290
- }
3383
+ minPort = 8100;
3384
+ maxPort = 8199;
3385
+ allocatedPorts = /* @__PURE__ */ new Set();
3386
+ lastAllocatedPort = this.minPort - 1;
3291
3387
  /**
3292
3388
  * Check whether a port is actually free at the OS level.
3293
3389
  * This is necessary when multiple server processes run simultaneously —
@@ -3347,6 +3443,7 @@ var PortManager = class {
3347
3443
 
3348
3444
  // server/runner/WasmRunnerFactory.ts
3349
3445
  var WasmRunnerFactory = class {
3446
+ portManager;
3350
3447
  constructor() {
3351
3448
  this.portManager = new PortManager();
3352
3449
  }
@@ -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:
@@ -59,6 +59,7 @@ export declare class HostFunctions {
59
59
  statusCode: number;
60
60
  statusText: string;
61
61
  body: Uint8Array;
62
+ headers: HeaderTuples;
62
63
  } | null;
63
64
  resetLocalResponse(): void;
64
65
  getRequestHeaders(): HeaderRecord;
@@ -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(request: HttpRequest): Promise<HttpResponse>;
71
+ execute(_request: HttpRequest): Promise<HttpResponse>;
72
72
  /**
73
73
  * Clean up resources (no-op for Proxy-WASM, but required by interface)
74
74
  */