@btst/stack 1.0.0 → 1.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.
Files changed (35) hide show
  1. package/README.md +790 -2
  2. package/dist/api/index.cjs +3 -2
  3. package/dist/api/index.d.cts +3 -3
  4. package/dist/api/index.d.mts +3 -3
  5. package/dist/api/index.d.ts +3 -3
  6. package/dist/api/index.mjs +3 -2
  7. package/dist/client/index.cjs +3 -15
  8. package/dist/client/index.d.cts +4 -10
  9. package/dist/client/index.d.mts +4 -10
  10. package/dist/client/index.d.ts +4 -10
  11. package/dist/client/index.mjs +3 -10
  12. package/dist/context/index.cjs +14 -2
  13. package/dist/context/index.d.cts +6 -3
  14. package/dist/context/index.d.mts +6 -3
  15. package/dist/context/index.d.ts +6 -3
  16. package/dist/context/index.mjs +14 -3
  17. package/dist/index.cjs +2 -1
  18. package/dist/index.d.cts +2 -2
  19. package/dist/index.d.mts +2 -2
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.mjs +1 -1
  22. package/dist/plugins/index.cjs +1 -2
  23. package/dist/plugins/index.d.cts +3 -3
  24. package/dist/plugins/index.d.mts +3 -3
  25. package/dist/plugins/index.d.ts +3 -3
  26. package/dist/plugins/index.mjs +1 -1
  27. package/dist/shared/{stack.CwGEQ10b.mjs → stack.3OUyGp_E.mjs} +2 -8
  28. package/dist/shared/{stack.Br2KMECJ.cjs → stack.CktCg4PJ.cjs} +1 -8
  29. package/dist/shared/{stack.Dva9muUy.d.cts → stack.DORw_1ps.d.cts} +3 -25
  30. package/dist/shared/{stack.Dva9muUy.d.mts → stack.DORw_1ps.d.mts} +3 -25
  31. package/dist/shared/{stack.Dva9muUy.d.ts → stack.DORw_1ps.d.ts} +3 -25
  32. package/dist/shared/{stack.DvFqFlOV.d.ts → stack.DrUAVfIH.d.cts} +2 -7
  33. package/dist/shared/{stack.DvFqFlOV.d.cts → stack.DrUAVfIH.d.mts} +2 -7
  34. package/dist/shared/{stack.DvFqFlOV.d.mts → stack.DrUAVfIH.d.ts} +2 -7
  35. package/package.json +3 -3
package/README.md CHANGED
@@ -1,3 +1,791 @@
1
- # BETTER STACK
1
+ # Better Stack
2
2
 
3
- WIP
3
+ <div align="center">
4
+
5
+ **A composable, plugin-based framework for building full-stack TypeScript applications**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@btst/stack.svg)](https://www.npmjs.com/package/@btst/stack)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ </div>
11
+
12
+ ## Overview
13
+
14
+ Better Stack is a modern, type-safe framework for building full-stack applications with a plugin architecture. It seamlessly integrates:
15
+
16
+ - **🔌 Plugin Architecture** - Build modular, reusable features as standalone plugins
17
+ - **🔄 Full-Stack Type Safety** - End-to-end TypeScript with automatic type inference, no casts needed
18
+ - **🗄️ Schema-First Database** - Define your data models once using Better DB with typed adapters
19
+ - **🚀 Production Ready** - Built on proven libraries (Better Call, Better DB, Yar Router)
20
+ - **⚡ Zero Config** - Works out of the box with sensible defaults
21
+ - **✨ Developer Experience** - Helper functions preserve route keys, hook names, and endpoint types
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @btst/stack
27
+ # or
28
+ pnpm add @btst/stack
29
+ # or
30
+ yarn add @btst/stack
31
+ ```
32
+
33
+ ### Dependencies
34
+
35
+ Better Stack works with your existing stack:
36
+
37
+ ```bash
38
+ npm install @btst/db @btst/adapter-memory better-call @btst/yar zod
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### 1. Create Your Backend API
44
+
45
+ ```typescript
46
+ // app/api/route.ts
47
+ import { betterStack } from "@btst/stack/api";
48
+ import { myPlugin } from "./plugins/my-plugin";
49
+ import { createMemoryAdapter } from "@btst/adapter-memory";
50
+
51
+ const api = betterStack({
52
+ plugins: {
53
+ myFeature: myPlugin.backend,
54
+ },
55
+ adapter: createMemoryAdapter,
56
+ });
57
+
58
+ // Export for Next.js App Router
59
+ export const GET = api.handler;
60
+ export const POST = api.handler;
61
+ export const PUT = api.handler;
62
+ export const DELETE = api.handler;
63
+ export const PATCH = api.handler;
64
+ ```
65
+
66
+ ### 2. Create Your Client
67
+
68
+ ```typescript
69
+ // app/lib/client.ts
70
+ import { createStackClient } from "@btst/stack/client";
71
+ import { myPlugin } from "./plugins/my-plugin";
72
+
73
+ export const client = createStackClient({
74
+ plugins: {
75
+ myFeature: myPlugin.client,
76
+ },
77
+ });
78
+
79
+ // Access router and hooks
80
+ export const { router, hooks } = client;
81
+ ```
82
+
83
+ ### 3. Use in Your Components
84
+
85
+ ```tsx
86
+ // app/components/MyComponent.tsx
87
+ "use client";
88
+ import { hooks } from "../lib/client";
89
+
90
+ export function MyComponent() {
91
+ const { useMyData } = hooks.myFeature;
92
+ const { data, isLoading } = useMyData();
93
+
94
+ if (isLoading) return <div>Loading...</div>;
95
+
96
+ return (
97
+ <div>
98
+ {data?.items.map((item) => (
99
+ <div key={item.id}>{item.name}</div>
100
+ ))}
101
+ </div>
102
+ );
103
+ }
104
+ ```
105
+
106
+ ## Building Custom Plugins
107
+
108
+ Better Stack's power comes from its plugin architecture. Here's how to build your own:
109
+
110
+ ### Plugin Architecture
111
+
112
+ Better Stack uses **separate backend and client plugins** to:
113
+ - ✅ Prevent SSR issues
114
+ - ✅ Enable better code splitting
115
+ - ✅ Allow independent deployment and versioning
116
+ - ✅ Improve tree-shaking
117
+
118
+ Each plugin type is completely independent:
119
+
120
+ 1. **Backend Plugin** - API endpoints, database schema, and business logic
121
+ 2. **Client Plugin** - Routes, components, and React hooks
122
+
123
+ ### Example: Messages Plugin (Backend)
124
+
125
+ ```typescript
126
+ // plugins/messages/backend.ts
127
+ import { createDbPlugin } from "@btst/db";
128
+ import { createEndpoint } from "better-call";
129
+ import { z } from "zod";
130
+ import {
131
+ defineBackendPlugin,
132
+ type BetterAuthDBSchema,
133
+ } from "@btst/stack/plugins";
134
+
135
+ // 1. Define your schema
136
+ const messagesSchema: BetterAuthDBSchema = {
137
+ messages: {
138
+ modelName: "Message",
139
+ fields: {
140
+ id: {
141
+ type: "number",
142
+ unique: true,
143
+ required: true,
144
+ },
145
+ content: {
146
+ type: "string",
147
+ required: true,
148
+ },
149
+ userId: {
150
+ type: "string",
151
+ required: true,
152
+ },
153
+ createdAt: {
154
+ type: "number",
155
+ required: true,
156
+ },
157
+ },
158
+ },
159
+ };
160
+
161
+ // 2. Create and export the backend plugin with full type inference
162
+ export const messagesBackendPlugin = defineBackendPlugin({
163
+ name: "messages",
164
+ dbPlugin: createDbPlugin("messages", messagesSchema),
165
+ routes: (adapter) => ({
166
+ // List messages
167
+ list: createEndpoint(
168
+ "/messages",
169
+ {
170
+ method: "GET",
171
+ query: z.object({
172
+ userId: z.string().optional(),
173
+ }),
174
+ },
175
+ async ({ query }) => {
176
+ const messages = await adapter.findMany({
177
+ model: "messages",
178
+ where: query.userId
179
+ ? [{ field: "userId", value: query.userId, operator: "eq" }]
180
+ : undefined,
181
+ });
182
+ return {
183
+ status: 200,
184
+ body: messages,
185
+ };
186
+ }
187
+ ),
188
+
189
+ // Create message
190
+ create: createEndpoint(
191
+ "/messages",
192
+ {
193
+ method: "POST",
194
+ body: z.object({
195
+ content: z.string().min(1),
196
+ userId: z.string().min(1),
197
+ }),
198
+ },
199
+ async ({ body }) => {
200
+ const message = await adapter.create({
201
+ model: "messages",
202
+ data: {
203
+ ...body,
204
+ id: Date.now(),
205
+ createdAt: Date.now(),
206
+ },
207
+ });
208
+ return {
209
+ status: 201,
210
+ body: message,
211
+ };
212
+ }
213
+ ),
214
+
215
+ // Delete message
216
+ delete: createEndpoint(
217
+ "/messages/:id",
218
+ {
219
+ method: "DELETE",
220
+ params: z.object({
221
+ id: z.coerce.number(),
222
+ }),
223
+ },
224
+ async ({ params }) => {
225
+ await adapter.delete({
226
+ model: "messages",
227
+ where: [{ field: "id", value: params.id, operator: "eq" }],
228
+ });
229
+ return {
230
+ status: 204,
231
+ body: null,
232
+ };
233
+ }
234
+ ),
235
+ }),
236
+ });
237
+ ```
238
+
239
+ ### Example: Messages Plugin (Client)
240
+
241
+ ```typescript
242
+ // plugins/messages/client.ts
243
+ import { defineClientPlugin } from "@btst/stack/plugins";
244
+ import { createRoute } from "@btst/yar";
245
+ import { useQuery } from "@tanstack/react-query";
246
+
247
+ // Components
248
+ const MessagesPage = ({ data }: { data: any }) => (
249
+ <div>{/* Render messages */}</div>
250
+ );
251
+
252
+ // Create and export the client plugin with full type inference
253
+ export const messagesClientPlugin = defineClientPlugin({
254
+ name: "messages",
255
+ routes: () => ({
256
+ // Use Yar's createRoute for proper route creation
257
+ messagesList: createRoute(
258
+ "/messages",
259
+ () => ({
260
+ PageComponent: MessagesPage,
261
+ loader: async () => {
262
+ const response = await fetch("/api/messages");
263
+ return response.json();
264
+ },
265
+ meta: (data) => [
266
+ { name: "title", content: "Messages" },
267
+ { name: "description", content: `${data.length} messages` },
268
+ ],
269
+ })
270
+ ),
271
+ }),
272
+ hooks: () => ({
273
+ useMessages: () => {
274
+ // Use React Query or your preferred data fetching library
275
+ return useQuery({
276
+ queryKey: ["messages"],
277
+ queryFn: async () => {
278
+ const response = await fetch("/api/messages");
279
+ return response.json();
280
+ },
281
+ });
282
+ },
283
+ }),
284
+ });
285
+ ```
286
+
287
+ > **💡 Type Safety Tip:** Using `defineClientPlugin` and `defineBackendPlugin` helpers ensures full type inference without needing type annotations or casts. Your route keys, hook names, and endpoint types are automatically preserved!
288
+
289
+ ### Using Your Plugins
290
+
291
+ Backend and client plugins are used independently in their respective contexts:
292
+
293
+ #### Backend Usage
294
+
295
+ ```typescript
296
+ // app/api/route.ts (Backend only)
297
+ import { betterStack } from "@btst/stack/api";
298
+ import { messagesBackendPlugin } from "./plugins/messages/backend";
299
+ import { createMemoryAdapter } from "@btst/adapter-memory";
300
+
301
+ const api = betterStack({
302
+ plugins: {
303
+ messages: messagesBackendPlugin,
304
+ },
305
+ adapter: createMemoryAdapter,
306
+ });
307
+
308
+ export const GET = api.handler;
309
+ export const POST = api.handler;
310
+ ```
311
+
312
+ #### Client Usage
313
+
314
+ ```typescript
315
+ // app/lib/client.ts (Client only)
316
+ import { createStackClient } from "@btst/stack/client";
317
+ import { messagesClientPlugin } from "./plugins/messages/client";
318
+
319
+ const client = createStackClient({
320
+ plugins: {
321
+ messages: messagesClientPlugin,
322
+ },
323
+ });
324
+
325
+ export const { router, hooks } = client;
326
+
327
+ // Use in components
328
+ // const { useMessages } = hooks.messages;
329
+ ```
330
+
331
+ ## Core Concepts
332
+
333
+ ### 1. Plugins
334
+
335
+ Plugins are self-contained features that can be composed together. Each plugin can provide:
336
+
337
+ - **Database Schema** - Table definitions using Better DB
338
+ - **API Endpoints** - Type-safe REST endpoints using Better Call
339
+ - **Client Routes** - Page routing using Yar Router
340
+ - **React Hooks** - Data fetching and mutations
341
+
342
+ ### 2. Adapters
343
+
344
+ Adapters connect your plugins to different databases:
345
+
346
+ ```typescript
347
+ // Memory (for testing)
348
+ import { createMemoryAdapter } from "@btst/adapter-memory";
349
+
350
+ // PostgreSQL
351
+ import { createPgAdapter } from "@btst/adapter-pg";
352
+
353
+ // MongoDB
354
+ import { createMongoAdapter } from "@btst/adapter-mongo";
355
+
356
+ const api = betterStack({
357
+ plugins: { /* ... */ },
358
+ adapter: createMemoryAdapter, // or createPgAdapter, createMongoAdapter
359
+ });
360
+ ```
361
+
362
+ ### 3. Type Safety
363
+
364
+ Better Stack provides end-to-end type safety with automatic type inference:
365
+
366
+ ```typescript
367
+ // ✅ Backend: Route keys and endpoint types are preserved
368
+ const myPlugin = defineBackendPlugin({
369
+ name: "users",
370
+ routes: (adapter) => ({
371
+ getUser: createEndpoint(
372
+ "/users/:id",
373
+ {
374
+ method: "GET",
375
+ params: z.object({ id: z.string() }),
376
+ },
377
+ async ({ params }) => {
378
+ // params.id is typed as string
379
+ return { status: 200, body: { id: params.id, name: "John" } };
380
+ }
381
+ ),
382
+ listUsers: createEndpoint(/* ... */),
383
+ })
384
+ });
385
+ // Type: BackendPlugin<{ getUser: Endpoint, listUsers: Endpoint }>
386
+
387
+ // ✅ Client: Route keys and hook types are preserved
388
+ const myClientPlugin = defineClientPlugin({
389
+ name: "users",
390
+ routes: () => ({
391
+ userProfile: { path: "/users/:id", Component: UserProfile },
392
+ usersList: { path: "/users", Component: UsersList },
393
+ }),
394
+ hooks: () => ({
395
+ useUser: (id: string) => { /* ... */ },
396
+ useUsers: () => { /* ... */ },
397
+ })
398
+ });
399
+ // Routes "userProfile" and "usersList" are fully typed!
400
+ // Hooks "useUser" and "useUsers" are accessible via hooks.users.*
401
+
402
+ ```
403
+
404
+ ## Plugin Utilities
405
+
406
+ Better Stack exports utilities for building plugins:
407
+
408
+ ```typescript
409
+ // Type-safe plugin helpers (recommended)
410
+ import {
411
+ defineBackendPlugin,
412
+ defineClientPlugin,
413
+ } from "@btst/stack/plugins";
414
+
415
+ // Type definitions for backend plugins
416
+ import type {
417
+ BackendPlugin,
418
+ Adapter,
419
+ DatabaseDefinition,
420
+ Endpoint,
421
+ Router,
422
+ } from "@btst/stack/plugins";
423
+
424
+ // Type definitions for client plugins
425
+ import type {
426
+ ClientPlugin,
427
+ Route,
428
+ } from "@btst/stack/plugins";
429
+
430
+ // Utilities (can be used in both)
431
+ import {
432
+ createApiClient,
433
+ } from "@btst/stack/plugins";
434
+ ```
435
+
436
+ ### Type-Safe Plugin Helpers
437
+
438
+ Use `defineBackendPlugin` and `defineClientPlugin` for automatic type inference:
439
+
440
+ ```typescript
441
+ // ✅ Recommended: Full type inference, no casts needed
442
+ const myPlugin = defineBackendPlugin({
443
+ name: "myFeature",
444
+ routes: (adapter) => ({
445
+ list: endpoint(...),
446
+ create: endpoint(...),
447
+ })
448
+ });
449
+ // Route keys "list" and "create" are fully typed!
450
+
451
+ // ❌ Old way: Manual type annotation
452
+ const myPlugin: BackendPlugin = {
453
+ name: "myFeature",
454
+ routes: (adapter) => ({
455
+ list: endpoint(...),
456
+ create: endpoint(...),
457
+ })
458
+ };
459
+ // Route keys are erased to generic Record<string, Endpoint>
460
+ ```
461
+
462
+ ### Why Separate Backend and Client Plugins?
463
+
464
+ **Traditional Approach (Unified Plugin):**
465
+ ```typescript
466
+ // ❌ This can cause SSR issues and bundle bloat
467
+ export const myPlugin = {
468
+ backend: { /* server-side code */ },
469
+ client: { /* client-side code */ },
470
+ };
471
+ ```
472
+
473
+ **Better Stack Approach (Separated Plugins):**
474
+ ```typescript
475
+ // ✅ Backend stays on the server
476
+ // plugins/my-plugin/backend.ts
477
+ export const myBackendPlugin: BackendPlugin = { /* ... */ };
478
+
479
+ // ✅ Client stays on the client
480
+ // plugins/my-plugin/client.ts
481
+ export const myClientPlugin: ClientPlugin = { /* ... */ };
482
+ ```
483
+
484
+ **Benefits:**
485
+ 1. **No SSR Issues** - Server code never reaches the client bundle
486
+ 2. **Better Code Splitting** - Frontend and backend can be deployed separately
487
+ 3. **Smaller Bundles** - Client bundles don't include server dependencies
488
+ 4. **Independent Versioning** - Update backend without touching frontend
489
+ 5. **Type Safety** - TypeScript ensures correct usage in each context
490
+
491
+ ### API Client Utility
492
+
493
+ Create a typed API client for server-side or client-side requests:
494
+
495
+ ```typescript
496
+ import { createApiClient } from "@btst/stack/plugins";
497
+
498
+ // For server-side (SSR)
499
+ const api = createApiClient({
500
+ baseURL: "http://localhost:3000",
501
+ basePath: "/api",
502
+ });
503
+
504
+ // For client-side (uses relative URLs)
505
+ const api = createApiClient({
506
+ basePath: "/api",
507
+ });
508
+ ```
509
+
510
+ ## Testing Your Plugins
511
+
512
+ Better Stack includes comprehensive testing utilities:
513
+
514
+ ```typescript
515
+ // __tests__/my-plugin.test.ts
516
+ import { describe, it, expect } from "vitest";
517
+ import { betterStack } from "@btst/stack/api";
518
+ import { createMemoryAdapter } from "@btst/adapter-memory";
519
+ import { myBackendPlugin } from "../plugins/my-plugin/backend";
520
+
521
+ describe("My Backend Plugin", () => {
522
+ it("should create and retrieve items", async () => {
523
+ // Adapter wrapper for testing
524
+ const testAdapter = (db) => createMemoryAdapter(db)({});
525
+
526
+ const api = betterStack({
527
+ plugins: { myFeature: myBackendPlugin },
528
+ adapter: testAdapter,
529
+ });
530
+
531
+ // Create an item
532
+ const createResponse = await api.handler(
533
+ new Request("http://localhost:3000/api/items", {
534
+ method: "POST",
535
+ headers: { "Content-Type": "application/json" },
536
+ body: JSON.stringify({ name: "Test Item" }),
537
+ })
538
+ );
539
+
540
+ expect(createResponse.status).toBe(201);
541
+
542
+ // Retrieve items
543
+ const listResponse = await api.handler(
544
+ new Request("http://localhost:3000/api/items", {
545
+ method: "GET",
546
+ })
547
+ );
548
+
549
+ const items = await listResponse.json();
550
+ expect(items).toHaveLength(1);
551
+ expect(items[0].name).toBe("Test Item");
552
+ });
553
+ });
554
+ ```
555
+
556
+ ## Framework Integration
557
+
558
+ ### Next.js App Router
559
+
560
+ ```typescript
561
+ // app/api/[...route]/route.ts
562
+ import { betterStack } from "@btst/stack/api";
563
+
564
+ const api = betterStack({
565
+ plugins: { /* ... */ },
566
+ adapter: /* ... */,
567
+ });
568
+
569
+ export const GET = api.handler;
570
+ export const POST = api.handler;
571
+ export const PUT = api.handler;
572
+ export const DELETE = api.handler;
573
+ export const PATCH = api.handler;
574
+ ```
575
+
576
+ ### Next.js Pages Router
577
+
578
+ ```typescript
579
+ // pages/api/[...route].ts
580
+ import { betterStack } from "@btst/stack/api";
581
+
582
+ const api = betterStack({
583
+ plugins: { /* ... */ },
584
+ adapter: /* ... */,
585
+ });
586
+
587
+ export default async function handler(req: NextApiRequest, res: NextApiResponse) {
588
+ const request = new Request(
589
+ `http://localhost:3000${req.url}`,
590
+ {
591
+ method: req.method,
592
+ headers: req.headers as HeadersInit,
593
+ body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
594
+ }
595
+ );
596
+
597
+ const response = await api.handler(request);
598
+ const data = await response.json();
599
+
600
+ res.status(response.status).json(data);
601
+ }
602
+ ```
603
+
604
+ ### Standalone Express/Node.js
605
+
606
+ ```typescript
607
+ import express from "express";
608
+ import { betterStack } from "@btst/stack/api";
609
+
610
+ const api = betterStack({
611
+ plugins: { /* ... */ },
612
+ adapter: /* ... */,
613
+ });
614
+
615
+ const app = express();
616
+
617
+ app.all("/api/*", async (req, res) => {
618
+ const request = new Request(`http://localhost:3000${req.url}`, {
619
+ method: req.method,
620
+ headers: req.headers as HeadersInit,
621
+ body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
622
+ });
623
+
624
+ const response = await api.handler(request);
625
+ const data = await response.json();
626
+
627
+ res.status(response.status).json(data);
628
+ });
629
+
630
+ app.listen(3000);
631
+ ```
632
+
633
+ ## Architecture
634
+
635
+ Better Stack is built on top of proven libraries:
636
+
637
+ ```
638
+ ┌─────────────────────────────────────────┐
639
+ │ Better Stack │
640
+ │ (Plugin Composition & Type Safety) │
641
+ ├─────────────────────────────────────────┤
642
+ │ Better Call │ Better DB │ Yar │
643
+ │ (API Layer) │ (Database) │ (Router) │
644
+ └─────────────────────────────────────────┘
645
+ ```
646
+
647
+ ### Core Libraries
648
+
649
+ - **[Better Call](https://github.com/olliethedev/better-call)** - Type-safe API endpoints with automatic request/response handling
650
+ - **[Better DB](https://github.com/olliethedev/better-auth)** - Schema-first database ORM with multiple adapter support
651
+ - **[Yar Router](https://github.com/olliethedev/yar)** - Lightweight client-side router for React
652
+
653
+ ## Publishing Your Plugin
654
+
655
+ Want to share your plugin with the community? Publish backend and client as separate packages for maximum flexibility.
656
+
657
+ ### Publishing Backend Plugin
658
+
659
+ 1. Create backend package:
660
+ ```bash
661
+ mkdir my-plugin-backend
662
+ cd my-plugin-backend
663
+ npm init
664
+ ```
665
+
666
+ 2. Structure:
667
+ ```
668
+ my-plugin-backend/
669
+ ├── src/
670
+ │ └── index.ts # Export BackendPlugin
671
+ ├── package.json
672
+ └── tsconfig.json
673
+ ```
674
+
675
+ 3. Add peer dependencies:
676
+ ```json
677
+ {
678
+ "name": "@yourorg/my-plugin-backend",
679
+ "peerDependencies": {
680
+ "@btst/stack": "^1.0.0",
681
+ "@btst/db": "^1.0.0",
682
+ "better-call": "^1.0.19"
683
+ }
684
+ }
685
+ ```
686
+
687
+ ### Publishing Client Plugin
688
+
689
+ 1. Create client package:
690
+ ```bash
691
+ mkdir my-plugin-client
692
+ cd my-plugin-client
693
+ npm init
694
+ ```
695
+
696
+ 2. Structure:
697
+ ```
698
+ my-plugin-client/
699
+ ├── src/
700
+ │ └── index.tsx # Export ClientPlugin
701
+ ├── package.json
702
+ └── tsconfig.json
703
+ ```
704
+
705
+ 3. Add peer dependencies:
706
+ ```json
707
+ {
708
+ "name": "@yourorg/my-plugin-client",
709
+ "peerDependencies": {
710
+ "@btst/stack": "^1.0.0",
711
+ "@btst/yar": "^1.1.1",
712
+ "react": "^18.0.0 || ^19.0.0"
713
+ }
714
+ }
715
+ ```
716
+
717
+ 4. Users can install only what they need:
718
+ ```bash
719
+ # Server-only deployment
720
+ npm install @yourorg/my-plugin-backend
721
+
722
+ # Client-only deployment
723
+ npm install @yourorg/my-plugin-client
724
+
725
+ # Full-stack deployment
726
+ npm install @yourorg/my-plugin-backend @yourorg/my-plugin-client
727
+ ```
728
+
729
+ ## Examples
730
+
731
+ Check out example plugins and applications:
732
+
733
+ - **Messages Plugin** - See `src/__tests__/plugins.test.ts` for a complete example
734
+ - **Coming Soon**: Example applications in the `examples/` directory
735
+
736
+ ## API Reference
737
+
738
+ ### Backend API
739
+
740
+ ```typescript
741
+ betterStack(config: BackendLibConfig): BackendLib
742
+ ```
743
+
744
+ Creates a backend instance with plugins and returns an API handler.
745
+
746
+ **Parameters:**
747
+ - `plugins` - Record of plugin instances
748
+ - `adapter` - Database adapter function
749
+ - `dbSchema` - (Optional) Additional database schema
750
+
751
+ **Returns:**
752
+ - `handler` - Request handler for your framework
753
+ - `router` - Better Call router instance
754
+ - `dbSchema` - Combined database schema
755
+
756
+ ### Client API
757
+
758
+ ```typescript
759
+ createStackClient<TPlugins>(config: ClientLibConfig<TPlugins>): ClientLib
760
+ ```
761
+
762
+ Creates a client instance with plugins and returns router and hooks.
763
+
764
+ **Parameters:**
765
+ - `plugins` - Record of plugin instances
766
+ - `baseURL` - (Optional) Base URL for API calls
767
+ - `basePath` - (Optional) API path prefix (default: "/api")
768
+
769
+ **Returns:**
770
+ - `router` - Yar router instance
771
+ - `hooks` - Plugin hooks organized by plugin name
772
+
773
+ ## Contributing
774
+
775
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
776
+
777
+ ## License
778
+
779
+ MIT © [olliethedev](https://github.com/olliethedev)
780
+
781
+ ## Support
782
+
783
+ - 📖 [Documentation](https://github.com/olliethedev/better-stack)
784
+ - 🐛 [Issue Tracker](https://github.com/olliethedev/better-stack/issues)
785
+ - 💬 [Discussions](https://github.com/olliethedev/better-stack/discussions)
786
+
787
+ ---
788
+
789
+ <div align="center">
790
+ <strong>Built with ❤️ using Better Stack</strong>
791
+ </div>