two-stroke 7.10.1 → 8.0.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/.github/workflows/ci.yml +7 -1
- package/.knip.json +4 -0
- package/bin/api-types.mjs +10 -6
- package/bin/bulk.mjs +5 -7
- package/bin/deploy.mjs +1 -1
- package/bin/format.mjs +2 -2
- package/bin/lint.mjs +2 -2
- package/bin/test.mjs +5 -5
- package/oxfmt.config.ts +15 -0
- package/oxlint.config.ts +90 -0
- package/package.json +28 -35
- package/src/cmd.mjs +0 -1
- package/src/index.ts +68 -84
- package/src/once.ts +15 -19
- package/src/open-api.ts +4 -2
- package/src/test.ts +17 -73
- package/src/types.ts +5 -10
- package/src/util.ts +2 -0
- package/eslint.config.js +0 -3
package/.github/workflows/ci.yml
CHANGED
package/.knip.json
ADDED
package/bin/api-types.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import openapiTS from "openapi-typescript";
|
|
5
|
-
import
|
|
5
|
+
import { format } from "oxfmt";
|
|
6
6
|
import ts from "typescript";
|
|
7
7
|
|
|
8
8
|
const services = process.argv[2].split(",");
|
|
@@ -17,6 +17,7 @@ await Promise.all(
|
|
|
17
17
|
if ("format" in schemaObject && schemaObject.format === "uuid") {
|
|
18
18
|
return schemaObject.nullable ? ts.factory.createUnionTypeNode([UUID, NULL]) : UUID;
|
|
19
19
|
}
|
|
20
|
+
return null;
|
|
20
21
|
},
|
|
21
22
|
});
|
|
22
23
|
const printer = ts.createPrinter({});
|
|
@@ -28,14 +29,17 @@ await Promise.all(
|
|
|
28
29
|
const result = printer.printNode(ts.EmitHint.Unspecified, output[0], resultFile);
|
|
29
30
|
fs.writeFileSync(
|
|
30
31
|
`src/__definitions__/${service.replace("https://", "")}-definitions.ts`,
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
(
|
|
33
|
+
await format(
|
|
34
|
+
`src/__definitions__/${service.replace("https://", "")}-definitions.ts`,
|
|
35
|
+
`// Autogenerated by two-stroke
|
|
33
36
|
|
|
34
|
-
${fs.readFileSync("src/__definitions__/type-definitions.ts", "
|
|
37
|
+
${fs.readFileSync("src/__definitions__/type-definitions.ts", "utf8")}
|
|
35
38
|
|
|
36
39
|
${result}`,
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
{ parser: "typescript", printWidth: 100 },
|
|
41
|
+
)
|
|
42
|
+
).code,
|
|
39
43
|
);
|
|
40
44
|
}),
|
|
41
45
|
);
|
package/bin/bulk.mjs
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
import fs from "fs";
|
|
5
|
-
import { cmd } from "../src/cmd.mjs";
|
|
6
5
|
import mime from "mime";
|
|
6
|
+
import { cmd } from "../src/cmd.mjs";
|
|
7
7
|
|
|
8
8
|
const entryOrRest = process.argv[2];
|
|
9
|
-
const entry = process.argv.slice(3);
|
|
9
|
+
const entry = new Set(process.argv.slice(3));
|
|
10
10
|
|
|
11
11
|
const ents = await fs.promises.readdir("dist", {
|
|
12
12
|
withFileTypes: true,
|
|
@@ -15,14 +15,12 @@ const ents = await fs.promises.readdir("dist", {
|
|
|
15
15
|
const files = await Promise.all(
|
|
16
16
|
ents
|
|
17
17
|
.filter((ent) => ent.isFile())
|
|
18
|
-
.filter((ent) =>
|
|
19
|
-
entryOrRest == "entry" ? entry.includes(ent.name) : !entry.includes(ent.name),
|
|
20
|
-
)
|
|
18
|
+
.filter((ent) => (entryOrRest == "entry" ? entry.has(ent.name) : !entry.has(ent.name)))
|
|
21
19
|
.map((ent) => {
|
|
22
20
|
const type = mime.getType(`${ent.parentPath}/${ent.name}`);
|
|
23
21
|
const key = `${ent.parentPath.substring(4)}/${ent.name}`;
|
|
24
22
|
return (async () => ({
|
|
25
|
-
key: entry.
|
|
23
|
+
key: entry.has(ent.name) ? `${process.env.DOMAIN}${key}` : key,
|
|
26
24
|
value: (await fs.promises.readFile(`${ent.parentPath}/${ent.name}`)).toString("base64"),
|
|
27
25
|
base64: true,
|
|
28
26
|
metadata: {
|
|
@@ -32,7 +30,7 @@ const files = await Promise.all(
|
|
|
32
30
|
: type === "text/javascript"
|
|
33
31
|
? "text/javascript;charset=utf-8"
|
|
34
32
|
: type,
|
|
35
|
-
"Cache-Control": entry.
|
|
33
|
+
"Cache-Control": entry.has(ent.name) ? "nocache" : "max-age=31536000, immutable",
|
|
36
34
|
},
|
|
37
35
|
}))();
|
|
38
36
|
}),
|
package/bin/deploy.mjs
CHANGED
package/bin/format.mjs
CHANGED
package/bin/lint.mjs
CHANGED
|
@@ -6,5 +6,5 @@ import { cmd } from "../src/cmd.mjs";
|
|
|
6
6
|
if (fs.existsSync("wrangler.jsonc")) {
|
|
7
7
|
cmd("wrangler types --env-file /dev/null --strict-vars false --check");
|
|
8
8
|
}
|
|
9
|
-
cmd("
|
|
10
|
-
cmd("
|
|
9
|
+
cmd("oxfmt --check");
|
|
10
|
+
cmd("oxlint");
|
package/bin/test.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
import {
|
|
5
|
-
import consumers from "stream/consumers";
|
|
4
|
+
import { Miniflare } from "miniflare";
|
|
6
5
|
import openapiTS from "openapi-typescript";
|
|
7
|
-
import
|
|
6
|
+
import { format } from "oxfmt";
|
|
7
|
+
import consumers from "stream/consumers";
|
|
8
8
|
import ts from "typescript";
|
|
9
|
-
import {
|
|
9
|
+
import { cmd } from "../src/cmd.mjs";
|
|
10
10
|
|
|
11
11
|
if (fs.existsSync("wrangler.jsonc")) {
|
|
12
12
|
cmd("wrangler deploy --env= --dry-run --outdir=dist");
|
|
@@ -32,7 +32,7 @@ if (fs.existsSync("wrangler.jsonc")) {
|
|
|
32
32
|
.join("\n\n");
|
|
33
33
|
fs.writeFileSync(
|
|
34
34
|
"test/api.d.ts",
|
|
35
|
-
await
|
|
35
|
+
(await format("test/api.d.ts", result, { parser: "typescript", printWidth: 100 })).code,
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
}
|
package/oxfmt.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from "oxfmt";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
sortImports: {
|
|
5
|
+
newlinesBetween: false,
|
|
6
|
+
groups: [
|
|
7
|
+
["value-builtin", "value-external"],
|
|
8
|
+
["value-internal", "value-parent", "value-sibling", "value-index"],
|
|
9
|
+
{ newlinesBetween: true },
|
|
10
|
+
"type-import",
|
|
11
|
+
"unknown",
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
ignorePatterns: ["worker-configuration.d.ts", "wrangler.jsonc"],
|
|
15
|
+
});
|
package/oxlint.config.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { defineConfig } from "oxlint";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
plugins: ["eslint", "typescript", "unicorn", "oxc", "import", "promise", "vitest"],
|
|
5
|
+
options: {
|
|
6
|
+
typeAware: true,
|
|
7
|
+
},
|
|
8
|
+
categories: {
|
|
9
|
+
correctness: "error",
|
|
10
|
+
suspicious: "warn",
|
|
11
|
+
perf: "error",
|
|
12
|
+
style: "warn",
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
"no-map-spread": "off",
|
|
16
|
+
"no-named-export": "off",
|
|
17
|
+
"no-magic-numbers": "off",
|
|
18
|
+
"consistent-indexed-object-style": "off",
|
|
19
|
+
"id-length": "off",
|
|
20
|
+
"no-await-expression-member": "off",
|
|
21
|
+
"max-nested-calls": "off",
|
|
22
|
+
"sort-keys": "off",
|
|
23
|
+
curly: "off",
|
|
24
|
+
"switch-case-braces": "off",
|
|
25
|
+
"vitest/require-hook": "off",
|
|
26
|
+
"no-ternary": "off",
|
|
27
|
+
"group-exports": "off",
|
|
28
|
+
"exports-last": "off",
|
|
29
|
+
"prefer-default-export": "off",
|
|
30
|
+
"no-null": "off",
|
|
31
|
+
"consistent-type-specifier-style": "off",
|
|
32
|
+
"max-statements": "off",
|
|
33
|
+
"consistent-type-imports": "error",
|
|
34
|
+
"relative-url-style": ["error", "always"],
|
|
35
|
+
"no-namespace": "off",
|
|
36
|
+
"no-duplicate-imports": ["error", { allowSeparateTypeImports: true }],
|
|
37
|
+
"func-style": "off",
|
|
38
|
+
"sort-imports": [
|
|
39
|
+
"error",
|
|
40
|
+
{
|
|
41
|
+
ignoreDeclarationSort: true,
|
|
42
|
+
ignoreMemberSort: false,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
"new-cap": "off",
|
|
46
|
+
"promise/prefer-await-to-callbacks": "off",
|
|
47
|
+
"promise/avoid-new": "off",
|
|
48
|
+
"prefer-await-to-then": "off",
|
|
49
|
+
"max-params": "off",
|
|
50
|
+
"no-underscore-dangle": "off",
|
|
51
|
+
"no-nested-ternary": "off",
|
|
52
|
+
"unicorn/no-nested-ternary": "off",
|
|
53
|
+
"no-anonymous-default-export": "off",
|
|
54
|
+
"no-continue": "off",
|
|
55
|
+
"init-declarations": "off",
|
|
56
|
+
"prefer-to-be-truthy": "off",
|
|
57
|
+
"vitest/prefer-expect-assertions": "off",
|
|
58
|
+
"vitest/prefer-importing-vitest-globals": "off",
|
|
59
|
+
"vitest/no-importing-vitest-globals": "error",
|
|
60
|
+
"vitest/require-top-level-describe": "off",
|
|
61
|
+
"vitest/max-expects": "off",
|
|
62
|
+
"vitest/no-hooks": "off",
|
|
63
|
+
},
|
|
64
|
+
overrides: [
|
|
65
|
+
{
|
|
66
|
+
files: ["*.test.ts", "*.spec.ts"],
|
|
67
|
+
rules: {
|
|
68
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
files: ["*-definitions.ts"],
|
|
73
|
+
rules: {
|
|
74
|
+
"no-unused-vars": "off",
|
|
75
|
+
"@typescript-eslint/no-redundant-type-constituents": "off",
|
|
76
|
+
"@typescript-eslint/no-duplicate-type-constituents": "off",
|
|
77
|
+
"group-exports": "off",
|
|
78
|
+
"exports-last": "off",
|
|
79
|
+
"consistent-indexed-object-style": "off",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
files: ["bin/*.mjs", "src/cmd.mjs"],
|
|
84
|
+
rules: {
|
|
85
|
+
"import/no-nodejs-modules": "off",
|
|
86
|
+
"prefer-destructuring": "off",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
});
|
package/package.json
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"name": "two-stroke",
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"description": "Simple Cloudflare Worker framework.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/change-engine/two-stroke"
|
|
9
|
+
},
|
|
2
10
|
"bin": {
|
|
3
11
|
"api-types": "./bin/api-types.mjs",
|
|
4
12
|
"bulk": "./bin/bulk.mjs",
|
|
@@ -9,59 +17,44 @@
|
|
|
9
17
|
"test": "./bin/test.mjs",
|
|
10
18
|
"type-check": "./bin/type-check.mjs"
|
|
11
19
|
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "src/index.ts",
|
|
12
22
|
"dependencies": {
|
|
13
|
-
"@cloudflare/vitest-pool-workers": "^0.18.0",
|
|
14
|
-
"@sentry/cli": "^3.6.0",
|
|
15
|
-
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
|
16
|
-
"@typescript-eslint/parser": "^8.62.1",
|
|
17
|
-
"@vitest/runner": "^4.1.8",
|
|
18
|
-
"@vitest/snapshot": "^4.1.8",
|
|
19
|
-
"eslint-config-prettier": "^10.1.8",
|
|
20
|
-
"eslint-config-two-stroke": "^1.5.7",
|
|
21
|
-
"eslint-config-typescript": "^3.0.0",
|
|
22
23
|
"jose": "^6.2.3",
|
|
23
24
|
"mime": "^4.1.0",
|
|
24
|
-
"
|
|
25
|
-
"msw": "^2.14.6",
|
|
25
|
+
"msw": "^2.15.0",
|
|
26
26
|
"openapi-fetch": "^0.17.0",
|
|
27
27
|
"openapi-typescript": "^7.13.0",
|
|
28
28
|
"toucan-js": "^4.1.1",
|
|
29
29
|
"zod": "^4.4.3"
|
|
30
30
|
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@cloudflare/vitest-pool-workers": "^0.18.4",
|
|
33
|
+
"knip": "^6.26.0",
|
|
34
|
+
"oxfmt": "^0.58.0",
|
|
35
|
+
"oxlint": "^1.74.0",
|
|
36
|
+
"oxlint-tsgolint": "^0.24.0",
|
|
37
|
+
"typescript": "^6.0.3",
|
|
38
|
+
"vitest": "^4.1.10"
|
|
39
|
+
},
|
|
31
40
|
"peerDependencies": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
41
|
+
"miniflare": "^4.20260708.1",
|
|
42
|
+
"oxfmt": "^0.58.0",
|
|
43
|
+
"oxlint": "^1.73.0",
|
|
44
|
+
"oxlint-tsgolint": "^0.24.0",
|
|
35
45
|
"typescript": ">=5",
|
|
36
46
|
"vitest": ">=4",
|
|
37
47
|
"wrangler": ">=3"
|
|
38
48
|
},
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
"node": "^24.9.0"
|
|
49
|
+
"prettier": {
|
|
50
|
+
"printWidth": 100
|
|
42
51
|
},
|
|
43
52
|
"eslintConfig": {
|
|
44
53
|
"extends": [
|
|
45
54
|
"two-stroke"
|
|
46
55
|
]
|
|
47
56
|
},
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
"name": "two-stroke",
|
|
51
|
-
"prettier": {
|
|
52
|
-
"printWidth": 100
|
|
53
|
-
},
|
|
54
|
-
"repository": {
|
|
55
|
-
"type": "git",
|
|
56
|
-
"url": "https://github.com/change-engine/two-stroke"
|
|
57
|
-
},
|
|
58
|
-
"type": "module",
|
|
59
|
-
"version": "7.10.1",
|
|
60
|
-
"devDependencies": {
|
|
61
|
-
"@types/eslint": "^9.6.1",
|
|
62
|
-
"eslint": "^10.6.0",
|
|
63
|
-
"prettier": "^3.9.4",
|
|
64
|
-
"typescript": "^6.0.3",
|
|
65
|
-
"vitest": "^4.1.10"
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": "^24.9.0"
|
|
66
59
|
}
|
|
67
60
|
}
|
package/src/cmd.mjs
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
import { Toucan } from "toucan-js";
|
|
2
|
-
import {
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
3
|
import { openAPI } from "./open-api";
|
|
4
4
|
import { type Handler, type Route } from "./types";
|
|
5
|
+
import { jwkVerifyMulti, pbkdfVerify } from "./util";
|
|
6
|
+
|
|
5
7
|
import type { JSONWebKeySet } from "jose";
|
|
6
|
-
import {
|
|
8
|
+
import type { ZodObject, ZodSafeParseResult, ZodType } from "zod/v4";
|
|
7
9
|
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
9
10
|
const noAuth = async () => null;
|
|
10
11
|
|
|
11
|
-
const escapeRegex = (str: string) => str.replace(/([.*+?^=!:$()|[\]\\])/g,
|
|
12
|
+
const escapeRegex = (str: string) => str.replace(/(?<seg>[.*+?^=!:$()|[\]\\])/g, String.raw`\$&`);
|
|
12
13
|
|
|
13
14
|
export function twoStroke<T>(
|
|
14
15
|
title: string,
|
|
15
16
|
release: string,
|
|
16
17
|
origin?: (o: string | null) => string,
|
|
17
18
|
) {
|
|
18
|
-
let _queue: (c: {
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
batch: MessageBatch<any>;
|
|
21
|
-
env: T;
|
|
22
|
-
sentry: Toucan;
|
|
23
|
-
}) => Promise<void>;
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
let _queue: (c: { batch: MessageBatch<any>; env: T; sentry: Toucan }) => Promise<void>;
|
|
25
20
|
const routes: Route<T, any>[] = [];
|
|
26
21
|
routes.push({
|
|
27
22
|
auth: noAuth,
|
|
@@ -46,7 +41,7 @@ export function twoStroke<T>(
|
|
|
46
41
|
): Promise<Response> {
|
|
47
42
|
const defaultHeaders = {
|
|
48
43
|
"Access-Control-Allow-Origin": origin
|
|
49
|
-
? env.SENTRY_ENVIRONMENT === "staging" &&
|
|
44
|
+
? (env.SENTRY_ENVIRONMENT === "staging" || env.SENTRY_ENVIRONMENT === "dev") &&
|
|
50
45
|
req.headers.get("Origin")?.split(":")[1]?.endsWith("localhost")
|
|
51
46
|
? (req.headers.get("Origin") ?? "")
|
|
52
47
|
: origin(req.headers.get("Origin"))
|
|
@@ -87,10 +82,10 @@ export function twoStroke<T>(
|
|
|
87
82
|
);
|
|
88
83
|
let claims;
|
|
89
84
|
try {
|
|
90
|
-
//
|
|
85
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
91
86
|
claims = await route.auth({ req, env });
|
|
92
|
-
} catch (
|
|
93
|
-
console.warn(
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(error);
|
|
94
89
|
return new Response("", {
|
|
95
90
|
status: 401,
|
|
96
91
|
statusText: "Invalid Authorization",
|
|
@@ -105,19 +100,21 @@ export function twoStroke<T>(
|
|
|
105
100
|
try {
|
|
106
101
|
rawBody = route.input
|
|
107
102
|
? req.headers.get("Content-Type") === "application/x-www-form-urlencoded"
|
|
108
|
-
?
|
|
109
|
-
|
|
103
|
+
? // oxlint-disable-next-line no-await-in-loop
|
|
104
|
+
Object.fromEntries(new URLSearchParams(await req.text()))
|
|
105
|
+
: // oxlint-disable-next-line no-await-in-loop
|
|
106
|
+
await req.json()
|
|
110
107
|
: undefined;
|
|
111
|
-
} catch (
|
|
108
|
+
} catch (error) {
|
|
112
109
|
console.error({
|
|
113
110
|
message: "Request body is required'",
|
|
114
|
-
error:
|
|
111
|
+
error: error instanceof Error ? error.message : "Error",
|
|
115
112
|
});
|
|
116
|
-
return
|
|
117
|
-
|
|
113
|
+
return Response.json(
|
|
114
|
+
{
|
|
118
115
|
error: "Request body is required",
|
|
119
116
|
issues: [],
|
|
120
|
-
}
|
|
117
|
+
},
|
|
121
118
|
{
|
|
122
119
|
status: 400,
|
|
123
120
|
headers: defaultHeaders,
|
|
@@ -132,11 +129,11 @@ export function twoStroke<T>(
|
|
|
132
129
|
error: undefined,
|
|
133
130
|
};
|
|
134
131
|
if (body.success)
|
|
132
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
135
133
|
response = await route.handler({
|
|
136
134
|
req,
|
|
137
135
|
env,
|
|
138
136
|
body: body.data,
|
|
139
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
140
137
|
claims,
|
|
141
138
|
params,
|
|
142
139
|
searchParams: new URL(req.url).searchParams,
|
|
@@ -149,12 +146,12 @@ export function twoStroke<T>(
|
|
|
149
146
|
error: body.error,
|
|
150
147
|
body: rawBody,
|
|
151
148
|
});
|
|
152
|
-
return
|
|
153
|
-
|
|
149
|
+
return Response.json(
|
|
150
|
+
{
|
|
154
151
|
error: "Request body schema invalid",
|
|
155
152
|
issues: JSON.parse(body.error?.message ?? "{}") as unknown,
|
|
156
153
|
name: body.error?.name,
|
|
157
|
-
}
|
|
154
|
+
},
|
|
158
155
|
{
|
|
159
156
|
status: 400,
|
|
160
157
|
headers: defaultHeaders,
|
|
@@ -162,10 +159,10 @@ export function twoStroke<T>(
|
|
|
162
159
|
);
|
|
163
160
|
}
|
|
164
161
|
} else {
|
|
162
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
165
163
|
response = await route.handler({
|
|
166
164
|
req,
|
|
167
165
|
env,
|
|
168
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
169
166
|
claims,
|
|
170
167
|
body: undefined,
|
|
171
168
|
params,
|
|
@@ -180,7 +177,6 @@ export function twoStroke<T>(
|
|
|
180
177
|
console.error({
|
|
181
178
|
message: "Response body schema invalid",
|
|
182
179
|
error: output.error,
|
|
183
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
184
180
|
body: response.body,
|
|
185
181
|
});
|
|
186
182
|
}
|
|
@@ -202,7 +198,6 @@ export function twoStroke<T>(
|
|
|
202
198
|
});
|
|
203
199
|
|
|
204
200
|
return new Response(
|
|
205
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
206
201
|
responseWithHeaders.headers.get("Content-Type") === "application/json"
|
|
207
202
|
? JSON.stringify(response.body)
|
|
208
203
|
: response.body,
|
|
@@ -214,9 +209,9 @@ export function twoStroke<T>(
|
|
|
214
209
|
status: 404,
|
|
215
210
|
headers: defaultHeaders,
|
|
216
211
|
});
|
|
217
|
-
} catch (
|
|
218
|
-
console.warn(
|
|
219
|
-
sentry.captureException(
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.warn(error);
|
|
214
|
+
sentry.captureException(error);
|
|
220
215
|
return new Response("", {
|
|
221
216
|
status: 500,
|
|
222
217
|
statusText: "Internal Server Error",
|
|
@@ -240,9 +235,9 @@ export function twoStroke<T>(
|
|
|
240
235
|
});
|
|
241
236
|
try {
|
|
242
237
|
return await _queue({ batch, env, sentry });
|
|
243
|
-
} catch (
|
|
244
|
-
console.warn(
|
|
245
|
-
sentry.captureException(
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn(error);
|
|
240
|
+
sentry.captureException(error);
|
|
246
241
|
}
|
|
247
242
|
},
|
|
248
243
|
async scheduled(
|
|
@@ -265,9 +260,9 @@ export function twoStroke<T>(
|
|
|
265
260
|
throw new Error("CRON Handler not found");
|
|
266
261
|
}
|
|
267
262
|
await handler({ env, sentry });
|
|
268
|
-
} catch (
|
|
269
|
-
console.warn(
|
|
270
|
-
sentry.captureException(
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.warn(error);
|
|
265
|
+
sentry.captureException(error);
|
|
271
266
|
}
|
|
272
267
|
},
|
|
273
268
|
async email(
|
|
@@ -286,9 +281,9 @@ export function twoStroke<T>(
|
|
|
286
281
|
});
|
|
287
282
|
try {
|
|
288
283
|
await _email({ message, env, sentry });
|
|
289
|
-
} catch (
|
|
290
|
-
console.warn(
|
|
291
|
-
sentry.captureException(
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.warn(error);
|
|
286
|
+
sentry.captureException(error);
|
|
292
287
|
}
|
|
293
288
|
},
|
|
294
289
|
emailHandler(
|
|
@@ -306,6 +301,7 @@ export function twoStroke<T>(
|
|
|
306
301
|
const [scheme, token] = (req.headers.get(customHeaderName) ?? " ").split(" ");
|
|
307
302
|
if (
|
|
308
303
|
(scheme === "token" || scheme === "Bearer") &&
|
|
304
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
309
305
|
(await pbkdfVerify(env[k] as string, token ?? ""))
|
|
310
306
|
)
|
|
311
307
|
return;
|
|
@@ -316,13 +312,17 @@ export function twoStroke<T>(
|
|
|
316
312
|
async ({ req, env }: { req: Request; env: T }) => {
|
|
317
313
|
const [scheme, token] = (req.headers.get("Authorization") ?? " ").split(" ");
|
|
318
314
|
if (scheme === "Bearer") {
|
|
315
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
319
316
|
const rawIssuer = env[k] as string;
|
|
317
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
320
318
|
const rawAudience = env[ak] as string;
|
|
321
319
|
const issuer = rawIssuer.startsWith("{")
|
|
322
|
-
?
|
|
320
|
+
? // oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
321
|
+
(JSON.parse(rawIssuer) as Record<string, JSONWebKeySet | true>)
|
|
323
322
|
: { [rawIssuer]: true as const };
|
|
324
323
|
const audience = rawAudience.startsWith("[")
|
|
325
|
-
?
|
|
324
|
+
? // oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
325
|
+
(JSON.parse(rawAudience) as string[])
|
|
326
326
|
: [rawAudience];
|
|
327
327
|
const claims = await jwkVerifyMulti<J>(token ?? "", issuer, audience);
|
|
328
328
|
if (!claims) {
|
|
@@ -361,26 +361,21 @@ export function twoStroke<T>(
|
|
|
361
361
|
console.log("Queue batch finished");
|
|
362
362
|
};
|
|
363
363
|
},
|
|
364
|
-
put<
|
|
365
|
-
I extends ZodType | undefined,
|
|
366
|
-
O extends ZodType,
|
|
367
|
-
A,
|
|
368
|
-
P extends string,
|
|
369
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
370
|
-
PP extends ZodObject<any> | undefined,
|
|
371
|
-
>(
|
|
364
|
+
put<I extends ZodType | undefined, O extends ZodType, A, P extends string>(
|
|
372
365
|
auth: Route<T, A>["auth"],
|
|
373
366
|
path: P,
|
|
374
367
|
input: I,
|
|
375
368
|
output: O,
|
|
376
369
|
handler: Handler<T, I, O, A, P>,
|
|
377
|
-
params?:
|
|
370
|
+
params?: ZodObject<any>,
|
|
378
371
|
) {
|
|
379
372
|
routes.push({
|
|
380
373
|
auth,
|
|
381
374
|
method: "PUT",
|
|
382
375
|
path,
|
|
383
|
-
matcher: new RegExp(
|
|
376
|
+
matcher: new RegExp(
|
|
377
|
+
`^${escapeRegex(path).replaceAll(/\/{(?<seg>[^}]*)}/g, String.raw`/(?<$seg>[^\/]*)`)}$`,
|
|
378
|
+
),
|
|
384
379
|
input,
|
|
385
380
|
output,
|
|
386
381
|
handler,
|
|
@@ -388,26 +383,21 @@ export function twoStroke<T>(
|
|
|
388
383
|
});
|
|
389
384
|
},
|
|
390
385
|
|
|
391
|
-
post<
|
|
392
|
-
I extends ZodType | undefined,
|
|
393
|
-
O extends ZodType,
|
|
394
|
-
A,
|
|
395
|
-
P extends string,
|
|
396
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
397
|
-
PP extends ZodObject<any> | undefined,
|
|
398
|
-
>(
|
|
386
|
+
post<I extends ZodType | undefined, O extends ZodType, A, P extends string>(
|
|
399
387
|
auth: Route<T, A>["auth"],
|
|
400
388
|
path: P,
|
|
401
389
|
input: I,
|
|
402
390
|
output: O,
|
|
403
391
|
handler: Handler<T, I, O, A, P>,
|
|
404
|
-
params?:
|
|
392
|
+
params?: ZodObject<any>,
|
|
405
393
|
) {
|
|
406
394
|
routes.push({
|
|
407
395
|
auth,
|
|
408
396
|
method: "POST",
|
|
409
397
|
path,
|
|
410
|
-
matcher: new RegExp(
|
|
398
|
+
matcher: new RegExp(
|
|
399
|
+
`^${escapeRegex(path).replaceAll(/\/{(?<seg>[^}]*)}/g, String.raw`/(?<$seg>[^\/]*)`)}$`,
|
|
400
|
+
),
|
|
411
401
|
input,
|
|
412
402
|
output,
|
|
413
403
|
handler,
|
|
@@ -415,47 +405,39 @@ export function twoStroke<T>(
|
|
|
415
405
|
});
|
|
416
406
|
},
|
|
417
407
|
|
|
418
|
-
get<
|
|
419
|
-
O extends ZodType,
|
|
420
|
-
A,
|
|
421
|
-
P extends string,
|
|
422
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
423
|
-
PP extends ZodObject<any> | undefined,
|
|
424
|
-
>(
|
|
408
|
+
get<O extends ZodType, A, P extends string>(
|
|
425
409
|
auth: Route<T, A>["auth"],
|
|
426
410
|
path: P,
|
|
427
411
|
output: O,
|
|
428
412
|
handler: Handler<T, undefined, O, A, P>,
|
|
429
|
-
params?:
|
|
413
|
+
params?: ZodObject<any>,
|
|
430
414
|
) {
|
|
431
415
|
routes.push({
|
|
432
416
|
auth,
|
|
433
417
|
method: "GET",
|
|
434
418
|
path,
|
|
435
|
-
matcher: new RegExp(
|
|
419
|
+
matcher: new RegExp(
|
|
420
|
+
`^${escapeRegex(path).replaceAll(/\/{(?<seg>[^}]*)}/g, String.raw`/(?<$seg>[^\/]*)`)}$`,
|
|
421
|
+
),
|
|
436
422
|
output,
|
|
437
423
|
handler,
|
|
438
424
|
params,
|
|
439
425
|
});
|
|
440
426
|
},
|
|
441
|
-
delete<
|
|
442
|
-
O extends ZodType,
|
|
443
|
-
A,
|
|
444
|
-
P extends string,
|
|
445
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
446
|
-
PP extends ZodObject<any> | undefined,
|
|
447
|
-
>(
|
|
427
|
+
delete<O extends ZodType, A, P extends string>(
|
|
448
428
|
auth: Route<T, A>["auth"],
|
|
449
429
|
path: P,
|
|
450
430
|
output: O,
|
|
451
431
|
handler: Handler<T, undefined, O, A, P>,
|
|
452
|
-
params?:
|
|
432
|
+
params?: ZodObject<any>,
|
|
453
433
|
) {
|
|
454
434
|
routes.push({
|
|
455
435
|
auth,
|
|
456
436
|
method: "DELETE",
|
|
457
437
|
path,
|
|
458
|
-
matcher: new RegExp(
|
|
438
|
+
matcher: new RegExp(
|
|
439
|
+
`^${escapeRegex(path).replaceAll(/\/{(?<seg>[^}]*)}/g, String.raw`/(?<$seg>[^\/]*)`)}$`,
|
|
440
|
+
),
|
|
459
441
|
output,
|
|
460
442
|
handler,
|
|
461
443
|
params,
|
|
@@ -474,13 +456,15 @@ export async function addToQueue<T>(queue: Queue<T>, message: T, config: AddToQu
|
|
|
474
456
|
|
|
475
457
|
for (let i = 0; i < (retries ?? 5); i++) {
|
|
476
458
|
try {
|
|
459
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
477
460
|
await queue.send(message, options);
|
|
478
461
|
return;
|
|
479
|
-
} catch (
|
|
462
|
+
} catch (error) {
|
|
480
463
|
const backoff = (backoffFactor ?? 2) ** i;
|
|
481
|
-
//
|
|
482
|
-
console.log(`Error adding to queue: ${
|
|
464
|
+
// oxlint-disable-next-line typescript/restrict-template-expressions
|
|
465
|
+
console.log(`Error adding to queue: ${error}`);
|
|
483
466
|
console.log(`Retrying in ${backoff} seconds`);
|
|
467
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
484
468
|
await new Promise((resolve) => setTimeout(resolve, backoff * 1000));
|
|
485
469
|
}
|
|
486
470
|
}
|
package/src/once.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ZodSafeParseResult, ZodType, z } from "zod/v4";
|
|
2
2
|
|
|
3
3
|
export const once = async <T extends { retry_count: number }>(
|
|
4
4
|
key: string,
|
|
@@ -17,7 +17,7 @@ export const once = async <T extends { retry_count: number }>(
|
|
|
17
17
|
{
|
|
18
18
|
delaySeconds: Math.min(
|
|
19
19
|
Math.round(
|
|
20
|
-
(0.5 + Math.random() / 2) *
|
|
20
|
+
(0.5 + Math.random() / 2) * retry_count ** backoffExponent + Math.random() * 4,
|
|
21
21
|
),
|
|
22
22
|
900,
|
|
23
23
|
),
|
|
@@ -50,15 +50,9 @@ export const retryHandler =
|
|
|
50
50
|
env: T;
|
|
51
51
|
}) => {
|
|
52
52
|
const isFinalAttempt = body.retry >= maxRetries;
|
|
53
|
-
await handler(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
isFinalAttempt,
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
58
|
-
async (cb) => {
|
|
59
|
-
waitUntil(cb());
|
|
60
|
-
},
|
|
61
|
-
);
|
|
53
|
+
await handler(body, env, isFinalAttempt, async (cb) => {
|
|
54
|
+
waitUntil(cb());
|
|
55
|
+
});
|
|
62
56
|
|
|
63
57
|
return { body: { ok: true } };
|
|
64
58
|
};
|
|
@@ -82,7 +76,7 @@ export const retryQueue =
|
|
|
82
76
|
parsedBatch: ZodSafeParseResult<z.output<M>>[];
|
|
83
77
|
env: T;
|
|
84
78
|
}) => {
|
|
85
|
-
const message = batch.messages
|
|
79
|
+
const [message] = batch.messages;
|
|
86
80
|
if (!message || batch.messages.length !== 1) {
|
|
87
81
|
console.warn({ batch });
|
|
88
82
|
throw new Error(
|
|
@@ -101,31 +95,33 @@ export const retryQueue =
|
|
|
101
95
|
message.ack();
|
|
102
96
|
await cb();
|
|
103
97
|
});
|
|
104
|
-
} catch (
|
|
98
|
+
} catch (error) {
|
|
105
99
|
if (!isFinalAttempt) message.retry();
|
|
106
|
-
throw
|
|
100
|
+
throw error;
|
|
107
101
|
}
|
|
108
102
|
};
|
|
109
103
|
|
|
110
104
|
const isDuplicateMessage = async (key: string, bucket: R2Bucket) => {
|
|
111
105
|
// Work around for CloudFlare R2 error:
|
|
112
|
-
//
|
|
106
|
+
// Put: We encountered an internal error. Please try again. (10001)
|
|
113
107
|
for (let i = 0; i < 5; i++) {
|
|
114
108
|
try {
|
|
115
109
|
// Will throw if that error occurs
|
|
110
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
116
111
|
const putResult = await bucket.put(`lock-${key}`, "", {
|
|
117
112
|
onlyIf: new Headers({
|
|
118
113
|
"If-Unmodified-Since": "Wed, 21 Oct 2015 07:28:00 GMT",
|
|
119
114
|
}),
|
|
120
115
|
});
|
|
121
|
-
//
|
|
116
|
+
// If the returned result is null then the object already existed
|
|
122
117
|
const isDupe = putResult === null;
|
|
123
118
|
if (isDupe) console.log("Duplicate message", { key });
|
|
124
119
|
return isDupe;
|
|
125
|
-
} catch (
|
|
126
|
-
//
|
|
127
|
-
console.warn(`Error putting ${key} to R2: ${
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// oxlint-disable-next-line typescript/restrict-template-expressions
|
|
122
|
+
console.warn(`Error putting ${key} to R2: ${error}`);
|
|
128
123
|
console.warn(`Retrying in ${i} seconds`);
|
|
124
|
+
// oxlint-disable-next-line no-await-in-loop
|
|
129
125
|
await new Promise((resolve) => setTimeout(resolve, i * 1000));
|
|
130
126
|
}
|
|
131
127
|
}
|
package/src/open-api.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { z
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
2
|
import { type Route } from "./types";
|
|
3
3
|
|
|
4
|
+
import type { ZodType } from "zod/v4";
|
|
5
|
+
|
|
4
6
|
export const openAPI =
|
|
5
7
|
<T, A>(title: string, release: string, noAuth: () => A, routes: Route<T, A>[]) =>
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
7
8
|
async () => ({
|
|
8
9
|
body: {
|
|
9
10
|
openapi: "3.1.0",
|
|
@@ -27,6 +28,7 @@ export const openAPI =
|
|
|
27
28
|
r.method.toLocaleLowerCase(),
|
|
28
29
|
{
|
|
29
30
|
parameters: [
|
|
31
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
30
32
|
...Object.entries((r.params?.shape ?? {}) as Record<string, ZodType>).map(
|
|
31
33
|
([k, v]) => ({
|
|
32
34
|
name: k,
|
package/src/test.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
|
2
1
|
import { env, exports } from "cloudflare:workers";
|
|
3
2
|
import { type JWTPayload, SignJWT, exportJWK, generateKeyPair } from "jose";
|
|
4
|
-
import
|
|
5
|
-
import { http, HttpResponse } from "msw";
|
|
3
|
+
import { HttpResponse, http } from "msw";
|
|
6
4
|
import { setupServer } from "msw/node";
|
|
5
|
+
import createClient from "openapi-fetch";
|
|
7
6
|
|
|
8
7
|
const msw = setupServer();
|
|
9
8
|
msw.listen();
|
|
10
9
|
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/require-await
|
|
12
10
|
export const setupTests = async <Paths extends {}>() => {
|
|
13
11
|
const url = new URL("https://example.com/");
|
|
14
12
|
|
|
@@ -21,11 +19,10 @@ export const setupTests = async <Paths extends {}>() => {
|
|
|
21
19
|
url,
|
|
22
20
|
client,
|
|
23
21
|
msw,
|
|
24
|
-
async
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
waitForQueue: async (trigger: () => Promise<void>) => {
|
|
26
23
|
const log: any[] = [];
|
|
27
24
|
const orig = console.log;
|
|
28
|
-
console.log =
|
|
25
|
+
console.log = (message) => {
|
|
29
26
|
orig(message);
|
|
30
27
|
log.push(message);
|
|
31
28
|
};
|
|
@@ -33,13 +30,11 @@ export const setupTests = async <Paths extends {}>() => {
|
|
|
33
30
|
await waitUntil(() => expect(log).contains("Queue batch finished"));
|
|
34
31
|
console.log = orig;
|
|
35
32
|
},
|
|
36
|
-
async
|
|
33
|
+
fakeJWK: async (issuer: keyof typeof env, audience: keyof typeof env, claims: JWTPayload) => {
|
|
37
34
|
const { publicKey, privateKey } = await generateKeyPair("RS256");
|
|
38
35
|
// Hack around Cloudflare not setting
|
|
39
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
40
36
|
// @ts-expect-error
|
|
41
37
|
publicKey[Symbol.toStringTag] = "CryptoKey";
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
43
38
|
// @ts-expect-error
|
|
44
39
|
privateKey[Symbol.toStringTag] = "CryptoKey";
|
|
45
40
|
const jwk = await exportJWK(publicKey);
|
|
@@ -47,25 +42,25 @@ export const setupTests = async <Paths extends {}>() => {
|
|
|
47
42
|
jwk.alg = "RS256";
|
|
48
43
|
beforeAll(() => {
|
|
49
44
|
msw.use(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
),
|
|
61
|
-
],
|
|
45
|
+
http.get(`${env[issuer]}/.well-known/openid-configuration`, () =>
|
|
46
|
+
HttpResponse.json({
|
|
47
|
+
jwks_uri: `${env[issuer]}/.well-known/jwks`,
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
http.get(`${env[issuer]}/.well-known/jwks`, () =>
|
|
51
|
+
HttpResponse.json({
|
|
52
|
+
keys: [jwk],
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
62
55
|
);
|
|
63
56
|
});
|
|
64
57
|
return await new SignJWT(claims)
|
|
65
58
|
.setProtectedHeader({ typ: "JWT", alg: "RS256", kid: jwk.kid })
|
|
66
59
|
.setIssuedAt()
|
|
67
60
|
.setNotBefore("5 minutes ago")
|
|
61
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion
|
|
68
62
|
.setIssuer(env[issuer] as string)
|
|
63
|
+
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion
|
|
69
64
|
.setAudience([env[audience] as string])
|
|
70
65
|
.setExpirationTime("1h")
|
|
71
66
|
.sign(privateKey);
|
|
@@ -82,54 +77,3 @@ async function waitUntil(condition: () => void, time = 100) {
|
|
|
82
77
|
await waitUntil(condition, time);
|
|
83
78
|
}
|
|
84
79
|
}
|
|
85
|
-
|
|
86
|
-
export function recordRequest(
|
|
87
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
-
cb: (data: any) => void,
|
|
89
|
-
statusCode: number,
|
|
90
|
-
data: string | object | undefined,
|
|
91
|
-
responseOptions?: {
|
|
92
|
-
headers: Record<string, string | string[] | undefined>;
|
|
93
|
-
},
|
|
94
|
-
) {
|
|
95
|
-
return ({ body }: { body?: BodyInit }) => {
|
|
96
|
-
cb(JSON.parse((body?.valueOf() as string) ?? ""));
|
|
97
|
-
return { statusCode, data, responseOptions: responseOptions ?? {} };
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function recordFormRequest(
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
-
cb: (data: any) => void,
|
|
104
|
-
statusCode: number,
|
|
105
|
-
data: string | object | undefined,
|
|
106
|
-
responseOptions?: {
|
|
107
|
-
headers: Record<string, string | string[] | undefined>;
|
|
108
|
-
},
|
|
109
|
-
) {
|
|
110
|
-
return ({ body }: { body?: BodyInit }) => {
|
|
111
|
-
cb(Object.fromEntries(new URLSearchParams((body?.valueOf() as string) ?? "").entries()));
|
|
112
|
-
return { statusCode, data, responseOptions: responseOptions ?? {} };
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function recordFirehoseRequest(
|
|
117
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
-
cb: (data: any) => void,
|
|
119
|
-
statusCode: number,
|
|
120
|
-
data: string | object | undefined,
|
|
121
|
-
responseOptions?: {
|
|
122
|
-
headers: Record<string, string | string[] | undefined>;
|
|
123
|
-
},
|
|
124
|
-
) {
|
|
125
|
-
return ({ body }: { body?: BodyInit }) => {
|
|
126
|
-
cb(JSON.parse((body?.valueOf() as string) ?? ""));
|
|
127
|
-
cb(
|
|
128
|
-
JSON.parse(
|
|
129
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
130
|
-
atob(JSON.parse((body?.valueOf() as string) ?? "")["Record"]["Data"]),
|
|
131
|
-
),
|
|
132
|
-
);
|
|
133
|
-
return { statusCode, data, responseOptions: responseOptions ?? {} };
|
|
134
|
-
};
|
|
135
|
-
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Toucan } from "toucan-js";
|
|
2
|
-
import {
|
|
1
|
+
import type { Toucan } from "toucan-js";
|
|
2
|
+
import type { ZodObject, ZodType, z } from "zod/v4";
|
|
3
3
|
|
|
4
4
|
export type Route<T, A> =
|
|
5
5
|
| {
|
|
@@ -8,9 +8,7 @@ export type Route<T, A> =
|
|
|
8
8
|
path: string;
|
|
9
9
|
matcher: RegExp;
|
|
10
10
|
output: ZodType;
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
11
|
handler: Handler<T, undefined, any, A, string>;
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
12
|
params?: ZodObject<any>;
|
|
15
13
|
}
|
|
16
14
|
| {
|
|
@@ -20,16 +18,13 @@ export type Route<T, A> =
|
|
|
20
18
|
matcher: RegExp;
|
|
21
19
|
input: ZodType | undefined;
|
|
22
20
|
output: ZodType;
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
24
21
|
handler: Handler<T, any, any, A, string>;
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
22
|
params?: ZodObject<any>;
|
|
27
23
|
};
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
: Record<string, string>;
|
|
25
|
+
type ExtractParameterNames<S extends string> = S extends `${string}{${infer Name}}${infer Rest}`
|
|
26
|
+
? Record<Name, string> & ExtractParameterNames<Rest>
|
|
27
|
+
: Record<string, string>;
|
|
33
28
|
|
|
34
29
|
export type Handler<
|
|
35
30
|
T,
|
package/src/util.ts
CHANGED
|
@@ -16,8 +16,10 @@ export async function pbkdfVerify(key: string, password: string) {
|
|
|
16
16
|
{
|
|
17
17
|
name: "PBKDF2",
|
|
18
18
|
hash: "SHA-256",
|
|
19
|
+
// oxlint-disable-next-line typescript/no-misused-spread unicorn/no-useless-spread
|
|
19
20
|
salt: new Uint8Array([...atob(key).slice(3, 19)].map((ch) => ch.charCodeAt(0))),
|
|
20
21
|
iterations: parseInt(
|
|
22
|
+
// oxlint-disable-next-line typescript/no-misused-spread unicorn/no-useless-spread
|
|
21
23
|
[...atob(key).slice(19, 22)].map((ch) => ch.charCodeAt(0).toString(16)).join(""),
|
|
22
24
|
16,
|
|
23
25
|
),
|
package/eslint.config.js
DELETED