two-stroke 7.4.0 → 7.6.0

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.
@@ -16,7 +16,7 @@ jobs:
16
16
  steps:
17
17
  - name: Dependabot metadata
18
18
  id: metadata
19
- uses: dependabot/fetch-metadata@v3.0.0
19
+ uses: dependabot/fetch-metadata@v3.1.0
20
20
  with:
21
21
  github-token: "${{ secrets.GITHUB_TOKEN }}"
22
22
  - name: Approve a PR
package/bin/api-types.mjs CHANGED
@@ -7,9 +7,7 @@ import ts from "typescript";
7
7
 
8
8
  const services = process.argv[2].split(",");
9
9
 
10
- const UUID = ts.factory.createTypeReferenceNode(
11
- ts.factory.createIdentifier("UUID"),
12
- );
10
+ const UUID = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UUID"));
13
11
  const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
14
12
 
15
13
  await Promise.all(
@@ -17,9 +15,7 @@ await Promise.all(
17
15
  const output = await openapiTS(`${service}/doc`, {
18
16
  transform(schemaObject) {
19
17
  if ("format" in schemaObject && schemaObject.format === "uuid") {
20
- return schemaObject.nullable
21
- ? ts.factory.createUnionTypeNode([UUID, NULL])
22
- : UUID;
18
+ return schemaObject.nullable ? ts.factory.createUnionTypeNode([UUID, NULL]) : UUID;
23
19
  }
24
20
  },
25
21
  });
@@ -29,11 +25,7 @@ await Promise.all(
29
25
  "",
30
26
  ts.ScriptTarget.Latest,
31
27
  );
32
- const result = printer.printNode(
33
- ts.EmitHint.Unspecified,
34
- output[0],
35
- resultFile,
36
- );
28
+ const result = printer.printNode(ts.EmitHint.Unspecified, output[0], resultFile);
37
29
  fs.writeFileSync(
38
30
  `src/__definitions__/${service.replace("https://", "")}-definitions.ts`,
39
31
  await prettier.format(
package/bin/bulk.mjs CHANGED
@@ -6,7 +6,7 @@ import { cmd } from "../src/cmd.mjs";
6
6
  import mime from "mime";
7
7
 
8
8
  const entryOrRest = process.argv[2];
9
- const entry = process.argv[3];
9
+ const entry = process.argv.slice(3);
10
10
 
11
11
  const ents = await fs.promises.readdir("dist", {
12
12
  withFileTypes: true,
@@ -16,16 +16,14 @@ const files = await Promise.all(
16
16
  ents
17
17
  .filter((ent) => ent.isFile())
18
18
  .filter((ent) =>
19
- entryOrRest == "entry" ? ent.name === entry : ent.name !== entry,
19
+ entryOrRest == "entry" ? entry.includes(ent.name) : !entry.includes(ent.name),
20
20
  )
21
21
  .map((ent) => {
22
22
  const type = mime.getType(`${ent.parentPath}/${ent.name}`);
23
23
  const key = `${ent.parentPath.substring(4)}/${ent.name}`;
24
24
  return (async () => ({
25
- key: ent.name === entry ? `${process.env.DOMAIN}${key}` : key,
26
- value: (
27
- await fs.promises.readFile(`${ent.parentPath}/${ent.name}`)
28
- ).toString("base64"),
25
+ key: entry.includes(ent.name) ? `${process.env.DOMAIN}${key}` : key,
26
+ value: (await fs.promises.readFile(`${ent.parentPath}/${ent.name}`)).toString("base64"),
29
27
  base64: true,
30
28
  metadata: {
31
29
  "Content-Type":
@@ -34,15 +32,14 @@ const files = await Promise.all(
34
32
  : type === "text/javascript"
35
33
  ? "text/javascript;charset=utf-8"
36
34
  : type,
37
- "Cache-Control":
38
- ent.name === entry ? "nocache" : "max-age=31536000, immutable",
35
+ "Cache-Control": entry.includes(ent.name) ? "nocache" : "max-age=31536000, immutable",
39
36
  },
40
37
  }))();
41
38
  }),
42
39
  );
43
40
 
44
- fs.writeFileSync(`dist/bulk_${process.argv[2]}.json`, JSON.stringify(files));
41
+ fs.writeFileSync(`dist/bulk_${entryOrRest}.json`, JSON.stringify(files));
45
42
 
46
43
  cmd(
47
- `wrangler kv bulk put dist/bulk_${process.argv[2]}.json --remote --namespace-id ${process.env.NAMESPACE}`,
44
+ `wrangler kv bulk put dist/bulk_${entryOrRest}.json --remote --namespace-id ${process.env.NAMESPACE}`,
48
45
  );
package/bin/lint.mjs CHANGED
@@ -4,7 +4,7 @@ import fs from "fs";
4
4
  import { cmd } from "../src/cmd.mjs";
5
5
 
6
6
  if (fs.existsSync("wrangler.jsonc")) {
7
- cmd("wrangler types --strict-vars false --check")
7
+ cmd("wrangler types --strict-vars false --check");
8
8
  }
9
9
  cmd("eslint --cache --max-warnings=0");
10
10
  cmd("prettier --cache --check .");
package/bin/test.mjs CHANGED
@@ -25,11 +25,7 @@ if (fs.existsSync("wrangler.jsonc")) {
25
25
  if (request.status === 200) {
26
26
  const types = await openapiTS(await consumers.json(request.body));
27
27
  const printer = ts.createPrinter({});
28
- const resultFile = ts.createSourceFile(
29
- "test/api.d.ts",
30
- "",
31
- ts.ScriptTarget.Latest,
32
- );
28
+ const resultFile = ts.createSourceFile("test/api.d.ts", "", ts.ScriptTarget.Latest);
33
29
  const result = types
34
30
  .map((t) => printer.printNode(ts.EmitHint.Unspecified, t, resultFile))
35
31
  .join("\n\n");
@@ -40,8 +36,7 @@ if (fs.existsSync("wrangler.jsonc")) {
40
36
  }
41
37
  }
42
38
  cmd("vitest", [
43
- ...(!process.argv.slice(2).includes("-w") &&
44
- !process.argv.slice(2).includes("--watch")
39
+ ...(!process.argv.slice(2).includes("-w") && !process.argv.slice(2).includes("--watch")
45
40
  ? ["--run"]
46
41
  : []),
47
42
  ...process.argv.slice(2),
package/package.json CHANGED
@@ -10,10 +10,10 @@
10
10
  "type-check": "./bin/type-check.mjs"
11
11
  },
12
12
  "dependencies": {
13
- "@cloudflare/vitest-pool-workers": "^0.14.1",
13
+ "@cloudflare/vitest-pool-workers": "^0.15.2",
14
14
  "@sentry/cli": "^3.3.4",
15
15
  "@types/node": "^25.5.0",
16
- "@typescript-eslint/eslint-plugin": "^8.57.2",
16
+ "@typescript-eslint/eslint-plugin": "^8.59.1",
17
17
  "@typescript-eslint/parser": "^8.57.2",
18
18
  "@vitest/runner": "^4.1.2",
19
19
  "@vitest/snapshot": "^4.1.2",
@@ -29,7 +29,7 @@
29
29
  "openapi-typescript": "^7.13.0",
30
30
  "pbkdf-subtle": "^1.1.5",
31
31
  "toucan-js": "^4.1.1",
32
- "zod": "^4.3.6"
32
+ "zod": "^4.4.2"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@sentry/cli": ">=2",
@@ -59,7 +59,7 @@
59
59
  "url": "https://github.com/change-engine/two-stroke"
60
60
  },
61
61
  "type": "module",
62
- "version": "7.4.0",
62
+ "version": "7.6.0",
63
63
  "devDependencies": {
64
64
  "@types/eslint": "^9.6.1",
65
65
  "eslint": "^10.1.0",
package/src/fake.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { twoStroke } from ".";
2
2
 
3
- export default twoStroke("fake", "0.1")
3
+ export default twoStroke("fake", "0.1");
package/src/index.ts CHANGED
@@ -3,15 +3,14 @@ import { verify as pbkdfVerify } from "pbkdf-subtle";
3
3
  import { Toucan } from "toucan-js";
4
4
  import { type ZodSafeParseResult, z, ZodObject, ZodType } from "zod/v4";
5
5
  import { openAPI } from "./open-api";
6
- import { type Env, type Handler, type Route } from "./types";
6
+ import { type Handler, type Route } from "./types";
7
7
 
8
8
  // eslint-disable-next-line @typescript-eslint/require-await
9
9
  const noAuth = async () => null;
10
10
 
11
- const escapeRegex = (str: string) =>
12
- str.replace(/([.*+?^=!:$()|[\]\\])/g, "\\$&");
11
+ const escapeRegex = (str: string) => str.replace(/([.*+?^=!:$()|[\]\\])/g, "\\$&");
13
12
 
14
- export function twoStroke<T extends Env>(
13
+ export function twoStroke<T>(
15
14
  title: string,
16
15
  release: string,
17
16
  origin?: (o: string | null) => string,
@@ -35,11 +34,7 @@ export function twoStroke<T extends Env>(
35
34
  const crons: {
36
35
  [cron: string]: (c: { env: T; sentry: Toucan }) => Promise<void>;
37
36
  } = {};
38
- let _email: (c: {
39
- message: ForwardableEmailMessage;
40
- env: T;
41
- sentry: Toucan;
42
- }) => Promise<void>;
37
+ let _email: (c: { message: ForwardableEmailMessage; env: T; sentry: Toucan }) => Promise<void>;
43
38
  return {
44
39
  async fetch(
45
40
  req: Request,
@@ -85,9 +80,10 @@ export function twoStroke<T extends Env>(
85
80
  for (const route of routes) {
86
81
  if (req.method === route.method && route.matcher.test(pathname)) {
87
82
  const params = Object.fromEntries(
88
- Object.entries(pathname.match(route.matcher)?.groups ?? {}).map(
89
- ([k, v]) => [k, decodeURIComponent(v)],
90
- ),
83
+ Object.entries(pathname.match(route.matcher)?.groups ?? {}).map(([k, v]) => [
84
+ k,
85
+ decodeURIComponent(v),
86
+ ]),
91
87
  );
92
88
  let claims;
93
89
  try {
@@ -108,8 +104,7 @@ export function twoStroke<T extends Env>(
108
104
  let rawBody;
109
105
  try {
110
106
  rawBody = route.input
111
- ? req.headers.get("Content-Type") ===
112
- "application/x-www-form-urlencoded"
107
+ ? req.headers.get("Content-Type") === "application/x-www-form-urlencoded"
113
108
  ? Object.fromEntries(new URLSearchParams(await req.text()))
114
109
  : await req.json()
115
110
  : undefined;
@@ -132,10 +127,10 @@ export function twoStroke<T extends Env>(
132
127
  const body = route.input
133
128
  ? route.input.safeParse(rawBody)
134
129
  : {
135
- success: true,
136
- data: undefined,
137
- error: undefined,
138
- };
130
+ success: true,
131
+ data: undefined,
132
+ error: undefined,
133
+ };
139
134
  if (body.success)
140
135
  response = await route.handler({
141
136
  req,
@@ -199,19 +194,16 @@ export function twoStroke<T extends Env>(
199
194
  Object.entries({
200
195
  ...defaultHeaders,
201
196
  "Content-Type": "application/json",
202
- "Strict-Transport-Security":
203
- "max-age=15552000; includeSubDomains",
197
+ "Strict-Transport-Security": "max-age=15552000; includeSubDomains",
204
198
  "X-Content-Type-Options": "nosniff",
205
199
  "Content-Security-Policy": "default-src 'self'",
206
200
  }).forEach(([k, v]) => {
207
- if (!responseWithHeaders.headers.has(k))
208
- responseWithHeaders.headers.set(k, v);
201
+ if (!responseWithHeaders.headers.has(k)) responseWithHeaders.headers.set(k, v);
209
202
  });
210
203
 
211
204
  return new Response(
212
205
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
213
- responseWithHeaders.headers.get("Content-Type") ===
214
- "application/json"
206
+ responseWithHeaders.headers.get("Content-Type") === "application/json"
215
207
  ? JSON.stringify(response.body)
216
208
  : response.body,
217
209
  responseWithHeaders,
@@ -300,53 +292,38 @@ export function twoStroke<T extends Env>(
300
292
  }
301
293
  },
302
294
  emailHandler(
303
- handler: (c: {
304
- env: T;
305
- message: ForwardableEmailMessage;
306
- sentry: Toucan;
307
- }) => Promise<void>,
295
+ handler: (c: { env: T; message: ForwardableEmailMessage; sentry: Toucan }) => Promise<void>,
308
296
  ) {
309
297
  _email = handler;
310
298
  },
311
- schedule(
312
- cron: string,
313
- handler: (c: { env: T; sentry: Toucan }) => Promise<void>,
314
- ) {
299
+ schedule(cron: string, handler: (c: { env: T; sentry: Toucan }) => Promise<void>) {
315
300
  crons[cron] = handler;
316
301
  },
317
302
  noAuth,
318
303
  pbkdf:
319
304
  (k: keyof T, customHeaderName: string = "Authorization") =>
320
- async ({ req, env }: { req: Request; env: T }) => {
321
- const [scheme, token] = (
322
- req.headers.get(customHeaderName) ?? " "
323
- ).split(" ");
324
- if (
325
- (scheme === "token" || scheme === "Bearer") &&
326
- (await pbkdfVerify(env[k] as string, token ?? ""))
327
- )
328
- return;
329
- throw Error("Invalid");
330
- },
305
+ async ({ req, env }: { req: Request; env: T }) => {
306
+ const [scheme, token] = (req.headers.get(customHeaderName) ?? " ").split(" ");
307
+ if (
308
+ (scheme === "token" || scheme === "Bearer") &&
309
+ (await pbkdfVerify(env[k] as string, token ?? ""))
310
+ )
311
+ return;
312
+ throw Error("Invalid");
313
+ },
331
314
  jwt:
332
315
  <J>(k: keyof T, ak: keyof T) =>
333
- async ({ req, env }: { req: Request; env: T }) => {
334
- const [scheme, token] = (req.headers.get("Authorization") ?? " ").split(
335
- " ",
336
- );
337
- if (scheme === "Bearer") {
338
- const claims = await jwkVerify<J>(
339
- token ?? "",
340
- env[k] as string,
341
- env[ak] as string,
342
- );
343
- if (!claims) {
344
- throw Error("Invalid");
345
- }
346
- return claims;
316
+ async ({ req, env }: { req: Request; env: T }) => {
317
+ const [scheme, token] = (req.headers.get("Authorization") ?? " ").split(" ");
318
+ if (scheme === "Bearer") {
319
+ const claims = await jwkVerify<J>(token ?? "", env[k] as string, env[ak] as string);
320
+ if (!claims) {
321
+ throw Error("Invalid");
347
322
  }
348
- throw Error("Invalid");
349
- },
323
+ return claims;
324
+ }
325
+ throw Error("Invalid");
326
+ },
350
327
  queueHandler<I extends ZodType>(
351
328
  input: I,
352
329
  handler: (c: {
@@ -387,9 +364,7 @@ export function twoStroke<T extends Env>(
387
364
  auth,
388
365
  method: "PUT",
389
366
  path,
390
- matcher: new RegExp(
391
- `^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`,
392
- ),
367
+ matcher: new RegExp(`^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`),
393
368
  input,
394
369
  output,
395
370
  handler,
@@ -415,9 +390,7 @@ export function twoStroke<T extends Env>(
415
390
  auth,
416
391
  method: "POST",
417
392
  path,
418
- matcher: new RegExp(
419
- `^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`,
420
- ),
393
+ matcher: new RegExp(`^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`),
421
394
  input,
422
395
  output,
423
396
  handler,
@@ -442,9 +415,7 @@ export function twoStroke<T extends Env>(
442
415
  auth,
443
416
  method: "GET",
444
417
  path,
445
- matcher: new RegExp(
446
- `^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`,
447
- ),
418
+ matcher: new RegExp(`^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`),
448
419
  output,
449
420
  handler,
450
421
  params,
@@ -467,9 +438,7 @@ export function twoStroke<T extends Env>(
467
438
  auth,
468
439
  method: "DELETE",
469
440
  path,
470
- matcher: new RegExp(
471
- `^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`,
472
- ),
441
+ matcher: new RegExp(`^${escapeRegex(path).replaceAll(/\/{([^}]*)}/g, "/(?<$1>[^\\/]*)")}$`),
473
442
  output,
474
443
  handler,
475
444
  params,
@@ -483,11 +452,7 @@ type AddToQueueConfig = QueueSendOptions & {
483
452
  backoffFactor?: number;
484
453
  };
485
454
 
486
- export async function addToQueue<T>(
487
- queue: Queue<T>,
488
- message: T,
489
- config: AddToQueueConfig = {},
490
- ) {
455
+ export async function addToQueue<T>(queue: Queue<T>, message: T, config: AddToQueueConfig = {}) {
491
456
  const { retries, backoffFactor, ...options } = config;
492
457
 
493
458
  for (let i = 0; i < (retries ?? 5); i++) {
package/src/once.ts CHANGED
@@ -1,128 +1,126 @@
1
1
  import type { z, ZodSafeParseResult, ZodType } from "zod/v4";
2
2
 
3
3
  export const once = async <T extends { retry_count: number }>(
4
- key: string,
5
- bucket: R2Bucket,
6
- queue: Queue<T>,
7
- backoffExponent: number,
8
- numRetries: number,
9
- ack: (cb: () => Promise<void>) => Promise<void>,
10
- cb: (retry: (m: T) => Promise<void>) => Promise<void>,
4
+ key: string,
5
+ bucket: R2Bucket,
6
+ queue: Queue<T>,
7
+ backoffExponent: number,
8
+ numRetries: number,
9
+ ack: (cb: () => Promise<void>) => Promise<void>,
10
+ cb: (retry: (m: T) => Promise<void>) => Promise<void>,
11
11
  ) => {
12
- const retry = async (m: T) => {
13
- const retry_count = m.retry_count + 1;
14
- if (retry_count > numRetries) throw new Error("Too many retries");
15
- await queue.send(
16
- { ...m, retry_count },
17
- { delaySeconds: Math.min(Math.pow(retry_count, backoffExponent), 900) },
18
- );
19
- };
20
- if (!(await isDuplicateMessage(key, bucket)))
21
- await ack(async () => {
22
- await cb(retry);
23
- });
12
+ const retry = async (m: T) => {
13
+ const retry_count = m.retry_count + 1;
14
+ if (retry_count > numRetries) throw new Error("Too many retries");
15
+ await queue.send(
16
+ { ...m, retry_count },
17
+ { delaySeconds: Math.min(Math.pow(retry_count, backoffExponent), 900) },
18
+ );
19
+ };
20
+ if (!(await isDuplicateMessage(key, bucket)))
21
+ await ack(async () => {
22
+ await cb(retry);
23
+ });
24
24
  };
25
25
 
26
-
27
26
  export const retryHandler =
28
- <T, M extends ZodType>(
29
- maxRetries: number,
30
- handler: (
31
- body: z.output<M>,
32
- env: T,
33
- isFinalAttempt: boolean,
34
- ack: (cb: () => Promise<void>) => Promise<void>,
35
- ) => Promise<void>,
36
- ) =>
37
- async ({
38
- body,
39
- waitUntil,
40
- env,
41
- }: {
42
- body: z.infer<M> & { retry: number };
43
- waitUntil: (p: Promise<void>) => void;
44
- env: T;
45
- }) => {
46
- const isFinalAttempt = body.retry >= maxRetries;
47
- await handler(
48
- body,
49
- env,
50
- isFinalAttempt,
51
- // eslint-disable-next-line @typescript-eslint/require-await
52
- async (cb) => {
53
- waitUntil(cb());
54
- },
55
- );
27
+ <T, M extends ZodType>(
28
+ maxRetries: number,
29
+ handler: (
30
+ body: z.output<M>,
31
+ env: T,
32
+ isFinalAttempt: boolean,
33
+ ack: (cb: () => Promise<void>) => Promise<void>,
34
+ ) => Promise<void>,
35
+ ) =>
36
+ async ({
37
+ body,
38
+ waitUntil,
39
+ env,
40
+ }: {
41
+ body: z.infer<M> & { retry: number };
42
+ waitUntil: (p: Promise<void>) => void;
43
+ env: T;
44
+ }) => {
45
+ const isFinalAttempt = body.retry >= maxRetries;
46
+ await handler(
47
+ body,
48
+ env,
49
+ isFinalAttempt,
50
+ // eslint-disable-next-line @typescript-eslint/require-await
51
+ async (cb) => {
52
+ waitUntil(cb());
53
+ },
54
+ );
56
55
 
57
- return { body: { ok: true } };
58
- };
56
+ return { body: { ok: true } };
57
+ };
59
58
 
60
59
  export const retryQueue =
61
- <T, M extends ZodType>(
62
- maxRetries: number,
63
- handler: (
64
- body: z.output<M>,
65
- env: T,
66
- isFinalAttempt: boolean,
67
- ack: (cb: () => Promise<void>) => Promise<void>,
68
- ) => Promise<void>,
69
- ) =>
70
- async ({
71
- batch,
72
- parsedBatch,
73
- env,
74
- }: {
75
- batch: MessageBatch<z.input<M>>;
76
- parsedBatch: ZodSafeParseResult<z.output<M>>[];
77
- env: T;
78
- }) => {
79
- const message = batch.messages[0];
80
- if (!message || batch.messages.length !== 1) {
81
- console.warn({ batch });
82
- throw new Error(
83
- `Queue must only process one message at a time, got ${batch.messages.length}`,
84
- );
85
- }
86
- if (!parsedBatch[0] || !parsedBatch[0].success) {
87
- console.error({ batch });
88
- throw new Error(`Queue message invalid`);
89
- }
90
-
91
- const isFinalAttempt = message.attempts >= maxRetries;
60
+ <T, M extends ZodType>(
61
+ maxRetries: number,
62
+ handler: (
63
+ body: z.output<M>,
64
+ env: T,
65
+ isFinalAttempt: boolean,
66
+ ack: (cb: () => Promise<void>) => Promise<void>,
67
+ ) => Promise<void>,
68
+ ) =>
69
+ async ({
70
+ batch,
71
+ parsedBatch,
72
+ env,
73
+ }: {
74
+ batch: MessageBatch<z.input<M>>;
75
+ parsedBatch: ZodSafeParseResult<z.output<M>>[];
76
+ env: T;
77
+ }) => {
78
+ const message = batch.messages[0];
79
+ if (!message || batch.messages.length !== 1) {
80
+ console.warn({ batch });
81
+ throw new Error(
82
+ `Queue must only process one message at a time, got ${batch.messages.length}`,
83
+ );
84
+ }
85
+ if (!parsedBatch[0] || !parsedBatch[0].success) {
86
+ console.error({ batch });
87
+ throw new Error(`Queue message invalid`);
88
+ }
92
89
 
93
- try {
94
- await handler(parsedBatch[0].data, env, isFinalAttempt, async (cb) => {
95
- message.ack();
96
- await cb();
97
- });
98
- } catch (err) {
99
- if (!isFinalAttempt) message.retry();
100
- throw err;
101
- }
102
- };
90
+ const isFinalAttempt = message.attempts >= maxRetries;
103
91
 
92
+ try {
93
+ await handler(parsedBatch[0].data, env, isFinalAttempt, async (cb) => {
94
+ message.ack();
95
+ await cb();
96
+ });
97
+ } catch (err) {
98
+ if (!isFinalAttempt) message.retry();
99
+ throw err;
100
+ }
101
+ };
104
102
 
105
103
  const isDuplicateMessage = async (key: string, bucket: R2Bucket) => {
106
- // Work around for CloudFlare R2 error:
107
- // put: We encountered an internal error. Please try again. (10001)
108
- for (let i = 0; i < 5; i++) {
109
- try {
110
- // Will throw if that error occurs
111
- const putResult = await bucket.put(`lock-${key}`, "", {
112
- onlyIf: new Headers({
113
- "If-Unmodified-Since": "Wed, 21 Oct 2015 07:28:00 GMT",
114
- }),
115
- });
116
- // if the returned result is null then the object already existed
117
- const isDupe = putResult === null;
118
- if (isDupe) console.log("Duplicate message", { key });
119
- return isDupe;
120
- } catch (err) {
121
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
122
- console.warn(`Error putting ${key} to R2: ${err}`);
123
- console.warn(`Retrying in ${i} seconds`);
124
- await new Promise((resolve) => setTimeout(resolve, i * 1000));
125
- }
104
+ // Work around for CloudFlare R2 error:
105
+ // put: We encountered an internal error. Please try again. (10001)
106
+ for (let i = 0; i < 5; i++) {
107
+ try {
108
+ // Will throw if that error occurs
109
+ const putResult = await bucket.put(`lock-${key}`, "", {
110
+ onlyIf: new Headers({
111
+ "If-Unmodified-Since": "Wed, 21 Oct 2015 07:28:00 GMT",
112
+ }),
113
+ });
114
+ // if the returned result is null then the object already existed
115
+ const isDupe = putResult === null;
116
+ if (isDupe) console.log("Duplicate message", { key });
117
+ return isDupe;
118
+ } catch (err) {
119
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
120
+ console.warn(`Error putting ${key} to R2: ${err}`);
121
+ console.warn(`Retrying in ${i} seconds`);
122
+ await new Promise((resolve) => setTimeout(resolve, i * 1000));
126
123
  }
127
- throw new Error(`Failed to put ${key} to R2`);
128
- };
124
+ }
125
+ throw new Error(`Failed to put ${key} to R2`);
126
+ };