two-stroke 7.3.0 → 7.5.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.
- package/bin/bulk.mjs +6 -6
- package/bin/type-check.mjs +16 -1
- package/package.json +2 -2
- package/src/once.ts +128 -0
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
|
|
9
|
+
const entry = process.argv.slice(3);
|
|
10
10
|
|
|
11
11
|
const ents = await fs.promises.readdir("dist", {
|
|
12
12
|
withFileTypes: true,
|
|
@@ -16,13 +16,13 @@ const files = await Promise.all(
|
|
|
16
16
|
ents
|
|
17
17
|
.filter((ent) => ent.isFile())
|
|
18
18
|
.filter((ent) =>
|
|
19
|
-
entryOrRest == "entry" ? ent.name
|
|
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
|
|
25
|
+
key: entry.includes(ent.name) ? `${process.env.DOMAIN}${key}` : key,
|
|
26
26
|
value: (
|
|
27
27
|
await fs.promises.readFile(`${ent.parentPath}/${ent.name}`)
|
|
28
28
|
).toString("base64"),
|
|
@@ -35,14 +35,14 @@ const files = await Promise.all(
|
|
|
35
35
|
? "text/javascript;charset=utf-8"
|
|
36
36
|
: type,
|
|
37
37
|
"Cache-Control":
|
|
38
|
-
ent.name
|
|
38
|
+
entry.includes(ent.name) ? "nocache" : "max-age=31536000, immutable",
|
|
39
39
|
},
|
|
40
40
|
}))();
|
|
41
41
|
}),
|
|
42
42
|
);
|
|
43
43
|
|
|
44
|
-
fs.writeFileSync(`dist/bulk_${
|
|
44
|
+
fs.writeFileSync(`dist/bulk_${entryOrRest}.json`, JSON.stringify(files));
|
|
45
45
|
|
|
46
46
|
cmd(
|
|
47
|
-
`wrangler kv bulk put dist/bulk_${
|
|
47
|
+
`wrangler kv bulk put dist/bulk_${entryOrRest}.json --remote --namespace-id ${process.env.NAMESPACE}`,
|
|
48
48
|
);
|
package/bin/type-check.mjs
CHANGED
|
@@ -4,7 +4,22 @@ import fs from "fs";
|
|
|
4
4
|
import { cmd } from "../src/cmd.mjs";
|
|
5
5
|
|
|
6
6
|
if (fs.existsSync("wrangler.jsonc")) {
|
|
7
|
-
|
|
7
|
+
const devVarsFiles = fs.globSync("**/.dev.vars");
|
|
8
|
+
const backed = [];
|
|
9
|
+
|
|
10
|
+
for (const file of devVarsFiles) {
|
|
11
|
+
const tmp = file + ".bak.bak";
|
|
12
|
+
fs.renameSync(file, tmp);
|
|
13
|
+
backed.push({ original: file, tmp });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
cmd("wrangler types --strict-vars false");
|
|
18
|
+
} finally {
|
|
19
|
+
for (const { original, tmp } of backed) {
|
|
20
|
+
fs.renameSync(tmp, original);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
8
23
|
}
|
|
9
24
|
cmd("wrangler deploy --env= --dry-run --outdir=dist");
|
|
10
25
|
cmd("tsc --noEmit");
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type-check": "./bin/type-check.mjs"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@cloudflare/vitest-pool-workers": "^0.
|
|
13
|
+
"@cloudflare/vitest-pool-workers": "^0.14.1",
|
|
14
14
|
"@sentry/cli": "^3.3.4",
|
|
15
15
|
"@types/node": "^25.5.0",
|
|
16
16
|
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"url": "https://github.com/change-engine/two-stroke"
|
|
60
60
|
},
|
|
61
61
|
"type": "module",
|
|
62
|
-
"version": "7.
|
|
62
|
+
"version": "7.5.0",
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/eslint": "^9.6.1",
|
|
65
65
|
"eslint": "^10.1.0",
|
package/src/once.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { z, ZodSafeParseResult, ZodType } from "zod/v4";
|
|
2
|
+
|
|
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>,
|
|
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
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
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
|
+
);
|
|
56
|
+
|
|
57
|
+
return { body: { ok: true } };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
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;
|
|
92
|
+
|
|
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
|
+
};
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
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
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`Failed to put ${key} to R2`);
|
|
128
|
+
};
|