beanstalkd-dashboard 0.1.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.
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>beanstalkd dashboard</title>
7
+ <script type="module" crossorigin src="/assets/index-N_ss1O2V.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BDcT3jO0.css">
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,7 @@
1
+ import { BeanstalkdClient } from 'beanstalkd-ts';
2
+ export declare class BeanstalkdServer {
3
+ readonly id: number;
4
+ readonly address: string;
5
+ readonly bsClient: BeanstalkdClient;
6
+ constructor(id: number, address: string);
7
+ }
@@ -0,0 +1,9 @@
1
+ import { BeanstalkdClient } from 'beanstalkd-ts';
2
+ export class BeanstalkdServer {
3
+ constructor(id, address) {
4
+ this.id = id;
5
+ this.address = address;
6
+ const [host, port] = address.split(':');
7
+ this.bsClient = new BeanstalkdClient({ host, port: Number(port) });
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { BeanstalkdClient } from 'beanstalkd-ts';
11
+ const tube = 'console-demo';
12
+ function producer() {
13
+ return __awaiter(this, void 0, void 0, function* () {
14
+ const bsClient = new BeanstalkdClient();
15
+ bsClient.defaultTtr = 300;
16
+ yield bsClient.connect();
17
+ yield bsClient.use(tube);
18
+ for (;;) {
19
+ yield Promise.all([
20
+ bsClient.put('demo content', { ttr: 1000 }),
21
+ bsClient.put('demo content', { ttr: 1000 }),
22
+ bsClient.put('demo content', { ttr: 1000 }),
23
+ bsClient.put('demo content', { ttr: 1000 }),
24
+ bsClient.put('demo content', { ttr: 1000 }),
25
+ bsClient.put('demo content', { delay: 10, ttr: 1000 }),
26
+ ]);
27
+ yield new Promise((r) => setTimeout(r, Math.round(Math.random() * 400)));
28
+ }
29
+ });
30
+ }
31
+ function worker() {
32
+ return __awaiter(this, arguments, void 0, function* (limit = Infinity) {
33
+ const bsClient = new BeanstalkdClient();
34
+ yield bsClient.connect();
35
+ yield bsClient.watch(tube);
36
+ yield bsClient.ignore('default');
37
+ for (let jobs = 0; jobs < limit; jobs++) {
38
+ const job = yield bsClient.reserve();
39
+ yield new Promise((r) => setTimeout(r, Math.round(Math.random() * 100)));
40
+ yield bsClient.deleteJob(job.id);
41
+ }
42
+ yield bsClient.close();
43
+ });
44
+ }
45
+ function main() {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ setInterval(() => {
48
+ worker(Math.round(Math.random() * 10));
49
+ worker(Math.round(Math.random() * 20));
50
+ }, 100);
51
+ yield Promise.all([
52
+ producer(),
53
+ producer(),
54
+ producer(),
55
+ producer(),
56
+ worker(),
57
+ worker(),
58
+ ]);
59
+ });
60
+ }
61
+ main().catch((err) => {
62
+ console.log('main error:', err);
63
+ });
@@ -0,0 +1 @@
1
+ import 'reflect-metadata';
@@ -0,0 +1,58 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var _a, _b;
11
+ import 'reflect-metadata';
12
+ import { createServer } from 'node:http';
13
+ import path from 'node:path';
14
+ import * as trpcExpress from '@trpc/server/adapters/express';
15
+ import { program } from 'commander';
16
+ import cors from 'cors';
17
+ import express from 'express';
18
+ import { container } from 'tsyringe';
19
+ import ViteExpress from 'vite-express';
20
+ import z from 'zod';
21
+ import { BeanstalkdServer } from './beanstalkd.js';
22
+ import injectionTokens from './injection-tokens.js';
23
+ import { appRouter } from './router.js';
24
+ const host = (_a = process.env.HOST) !== null && _a !== void 0 ? _a : '127.0.0.1';
25
+ const port = Number((_b = process.env.PORT) !== null && _b !== void 0 ? _b : '3000');
26
+ const optionsSchema = z.object({
27
+ servers: z.string().default('localhost:11300'),
28
+ });
29
+ const prog = program
30
+ .option('--servers <addresses>', 'Beanstalkd server addresses in format [host:port,...] (comma separated). Example: localhost:11300', 'localhost:11300')
31
+ .parse(process.argv);
32
+ function main() {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ const { servers } = optionsSchema.parse(prog.opts());
35
+ const app = express();
36
+ const server = createServer(app);
37
+ const bsServers = servers
38
+ .split(',')
39
+ .map((address, i) => new BeanstalkdServer(i + 1, address));
40
+ yield Promise.all(bsServers.map((server) => server.bsClient.connect()));
41
+ container.registerInstance(injectionTokens.beanstalkdServers, bsServers);
42
+ app.use(cors({}));
43
+ app.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter }));
44
+ ViteExpress.config({
45
+ mode: 'production',
46
+ inlineViteConfig: {
47
+ build: {
48
+ outDir: path.join(import.meta.dirname, '..', 'dist'),
49
+ },
50
+ },
51
+ });
52
+ ViteExpress.bind(app, server);
53
+ server.listen(port, host, () => {
54
+ console.log(`Server started listening on http://${host}:${port}`);
55
+ });
56
+ });
57
+ }
58
+ main().catch((err) => console.log('Main error:', err));
@@ -0,0 +1,6 @@
1
+ import type { InjectionToken } from 'tsyringe';
2
+ import type { BeanstalkdServer } from './beanstalkd.js';
3
+ declare const _default: {
4
+ beanstalkdServers: InjectionToken<BeanstalkdServer[]>;
5
+ };
6
+ export default _default;
@@ -0,0 +1,2 @@
1
+ const beanstalkdServers = Symbol('beanstalkd-servers');
2
+ export default { beanstalkdServers };
@@ -0,0 +1,91 @@
1
+ import { type JobStats, type TubeStats } from 'beanstalkd-ts';
2
+ import type { BeanstalkdServer } from './beanstalkd.js';
3
+ export interface TubeWithStats {
4
+ name: string;
5
+ stats: TubeStats;
6
+ }
7
+ export interface JobWitStats {
8
+ job: {
9
+ id: number;
10
+ payload: string;
11
+ };
12
+ stats: JobStats;
13
+ }
14
+ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
15
+ ctx: object;
16
+ meta: object;
17
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
18
+ transformer: false;
19
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
20
+ servers: {
21
+ list: import("@trpc/server").TRPCQueryProcedure<{
22
+ input: void;
23
+ output: BeanstalkdServer[];
24
+ meta: object;
25
+ }>;
26
+ };
27
+ jobs: {
28
+ peekBuried: import("@trpc/server").TRPCQueryProcedure<{
29
+ input: {
30
+ serverId: number;
31
+ tube: string;
32
+ };
33
+ output: JobWitStats | null;
34
+ meta: object;
35
+ }>;
36
+ peekDelayed: import("@trpc/server").TRPCQueryProcedure<{
37
+ input: {
38
+ serverId: number;
39
+ tube: string;
40
+ };
41
+ output: JobWitStats | null;
42
+ meta: object;
43
+ }>;
44
+ peekReady: import("@trpc/server").TRPCQueryProcedure<{
45
+ input: {
46
+ serverId: number;
47
+ tube: string;
48
+ };
49
+ output: JobWitStats | null;
50
+ meta: object;
51
+ }>;
52
+ };
53
+ tubes: {
54
+ clear: import("@trpc/server").TRPCMutationProcedure<{
55
+ input: {
56
+ serverId: number;
57
+ tube: string;
58
+ };
59
+ output: string;
60
+ meta: object;
61
+ }>;
62
+ pause: import("@trpc/server").TRPCMutationProcedure<{
63
+ input: {
64
+ serverId: number;
65
+ tube: string;
66
+ seconds: number;
67
+ };
68
+ output: string;
69
+ meta: object;
70
+ }>;
71
+ list: import("@trpc/server").TRPCQueryProcedure<{
72
+ input: {
73
+ serverId: number;
74
+ };
75
+ output: {
76
+ name: string;
77
+ stats: TubeStats;
78
+ }[];
79
+ meta: object;
80
+ }>;
81
+ tubeStats: import("@trpc/server").TRPCQueryProcedure<{
82
+ input: {
83
+ serverId: number;
84
+ tube: string;
85
+ };
86
+ output: TubeStats;
87
+ meta: object;
88
+ }>;
89
+ };
90
+ }>>;
91
+ export type AppRouter = typeof appRouter;
@@ -0,0 +1,118 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { NotFoundError } from 'beanstalkd-ts';
11
+ import { container } from 'tsyringe';
12
+ import z from 'zod';
13
+ import injectionTokens from './injection-tokens.js';
14
+ import { publicProcedure, router } from './trpc.js';
15
+ function servers() {
16
+ return container.resolve(injectionTokens.beanstalkdServers);
17
+ }
18
+ function getServer(id) {
19
+ const server = servers().find((s) => s.id === id);
20
+ if (!server)
21
+ throw new Error(`Server${id} not found.`);
22
+ return server;
23
+ }
24
+ export const appRouter = router({
25
+ servers: {
26
+ list: publicProcedure.query(() => __awaiter(void 0, void 0, void 0, function* () { return servers(); })),
27
+ },
28
+ jobs: {
29
+ peekBuried: jobStatsProcedure('buried'),
30
+ peekDelayed: jobStatsProcedure('delayed'),
31
+ peekReady: jobStatsProcedure('ready'),
32
+ },
33
+ tubes: {
34
+ clear: publicProcedure
35
+ .input(z.object({ serverId: z.int(), tube: z.string() }))
36
+ .mutation((opts) => __awaiter(void 0, void 0, void 0, function* () {
37
+ const server = getServer(opts.input.serverId);
38
+ const states = ['buried', 'delayed', 'ready'];
39
+ yield server.bsClient.use(opts.input.tube);
40
+ for (const state of states) {
41
+ for (;;) {
42
+ const job = yield peekJob(server, state);
43
+ if (!job)
44
+ break;
45
+ yield server.bsClient.deleteJob(job.jobId);
46
+ }
47
+ }
48
+ yield server.bsClient.use('default');
49
+ return 'ok';
50
+ })),
51
+ pause: publicProcedure
52
+ .input(z.object({ serverId: z.int(), tube: z.string(), seconds: z.int() }))
53
+ .mutation((opts) => __awaiter(void 0, void 0, void 0, function* () {
54
+ const server = getServer(opts.input.serverId);
55
+ yield server.bsClient.pauseTube(opts.input.tube, opts.input.seconds);
56
+ return 'ok';
57
+ })),
58
+ list: publicProcedure
59
+ .input(z.object({ serverId: z.int() }))
60
+ .query((opts) => __awaiter(void 0, void 0, void 0, function* () {
61
+ const server = getServer(opts.input.serverId);
62
+ const tubes = yield server.bsClient.listTubes();
63
+ const tubeStats = yield Promise.all(tubes.map((tube) => server.bsClient.statsTube(tube)));
64
+ return tubes.map((tube, i) =>
65
+ // biome-ignore lint/style/noNonNullAssertion: we fetch stats for each tube above
66
+ ({ name: tube, stats: tubeStats[i] }));
67
+ })),
68
+ tubeStats: publicProcedure
69
+ .input(z.object({ serverId: z.int(), tube: z.string() }))
70
+ .query((opts) => __awaiter(void 0, void 0, void 0, function* () {
71
+ const server = getServer(opts.input.serverId);
72
+ return yield server.bsClient.statsTube(opts.input.tube);
73
+ })),
74
+ },
75
+ });
76
+ function jobStatsProcedure(state) {
77
+ return publicProcedure
78
+ .input(z.object({ serverId: z.int(), tube: z.string() }))
79
+ .query((opts) => __awaiter(this, void 0, void 0, function* () {
80
+ const server = getServer(opts.input.serverId);
81
+ const { tube } = opts.input;
82
+ yield server.bsClient.use(tube);
83
+ const job = yield peekJob(server, state);
84
+ if (!job)
85
+ return null;
86
+ try {
87
+ const jobStats = yield server.bsClient.statsJob(job.jobId);
88
+ return {
89
+ job: { id: job.jobId, payload: job.payload.toString() },
90
+ stats: jobStats,
91
+ };
92
+ }
93
+ catch (err) {
94
+ if (err instanceof NotFoundError) {
95
+ return null;
96
+ }
97
+ throw err; // rethrow
98
+ }
99
+ }));
100
+ }
101
+ function peekJob(server, state) {
102
+ return __awaiter(this, void 0, void 0, function* () {
103
+ try {
104
+ const job = yield server.bsClient[state === 'buried'
105
+ ? 'peekBuried'
106
+ : state === 'delayed'
107
+ ? 'peekDelayed'
108
+ : 'peekReady']();
109
+ return job;
110
+ }
111
+ catch (err) {
112
+ if (err instanceof NotFoundError) {
113
+ return null;
114
+ }
115
+ throw err; // rethrow
116
+ }
117
+ });
118
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Export reusable router and procedure helpers
3
+ * that can be used throughout the router
4
+ */
5
+ export declare const router: import("@trpc/server").TRPCRouterBuilder<{
6
+ ctx: object;
7
+ meta: object;
8
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
9
+ transformer: false;
10
+ }>;
11
+ export declare const publicProcedure: import("@trpc/server").TRPCProcedureBuilder<object, object, object, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
@@ -0,0 +1,12 @@
1
+ import { initTRPC } from '@trpc/server';
2
+ /**
3
+ * Initialization of tRPC backend
4
+ * Should be done only once per backend!
5
+ */
6
+ const t = initTRPC.create();
7
+ /**
8
+ * Export reusable router and procedure helpers
9
+ * that can be used throughout the router
10
+ */
11
+ export const router = t.router;
12
+ export const publicProcedure = t.procedure;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "beanstalkd-dashboard",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "bin": "./bin/cli.js",
7
+ "files": [
8
+ "./bin/cli.js",
9
+ "./dist",
10
+ "./dist-server"
11
+ ],
12
+ "scripts": {
13
+ "start": "node dist-server/index.js",
14
+ "dev": "vite",
15
+ "dev:trpc": "bun --watch --no-clear-console server/index.ts",
16
+ "build": "npm run build:server && npm run build:ui",
17
+ "build:server": "tsc -p tsconfig.server.json",
18
+ "build:ui": "tsc -b && vite build",
19
+ "preview": "vite preview",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@fontsource-variable/space-grotesk": "^5.2.8",
24
+ "@fontsource/archivo-black": "^5.2.6",
25
+ "@radix-ui/react-accordion": "^1.2.12",
26
+ "@radix-ui/react-dialog": "^1.1.15",
27
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
28
+ "@radix-ui/react-label": "^2.1.7",
29
+ "@radix-ui/react-select": "^2.2.6",
30
+ "@radix-ui/react-visually-hidden": "^1.2.3",
31
+ "@tailwindcss/vite": "^4.1.12",
32
+ "@tanstack/react-query": "^5.85.3",
33
+ "@tanstack/react-table": "^8.21.3",
34
+ "@trpc/client": "^11.4.4",
35
+ "@trpc/server": "^11.4.4",
36
+ "@trpc/tanstack-react-query": "^11.4.4",
37
+ "beanstalkd-ts": "^0.1.6",
38
+ "class-variance-authority": "^0.7.1",
39
+ "clsx": "^2.1.1",
40
+ "commander": "^14.0.0",
41
+ "cors": "^2.8.5",
42
+ "express": "^5.1.0",
43
+ "lucide-react": "^0.539.0",
44
+ "preact": "^10.27.0",
45
+ "preact-iso": "^2.9.2",
46
+ "react": "^19.1.1",
47
+ "reflect-metadata": "^0.2.2",
48
+ "tailwind-merge": "^3.3.1",
49
+ "tailwindcss": "^4.1.12",
50
+ "tsyringe": "^4.10.0",
51
+ "vite-express": "^0.21.1",
52
+ "zod": "^4.0.17",
53
+ "zustand": "^5.0.7"
54
+ },
55
+ "devDependencies": {
56
+ "@biomejs/biome": "^2.2.0",
57
+ "@preact/preset-vite": "^2.10.2",
58
+ "@types/cors": "^2.8.19",
59
+ "@types/express": "^5.0.3",
60
+ "ts-node": "^10.9.2",
61
+ "tw-animate-css": "^1.3.6",
62
+ "typescript": "~5.9.2",
63
+ "vite": "^7.1.2"
64
+ }
65
+ }