Skip to content

Commit 26b27f5

Browse files
fix: allow strings for ISR expiration, parse input (#14691)
* fix: allow strings for ISR expiration, parse input * improvements
1 parent 28bc898 commit 26b27f5

File tree

5 files changed

+94
-5
lines changed

5 files changed

+94
-5
lines changed

.changeset/two-humans-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/adapter-vercel': minor
3+
---
4+
5+
feat: parse isr.expiration, allowing it to be a string

packages/adapter-vercel/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface ServerlessConfig {
3939
/**
4040
* Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. Setting the value to `false` means it will never expire.
4141
*/
42-
expiration: number | false;
42+
expiration: number | string | false;
4343
/**
4444
* Random token that can be provided in the URL to bypass the cached version of the asset, by requesting the asset
4545
* with a __prerender_bypass=<token> cookie.

packages/adapter-vercel/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import process from 'node:process';
55
import { fileURLToPath } from 'node:url';
66
import { nodeFileTrace } from '@vercel/nft';
77
import esbuild from 'esbuild';
8-
import { get_pathname, pattern_to_src } from './utils.js';
8+
import { get_pathname, parse_isr_expiration, pattern_to_src } from './utils.js';
99
import { VERSION } from '@sveltejs/kit';
1010

1111
/**
@@ -433,7 +433,11 @@ const plugin = function (defaults = {}) {
433433
fs.symlinkSync(`../${relative}`, `${base}/__data.json.func`);
434434

435435
const pathname = get_pathname(route);
436-
const json = JSON.stringify(isr, null, '\t');
436+
const json = JSON.stringify(
437+
{ ...isr, expiration: parse_isr_expiration(isr.expiration, route.id) },
438+
null,
439+
'\t'
440+
);
437441

438442
write(`${base}.prerender-config.json`, json);
439443
write(`${base}/__data.json.prerender-config.json`, json);

packages/adapter-vercel/test/utils.spec.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { assert, test } from 'vitest';
2-
import { get_pathname, pattern_to_src } from '../utils.js';
1+
import { assert, test, describe } from 'vitest';
2+
import { get_pathname, parse_isr_expiration, pattern_to_src } from '../utils.js';
33

44
// workaround so that TypeScript doesn't follow that import which makes it pick up that file and then error on missing import aliases
55
const { parse_route_id } = await import('../../kit/src/' + 'utils/routing.js');
@@ -130,3 +130,44 @@ test('pattern_to_src for route with rest parameter', () => {
130130
test('pattern_to_src for route with rest parameter in the middle', () => {
131131
run_pattern_to_src_test('/foo/[...bar]/baz', '^/foo(/[^]*)?/baz/?');
132132
});
133+
134+
describe('parse_isr_expiration', () => {
135+
test.each(
136+
/** @type {const} */ ([
137+
[1, 1],
138+
['1', 1],
139+
[false, false],
140+
['false', false]
141+
])
142+
)('works for valid inputs ($0)', (input, output) => {
143+
const result = parse_isr_expiration(input, '/isr');
144+
assert.equal(result, output);
145+
});
146+
147+
test('does not allow floats', () => {
148+
assert.throws(() => parse_isr_expiration(1.5, '/isr'), /should be an integer, in \/isr/);
149+
});
150+
151+
test('does not allow `true`', () => {
152+
const val = /** @type {false} */ (true);
153+
assert.throws(() => parse_isr_expiration(val, '/isr'), /should be an integer, in \/isr/);
154+
});
155+
156+
test('does not allow negative numbers', () => {
157+
assert.throws(() => parse_isr_expiration(-1, '/isr'), /should be non-negative, in \/isr/);
158+
});
159+
160+
test('does not allow strings that do not parse to valid numbers', () => {
161+
assert.throws(
162+
() => parse_isr_expiration('foo', '/isr'),
163+
/value was a string but could not be parsed as an integer, in \/isr/
164+
);
165+
});
166+
167+
test('does not allow strings that parse to floats', () => {
168+
assert.throws(
169+
() => parse_isr_expiration('1.1', '/isr'),
170+
/value was a string but could not be parsed as an integer, in \/isr/
171+
);
172+
});
173+
});

packages/adapter-vercel/utils.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,42 @@ export function pattern_to_src(pattern) {
6767

6868
return src;
6969
}
70+
71+
const integer = /^\d+$/;
72+
73+
/**
74+
* @param {false | string | number} value
75+
* @param {string} route_id
76+
* @returns {number | false}
77+
*/
78+
export function parse_isr_expiration(value, route_id) {
79+
if (value === false || value === 'false') return false; // 1 year
80+
81+
/** @param {string} desc */
82+
const err = (desc) => {
83+
throw new Error(
84+
`Invalid isr.expiration value: ${JSON.stringify(value)} (${desc}, in ${route_id})`
85+
);
86+
};
87+
88+
let parsed;
89+
if (typeof value === 'string') {
90+
if (!integer.test(value)) {
91+
err('value was a string but could not be parsed as an integer');
92+
}
93+
parsed = Number.parseInt(value, 10);
94+
} else {
95+
if (!Number.isInteger(value)) {
96+
err('should be an integer');
97+
}
98+
parsed = value;
99+
}
100+
101+
if (Number.isNaN(parsed)) {
102+
err('should be a number');
103+
}
104+
if (parsed < 0) {
105+
err('should be non-negative');
106+
}
107+
return parsed;
108+
}

0 commit comments

Comments
 (0)