@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 +19 -0
- package/cache/build-client.d.ts +3 -1
- package/cache/build-client.js +68 -10
- package/cache/index.js +13 -1
- package/db-connections/index.js +2 -1
- package/docs/modules/index.md +2 -2
- package/package.json +2 -2
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
|
package/cache/build-client.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ export declare class BuildCache {
|
|
|
2
2
|
private readonly endpoint;
|
|
3
3
|
private readonly headers;
|
|
4
4
|
private readonly onError?;
|
|
5
|
-
|
|
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?: {
|
package/cache/build-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/db-connections/index.js
CHANGED
|
@@ -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) {
|
package/docs/modules/index.md
CHANGED
|
@@ -56,7 +56,7 @@ attachDatabasePool(pgPool);
|
|
|
56
56
|
|
|
57
57
|
#### Defined in
|
|
58
58
|
|
|
59
|
-
[packages/functions/src/db-connections/index.ts:
|
|
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:
|
|
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.
|
|
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.
|
|
75
|
+
"@vercel/oidc": "3.0.1"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"pretest": "pnpm run build:code",
|