@donkeylabs/server 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/cli/commands/init.ts +201 -12
  2. package/package.json +1 -1
@@ -4,9 +4,10 @@
4
4
  * Initialize a new @donkeylabs/server project with proper database setup.
5
5
  */
6
6
 
7
- import { mkdir, writeFile, readFile, readdir } from "node:fs/promises";
8
- import { join, resolve } from "node:path";
7
+ import { mkdir, writeFile, readFile, readdir, copyFile } from "node:fs/promises";
8
+ import { join, resolve, dirname } from "node:path";
9
9
  import { existsSync } from "node:fs";
10
+ import { fileURLToPath } from "node:url";
10
11
  import pc from "picocolors";
11
12
  import prompts from "prompts";
12
13
 
@@ -96,6 +97,14 @@ export async function initCommand(args: string[]): Promise<void> {
96
97
  );
97
98
  console.log(pc.green(" Created:"), "src/index.ts");
98
99
 
100
+ // Copy docs and create CLAUDE.md for AI assistants
101
+ await copyDocsToProject(targetDir);
102
+ await writeFile(
103
+ join(targetDir, "CLAUDE.md"),
104
+ generateClaudeMd(options.projectName)
105
+ );
106
+ console.log(pc.green(" Created:"), "CLAUDE.md + docs/");
107
+
99
108
  // Update .gitignore
100
109
  const gitignorePath = join(targetDir, ".gitignore");
101
110
  const gitignoreContent = existsSync(gitignorePath)
@@ -130,11 +139,12 @@ export async function initCommand(args: string[]): Promise<void> {
130
139
 
131
140
  // Update package.json
132
141
  const pkgPath = join(targetDir, "package.json");
133
- let pkg: any = { name: options.projectName, type: "module", scripts: {} };
142
+ let pkg: any = { name: options.projectName, type: "module", scripts: {}, dependencies: {} };
134
143
 
135
144
  if (existsSync(pkgPath)) {
136
145
  pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
137
146
  pkg.scripts = pkg.scripts || {};
147
+ pkg.dependencies = pkg.dependencies || {};
138
148
  }
139
149
 
140
150
  pkg.name = pkg.name || options.projectName;
@@ -143,8 +153,20 @@ export async function initCommand(args: string[]): Promise<void> {
143
153
  pkg.scripts.start = "bun src/index.ts";
144
154
  pkg.scripts["gen:types"] = "donkeylabs generate";
145
155
 
156
+ // Add dependencies
157
+ pkg.dependencies["@donkeylabs/server"] = "latest";
158
+ pkg.dependencies["kysely"] = "^0.27.0";
159
+ pkg.dependencies["zod"] = "^3.24.0";
160
+ if (options.useDatabase) {
161
+ pkg.dependencies["kysely-bun-sqlite"] = "^0.3.0";
162
+ }
163
+
164
+ // Add dev dependencies
165
+ pkg.devDependencies = pkg.devDependencies || {};
166
+ pkg.devDependencies["@types/bun"] = "latest";
167
+
146
168
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
147
- console.log(pc.green(" Updated:"), "package.json");
169
+ console.log(pc.green(" Created:"), "package.json");
148
170
 
149
171
  // Print next steps
150
172
  console.log(`
@@ -152,17 +174,13 @@ ${pc.bold(pc.green("Success!"))} Project initialized.
152
174
 
153
175
  ${pc.bold("Next steps:")}
154
176
  1. Install dependencies:
155
- ${pc.cyan(getDependencyCommand(options.useDatabase))}
177
+ ${pc.cyan("bun install")}
156
178
  ${options.useDatabase ? `
157
179
  2. Set up your database:
158
180
  ${pc.cyan(`cp .env.example .env`)}
159
- ${pc.dim("# Edit .env with your database path")}
160
181
  ` : ""}
161
182
  ${options.useDatabase ? "3" : "2"}. Start development:
162
183
  ${pc.cyan("bun run dev")}
163
-
164
- ${options.useDatabase ? "4" : "3"}. Generate types after adding plugins:
165
- ${pc.cyan("bun run gen:types")}
166
184
  `);
167
185
  }
168
186
 
@@ -281,7 +299,178 @@ function generateTsConfig(): string {
281
299
  `;
282
300
  }
283
301
 
284
- function getDependencyCommand(useDatabase: boolean): string {
285
- const base = "bun add @donkeylabs/server kysely zod";
286
- return useDatabase ? `${base} kysely-bun-sqlite` : base;
302
+ // Docs to copy to user projects (subset relevant for users)
303
+ const USER_DOCS = [
304
+ "plugins.md",
305
+ "router.md",
306
+ "handlers.md",
307
+ "core-services.md",
308
+ "errors.md",
309
+ "cache.md",
310
+ "logger.md",
311
+ "events.md",
312
+ "jobs.md",
313
+ "cron.md",
314
+ "sse.md",
315
+ "rate-limiter.md",
316
+ "middleware.md",
317
+ "api-client.md",
318
+ "svelte-frontend.md",
319
+ ];
320
+
321
+ async function copyDocsToProject(targetDir: string): Promise<void> {
322
+ const docsDir = join(targetDir, "docs");
323
+ await mkdir(docsDir, { recursive: true });
324
+
325
+ // Find the package's docs directory
326
+ const __filename = fileURLToPath(import.meta.url);
327
+ const __dirname = dirname(__filename);
328
+ const packageDocsDir = resolve(__dirname, "../../docs");
329
+
330
+ for (const doc of USER_DOCS) {
331
+ const srcPath = join(packageDocsDir, doc);
332
+ const destPath = join(docsDir, doc);
333
+
334
+ if (existsSync(srcPath)) {
335
+ await copyFile(srcPath, destPath);
336
+ }
337
+ }
338
+ }
339
+
340
+ function generateClaudeMd(projectName: string): string {
341
+ return `# ${projectName}
342
+
343
+ Built with @donkeylabs/server - a type-safe RPC framework for Bun.
344
+
345
+ ## Project Structure
346
+
347
+ \`\`\`
348
+ src/
349
+ ├── index.ts # Server entry + routes (start here)
350
+ ├── db.ts # Database setup
351
+ └── plugins/ # Your plugins
352
+ └── example/
353
+ ├── index.ts # Plugin definition
354
+ ├── schema.ts # DB types (if needed)
355
+ └── migrations/ # SQL migrations
356
+ docs/ # Framework documentation
357
+ \`\`\`
358
+
359
+ ## Quick Start
360
+
361
+ See \`src/index.ts\` for a working example with routes.
362
+
363
+ ## Plugins
364
+
365
+ Plugins encapsulate business logic with optional database schemas.
366
+
367
+ \`\`\`ts
368
+ import { createPlugin } from "@donkeylabs/server";
369
+
370
+ export const notesPlugin = createPlugin.define({
371
+ name: "notes",
372
+ service: async (ctx) => ({
373
+ async create(title: string) {
374
+ return ctx.db.insertInto("notes").values({ title }).execute();
375
+ },
376
+ }),
377
+ });
378
+ \`\`\`
379
+
380
+ → See **docs/plugins.md** for schemas, migrations, and dependencies.
381
+
382
+ ## Routes
383
+
384
+ Routes handle HTTP requests and call plugin services.
385
+
386
+ \`\`\`ts
387
+ const router = createRouter("notes")
388
+ .route("create").typed({
389
+ input: z.object({ title: z.string() }),
390
+ handle: async (input, ctx) => ctx.plugins.notes.create(input.title),
391
+ });
392
+ \`\`\`
393
+
394
+ → See **docs/router.md** for typed handlers, raw handlers, and middleware.
395
+
396
+ ## Handlers
397
+
398
+ Two types: \`typed\` (validated JSON) and \`raw\` (full Request/Response).
399
+
400
+ → See **docs/handlers.md** for input/output validation and error handling.
401
+
402
+ ## Errors
403
+
404
+ Use built-in error factories for proper HTTP responses.
405
+
406
+ \`\`\`ts
407
+ throw ctx.errors.NotFound("User not found");
408
+ throw ctx.errors.BadRequest("Invalid email");
409
+ \`\`\`
410
+
411
+ → See **docs/errors.md** for all error types and custom errors.
412
+
413
+ ## Core Services
414
+
415
+ Available via \`ctx.core\`. **Only use what you need.**
416
+
417
+ | Service | Purpose | Docs |
418
+ |---------|---------|------|
419
+ | \`logger\` | Structured logging | docs/logger.md |
420
+ | \`cache\` | In-memory key-value cache | docs/cache.md |
421
+ | \`events\` | Pub/sub between plugins | docs/events.md |
422
+ | \`jobs\` | Background job queue | docs/jobs.md |
423
+ | \`cron\` | Scheduled tasks | docs/cron.md |
424
+ | \`sse\` | Server-sent events | docs/sse.md |
425
+ | \`rateLimiter\` | Request rate limiting | docs/rate-limiter.md |
426
+
427
+ → See **docs/core-services.md** for overview.
428
+
429
+ ## Middleware
430
+
431
+ Add authentication, logging, or other cross-cutting concerns.
432
+
433
+ → See **docs/middleware.md** for usage patterns.
434
+
435
+ ## API Client
436
+
437
+ Generate a typed client for your frontend:
438
+
439
+ \`\`\`sh
440
+ bun run gen:client --output ./frontend/src/lib/api
441
+ \`\`\`
442
+
443
+ → See **docs/api-client.md** for client configuration and usage.
444
+
445
+ ## Svelte 5 Frontend
446
+
447
+ Build type-safe frontends with Svelte 5 and SvelteKit.
448
+
449
+ \`\`\`svelte
450
+ <script lang="ts">
451
+ import { api } from "$lib/api";
452
+ let items = $state<Item[]>([]);
453
+
454
+ $effect(() => {
455
+ api.items.list({}).then((r) => items = r.items);
456
+ });
457
+ </script>
458
+ \`\`\`
459
+
460
+ → See **docs/svelte-frontend.md** for patterns and SSE integration.
461
+
462
+ ## CLI Commands
463
+
464
+ \`\`\`sh
465
+ bun run dev # Start with hot reload
466
+ bun run gen:types # Generate types after adding plugins
467
+ \`\`\`
468
+
469
+ ## Guidelines
470
+
471
+ - **Keep it simple** - don't add services you don't need
472
+ - **One concern per plugin** - auth, notes, billing as separate plugins
473
+ - **Minimal logging** - log errors and key events, not every call
474
+ - **Read the docs** - check docs/*.md before implementing something complex
475
+ `;
287
476
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/server",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "Type-safe plugin system for building RPC-style APIs with Bun",
6
6
  "main": "./src/index.ts",