@vercel/functions 3.1.0 → 3.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @vercel/functions
2
2
 
3
+ ## 3.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Add configurable timeout to RuntimeCache with sensible default to fail faster on gateway timeouts ([#14036](https://github.com/vercel/vercel/pull/14036))
8
+
9
+ The RuntimeCache client now includes a configurable timeout (default 500ms) for all fetch requests to prevent indefinite hangs on 502/504 gateway timeouts.
10
+ The timeout can be configured at build time via the `RUNTIME_CACHE_TIMEOUT` environment variable. When a timeout occurs, the request is aborted and the
11
+ error is logged via the onError callback.
12
+
13
+ ## 3.1.1
14
+
15
+ ### Patch Changes
16
+
17
+ - Stronger detection of Vercel Runtime to reduce warnings ([#13776](https://github.com/vercel/vercel/pull/13776))
18
+
19
+ - Updated dependencies [[`da6ca6d80915221b7f60cd711e4fada41a828e4c`](https://github.com/vercel/vercel/commit/da6ca6d80915221b7f60cd711e4fada41a828e4c), [`da6ca6d80915221b7f60cd711e4fada41a828e4c`](https://github.com/vercel/vercel/commit/da6ca6d80915221b7f60cd711e4fada41a828e4c), [`da6ca6d80915221b7f60cd711e4fada41a828e4c`](https://github.com/vercel/vercel/commit/da6ca6d80915221b7f60cd711e4fada41a828e4c)]:
20
+ - @vercel/oidc@3.0.1
21
+
3
22
  ## 3.1.0
4
23
 
5
24
  ### Minor Changes
@@ -2,10 +2,12 @@ export declare class BuildCache {
2
2
  private readonly endpoint;
3
3
  private readonly headers;
4
4
  private readonly onError?;
5
- constructor({ endpoint, headers, onError, }: {
5
+ private readonly timeout;
6
+ constructor({ endpoint, headers, onError, timeout, }: {
6
7
  endpoint: string;
7
8
  headers: Record<string, string>;
8
9
  onError?: (error: Error) => void;
10
+ timeout?: number;
9
11
  });
10
12
  get: (key: string) => Promise<unknown>;
11
13
  set: (key: string, value: unknown, options?: {
@@ -26,15 +26,20 @@ class BuildCache {
26
26
  constructor({
27
27
  endpoint,
28
28
  headers,
29
- onError
29
+ onError,
30
+ timeout = 500
30
31
  }) {
31
32
  this.get = async (key) => {
33
+ const controller = new AbortController();
34
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
32
35
  try {
33
36
  const res = await fetch(`${this.endpoint}${key}`, {
34
37
  headers: this.headers,
35
- method: "GET"
38
+ method: "GET",
39
+ signal: controller.signal
36
40
  });
37
41
  if (res.status === 404) {
42
+ clearTimeout(timeoutId);
38
43
  return null;
39
44
  }
40
45
  if (res.status === 200) {
@@ -43,18 +48,33 @@ class BuildCache {
43
48
  );
44
49
  if (cacheState !== import_index.PkgCacheState.Fresh) {
45
50
  res.body?.cancel?.();
51
+ clearTimeout(timeoutId);
46
52
  return null;
47
53
  }
48
- return await res.json();
54
+ const result = await res.json();
55
+ clearTimeout(timeoutId);
56
+ return result;
49
57
  } else {
58
+ clearTimeout(timeoutId);
50
59
  throw new Error(`Failed to get cache: ${res.statusText}`);
51
60
  }
52
61
  } catch (error) {
53
- this.onError?.(error);
62
+ clearTimeout(timeoutId);
63
+ if (error.name === "AbortError") {
64
+ const timeoutError = new Error(
65
+ `Cache request timed out after ${this.timeout}ms`
66
+ );
67
+ timeoutError.stack = error.stack;
68
+ this.onError?.(timeoutError);
69
+ } else {
70
+ this.onError?.(error);
71
+ }
54
72
  return null;
55
73
  }
56
74
  };
57
75
  this.set = async (key, value, options) => {
76
+ const controller = new AbortController();
77
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
58
78
  try {
59
79
  const optionalHeaders = {};
60
80
  if (options?.ttl) {
@@ -72,47 +92,85 @@ class BuildCache {
72
92
  ...this.headers,
73
93
  ...optionalHeaders
74
94
  },
75
- body: JSON.stringify(value)
95
+ body: JSON.stringify(value),
96
+ signal: controller.signal
76
97
  });
98
+ clearTimeout(timeoutId);
77
99
  if (res.status !== 200) {
78
100
  throw new Error(`Failed to set cache: ${res.status} ${res.statusText}`);
79
101
  }
80
102
  } catch (error) {
81
- this.onError?.(error);
103
+ clearTimeout(timeoutId);
104
+ if (error.name === "AbortError") {
105
+ const timeoutError = new Error(
106
+ `Cache request timed out after ${this.timeout}ms`
107
+ );
108
+ timeoutError.stack = error.stack;
109
+ this.onError?.(timeoutError);
110
+ } else {
111
+ this.onError?.(error);
112
+ }
82
113
  }
83
114
  };
84
115
  this.delete = async (key) => {
116
+ const controller = new AbortController();
117
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
85
118
  try {
86
119
  const res = await fetch(`${this.endpoint}${key}`, {
87
120
  method: "DELETE",
88
- headers: this.headers
121
+ headers: this.headers,
122
+ signal: controller.signal
89
123
  });
124
+ clearTimeout(timeoutId);
90
125
  if (res.status !== 200) {
91
126
  throw new Error(`Failed to delete cache: ${res.statusText}`);
92
127
  }
93
128
  } catch (error) {
94
- this.onError?.(error);
129
+ clearTimeout(timeoutId);
130
+ if (error.name === "AbortError") {
131
+ const timeoutError = new Error(
132
+ `Cache request timed out after ${this.timeout}ms`
133
+ );
134
+ timeoutError.stack = error.stack;
135
+ this.onError?.(timeoutError);
136
+ } else {
137
+ this.onError?.(error);
138
+ }
95
139
  }
96
140
  };
97
141
  this.expireTag = async (tag) => {
142
+ const controller = new AbortController();
143
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
98
144
  try {
99
145
  if (Array.isArray(tag)) {
100
146
  tag = tag.join(",");
101
147
  }
102
148
  const res = await fetch(`${this.endpoint}revalidate?tags=${tag}`, {
103
149
  method: "POST",
104
- headers: this.headers
150
+ headers: this.headers,
151
+ signal: controller.signal
105
152
  });
153
+ clearTimeout(timeoutId);
106
154
  if (res.status !== 200) {
107
155
  throw new Error(`Failed to revalidate tag: ${res.statusText}`);
108
156
  }
109
157
  } catch (error) {
110
- this.onError?.(error);
158
+ clearTimeout(timeoutId);
159
+ if (error.name === "AbortError") {
160
+ const timeoutError = new Error(
161
+ `Cache request timed out after ${this.timeout}ms`
162
+ );
163
+ timeoutError.stack = error.stack;
164
+ this.onError?.(timeoutError);
165
+ } else {
166
+ this.onError?.(error);
167
+ }
111
168
  }
112
169
  };
113
170
  this.endpoint = endpoint;
114
171
  this.headers = headers;
115
172
  this.onError = onError;
173
+ this.timeout = timeout;
116
174
  }
117
175
  }
118
176
  // Annotate the CommonJS export names for ESM import in node:
package/cache/index.js CHANGED
@@ -114,10 +114,22 @@ function getCacheImplementation(debug) {
114
114
  console.error("Failed to parse RUNTIME_CACHE_HEADERS:", e);
115
115
  return inMemoryCacheInstance;
116
116
  }
117
+ let timeout = 500;
118
+ if (process.env.RUNTIME_CACHE_TIMEOUT) {
119
+ const parsed = parseInt(process.env.RUNTIME_CACHE_TIMEOUT, 10);
120
+ if (!isNaN(parsed) && parsed > 0) {
121
+ timeout = parsed;
122
+ } else {
123
+ console.warn(
124
+ `Invalid RUNTIME_CACHE_TIMEOUT value: "${process.env.RUNTIME_CACHE_TIMEOUT}". Using default: ${timeout}ms`
125
+ );
126
+ }
127
+ }
117
128
  buildCacheInstance = new import_build_client.BuildCache({
118
129
  endpoint: RUNTIME_CACHE_ENDPOINT,
119
130
  headers: parsedHeaders,
120
- onError: (error) => console.error(error)
131
+ onError: (error) => console.error(error),
132
+ timeout
121
133
  });
122
134
  }
123
135
  return buildCacheInstance;
@@ -61,7 +61,8 @@ let idleTimeoutResolve = () => {
61
61
  const bootTime = Date.now();
62
62
  const maximumDuration = 15 * 60 * 1e3 - 1e3;
63
63
  function waitUntilIdleTimeout(dbPool) {
64
- if (!process.env.VERCEL_URL) {
64
+ if (!process.env.VERCEL_URL || // This is not set during builds where we don't need to wait for idle connections using the mechanism
65
+ !process.env.VERCEL_REGION) {
65
66
  return;
66
67
  }
67
68
  if (idleTimeout) {
@@ -56,7 +56,7 @@ attachDatabasePool(pgPool);
56
56
 
57
57
  #### Defined in
58
58
 
59
- [packages/functions/src/db-connections/index.ts:221](https://github.com/vercel/vercel/blob/main/packages/functions/src/db-connections/index.ts#L221)
59
+ [packages/functions/src/db-connections/index.ts:225](https://github.com/vercel/vercel/blob/main/packages/functions/src/db-connections/index.ts#L225)
60
60
 
61
61
  ---
62
62
 
@@ -101,7 +101,7 @@ Use attachDatabasePool instead.
101
101
 
102
102
  #### Defined in
103
103
 
104
- [packages/functions/src/db-connections/index.ts:221](https://github.com/vercel/vercel/blob/main/packages/functions/src/db-connections/index.ts#L221)
104
+ [packages/functions/src/db-connections/index.ts:225](https://github.com/vercel/vercel/blob/main/packages/functions/src/db-connections/index.ts#L225)
105
105
 
106
106
  ---
107
107
 
package/package.json CHANGED
@@ -30,7 +30,7 @@
30
30
  "require": "./middleware.js"
31
31
  }
32
32
  },
33
- "version": "3.1.0",
33
+ "version": "3.1.2",
34
34
  "repository": {
35
35
  "directory": "packages/functions",
36
36
  "type": "git",
@@ -72,7 +72,7 @@
72
72
  "access": "public"
73
73
  },
74
74
  "dependencies": {
75
- "@vercel/oidc": "3.0.0"
75
+ "@vercel/oidc": "3.0.1"
76
76
  },
77
77
  "scripts": {
78
78
  "pretest": "pnpm run build:code",