nitro-queue 0.0.1

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/README.ko.md ADDED
@@ -0,0 +1,58 @@
1
+ # Outline
2
+ cloudflare queue를 지원하는 nitro v3용 모듈
3
+ ```bash
4
+ pnpm add nitro-queue
5
+ ```
6
+
7
+ # Features
8
+ - queues 폴더를 읽고 자동으로 queue route 세팅
9
+ - npx nitro-queue types cloudflare.d.ts 명령어로 env의 queue 타입 덮어쓰기
10
+ - 모듈이 queue를 cloudflare:queue에서 알아서 라우팅해줍니다.
11
+
12
+ # Prerequisites
13
+ ## init module
14
+ ```ts
15
+ // nitro.config.ts
16
+ import queue from 'nitro-queue'
17
+ import { defineNitroConfig } from 'nitro/config'
18
+
19
+ export default defineNitroConfig({
20
+ modules: [queue()],
21
+ })
22
+ ```
23
+
24
+ ## cloudflare.d.ts
25
+ cloudflare는 wrangler config 파일을 통해 type을 생성할 수 있습니다.
26
+ nitro-queue는 wrangler로 생성한 type을 프로젝트 queue 타입에 맞게 재수정합니다.
27
+ ```json
28
+ {
29
+ "scripts": {
30
+ "prepare": "pnpm types:cloudflare && pnpm types:cloudflare",
31
+ "types:cloudflare": "wrangler types cloudflare.d.ts --config ./.output/server/wrangler.json",
32
+ "types:queue": "npx nitro-queue types cloudflare.d.ts",
33
+ },
34
+ }
35
+
36
+ ```
37
+ # How to use
38
+ ```ts
39
+ // queues/user/remove.ts
40
+ export default defineQueue<{
41
+ userId: string,
42
+ }>({
43
+ run(payload) {
44
+ console.log(payload.userId)
45
+ }
46
+ })
47
+
48
+ // routes/publish.ts
49
+ export default defineHandler(async (e) => {
50
+ e.runtime.cloudflare.env.MY_QUEUE.send({
51
+ type: 'user:remove',
52
+ payload: { userId }
53
+ })
54
+ })
55
+ ```
56
+
57
+ # 주의점
58
+ - 테스트를 면밀히 거치지 않았습니다. 버그가 발생한다면 이슈를 통해 알려주시면 감사드리겠습니다.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Outline
2
+
3
+ A module for Nitro v3 that provides Cloudflare Queue support.
4
+
5
+ ```bash
6
+ pnpm add nitro-queue
7
+ ```
8
+
9
+ # Features
10
+
11
+ - Automatically configures queue routes by scanning the `queues` directory
12
+ - Overwrite queue types in your env using the `npx nitro-queue types cloudflare.d.ts` command
13
+ - The module handles routing queues from `cloudflare:queue` automatically
14
+
15
+ # Prerequisites
16
+
17
+ ## Initialize the module
18
+
19
+ ```ts
20
+ // nitro.config.ts
21
+ import queue from 'nitro-queue'
22
+ import { defineNitroConfig } from 'nitro/config'
23
+
24
+ export default defineNitroConfig({
25
+ modules: [queue()],
26
+ })
27
+ ```
28
+
29
+ ## cloudflare.d.ts
30
+
31
+ Cloudflare can generate types through the wrangler config file.
32
+ nitro-queue then modifies the types generated by wrangler to match your project's queue types.
33
+
34
+ ```json
35
+ {
36
+ "scripts": {
37
+ "prepare": "pnpm types:cloudflare && pnpm types:queue",
38
+ "types:cloudflare": "wrangler types cloudflare.d.ts --config ./.output/server/wrangler.json",
39
+ "types:queue": "npx nitro-queue types cloudflare.d.ts"
40
+ }
41
+ }
42
+ ```
43
+
44
+ # How to use
45
+
46
+ ```ts
47
+ // queues/user/remove.ts
48
+ export default defineQueue<{
49
+ userId: string,
50
+ }>({
51
+ run(payload) {
52
+ console.log(payload.userId)
53
+ }
54
+ })
55
+
56
+ // routes/publish.ts
57
+ export default defineHandler(async (e) => {
58
+ e.runtime.cloudflare.env.MY_QUEUE.send({
59
+ type: 'user:remove',
60
+ payload: { userId }
61
+ })
62
+ })
63
+ ```
64
+
65
+ # Note
66
+
67
+ This module has not been thoroughly tested. If you encounter any bugs, please feel free to open an issue. Your feedback is greatly appreciated.
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+
package/dist/cli.mjs ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { replaceInFileSync } from 'replace-in-file';
4
+
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+ if (command === "types") {
8
+ const targetFile = args[1];
9
+ if (!targetFile) {
10
+ console.error("Usage: nitro-queue types <filename>");
11
+ process.exit(1);
12
+ }
13
+ try {
14
+ readFileSync(targetFile);
15
+ } catch {
16
+ console.error(`File not found: ${targetFile}`);
17
+ process.exit(1);
18
+ }
19
+ replaceInFileSync({
20
+ files: targetFile,
21
+ from: /^/,
22
+ to: `import type { QueueMessageBody } from './node_modules/.nitro/server/queue'
23
+ `
24
+ });
25
+ replaceInFileSync({
26
+ files: targetFile,
27
+ from: /: Queue;/g,
28
+ to: ": Queue<QueueMessageBody>;"
29
+ });
30
+ console.log(`Patched: ${targetFile}`);
31
+ }
@@ -0,0 +1,29 @@
1
+ import { NitroRuntimeHooks, NitroModule } from 'nitro/types';
2
+
3
+ interface QueueRetryOptions {
4
+ delaySeconds?: number;
5
+ }
6
+ interface Message<Body = unknown> {
7
+ readonly id: string;
8
+ readonly timestamp: Date;
9
+ readonly body: Body;
10
+ readonly attempts: number;
11
+ retry(options?: QueueRetryOptions): void;
12
+ ack(): void;
13
+ }
14
+
15
+ interface QueueEndpoint<Payload = null> {
16
+ run: (payload: Payload, context: {
17
+ queue: Parameters<NitroRuntimeHooks['cloudflare:queue']>[0]
18
+ message: Message<unknown>
19
+ }) => void | Promise<void>
20
+ }
21
+
22
+ declare function defineQueue<Payload = null>(endpoint: QueueEndpoint<Payload>): {
23
+ endpoint: QueueEndpoint<Payload>;
24
+ $payload: Payload;
25
+ };
26
+
27
+ declare function initQueue(): NitroModule;
28
+
29
+ export { initQueue as default, defineQueue };
@@ -0,0 +1,29 @@
1
+ import { NitroRuntimeHooks, NitroModule } from 'nitro/types';
2
+
3
+ interface QueueRetryOptions {
4
+ delaySeconds?: number;
5
+ }
6
+ interface Message<Body = unknown> {
7
+ readonly id: string;
8
+ readonly timestamp: Date;
9
+ readonly body: Body;
10
+ readonly attempts: number;
11
+ retry(options?: QueueRetryOptions): void;
12
+ ack(): void;
13
+ }
14
+
15
+ interface QueueEndpoint<Payload = null> {
16
+ run: (payload: Payload, context: {
17
+ queue: Parameters<NitroRuntimeHooks['cloudflare:queue']>[0]
18
+ message: Message<unknown>
19
+ }) => void | Promise<void>
20
+ }
21
+
22
+ declare function defineQueue<Payload = null>(endpoint: QueueEndpoint<Payload>): {
23
+ endpoint: QueueEndpoint<Payload>;
24
+ $payload: Payload;
25
+ };
26
+
27
+ declare function initQueue(): NitroModule;
28
+
29
+ export { initQueue as default, defineQueue };
package/dist/index.mjs ADDED
@@ -0,0 +1,89 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { join, relative, dirname } from 'pathe';
3
+ import { stat, readdir, mkdir, writeFile } from 'node:fs/promises';
4
+
5
+ function defineQueue(endpoint) {
6
+ return {
7
+ endpoint,
8
+ $payload: {}
9
+ };
10
+ }
11
+
12
+ async function scanQueues(queuesDir) {
13
+ const queues = [];
14
+ try {
15
+ await stat(queuesDir);
16
+ } catch {
17
+ return queues;
18
+ }
19
+ await scanDirectory(queuesDir, queuesDir, queues);
20
+ return queues;
21
+ }
22
+ async function scanDirectory(baseDir, currentDir, queues) {
23
+ const entries = await readdir(currentDir, { withFileTypes: true });
24
+ for (const entry of entries) {
25
+ const fullPath = join(currentDir, entry.name);
26
+ if (entry.isDirectory()) {
27
+ await scanDirectory(baseDir, fullPath, queues);
28
+ } else if (entry.isFile() && entry.name.endsWith(".ts")) {
29
+ const relativePath = relative(baseDir, fullPath);
30
+ const name = relativePath.replace(/\.ts$/, "").replace(/\//g, ":");
31
+ queues.push({ name, filePath: fullPath });
32
+ }
33
+ }
34
+ }
35
+
36
+ async function generateQueueTypes(queues, outputPath, queuesDir) {
37
+ await mkdir(dirname(outputPath), { recursive: true });
38
+ const content = generateTypeContent(queues, outputPath);
39
+ await writeFile(outputPath, content, "utf-8");
40
+ }
41
+ function generateTypeContent(queues, outputPath, _queuesDir) {
42
+ const outputDir = dirname(outputPath);
43
+ const lines = [
44
+ "type QueueMessageBody = "
45
+ ];
46
+ for (const queue of queues) {
47
+ const relativePath = relative(outputDir, queue.filePath).replace(/\.ts$/, "");
48
+ lines.push(` | { type: '${queue.name}', payload: typeof import('${relativePath}').default.$payload }`);
49
+ }
50
+ lines.push("export { QueueMessageBody }");
51
+ lines.push("");
52
+ return lines.join("\n");
53
+ }
54
+
55
+ function initQueue() {
56
+ return {
57
+ name: "nitro-queue",
58
+ async setup(nitro) {
59
+ const serverDir = nitro.options.serverDir || nitro.options.rootDir;
60
+ const queuesDir = join(serverDir, "queues");
61
+ const outputPath = join(nitro.options.rootDir, "node_modules/.nitro/server/nitro-queue.d.ts");
62
+ async function generateTypes() {
63
+ const queues = await scanQueues(queuesDir);
64
+ await generateQueueTypes(queues, outputPath);
65
+ }
66
+ async function generateVirtualHandlers() {
67
+ const queues = await scanQueues(queuesDir);
68
+ const imports = [];
69
+ const handlerEntries = [];
70
+ queues.forEach((queue, index) => {
71
+ const varName = `handler${index}`;
72
+ imports.push(`import ${varName} from '${queue.filePath}'`);
73
+ handlerEntries.push(` '${queue.name}': ${varName}`);
74
+ });
75
+ return `${imports.join("\n")}
76
+
77
+ export const handlers = {
78
+ ${handlerEntries.join(",\n")}
79
+ }`;
80
+ }
81
+ nitro.hooks.hook("build:before", generateTypes);
82
+ const virtualContent = await generateVirtualHandlers();
83
+ nitro.options.virtual["#queue-handlers"] = virtualContent;
84
+ nitro.options.plugins.push(fileURLToPath(new URL("./runtime/plugin", import.meta.url)));
85
+ }
86
+ };
87
+ }
88
+
89
+ export { initQueue as default, defineQueue };
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+
3
+ export { _default as default };
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+
3
+ export { _default as default };
@@ -0,0 +1,21 @@
1
+ import { handlers } from '#queue-handlers';
2
+ import { definePlugin } from 'nitro';
3
+
4
+ const plugin = definePlugin((nitroApp) => {
5
+ nitroApp.hooks.hook("cloudflare:queue", async (queue) => {
6
+ for (const message of queue.batch.messages) {
7
+ if (typeof message.body !== "object" || message.body === null)
8
+ continue;
9
+ if ("type" in message.body && typeof message.body.type === "string") {
10
+ const body = message.body;
11
+ const handler = handlers[body.type];
12
+ if (!handler) {
13
+ throw new Error(`Unknown queue type: ${body.type}`);
14
+ }
15
+ await handler.endpoint.run(body.payload, { queue, message });
16
+ }
17
+ }
18
+ });
19
+ });
20
+
21
+ export { plugin as default };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "nitro-queue",
3
+ "type": "module",
4
+ "private": false,
5
+ "version": "0.0.1",
6
+ "description": "Queue module for Nitro",
7
+ "license": "MIT",
8
+ "bin": {
9
+ "nitro-queue": "./dist/cli.mjs"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs"
15
+ }
16
+ },
17
+ "main": "./dist/index.mjs",
18
+ "module": "./dist/index.mjs",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "peerDependencies": {
24
+ "nitro": "^3.0.1-alpha.2"
25
+ },
26
+ "dependencies": {
27
+ "pathe": "^2.0.3",
28
+ "replace-in-file": "^8.4.0"
29
+ },
30
+ "devDependencies": {
31
+ "@antfu/eslint-config": "^3.14.0",
32
+ "@cloudflare/workers-types": "^4.20260206.0",
33
+ "@types/node": "^25.2.1",
34
+ "chokidar-cli": "^3.0.0",
35
+ "eslint": "^9.19.0",
36
+ "hookable": "^6.0.1",
37
+ "npm-run-all": "^4.1.5",
38
+ "typescript": "^5.7.3",
39
+ "unbuild": "^3.3.1",
40
+ "wrangler": "^4.63.0"
41
+ },
42
+ "scripts": {
43
+ "prepare.playground": "cd playground && pnpm i",
44
+ "build": "unbuild",
45
+ "dev": "chokidar 'src/**/*' -c 'unbuild --stub'",
46
+ "lint": "eslint .",
47
+ "lint:fix": "eslint . --fix",
48
+ "play:dev": "npm run dev && npm -C playground run dev",
49
+ "play:build": "npm run build && npm -C playground run build"
50
+ }
51
+ }