two-stroke 2.0.0 → 4.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/bin/test.mjs CHANGED
@@ -13,6 +13,8 @@ if (fs.existsSync("wrangler.toml")) {
13
13
  const miniflare = new Miniflare({
14
14
  modules: true,
15
15
  scriptPath: "dist/index.js",
16
+ compatibilityDate: "2024-09-23",
17
+ compatibilityFlags: ["nodejs_compat"],
16
18
  });
17
19
  const request = await fetch(
18
20
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -20,25 +22,27 @@ if (fs.existsSync("wrangler.toml")) {
20
22
  { SENTRY_DSN: null, SENTRY_ENVIRONMENT: null },
21
23
  null,
22
24
  );
23
- const types = await openapiTS(await consumers.json(request.body));
24
25
  await miniflare.dispose();
25
- const printer = ts.createPrinter({});
26
- const resultFile = ts.createSourceFile(
27
- "test/api.d.ts",
28
- "",
29
- ts.ScriptTarget.Latest,
30
- );
31
- const result = printer.printNode(
32
- ts.EmitHint.Unspecified,
33
- types[0],
34
- resultFile,
35
- );
36
- fs.writeFileSync(
37
- "test/api.d.ts",
38
- await prettier.format(result, { parser: "typescript" }),
39
- );
26
+ if (request.status === 200) {
27
+ const types = await openapiTS(await consumers.json(request.body));
28
+ const printer = ts.createPrinter({});
29
+ const resultFile = ts.createSourceFile(
30
+ "test/api.d.ts",
31
+ "",
32
+ ts.ScriptTarget.Latest,
33
+ );
34
+ const result = printer.printNode(
35
+ ts.EmitHint.Unspecified,
36
+ types[0],
37
+ resultFile,
38
+ );
39
+ fs.writeFileSync(
40
+ "test/api.d.ts",
41
+ await prettier.format(result, { parser: "typescript" }),
42
+ );
43
+ }
40
44
  }
41
- cmd("vitest --globals --no-file-parallelism --pool threads", [
45
+ cmd("vitest", [
42
46
  ...(!process.argv.slice(2).includes("-w") &&
43
47
  !process.argv.slice(2).includes("--watch")
44
48
  ? ["--run"]
package/package.json CHANGED
@@ -10,22 +10,22 @@
10
10
  "dependencies": {
11
11
  "@anatine/zod-openapi": "^2.2.6",
12
12
  "@asteasolutions/zod-to-openapi": "^7.2.0",
13
- "@cloudflare/workers-types": "^4.20241011.0",
14
- "@sentry/cli": "^2.37.0",
15
- "@typescript-eslint/eslint-plugin": "^8.8.1",
16
- "@typescript-eslint/parser": "^8.8.1",
17
- "@vitest/coverage-v8": "^2.1.3",
13
+ "@cloudflare/vitest-pool-workers": "^0.5.22",
14
+ "@cloudflare/workers-types": "^4.20241022.0",
15
+ "@sentry/cli": "^2.38.0",
16
+ "@typescript-eslint/eslint-plugin": "^8.11.0",
17
+ "@typescript-eslint/parser": "^8.11.0",
18
+ "@vitest/coverage-istanbul": "^2.1.3",
18
19
  "eslint-config-prettier": "^9.1.0",
19
20
  "eslint-config-two-stroke": "^1.1.5",
20
21
  "eslint-config-typescript": "^3.0.0",
21
- "jose": "^5.9.4",
22
+ "jose": "^5.9.6",
22
23
  "jwk-subtle": "^1.0.7",
23
- "miniflare": "^3.20241004.0",
24
- "openapi-fetch": "^0.12.2",
25
- "openapi-typescript": "^7.4.1",
24
+ "miniflare": "^3.20241022.0",
25
+ "openapi-fetch": "^0.13.0",
26
+ "openapi-typescript": "^7.4.2",
26
27
  "openapi3-ts": "^4.4.0",
27
28
  "pbkdf-subtle": "^1.0.0",
28
- "toml": "^3.0.0",
29
29
  "toucan-js": "^4.0.0",
30
30
  "zod": "^3.23.8"
31
31
  },
@@ -51,11 +51,11 @@
51
51
  "name": "two-stroke",
52
52
  "packageManager": "yarn@4.3.0",
53
53
  "type": "module",
54
- "version": "2.0.0",
54
+ "version": "4.0.0",
55
55
  "devDependencies": {
56
56
  "@types/eslint": "^9.6.1",
57
- "@types/node": "^22.7.5",
58
- "eslint": "^9.12.0",
57
+ "@types/node": "^22.8.1",
58
+ "eslint": "^9.13.0",
59
59
  "prettier": "^3.3.3",
60
60
  "typescript": "^5.6.3",
61
61
  "vitest": "^2.1.3"
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { verify as jwkVerify } from "jwk-subtle";
2
2
  import { verify as pbkdfVerify } from "pbkdf-subtle";
3
3
  import { Toucan } from "toucan-js";
4
- import { SafeParseError, ZodObject, ZodSchema, z } from "zod";
4
+ import { SafeParseReturnType, ZodObject, ZodSchema, z } from "zod";
5
5
  import { openAPI } from "./open-api";
6
6
  import { Env, Handler, Route } from "./types";
7
7
 
@@ -288,56 +288,24 @@ export function twoStroke<T extends Env>(title: string, release: string) {
288
288
  }
289
289
  throw Error("Invalid");
290
290
  },
291
- queueHandler<I extends ZodSchema, Parsed = z.infer<I>>(
291
+ queueHandler<I extends ZodSchema>(
292
292
  input: I,
293
293
  handler: (c: {
294
294
  env: T;
295
- batch: MessageBatch<Parsed>;
296
- sentry: Toucan;
297
- }) => Promise<void>,
298
- parseFailedHandler?: (c: {
299
- env: T;
300
- batch: MessageBatch & {
301
- messages: (Message<unknown> & {
302
- error: SafeParseError<z.input<I>>;
303
- })[];
304
- };
295
+ batch: MessageBatch<z.input<I>>;
305
296
  sentry: Toucan;
297
+ parsedBatch: SafeParseReturnType<z.input<I>, z.infer<I>>[];
306
298
  }) => Promise<void>,
307
299
  ) {
308
300
  _queue = async ({ batch, env, sentry }) => {
309
- const { messages, failed } = batch.messages.reduce<{
310
- messages: Message<Parsed>[];
311
- failed: (Message<unknown> & { error: SafeParseError<z.input<I>> })[];
312
- }>(
313
- (acc, message) => {
314
- const body: z.SafeParseReturnType<
315
- z.input<I>,
316
- Parsed
317
- > = input.safeParse(message.body);
318
- if (!body.success) {
319
- console.error(body.error, message);
320
- return {
321
- messages: acc.messages,
322
- failed: [...acc.failed, { ...message, error: body }],
323
- };
324
- }
325
-
326
- return {
327
- messages: [...acc.messages, { ...message, body: body.data }],
328
- failed: acc.failed,
329
- };
330
- },
331
- { messages: [], failed: [] },
332
- );
333
-
334
- await handler({ batch: { ...batch, messages }, env, sentry });
335
- if (failed.length > 0)
336
- await parseFailedHandler?.({
337
- batch: { ...batch, messages: failed },
338
- env,
339
- sentry,
340
- });
301
+ const parsedBatch = batch.messages.map((message) => {
302
+ const passed = input.safeParse(message.body);
303
+ if (!passed.success) {
304
+ console.error(passed.error, message);
305
+ }
306
+ return passed;
307
+ });
308
+ await handler({ batch, env, sentry, parsedBatch });
341
309
  console.log("Queue batch finished");
342
310
  };
343
311
  },
package/src/test.ts CHANGED
@@ -1,113 +1,24 @@
1
- import fs from "fs";
2
1
  import { JWTPayload, SignJWT, exportJWK, generateKeyPair } from "jose";
3
- import { Miniflare, createFetchMock } from "miniflare";
4
- import nodefs from "node:fs/promises";
5
- import path from "node:path";
2
+ import { fetchMock, SELF, env } from "cloudflare:test";
6
3
  import createClient from "openapi-fetch";
7
4
  import consumers from "stream/consumers";
8
- import toml from "toml";
9
- import { URLSearchParams } from "url";
10
- import { Env } from "./types";
11
5
 
12
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
13
- export const setupTests = async <Paths extends {}>(bindings: Env) => {
14
- const fetchMock = createFetchMock();
15
- fetchMock.disableNetConnect();
16
-
17
- const config = toml.parse(fs.readFileSync("wrangler.toml", "utf8")) as {
18
- compatibility_date: string;
19
- compatibility_flags: string[];
20
- queues?: {
21
- consumers?: { queue: string }[];
22
- producers?: { queue: string; binding: string }[];
23
- };
24
- r2_buckets?: { binding: string }[];
25
- kv_namespaces?: { binding: string }[];
26
- d1_databases?: { binding: string }[];
27
- services?: { binding: string; service: string }[];
28
- durable_objects?: { bindings?: { name: string; class_name: string }[] };
29
- };
30
-
31
- const miniflare = new Miniflare({
32
- modules: true,
33
- scriptPath: "dist/index.js",
34
- compatibilityDate: config.compatibility_date,
35
- compatibilityFlags: config.compatibility_flags,
36
- bindings: {
37
- TOKEN_HASH:
38
- "djAxlhzT1IU9QIP3UKdipECQPAGGoPQK86/GnTBcbHLtPC3ni6JkTQ/iIeF0KG0y1CZ+J+9W",
39
- ...bindings,
40
- },
41
- queueConsumers: (config.queues?.consumers ?? []).map(({ queue }) => queue),
42
- queueProducers: Object.fromEntries(
43
- (config.queues?.producers ?? []).map(({ binding, queue: queueName }) => [
44
- binding,
45
- { queueName },
46
- ]),
47
- ),
48
- r2Buckets: (config.r2_buckets ?? []).map(({ binding }) => binding),
49
- kvNamespaces: (config.kv_namespaces ?? []).map(({ binding }) => binding),
50
- d1Databases: (config.d1_databases ?? []).map(({ binding }) => binding),
51
- serviceBindings: Object.fromEntries(
52
- (config.services ?? []).map(({ binding, service }) => [binding, service]),
53
- ),
54
- fetchMock,
55
- durableObjects: Object.fromEntries(
56
- (config.durable_objects?.bindings ?? []).map(({ name, class_name }) => [
57
- name,
58
- class_name,
59
- ]),
60
- ),
6
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/require-await
7
+ export const setupTests = async <Paths extends {}>() => {
8
+ beforeAll(() => {
9
+ fetchMock.activate();
10
+ fetchMock.disableNetConnect();
61
11
  });
62
12
 
63
- const url = await miniflare.ready;
64
-
65
- // Hack until miniflare supports auto running D1 migrations.
66
- // Note: Each migration file can contain only a single statement.
67
- await Promise.all(
68
- (config.d1_databases ?? []).map(
69
- async ({ binding }: { binding: string }) => {
70
- const d1 = await miniflare.getD1Database(binding);
71
-
72
- const migrationsDir = "./migrations";
73
- let fileNames: string[] = [];
74
- try {
75
- fileNames = await nodefs.readdir(migrationsDir);
76
- } catch (error) {
77
- console.log("Error loading migrations", error);
78
- }
13
+ const url = new URL("https://example.com/");
79
14
 
80
- if (fileNames.length === 0)
81
- console.log("No migrations found in ./migrations");
82
-
83
- for (const migrationName of fileNames.sort((a, b) => {
84
- const migrationNumberA = parseInt(a.split("_")[0]!);
85
- const migrationNumberB = parseInt(b.split("_")[0]!);
86
- if (migrationNumberA < migrationNumberB) {
87
- return -1;
88
- }
89
- if (migrationNumberA > migrationNumberB) {
90
- return 1;
91
- }
92
-
93
- // numbers must be equal
94
- return 0;
95
- })) {
96
- const migrationPath = path.join(migrationsDir, migrationName);
97
- let migration = await nodefs.readFile(migrationPath, "utf8");
98
- // Remove migration comment
99
- migration = migration.split("\n").slice(1).join(" ");
100
- await d1.exec(migration);
101
- }
102
- },
103
- ),
104
- );
105
-
106
- const client = createClient<Paths>({ baseUrl: url.toString() });
15
+ const client = createClient<Paths>({
16
+ baseUrl: url.toString(),
17
+ fetch: (...r) => SELF.fetch(...r),
18
+ });
107
19
 
108
20
  return {
109
21
  url,
110
- miniflare,
111
22
  fetchMock,
112
23
  client,
113
24
  async waitForQueue(trigger: () => Promise<void>) {
@@ -122,48 +33,57 @@ export const setupTests = async <Paths extends {}>(bindings: Env) => {
122
33
  await waitUntil(() => expect(log).contains("Queue batch finished"));
123
34
  console.log = orig;
124
35
  },
125
- async fakeJWK(issuer: string, audience: string, claims: JWTPayload) {
36
+ async fakeJWK(
37
+ issuer: keyof typeof env,
38
+ audience: keyof typeof env,
39
+ claims: JWTPayload,
40
+ ) {
126
41
  const { publicKey, privateKey } = await generateKeyPair("RS256");
127
42
  const jwk = await exportJWK(publicKey);
128
43
  jwk.kid = "test";
129
44
  jwk.alg = "RS256";
130
- fetchMock
131
- .get(bindings[issuer] as string)
132
- .intercept({ method: "GET", path: "/.well-known/openid-configuration" })
133
- .reply(
134
- 200,
135
- {
136
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
137
- jwks_uri: `${bindings[issuer]}/.well-known/jwks`,
138
- },
139
- {
140
- headers: {
141
- "Content-Type": "application/json",
45
+ beforeAll(() => {
46
+ fetchMock
47
+ .get(env[issuer] as string)
48
+ .intercept({
49
+ method: "GET",
50
+ path: "/.well-known/openid-configuration",
51
+ })
52
+ .reply(
53
+ 200,
54
+ {
55
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
56
+ jwks_uri: `${env[issuer]}/.well-known/jwks`,
57
+ },
58
+ {
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ },
62
+ },
63
+ )
64
+ .persist();
65
+ fetchMock
66
+ .get(env[issuer] as string)
67
+ .intercept({ method: "GET", path: "/.well-known/jwks" })
68
+ .reply(
69
+ 200,
70
+ {
71
+ keys: [jwk],
142
72
  },
143
- },
144
- )
145
- .persist();
146
- fetchMock
147
- .get(bindings[issuer] as string)
148
- .intercept({ method: "GET", path: "/.well-known/jwks" })
149
- .reply(
150
- 200,
151
- {
152
- keys: [jwk],
153
- },
154
- {
155
- headers: {
156
- "Content-Type": "application/json",
73
+ {
74
+ headers: {
75
+ "Content-Type": "application/json",
76
+ },
157
77
  },
158
- },
159
- )
160
- .persist();
78
+ )
79
+ .persist();
80
+ });
161
81
  return await new SignJWT(claims)
162
82
  .setProtectedHeader({ typ: "JWT", alg: "RS256", kid: jwk.kid })
163
83
  .setIssuedAt()
164
84
  .setNotBefore("5 minutes ago")
165
- .setIssuer(bindings[issuer] as string)
166
- .setAudience([bindings[audience] as string])
85
+ .setIssuer(env[issuer] as string)
86
+ .setAudience([env[audience] as string])
167
87
  .setExpirationTime("1h")
168
88
  .sign(privateKey);
169
89
  },
package/src/types.ts CHANGED
@@ -9,7 +9,8 @@ export type Env = {
9
9
  | D1Database
10
10
  | Fetcher
11
11
  | Hyperdrive
12
- | DurableObjectNamespace;
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ | DurableObjectNamespace<any>;
13
14
  };
14
15
  export type Route<T extends Env, A> =
15
16
  | {
package/tsconfig.json CHANGED
@@ -11,6 +11,11 @@
11
11
  "skipLibCheck": true,
12
12
  "strict": true,
13
13
  "target": "ESNext",
14
- "types": ["@cloudflare/workers-types", "vitest/globals"]
15
- }
14
+ "types": [
15
+ "@cloudflare/workers-types",
16
+ "vitest/globals",
17
+ "@cloudflare/vitest-pool-workers"
18
+ ]
19
+ },
20
+ "exclude": ["../../dist"]
16
21
  }