@donkeylabs/cli 0.1.0 → 0.4.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/package.json +2 -2
- package/src/client/base.ts +481 -0
- package/src/commands/generate.ts +262 -53
- package/src/index.ts +0 -0
- package/templates/starter/package.json +3 -3
- package/templates/starter/src/index.ts +19 -30
- package/templates/starter/src/routes/health/handlers/ping.ts +22 -0
- package/templates/starter/src/routes/health/index.ts +16 -2
- package/templates/sveltekit-app/bun.lock +547 -0
- package/templates/sveltekit-app/donkeylabs.config.ts +2 -0
- package/templates/sveltekit-app/package.json +10 -8
- package/templates/sveltekit-app/scripts/watch-server.ts +55 -0
- package/templates/sveltekit-app/src/lib/api.ts +195 -81
- package/templates/sveltekit-app/src/routes/+page.server.ts +3 -3
- package/templates/sveltekit-app/src/routes/+page.svelte +235 -96
- package/templates/sveltekit-app/src/server/index.ts +29 -247
- package/templates/sveltekit-app/src/server/plugins/demo/index.ts +144 -0
- package/templates/sveltekit-app/src/server/routes/cache/handlers/delete.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/cache/handlers/get.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/cache/handlers/keys.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/cache/handlers/set.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/cache/index.ts +46 -0
- package/templates/sveltekit-app/src/server/routes/counter/handlers/decrement.ts +17 -0
- package/templates/sveltekit-app/src/server/routes/counter/handlers/get.ts +17 -0
- package/templates/sveltekit-app/src/server/routes/counter/handlers/increment.ts +17 -0
- package/templates/sveltekit-app/src/server/routes/counter/handlers/reset.ts +17 -0
- package/templates/sveltekit-app/src/server/routes/counter/index.ts +39 -0
- package/templates/sveltekit-app/src/server/routes/cron/handlers/list.ts +17 -0
- package/templates/sveltekit-app/src/server/routes/cron/index.ts +24 -0
- package/templates/sveltekit-app/src/server/routes/events/handlers/emit.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/events/index.ts +19 -0
- package/templates/sveltekit-app/src/server/routes/index.ts +8 -0
- package/templates/sveltekit-app/src/server/routes/jobs/handlers/enqueue.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/jobs/handlers/stats.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/jobs/index.ts +28 -0
- package/templates/sveltekit-app/src/server/routes/ratelimit/handlers/check.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/ratelimit/handlers/reset.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/ratelimit/index.ts +29 -0
- package/templates/sveltekit-app/src/server/routes/sse/handlers/broadcast.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/sse/handlers/clients.ts +15 -0
- package/templates/sveltekit-app/src/server/routes/sse/index.ts +28 -0
- package/templates/sveltekit-app/{svelte.config.js → svelte.config.ts} +4 -5
- package/templates/sveltekit-app/tsconfig.json +4 -9
- package/templates/sveltekit-app/vite.config.ts +2 -1
- package/templates/starter/CLAUDE.md +0 -144
- package/templates/starter/src/client.test.ts +0 -7
- package/templates/starter/src/db.ts +0 -9
- package/templates/starter/src/routes/health/ping/index.ts +0 -13
- package/templates/starter/src/routes/health/ping/models/model.ts +0 -23
- package/templates/starter/src/routes/health/ping/schema.ts +0 -14
- package/templates/starter/src/routes/health/ping/tests/integ.test.ts +0 -20
- package/templates/starter/src/routes/health/ping/tests/unit.test.ts +0 -21
- package/templates/starter/src/test-ctx.ts +0 -24
|
@@ -1,263 +1,45 @@
|
|
|
1
1
|
// Server entry for @donkeylabs/adapter-sveltekit
|
|
2
|
-
import { AppServer
|
|
2
|
+
import { AppServer } from "@donkeylabs/server";
|
|
3
3
|
import { Kysely } from "kysely";
|
|
4
4
|
import { BunSqliteDialect } from "kysely-bun-sqlite";
|
|
5
5
|
import { Database } from "bun:sqlite";
|
|
6
|
-
import {
|
|
6
|
+
import { demoPlugin } from "./plugins/demo";
|
|
7
|
+
|
|
8
|
+
// Import routes
|
|
9
|
+
import {
|
|
10
|
+
counterRoutes,
|
|
11
|
+
cacheRoutes,
|
|
12
|
+
jobsRoutes,
|
|
13
|
+
cronRoutes,
|
|
14
|
+
ratelimitRoutes,
|
|
15
|
+
eventsRoutes,
|
|
16
|
+
sseRoutes,
|
|
17
|
+
} from "./routes";
|
|
7
18
|
|
|
8
19
|
// Simple in-memory database
|
|
9
20
|
const db = new Kysely<{}>({
|
|
10
21
|
dialect: new BunSqliteDialect({ database: new Database(":memory:") }),
|
|
11
22
|
});
|
|
12
23
|
|
|
13
|
-
//
|
|
14
|
-
const eventMessages = [
|
|
15
|
-
"User logged in",
|
|
16
|
-
"New order placed",
|
|
17
|
-
"Payment received",
|
|
18
|
-
"Item shipped",
|
|
19
|
-
"Review submitted",
|
|
20
|
-
"Comment added",
|
|
21
|
-
"File uploaded",
|
|
22
|
-
"Task completed",
|
|
23
|
-
"Alert triggered",
|
|
24
|
-
"Sync finished",
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
// Demo plugin with all core service integrations
|
|
28
|
-
const demoPlugin = createPlugin.define({
|
|
29
|
-
name: "demo",
|
|
30
|
-
service: async (ctx) => {
|
|
31
|
-
let counter = 0;
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
// Counter
|
|
35
|
-
getCounter: () => counter,
|
|
36
|
-
increment: () => ++counter,
|
|
37
|
-
decrement: () => --counter,
|
|
38
|
-
reset: () => { counter = 0; return counter; },
|
|
39
|
-
|
|
40
|
-
// Cache helpers
|
|
41
|
-
cacheSet: async (key: string, value: any, ttl?: number) => {
|
|
42
|
-
await ctx.core.cache.set(key, value, ttl);
|
|
43
|
-
return { success: true };
|
|
44
|
-
},
|
|
45
|
-
cacheGet: async (key: string) => {
|
|
46
|
-
const value = await ctx.core.cache.get(key);
|
|
47
|
-
const exists = await ctx.core.cache.has(key);
|
|
48
|
-
return { value, exists };
|
|
49
|
-
},
|
|
50
|
-
cacheDelete: async (key: string) => {
|
|
51
|
-
await ctx.core.cache.delete(key);
|
|
52
|
-
return { success: true };
|
|
53
|
-
},
|
|
54
|
-
cacheKeys: async () => {
|
|
55
|
-
const keys = await ctx.core.cache.keys();
|
|
56
|
-
return { keys, size: keys.length };
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
// Jobs helpers
|
|
60
|
-
enqueueJob: async (name: string, data: any, delay?: number) => {
|
|
61
|
-
let jobId: string;
|
|
62
|
-
if (delay && delay > 0) {
|
|
63
|
-
const runAt = new Date(Date.now() + delay);
|
|
64
|
-
jobId = await ctx.core.jobs.schedule(name, data, runAt);
|
|
65
|
-
} else {
|
|
66
|
-
jobId = await ctx.core.jobs.enqueue(name, data);
|
|
67
|
-
}
|
|
68
|
-
return { jobId };
|
|
69
|
-
},
|
|
70
|
-
getJobStats: async () => {
|
|
71
|
-
const pending = await ctx.core.jobs.getByName("demo-job", "pending");
|
|
72
|
-
const running = await ctx.core.jobs.getByName("demo-job", "running");
|
|
73
|
-
const completed = await ctx.core.jobs.getByName("demo-job", "completed");
|
|
74
|
-
return {
|
|
75
|
-
pending: pending.length,
|
|
76
|
-
running: running.length,
|
|
77
|
-
completed: completed.length,
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
// Cron helpers
|
|
82
|
-
getCronTasks: () => ctx.core.cron.list().map(t => ({
|
|
83
|
-
id: t.id,
|
|
84
|
-
name: t.name,
|
|
85
|
-
expression: t.expression,
|
|
86
|
-
enabled: t.enabled,
|
|
87
|
-
lastRun: t.lastRun?.toISOString(),
|
|
88
|
-
nextRun: t.nextRun?.toISOString(),
|
|
89
|
-
})),
|
|
90
|
-
|
|
91
|
-
// Rate limiter helpers
|
|
92
|
-
checkRateLimit: async (key: string, limit: number, window: number) => {
|
|
93
|
-
return ctx.core.rateLimiter.check(key, limit, window);
|
|
94
|
-
},
|
|
95
|
-
resetRateLimit: async (key: string) => {
|
|
96
|
-
await ctx.core.rateLimiter.reset(key);
|
|
97
|
-
return { success: true };
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
// Events helpers (internal pub/sub)
|
|
101
|
-
emitEvent: async (event: string, data: any) => {
|
|
102
|
-
await ctx.core.events.emit(event, data);
|
|
103
|
-
return { success: true };
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
// SSE broadcast
|
|
107
|
-
broadcast: (channel: string, event: string, data: any) => {
|
|
108
|
-
ctx.core.sse.broadcast(channel, event, data);
|
|
109
|
-
return { success: true };
|
|
110
|
-
},
|
|
111
|
-
getSSEClients: () => ({
|
|
112
|
-
total: ctx.core.sse.getClients().length,
|
|
113
|
-
byChannel: ctx.core.sse.getClientsByChannel("events").length,
|
|
114
|
-
}),
|
|
115
|
-
};
|
|
116
|
-
},
|
|
117
|
-
init: async (ctx) => {
|
|
118
|
-
// Register job handler for demo
|
|
119
|
-
ctx.core.jobs.register("demo-job", async (data) => {
|
|
120
|
-
ctx.core.logger.info("Demo job executed", { data });
|
|
121
|
-
// Broadcast job completion via SSE
|
|
122
|
-
ctx.core.sse.broadcast("events", "job-completed", {
|
|
123
|
-
id: Date.now(),
|
|
124
|
-
message: `Job completed: ${data.message || "No message"}`,
|
|
125
|
-
timestamp: new Date().toISOString(),
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Schedule cron job to broadcast SSE events every 5 seconds
|
|
130
|
-
ctx.core.cron.schedule("*/5 * * * * *", () => {
|
|
131
|
-
const message = eventMessages[Math.floor(Math.random() * eventMessages.length)];
|
|
132
|
-
ctx.core.sse.broadcast("events", "cron-event", {
|
|
133
|
-
id: Date.now(),
|
|
134
|
-
message,
|
|
135
|
-
timestamp: new Date().toISOString(),
|
|
136
|
-
source: "cron",
|
|
137
|
-
});
|
|
138
|
-
}, { name: "sse-broadcaster" });
|
|
139
|
-
|
|
140
|
-
// Listen for internal events and broadcast to SSE
|
|
141
|
-
ctx.core.events.on("demo.*", (data) => {
|
|
142
|
-
ctx.core.sse.broadcast("events", "internal-event", {
|
|
143
|
-
id: Date.now(),
|
|
144
|
-
message: `Internal event: ${JSON.stringify(data)}`,
|
|
145
|
-
timestamp: new Date().toISOString(),
|
|
146
|
-
source: "events",
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
ctx.core.logger.info("Demo plugin initialized with all core services");
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// Create routes
|
|
155
|
-
const api = createRouter("api");
|
|
156
|
-
|
|
157
|
-
// Counter routes
|
|
158
|
-
api.route("counter.get").typed({
|
|
159
|
-
handle: async (_input, ctx) => ({ count: ctx.plugins.demo.getCounter() }),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
api.route("counter.increment").typed({
|
|
163
|
-
handle: async (_input, ctx) => ({ count: ctx.plugins.demo.increment() }),
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
api.route("counter.decrement").typed({
|
|
167
|
-
handle: async (_input, ctx) => ({ count: ctx.plugins.demo.decrement() }),
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
api.route("counter.reset").typed({
|
|
171
|
-
handle: async (_input, ctx) => ({ count: ctx.plugins.demo.reset() }),
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Cache routes
|
|
175
|
-
api.route("cache.set").typed({
|
|
176
|
-
input: z.object({
|
|
177
|
-
key: z.string(),
|
|
178
|
-
value: z.any(),
|
|
179
|
-
ttl: z.number().optional()
|
|
180
|
-
}),
|
|
181
|
-
handle: async (input, ctx) => ctx.plugins.demo.cacheSet(input.key, input.value, input.ttl),
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
api.route("cache.get").typed({
|
|
185
|
-
input: z.object({ key: z.string() }),
|
|
186
|
-
handle: async (input, ctx) => ctx.plugins.demo.cacheGet(input.key),
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
api.route("cache.delete").typed({
|
|
190
|
-
input: z.object({ key: z.string() }),
|
|
191
|
-
handle: async (input, ctx) => ctx.plugins.demo.cacheDelete(input.key),
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
api.route("cache.keys").typed({
|
|
195
|
-
handle: async (_input, ctx) => ctx.plugins.demo.cacheKeys(),
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// Jobs routes
|
|
199
|
-
api.route("jobs.enqueue").typed({
|
|
200
|
-
input: z.object({
|
|
201
|
-
name: z.string().default("demo-job"),
|
|
202
|
-
data: z.any().default({}),
|
|
203
|
-
delay: z.number().optional()
|
|
204
|
-
}),
|
|
205
|
-
handle: async (input, ctx) => ctx.plugins.demo.enqueueJob(input.name, input.data, input.delay),
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
api.route("jobs.stats").typed({
|
|
209
|
-
handle: async (_input, ctx) => ctx.plugins.demo.getJobStats(),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// Cron routes
|
|
213
|
-
api.route("cron.list").typed({
|
|
214
|
-
handle: async (_input, ctx) => ({ tasks: ctx.plugins.demo.getCronTasks() }),
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// Rate limiter routes
|
|
218
|
-
api.route("ratelimit.check").typed({
|
|
219
|
-
input: z.object({
|
|
220
|
-
key: z.string().default("demo"),
|
|
221
|
-
limit: z.number().default(5),
|
|
222
|
-
window: z.number().default(60000)
|
|
223
|
-
}),
|
|
224
|
-
handle: async (input, ctx) => ctx.plugins.demo.checkRateLimit(input.key, input.limit, input.window),
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
api.route("ratelimit.reset").typed({
|
|
228
|
-
input: z.object({ key: z.string().default("demo") }),
|
|
229
|
-
handle: async (input, ctx) => ctx.plugins.demo.resetRateLimit(input.key),
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Events routes (internal pub/sub)
|
|
233
|
-
api.route("events.emit").typed({
|
|
234
|
-
input: z.object({
|
|
235
|
-
event: z.string().default("demo.test"),
|
|
236
|
-
data: z.any().default({ test: true })
|
|
237
|
-
}),
|
|
238
|
-
handle: async (input, ctx) => ctx.plugins.demo.emitEvent(input.event, input.data),
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// SSE routes
|
|
242
|
-
api.route("sse.broadcast").typed({
|
|
243
|
-
input: z.object({
|
|
244
|
-
channel: z.string().default("events"),
|
|
245
|
-
event: z.string().default("manual"),
|
|
246
|
-
data: z.any()
|
|
247
|
-
}),
|
|
248
|
-
handle: async (input, ctx) => ctx.plugins.demo.broadcast(input.channel, input.event, input.data),
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
api.route("sse.clients").typed({
|
|
252
|
-
handle: async (_input, ctx) => ctx.plugins.demo.getSSEClients(),
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Create server
|
|
24
|
+
// Create server with auto type generation in dev mode
|
|
256
25
|
export const server = new AppServer({
|
|
257
26
|
db,
|
|
258
27
|
port: 0, // Port managed by adapter
|
|
28
|
+
generateTypes: {
|
|
29
|
+
output: "./src/lib/api.ts",
|
|
30
|
+
},
|
|
259
31
|
});
|
|
260
32
|
|
|
261
|
-
// Register plugin
|
|
33
|
+
// Register plugin
|
|
262
34
|
server.registerPlugin(demoPlugin);
|
|
263
|
-
|
|
35
|
+
|
|
36
|
+
// Register all routes
|
|
37
|
+
server.use(counterRoutes);
|
|
38
|
+
server.use(cacheRoutes);
|
|
39
|
+
server.use(jobsRoutes);
|
|
40
|
+
server.use(cronRoutes);
|
|
41
|
+
server.use(ratelimitRoutes);
|
|
42
|
+
server.use(eventsRoutes);
|
|
43
|
+
server.use(sseRoutes);
|
|
44
|
+
|
|
45
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Demo plugin with all core service integrations
|
|
2
|
+
import { createPlugin } from "@donkeylabs/server";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// Random event messages for SSE demo
|
|
6
|
+
const eventMessages = [
|
|
7
|
+
"User logged in",
|
|
8
|
+
"New order placed",
|
|
9
|
+
"Payment received",
|
|
10
|
+
"Item shipped",
|
|
11
|
+
"Review submitted",
|
|
12
|
+
"Comment added",
|
|
13
|
+
"File uploaded",
|
|
14
|
+
"Task completed",
|
|
15
|
+
"Alert triggered",
|
|
16
|
+
"Sync finished",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export const demoPlugin = createPlugin.define({
|
|
21
|
+
name: "demo",
|
|
22
|
+
service: async (ctx) => {
|
|
23
|
+
let counter = 0;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
// Counter
|
|
27
|
+
getCounter: () => counter,
|
|
28
|
+
increment: () => ++counter,
|
|
29
|
+
decrement: () => --counter,
|
|
30
|
+
reset: () => { counter = 0; return counter; },
|
|
31
|
+
|
|
32
|
+
// Cache helpers
|
|
33
|
+
cacheSet: async (key: string, value: any, ttl?: number) => {
|
|
34
|
+
await ctx.core.cache.set(key, value, ttl);
|
|
35
|
+
return { success: true };
|
|
36
|
+
},
|
|
37
|
+
cacheGet: async (key: string) => {
|
|
38
|
+
const value = await ctx.core.cache.get(key);
|
|
39
|
+
const exists = await ctx.core.cache.has(key);
|
|
40
|
+
return { value, exists };
|
|
41
|
+
},
|
|
42
|
+
cacheDelete: async (key: string) => {
|
|
43
|
+
await ctx.core.cache.delete(key);
|
|
44
|
+
return { success: true };
|
|
45
|
+
},
|
|
46
|
+
cacheKeys: async () => {
|
|
47
|
+
const keys = await ctx.core.cache.keys();
|
|
48
|
+
return { keys, size: keys.length };
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Jobs helpers
|
|
52
|
+
enqueueJob: async (name: string, data: any, delay?: number) => {
|
|
53
|
+
let jobId: string;
|
|
54
|
+
if (delay && delay > 0) {
|
|
55
|
+
const runAt = new Date(Date.now() + delay);
|
|
56
|
+
jobId = await ctx.core.jobs.schedule(name, data, runAt);
|
|
57
|
+
} else {
|
|
58
|
+
jobId = await ctx.core.jobs.enqueue(name, data);
|
|
59
|
+
}
|
|
60
|
+
return { jobId };
|
|
61
|
+
},
|
|
62
|
+
getJobStats: async () => {
|
|
63
|
+
const pending = await ctx.core.jobs.getByName("demo-job", "pending");
|
|
64
|
+
const running = await ctx.core.jobs.getByName("demo-job", "running");
|
|
65
|
+
const completed = await ctx.core.jobs.getByName("demo-job", "completed");
|
|
66
|
+
return {
|
|
67
|
+
pending: pending.length,
|
|
68
|
+
running: running.length,
|
|
69
|
+
completed: completed.length,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Cron helpers
|
|
74
|
+
getCronTasks: () => ctx.core.cron.list().map(t => ({
|
|
75
|
+
id: t.id,
|
|
76
|
+
name: t.name,
|
|
77
|
+
expression: t.expression,
|
|
78
|
+
enabled: t.enabled,
|
|
79
|
+
lastRun: t.lastRun?.toISOString(),
|
|
80
|
+
nextRun: t.nextRun?.toISOString(),
|
|
81
|
+
})),
|
|
82
|
+
|
|
83
|
+
// Rate limiter helpers
|
|
84
|
+
checkRateLimit: async (key: string, limit: number, window: number) => {
|
|
85
|
+
return ctx.core.rateLimiter.check(key, limit, window);
|
|
86
|
+
},
|
|
87
|
+
resetRateLimit: async (key: string) => {
|
|
88
|
+
await ctx.core.rateLimiter.reset(key);
|
|
89
|
+
return { success: true };
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Events helpers (internal pub/sub)
|
|
93
|
+
emitEvent: async (event: string, data: any) => {
|
|
94
|
+
await ctx.core.events.emit(event, data);
|
|
95
|
+
return { success: true };
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// SSE broadcast
|
|
99
|
+
broadcast: (channel: string, event: string, data: any) => {
|
|
100
|
+
ctx.core.sse.broadcast(channel, event, data);
|
|
101
|
+
return { success: true };
|
|
102
|
+
},
|
|
103
|
+
getSSEClients: () => ({
|
|
104
|
+
total: ctx.core.sse.getClients().length,
|
|
105
|
+
byChannel: ctx.core.sse.getClientsByChannel("events").length,
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
init: async (ctx) => {
|
|
110
|
+
// Register job handler for demo
|
|
111
|
+
ctx.core.jobs.register("demo-job", async (data) => {
|
|
112
|
+
ctx.core.logger.info("Demo job executed", { data });
|
|
113
|
+
// Broadcast job completion via SSE
|
|
114
|
+
ctx.core.sse.broadcast("events", "job-completed", {
|
|
115
|
+
id: Date.now(),
|
|
116
|
+
message: `Job completed: ${data.message || "No message"}`,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Schedule cron job to broadcast SSE events every 5 seconds
|
|
122
|
+
ctx.core.cron.schedule("*/5 * * * * *", () => {
|
|
123
|
+
const message = eventMessages[Math.floor(Math.random() * eventMessages.length)];
|
|
124
|
+
ctx.core.sse.broadcast("events", "cron-event", {
|
|
125
|
+
id: Date.now(),
|
|
126
|
+
message,
|
|
127
|
+
timestamp: new Date().toISOString(),
|
|
128
|
+
source: "cron",
|
|
129
|
+
});
|
|
130
|
+
}, { name: "sse-broadcaster" });
|
|
131
|
+
|
|
132
|
+
// Listen for internal events and broadcast to SSE
|
|
133
|
+
ctx.core.events.on("demo.*", (data) => {
|
|
134
|
+
ctx.core.sse.broadcast("events", "internal-event", {
|
|
135
|
+
id: Date.now(),
|
|
136
|
+
message: `Internal event: ${JSON.stringify(data)}`,
|
|
137
|
+
timestamp: new Date().toISOString(),
|
|
138
|
+
source: "events",
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
ctx.core.logger.info("Demo plugin initialized with all core services");
|
|
143
|
+
},
|
|
144
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class DeleteCacheHandler implements Handler<Routes.Api.Cache.Delete> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Cache.Delete.Input): Routes.Api.Cache.Delete.Output {
|
|
13
|
+
return this.ctx.plugins.demo.cacheDelete(input.key);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class GetCacheHandler implements Handler<Routes.Api.Cache.Get> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Cache.Get.Input): Routes.Api.Cache.Get.Output {
|
|
13
|
+
return this.ctx.plugins.demo.cacheGet(input.key);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class KeysCacheHandler implements Handler<Routes.Api.Cache.Keys> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Cache.Keys.Input): Routes.Api.Cache.Keys.Output {
|
|
13
|
+
return this.ctx.plugins.demo.cacheKeys();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class SetCacheHandler implements Handler<Routes.Api.Cache.Set> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Cache.Set.Input): Routes.Api.Cache.Set.Output {
|
|
13
|
+
return this.ctx.plugins.demo.cacheSet(input.key, input.value, input.ttl);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
import { createRouter, defineRoute } from "@donkeylabs/server";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { SetCacheHandler } from "./handlers/set";
|
|
5
|
+
import { GetCacheHandler } from "./handlers/get";
|
|
6
|
+
import { DeleteCacheHandler } from "./handlers/delete";
|
|
7
|
+
import { KeysCacheHandler } from "./handlers/keys";
|
|
8
|
+
|
|
9
|
+
const router = createRouter("api");
|
|
10
|
+
|
|
11
|
+
router.route("cache.set").typed(
|
|
12
|
+
defineRoute({
|
|
13
|
+
input: z.object({
|
|
14
|
+
key: z.string(),
|
|
15
|
+
value: z.any(),
|
|
16
|
+
ttl: z.number().optional()
|
|
17
|
+
}),
|
|
18
|
+
output: z.object({ success: z.boolean() }),
|
|
19
|
+
handle: SetCacheHandler,
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
router.route("cache.get").typed(
|
|
24
|
+
defineRoute({
|
|
25
|
+
input: z.object({ key: z.string() }),
|
|
26
|
+
output: z.object({ value: z.any().optional(), exists: z.boolean() }),
|
|
27
|
+
handle: GetCacheHandler,
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
router.route("cache.delete").typed(
|
|
32
|
+
defineRoute({
|
|
33
|
+
input: z.object({ key: z.string() }),
|
|
34
|
+
output: z.object({ success: z.boolean() }),
|
|
35
|
+
handle: DeleteCacheHandler,
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
router.route("cache.keys").typed(
|
|
40
|
+
defineRoute({
|
|
41
|
+
output: z.object({ keys: z.array(z.string()) }),
|
|
42
|
+
handle: KeysCacheHandler,
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export default router;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class DecrementCounterHandler implements Handler<Routes.Api.Counter.Decrement> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Counter.Decrement.Input): Routes.Api.Counter.Decrement.Output {
|
|
13
|
+
return {
|
|
14
|
+
count: this.ctx.plugins.demo.decrement(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class GetCounterHandler implements Handler<Routes.Api.Counter.Get> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Counter.Get.Input): Routes.Api.Counter.Get.Output {
|
|
13
|
+
return {
|
|
14
|
+
count: this.ctx.plugins.demo.getCounter(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class IncrementCounterHandler implements Handler<Routes.Api.Counter.Increment> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Counter.Increment.Input): Routes.Api.Counter.Increment.Output {
|
|
13
|
+
return {
|
|
14
|
+
count: this.ctx.plugins.demo.increment(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class ResetCounterHandler implements Handler<Routes.Api.Counter.Reset> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Counter.Reset.Input): Routes.Api.Counter.Reset.Output {
|
|
13
|
+
return {
|
|
14
|
+
count: this.ctx.plugins.demo.reset(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
import { createRouter, defineRoute } from "@donkeylabs/server";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { GetCounterHandler } from "./handlers/get";
|
|
5
|
+
import { IncrementCounterHandler } from "./handlers/increment";
|
|
6
|
+
import { DecrementCounterHandler } from "./handlers/decrement";
|
|
7
|
+
import { ResetCounterHandler } from "./handlers/reset";
|
|
8
|
+
|
|
9
|
+
const router = createRouter("api");
|
|
10
|
+
|
|
11
|
+
router.route("counter.get").typed(
|
|
12
|
+
defineRoute({
|
|
13
|
+
output: z.object({ count: z.number() }),
|
|
14
|
+
handle: GetCounterHandler,
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
router.route("counter.increment").typed(
|
|
19
|
+
defineRoute({
|
|
20
|
+
output: z.object({ count: z.number() }),
|
|
21
|
+
handle: IncrementCounterHandler,
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
router.route("counter.decrement").typed(
|
|
26
|
+
defineRoute({
|
|
27
|
+
output: z.object({ count: z.number() }),
|
|
28
|
+
handle: DecrementCounterHandler,
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
router.route("counter.reset").typed(
|
|
33
|
+
defineRoute({
|
|
34
|
+
output: z.object({ count: z.number() }),
|
|
35
|
+
handle: ResetCounterHandler,
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export default router;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
import type { Handler, AppContext } from "$lib/api";
|
|
3
|
+
import type { Routes } from "$lib/api";
|
|
4
|
+
|
|
5
|
+
export class ListCronHandler implements Handler<Routes.Api.Cron.List> {
|
|
6
|
+
ctx: AppContext;
|
|
7
|
+
|
|
8
|
+
constructor(ctx: AppContext) {
|
|
9
|
+
this.ctx = ctx;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
handle(input: Routes.Api.Cron.List.Input): Routes.Api.Cron.List.Output {
|
|
13
|
+
return {
|
|
14
|
+
tasks: this.ctx.plugins.demo.getCronTasks()
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|