@bloomneo/appkit 1.5.1 → 1.5.2

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 (111) hide show
  1. package/AGENTS.md +195 -0
  2. package/CHANGELOG.md +253 -0
  3. package/README.md +147 -799
  4. package/bin/commands/generate.js +7 -7
  5. package/cookbook/README.md +26 -0
  6. package/cookbook/api-key-service.ts +106 -0
  7. package/cookbook/auth-protected-crud.ts +112 -0
  8. package/cookbook/file-upload-pipeline.ts +113 -0
  9. package/cookbook/multi-tenant-saas.ts +87 -0
  10. package/cookbook/real-time-chat.ts +121 -0
  11. package/dist/auth/auth.d.ts +21 -4
  12. package/dist/auth/auth.d.ts.map +1 -1
  13. package/dist/auth/auth.js +56 -44
  14. package/dist/auth/auth.js.map +1 -1
  15. package/dist/auth/defaults.d.ts +1 -1
  16. package/dist/auth/defaults.js +35 -35
  17. package/dist/cache/cache.d.ts +29 -6
  18. package/dist/cache/cache.d.ts.map +1 -1
  19. package/dist/cache/cache.js +72 -44
  20. package/dist/cache/cache.js.map +1 -1
  21. package/dist/cache/defaults.js +25 -25
  22. package/dist/cache/index.d.ts +19 -10
  23. package/dist/cache/index.d.ts.map +1 -1
  24. package/dist/cache/index.js +21 -18
  25. package/dist/cache/index.js.map +1 -1
  26. package/dist/config/defaults.d.ts +1 -1
  27. package/dist/config/defaults.js +8 -8
  28. package/dist/config/index.d.ts +3 -3
  29. package/dist/config/index.js +4 -4
  30. package/dist/database/adapters/mongoose.js +2 -2
  31. package/dist/database/adapters/prisma.js +2 -2
  32. package/dist/database/defaults.d.ts +1 -1
  33. package/dist/database/defaults.js +4 -4
  34. package/dist/database/index.js +2 -2
  35. package/dist/database/index.js.map +1 -1
  36. package/dist/email/defaults.js +20 -20
  37. package/dist/error/defaults.d.ts +1 -1
  38. package/dist/error/defaults.js +12 -12
  39. package/dist/error/error.d.ts +12 -0
  40. package/dist/error/error.d.ts.map +1 -1
  41. package/dist/error/error.js +19 -0
  42. package/dist/error/error.js.map +1 -1
  43. package/dist/error/index.d.ts +14 -3
  44. package/dist/error/index.d.ts.map +1 -1
  45. package/dist/error/index.js +14 -3
  46. package/dist/error/index.js.map +1 -1
  47. package/dist/event/defaults.js +30 -30
  48. package/dist/logger/defaults.d.ts +1 -1
  49. package/dist/logger/defaults.js +40 -40
  50. package/dist/logger/index.d.ts +1 -0
  51. package/dist/logger/index.d.ts.map +1 -1
  52. package/dist/logger/index.js.map +1 -1
  53. package/dist/logger/logger.d.ts +8 -0
  54. package/dist/logger/logger.d.ts.map +1 -1
  55. package/dist/logger/logger.js +13 -3
  56. package/dist/logger/logger.js.map +1 -1
  57. package/dist/logger/transports/console.js +1 -1
  58. package/dist/logger/transports/http.d.ts +1 -1
  59. package/dist/logger/transports/http.js +1 -1
  60. package/dist/logger/transports/webhook.d.ts +1 -1
  61. package/dist/logger/transports/webhook.js +1 -1
  62. package/dist/queue/defaults.d.ts +2 -2
  63. package/dist/queue/defaults.js +38 -38
  64. package/dist/security/defaults.d.ts +1 -1
  65. package/dist/security/defaults.js +29 -29
  66. package/dist/security/index.d.ts +1 -1
  67. package/dist/security/index.js +3 -3
  68. package/dist/security/security.d.ts +1 -1
  69. package/dist/security/security.js +4 -4
  70. package/dist/storage/defaults.js +19 -19
  71. package/dist/util/defaults.d.ts +1 -1
  72. package/dist/util/defaults.js +34 -34
  73. package/dist/util/env.d.ts +35 -0
  74. package/dist/util/env.d.ts.map +1 -0
  75. package/dist/util/env.js +50 -0
  76. package/dist/util/env.js.map +1 -0
  77. package/dist/util/errors.d.ts +52 -0
  78. package/dist/util/errors.d.ts.map +1 -0
  79. package/dist/util/errors.js +82 -0
  80. package/dist/util/errors.js.map +1 -0
  81. package/examples/.env.example +80 -0
  82. package/examples/README.md +16 -0
  83. package/examples/auth.ts +228 -0
  84. package/examples/cache.ts +36 -0
  85. package/examples/config.ts +45 -0
  86. package/examples/database.ts +69 -0
  87. package/examples/email.ts +53 -0
  88. package/examples/error.ts +50 -0
  89. package/examples/event.ts +42 -0
  90. package/examples/logger.ts +41 -0
  91. package/examples/queue.ts +58 -0
  92. package/examples/security.ts +46 -0
  93. package/examples/storage.ts +44 -0
  94. package/examples/util.ts +47 -0
  95. package/llms.txt +591 -0
  96. package/package.json +19 -10
  97. package/src/auth/README.md +850 -0
  98. package/src/cache/README.md +756 -0
  99. package/src/config/README.md +604 -0
  100. package/src/database/README.md +818 -0
  101. package/src/email/README.md +759 -0
  102. package/src/error/README.md +660 -0
  103. package/src/event/README.md +729 -0
  104. package/src/logger/README.md +435 -0
  105. package/src/queue/README.md +851 -0
  106. package/src/security/README.md +612 -0
  107. package/src/storage/README.md +1008 -0
  108. package/src/util/README.md +955 -0
  109. package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
  110. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
  111. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
@@ -0,0 +1,818 @@
1
+ # @bloomneo/appkit - Database Module 💾
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@bloomneo/appkit.svg)](https://www.npmjs.com/package/@bloomneo/appkit)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
+
7
+ > Ultra-simple database wrapper with automatic tenant isolation and progressive
8
+ > multi-organization support that grows with your needs
9
+
10
+ **One simple function** - `databaseClass.get()` - handles everything from single
11
+ databases to complex multi-org, multi-tenant architectures. **Zero configuration
12
+ needed**, production-ready by default, with **mandatory future-proofing** built
13
+ in.
14
+
15
+ ## 🚀 Why Choose AppKit Database?
16
+
17
+ - **⚡ One Function** - `databaseClass.get()` handles all use cases, environment
18
+ controls behavior
19
+ - **🔧 Zero Configuration** - Just `DATABASE_URL`, everything else is optional
20
+ - **📈 Progressive Scaling** - Start simple, add tenants/orgs with zero code
21
+ changes
22
+ - **🛡️ Future-Proof Schema** - Mandatory `tenant_id` field prevents migration
23
+ pain
24
+ - **🔥 Hot Reload** - Change `.env` file, connections update instantly
25
+ - **🌍 Multi-Cloud Ready** - Each org can use different cloud providers
26
+ - **🤖 LLM-Optimized** - Clear variable naming patterns for AI code generation
27
+
28
+ ## 📦 Installation
29
+
30
+ ```bash
31
+ npm install @bloomneo/appkit
32
+ ```
33
+
34
+ ### Database-Specific Dependencies
35
+
36
+ ```bash
37
+ # PostgreSQL/MySQL/SQLite with Prisma
38
+ npm install @bloomneo/appkit @prisma/client
39
+
40
+ # MongoDB with Mongoose
41
+ npm install @bloomneo/appkit mongoose
42
+
43
+ # Multi-database setup (both ORMs)
44
+ npm install @bloomneo/appkit @prisma/client mongoose
45
+ ```
46
+
47
+ ## 🏃‍♂️ Quick Start (30 seconds)
48
+
49
+ ### Single Database (Day 1)
50
+
51
+ ```typescript
52
+ import { database } from '@bloomneo/appkit/database';
53
+
54
+ // PostgreSQL/MySQL with Prisma
55
+ const database = await databaseClass.get();
56
+ const users = await database.user.findMany();
57
+
58
+ // MongoDB with Mongoose
59
+ const database = await databaseClass.get();
60
+ const users = await database.User.find();
61
+ ```
62
+
63
+ ### Multi-Tenant (Month 6 - Zero Code Changes!)
64
+
65
+ ```bash
66
+ # Add to .env file - code stays exactly the same
67
+ BLOOM_DB_TENANT=auto
68
+ ```
69
+
70
+ ```typescript
71
+ // Same code, now tenant-filtered automatically
72
+ const database = await databaseClass.get(); // User's tenant data only
73
+
74
+ // Prisma (SQL databases)
75
+ const users = await database.user.findMany(); // Auto-filtered by tenant
76
+
77
+ // Mongoose (MongoDB)
78
+ const users = await database.User.find(); // Auto-filtered by tenant
79
+
80
+ // Admin access to all tenants
81
+ const dbTenants = await databaseClass.getTenants();
82
+ const allUsers = await dbTenants.user.findMany(); // Prisma - All tenant data
83
+ const allUsers = await dbTenants.User.find(); // Mongoose - All tenant data
84
+ ```
85
+
86
+ ### Multi-Organization (Year 1 - Still Zero Code Changes!)
87
+
88
+ ```bash
89
+ # Add org-specific databases to .env
90
+ ORG_ACME=postgresql://acme.aws.com/prod # PostgreSQL on AWS
91
+ ORG_TECH=mongodb://tech.azure.com/prod # MongoDB on Azure
92
+ ORG_STARTUP=mysql://startup.gcp.com/prod # MySQL on GCP
93
+ ```
94
+
95
+ ```typescript
96
+ // Same code, now org-aware with auto-adapter detection
97
+ const acmedatabase = await databaseClass.org('acme').get(); // Uses Prisma for PostgreSQL
98
+ const techdatabase = await databaseClass.org('tech').get(); // Uses Mongoose for MongoDB
99
+ const startupdatabase = await databaseClass.org('startup').get(); // Uses Prisma for MySQL
100
+
101
+ // Different database queries, same simple API
102
+ const acmeUsers = await acmedatabase.user.findMany(); // Prisma syntax
103
+ const techUsers = await techdatabase.User.find(); // Mongoose syntax
104
+ const startupUsers = await startupdatabase.user.findMany(); // Prisma syntax
105
+
106
+ // Org admin access
107
+ const acmeDbTenants = await databaseClass.org('acme').getTenants();
108
+ const techDbTenants = await databaseClass.org('tech').getTenants();
109
+ ```
110
+
111
+ **That's it!** Your code never changes, only your environment evolves.
112
+
113
+ ## 🎯 Core API
114
+
115
+ ### **One Function Rule: `databaseClass.get()`**
116
+
117
+ ```typescript
118
+ // Normal user access (single tenant or their specific tenant)
119
+ const database = await databaseClass.get();
120
+
121
+ // Admin access to all tenants
122
+ const dbTenants = await databaseClass.getTenants();
123
+
124
+ // Organization-specific access
125
+ const acmedatabase = await databaseClass.org('acme').get();
126
+ const acmeDbTenants = await databaseClass.org('acme').getTenants();
127
+ ```
128
+
129
+ ### **LLM-Friendly Variable Naming**
130
+
131
+ ```typescript
132
+ // Standard patterns for AI code generation:
133
+ const database = await databaseClass.get(); // Single/tenant user data
134
+ const dbTenants = await databaseClass.getTenants(); // All tenants (admin)
135
+ const acmedatabase = await databaseClass.org('acme').get(); // Acme org data
136
+ const acmeDbTenants = await databaseClass.org('acme').getTenants(); // All Acme tenants
137
+ ```
138
+
139
+ ## 🛡️ Mandatory Future-Proofing
140
+
141
+ ### **Required Schema Pattern**
142
+
143
+ **EVERY table/collection MUST include `tenant_id` field from Day 1:**
144
+
145
+ #### **SQL Databases (Prisma)**
146
+
147
+ ```sql
148
+ -- ✅ CORRECT: Future-proof schema
149
+ CREATE TABLE users (
150
+ id uuid PRIMARY KEY,
151
+ email text UNIQUE,
152
+ name text,
153
+ tenant_id text, -- MANDATORY: nullable for future compatibility
154
+ created_at timestamp DEFAULT now(),
155
+
156
+ INDEX idx_users_tenant (tenant_id) -- MANDATORY: performance index
157
+ );
158
+
159
+ CREATE TABLE posts (
160
+ id uuid PRIMARY KEY,
161
+ title text,
162
+ content text,
163
+ user_id uuid REFERENCES users(id),
164
+ tenant_id text, -- MANDATORY: on EVERY table
165
+ created_at timestamp DEFAULT now(),
166
+
167
+ INDEX idx_posts_tenant (tenant_id) -- MANDATORY: on EVERY table
168
+ );
169
+ ```
170
+
171
+ ```prisma
172
+ // Prisma schema example
173
+ model User {
174
+ id String @id @default(cuid())
175
+ email String @unique
176
+ name String
177
+ tenant_id String? // MANDATORY: nullable for future use
178
+ createdAt DateTime @default(now())
179
+
180
+ @@index([tenant_id]) // MANDATORY: performance index
181
+ @@map("users")
182
+ }
183
+
184
+ model Post {
185
+ id String @id @default(cuid())
186
+ title String
187
+ content String
188
+ userId String
189
+ tenant_id String? // MANDATORY: on EVERY table
190
+ createdAt DateTime @default(now())
191
+
192
+ user User @relation(fields: [userId], references: [id])
193
+
194
+ @@index([tenant_id]) // MANDATORY: on EVERY table
195
+ @@map("posts")
196
+ }
197
+ ```
198
+
199
+ #### **MongoDB (Mongoose)**
200
+
201
+ ```javascript
202
+ // Mongoose schema example
203
+ const userSchema = new Schema({
204
+ email: { type: String, unique: true, required: true },
205
+ name: { type: String, required: true },
206
+ tenant_id: { type: String, index: true }, // MANDATORY: indexed for performance
207
+ createdAt: { type: Date, default: Date.now },
208
+ });
209
+
210
+ // MANDATORY: Index for performance
211
+ userSchema.index({ tenant_id: 1 });
212
+
213
+ const postSchema = new Schema({
214
+ title: { type: String, required: true },
215
+ content: { type: String, required: true },
216
+ userId: { type: Schema.Types.ObjectId, ref: 'User', required: true },
217
+ tenant_id: { type: String, index: true }, // MANDATORY: on EVERY schema
218
+ createdAt: { type: Date, default: Date.now },
219
+ });
220
+
221
+ // MANDATORY: Index on every schema
222
+ postSchema.index({ tenant_id: 1 });
223
+
224
+ export const User = model('User', userSchema);
225
+ export const Post = model('Post', postSchema);
226
+ ```
227
+
228
+ ### **Why Mandatory `tenant_id`?**
229
+
230
+ - ✅ **Zero Migration Pain** - Enable multi-tenancy later with just environment
231
+ variables
232
+ - ✅ **Performance Ready** - Indexes in place from day 1
233
+ - ✅ **No Data Restructuring** - Never need to alter table schemas
234
+ - ✅ **Gradual Adoption** - Start single-tenant, scale when needed
235
+
236
+ ## 🌍 Environment Configuration
237
+
238
+ ### **Minimal Setup (2 Variables)**
239
+
240
+ ```bash
241
+ # Required: Main database connection
242
+ DATABASE_URL=postgresql://localhost:5432/myapp # PostgreSQL
243
+ # OR
244
+ DATABASE_URL=mongodb://localhost:27017/myapp # MongoDB
245
+ # OR
246
+ DATABASE_URL=mysql://localhost:3306/myapp # MySQL
247
+
248
+ # Optional: Enable tenant mode (auto-detects from requests)
249
+ BLOOM_DB_TENANT=auto
250
+ ```
251
+
252
+ ### **Multi-Database & Multi-Organization Setup**
253
+
254
+ ```bash
255
+ # Fallback database
256
+ DATABASE_URL=postgresql://localhost:5432/main
257
+
258
+ # Organization-specific databases (any provider)
259
+ ORG_ACME=postgresql://acme.aws.com/prod # PostgreSQL on AWS
260
+ ORG_TECH=mongodb://tech.azure.com/db # MongoDB on Azure
261
+ ORG_STARTUP=mysql://startup.gcp.com/prod # MySQL on GCP
262
+ ORG_LOCAL=sqlite:///local/dev.db # SQLite for development
263
+ ORG_LEGACY=mongodb://legacy.onprem.com/data # On-premise MongoDB
264
+
265
+ # Enable tenant mode within each org
266
+ BLOOM_DB_TENANT=auto
267
+ ```
268
+
269
+ ### **Hot Reload Magic**
270
+
271
+ ```bash
272
+ # Change .env file while app is running:
273
+ echo "ORG_NEWCLIENT=postgresql://newclient.com/db" >> .env
274
+
275
+ # Connections update instantly - no server restart needed! 🔥
276
+ ```
277
+
278
+ ## 💡 Real-World Examples
279
+
280
+ ### **Progressive Scaling Journey**
281
+
282
+ ```typescript
283
+ /**
284
+ * Day 1: Simple blog application
285
+ */
286
+ async function getBlogPosts() {
287
+ const database = await databaseClass.get();
288
+ return await database.posts.findMany({
289
+ include: { user: true },
290
+ orderBy: { createdAt: 'desc' },
291
+ });
292
+ }
293
+
294
+ /**
295
+ * Month 6: Add team workspaces (zero code changes!)
296
+ * Just add: BLOOM_DB_TENANT=auto to .env
297
+ */
298
+ async function getBlogPosts() {
299
+ const database = await databaseClass.get(); // Now auto-filters by tenant
300
+ return await database.posts.findMany({
301
+ include: { user: true },
302
+ orderBy: { createdAt: 'desc' },
303
+ });
304
+ }
305
+
306
+ /**
307
+ * Year 1: Multi-organization SaaS (still zero code changes!)
308
+ * Just add org URLs to .env
309
+ */
310
+ async function getBlogPosts() {
311
+ const database = await databaseClass.get(); // Now org + tenant aware
312
+ return await database.posts.findMany({
313
+ include: { user: true },
314
+ orderBy: { createdAt: 'desc' },
315
+ });
316
+ }
317
+
318
+ /**
319
+ * Admin dashboard (any time)
320
+ */
321
+ async function getAllOrgPosts(orgId) {
322
+ const dbTenants = await databaseClass.org(orgId).getTenants();
323
+ return await dbTenants.posts.findMany({
324
+ include: { user: true },
325
+ orderBy: { createdAt: 'desc' },
326
+ });
327
+ }
328
+ ```
329
+
330
+ ### **Multi-Tenant API Endpoints**
331
+
332
+ ```typescript
333
+ import { database } from '@bloomneo/appkit/database';
334
+
335
+ // User endpoints - auto-filtered by tenant
336
+ app.get('/api/users', async (req, res) => {
337
+ const database = await databaseClass.get();
338
+ const users = await database.user.findMany();
339
+ res.json(users); // Only user's tenant data
340
+ });
341
+
342
+ app.post('/api/users', async (req, res) => {
343
+ const database = await databaseClass.get();
344
+ const user = await database.user.create({
345
+ data: req.body, // tenant_id added automatically
346
+ });
347
+ res.json(user);
348
+ });
349
+
350
+ // Admin endpoints - see all tenant data
351
+ app.get('/api/admin/users', requireRole('admin'), async (req, res) => {
352
+ const dbTenants = await databaseClass.getTenants();
353
+ const users = await dbTenants.user.findMany({
354
+ include: { _count: { select: { posts: true } } },
355
+ });
356
+ res.json(users); // All tenants data
357
+ });
358
+
359
+ // Organization management
360
+ app.get('/api/orgs/:orgId/users', requireRole('admin'), async (req, res) => {
361
+ const { orgId } = req.params;
362
+ const orgdatabase = await databaseClass.org(orgId).get();
363
+ const users = await orgdatabase.user.findMany();
364
+ res.json(users); // Specific org data
365
+ });
366
+ ```
367
+
368
+ ### **Multi-Cloud Enterprise Setup**
369
+
370
+ ```typescript
371
+ /**
372
+ * Enterprise deployment with different cloud providers and databases per organization
373
+ */
374
+
375
+ // Environment configuration supports any database provider:
376
+ const envConfig = `
377
+ # System/Admin database
378
+ DATABASE_URL=postgresql://admin.company.com/system
379
+
380
+ # Customer organizations on different clouds and databases
381
+ ORG_ENTERPRISE_CORP=postgresql://enterprise.dedicated.aws.com/prod
382
+ ORG_TECH_STARTUP=mongodb://tech.shared.azure.com/startup_db
383
+ ORG_LOCAL_BUSINESS=mysql://local.gcp.com:3306/business_db
384
+ ORG_DEV_TESTING=sqlite:///tmp/testing.db
385
+
386
+ # Enable tenant mode across all orgs
387
+ BLOOM_DB_TENANT=auto
388
+ `;
389
+
390
+ // Code remains identical regardless of backend:
391
+ async function getUserData(orgId, userId) {
392
+ const database = await databaseClass.org(orgId).get();
393
+
394
+ // Works with any database type - AppKit handles the differences
395
+ if (database.user?.findUnique) {
396
+ // Prisma client (PostgreSQL, MySQL, SQLite)
397
+ return await database.user.findUnique({
398
+ where: { id: userId },
399
+ include: { posts: true, profile: true },
400
+ });
401
+ } else if (database.User?.findOne) {
402
+ // Mongoose client (MongoDB)
403
+ return await database.User.findOne({ _id: userId })
404
+ .populate('posts')
405
+ .populate('profile');
406
+ }
407
+ }
408
+ ```
409
+
410
+ ## 🔧 Automatic Context Detection
411
+
412
+ ### **Tenant Detection Sources** (when `BLOOM_DB_TENANT=auto`)
413
+
414
+ ```typescript
415
+ // AppKit automatically detects tenant from:
416
+ const tenantId =
417
+ req.headers['x-tenant-id'] || // API header (recommended)
418
+ req.user?.tenant_id || // Authenticated user metadata
419
+ req.params?.tenantId || // URL parameter
420
+ req.query?.tenant || // Query parameter
421
+ req.subdomain || // Subdomain (team.app.com)
422
+ null; // Single tenant mode
423
+ ```
424
+
425
+ ### **Organization Detection Sources**
426
+
427
+ ```typescript
428
+ // AppKit automatically detects organization from:
429
+ const orgId =
430
+ req.headers['x-org-id'] || // API header (recommended)
431
+ req.user?.org_id || // Authenticated user metadata
432
+ req.params?.orgId || // URL parameter
433
+ req.query?.org || // Query parameter
434
+ req.subdomain || // Subdomain (acme.app.com)
435
+ null; // Single org mode
436
+ ```
437
+
438
+ ### **Manual Override** (when needed)
439
+
440
+ ```typescript
441
+ // Override auto-detection when needed
442
+ const specificTenantdatabase = await databaseClass.get({
443
+ tenant: 'specific-tenant',
444
+ });
445
+ const specificOrgdatabase = await databaseClass.org('specific-org').get();
446
+ ```
447
+
448
+ ## 🚀 Framework Integration
449
+
450
+ ### **Express.js**
451
+
452
+ ```typescript
453
+ import express from 'express';
454
+ import { database } from '@bloomneo/appkit/database';
455
+
456
+ const app = express();
457
+
458
+ // Simple route - auto-detects tenant from request
459
+ app.get('/users', async (req, res) => {
460
+ const database = await databaseClass.get();
461
+ const users = await database.user.findMany();
462
+ res.json(users);
463
+ });
464
+
465
+ // Admin route - access all tenants
466
+ app.get('/admin/users', requireAdmin, async (req, res) => {
467
+ const dbTenants = await databaseClass.getTenants();
468
+ const users = await dbTenants.user.findMany();
469
+ res.json(users);
470
+ });
471
+ ```
472
+
473
+ ### **Fastify**
474
+
475
+ ```typescript
476
+ import Fastify from 'fastify';
477
+ import { database } from '@bloomneo/appkit/database';
478
+
479
+ const fastify = Fastify();
480
+
481
+ fastify.get('/users', async (request, reply) => {
482
+ const database = await databaseClass.get();
483
+ const users = await database.user.findMany();
484
+ return users;
485
+ });
486
+
487
+ fastify.get(
488
+ '/admin/users',
489
+ { preHandler: requireAdmin },
490
+ async (request, reply) => {
491
+ const dbTenants = await databaseClass.getTenants();
492
+ const users = await dbTenants.user.findMany();
493
+ return users;
494
+ }
495
+ );
496
+ ```
497
+
498
+ ### **Next.js API Routes**
499
+
500
+ ```typescript
501
+ // pages/api/users.ts
502
+ import { database } from '@bloomneo/appkit/database';
503
+
504
+ export default async function handler(req, res) {
505
+ const database = await databaseClass.get();
506
+
507
+ if (req.method === 'GET') {
508
+ const users = await database.user.findMany();
509
+ res.json(users);
510
+ } else if (req.method === 'POST') {
511
+ const user = await database.user.create({ data: req.body });
512
+ res.json(user);
513
+ }
514
+ }
515
+
516
+ // pages/api/admin/users.ts
517
+ import { database } from '@bloomneo/appkit/database';
518
+
519
+ export default async function handler(req, res) {
520
+ const dbTenants = await databaseClass.getTenants();
521
+ const users = await dbTenants.user.findMany();
522
+ res.json(users);
523
+ }
524
+ ```
525
+
526
+ ## 🛠️ Advanced Features
527
+
528
+ ### **Health Monitoring**
529
+
530
+ ```typescript
531
+ // System health check
532
+ const health = await databaseClass.health();
533
+ console.log(health);
534
+ // {
535
+ // healthy: true,
536
+ // connections: 3,
537
+ // timestamp: "2024-01-15T10:30:00.000Z"
538
+ // }
539
+ ```
540
+
541
+ ### **Tenant Management**
542
+
543
+ ```typescript
544
+ // List all tenants
545
+ const tenants = await databaseClass.list();
546
+ console.log(tenants); // ['team-alpha', 'team-beta', 'team-gamma']
547
+
548
+ // Check if tenant exists
549
+ const exists = await databaseClass.exists('team-alpha');
550
+ console.log(exists); // true
551
+
552
+ // Create tenant (validates ID format)
553
+ await databaseClass.create('new-team');
554
+
555
+ // Delete tenant (requires confirmation)
556
+ await databaseClass.delete('old-team', { confirm: true });
557
+ ```
558
+
559
+ ### **Connection Management**
560
+
561
+ ```typescript
562
+ // Graceful shutdown
563
+ process.on('SIGTERM', async () => {
564
+ await databaseClass.disconnect();
565
+ process.exit(0);
566
+ });
567
+ ```
568
+
569
+ ## 📊 Performance & Scaling
570
+
571
+ ### **Connection Pooling**
572
+
573
+ - **Automatic caching** - Connections reused per org/tenant combination
574
+ - **Hot reload** - New .env configurations picked up instantly
575
+ - **Memory efficient** - Connections shared across requests
576
+
577
+ ### **Database Performance**
578
+
579
+ - **Mandatory indexes** - `tenant_id` indexed on all tables from day 1
580
+ - **Query optimization** - Automatic tenant filtering at database level
581
+ - **Connection limits** - Respects database provider connection pools
582
+
583
+ ### **Scaling Characteristics**
584
+
585
+ - **Single tenant**: 1 connection per app
586
+ - **Multi-tenant**: 1 connection (shared filtering)
587
+ - **Multi-org**: 1 connection per organization
588
+ - **Multi-org + tenant**: 1 connection per org (shared tenant filtering)
589
+
590
+ ## 🔍 Migration Guide
591
+
592
+ ### **From Direct Prisma**
593
+
594
+ ```typescript
595
+ // Before: Direct Prisma usage
596
+ import { PrismaClient } from '@prisma/client';
597
+ const prisma = new PrismaClient();
598
+ const users = await prisma.user.findMany();
599
+
600
+ // After: AppKit Database
601
+ import { database } from '@bloomneo/appkit/database';
602
+ const database = await databaseClass.get();
603
+ const users = await database.user.findMany();
604
+ ```
605
+
606
+ ### **From Manual Multi-Tenancy**
607
+
608
+ ```typescript
609
+ // Before: Manual tenant filtering everywhere
610
+ const users = await prisma.user.findMany({
611
+ where: { tenant_id: getTenantId(req) },
612
+ });
613
+
614
+ // After: Automatic tenant filtering
615
+ const database = await databaseClass.get();
616
+ const users = await database.user.findMany(); // tenant_id added automatically
617
+ ```
618
+
619
+ ### **Schema Migration**
620
+
621
+ ```sql
622
+ -- Add tenant_id to existing tables
623
+ ALTER TABLE users ADD COLUMN tenant_id text;
624
+ ALTER TABLE posts ADD COLUMN tenant_id text;
625
+ ALTER TABLE comments ADD COLUMN tenant_id text;
626
+
627
+ -- Add performance indexes
628
+ CREATE INDEX idx_users_tenant ON users(tenant_id);
629
+ CREATE INDEX idx_posts_tenant ON posts(tenant_id);
630
+ CREATE INDEX idx_comments_tenant ON comments(tenant_id);
631
+
632
+ -- Set existing data to null (single tenant mode)
633
+ -- No data changes needed - null = single tenant
634
+ ```
635
+
636
+ ## 🤖 LLM Guidelines
637
+
638
+ ### **Variable Naming Patterns**
639
+
640
+ ```typescript
641
+ // ✅ Standard patterns for AI code generation:
642
+
643
+ // Normal user access (single or tenant mode)
644
+ const database = await databaseClass.get();
645
+
646
+ // Admin access to all tenants
647
+ const dbTenants = await databaseClass.getTenants();
648
+
649
+ // Organization-specific access (use org name in variable)
650
+ const acmedatabase = await databaseClass.org('acme').get();
651
+ const techdatabase = await databaseClass.org('tech').get();
652
+ const startupdatabase = await databaseClass.org('startup').get();
653
+
654
+ // Organization admin access (use org name + DbTenants)
655
+ const acmeDbTenants = await databaseClass.org('acme').getTenants();
656
+ const techDbTenants = await databaseClass.org('tech').getTenants();
657
+ ```
658
+
659
+ ### **Common Patterns**
660
+
661
+ ```typescript
662
+ // ✅ User data access
663
+ const database = await databaseClass.get();
664
+ const users = await database.user.findMany();
665
+
666
+ // ✅ Admin functionality
667
+ const dbTenants = await databaseClass.getTenants();
668
+ const allUsers = await dbTenants.user.findMany();
669
+
670
+ // ✅ Organization management
671
+ const acmedatabase = await databaseClass.org('acme').get();
672
+ const acmeUsers = await acmedatabase.user.findMany();
673
+
674
+ // ✅ Cross-tenant analytics (admin)
675
+ const acmeDbTenants = await databaseClass.org('acme').getTenants();
676
+ const analytics = await acmeDbTenants.user.groupBy({
677
+ by: ['tenant_id'],
678
+ _count: true,
679
+ });
680
+ ```
681
+
682
+ ### **Schema Requirements**
683
+
684
+ ```typescript
685
+ // ✅ ALWAYS include in ALL models (SQL):
686
+ model AnyTable {
687
+ id String @id @default(cuid())
688
+ // ... your fields ...
689
+ tenant_id String? // MANDATORY: nullable for future use
690
+
691
+ @@index([tenant_id]) // MANDATORY: performance index
692
+ }
693
+
694
+ // ✅ ALWAYS include in ALL schemas (MongoDB):
695
+ const anySchema = new Schema({
696
+ // ... your fields ...
697
+ tenant_id: { type: String, index: true } // MANDATORY: indexed
698
+ });
699
+ anySchema.index({ tenant_id: 1 }); // MANDATORY: performance index
700
+ ```
701
+
702
+ ## 🚨 Common Mistakes to Avoid
703
+
704
+ ### **❌ Schema Mistakes**
705
+
706
+ ```sql
707
+ -- ❌ DON'T: Missing tenant_id field
708
+ CREATE TABLE users (
709
+ id uuid PRIMARY KEY,
710
+ email text,
711
+ name text
712
+ -- Missing tenant_id - will need painful migration later
713
+ );
714
+
715
+ -- ✅ DO: Always include tenant_id (SQL)
716
+ CREATE TABLE users (
717
+ id uuid PRIMARY KEY,
718
+ email text,
719
+ name text,
720
+ tenant_id text, -- Future-proof from day 1
721
+ INDEX idx_tenant (tenant_id)
722
+ );
723
+ ```
724
+
725
+ ```javascript
726
+ // ❌ DON'T: Missing tenant_id field (MongoDB)
727
+ const userSchema = new Schema({
728
+ email: String,
729
+ name: String,
730
+ // Missing tenant_id - will need painful migration later
731
+ });
732
+
733
+ // ✅ DO: Always include tenant_id (MongoDB)
734
+ const userSchema = new Schema({
735
+ email: String,
736
+ name: String,
737
+ tenant_id: { type: String, index: true }, // Future-proof from day 1
738
+ });
739
+ userSchema.index({ tenant_id: 1 });
740
+ ```
741
+
742
+ ### **❌ API Usage Mistakes**
743
+
744
+ ```typescript
745
+ // ❌ DON'T: Hard-code tenant access (any database)
746
+ const users = await prisma.user.findMany({
747
+ where: { tenant_id: 'hardcoded-tenant' },
748
+ });
749
+ const users = await User.find({ tenant_id: 'hardcoded-tenant' });
750
+
751
+ // ✅ DO: Use databaseClass.get() for automatic filtering
752
+ const database = await databaseClass.get();
753
+ const users = await database.user.findMany(); // Prisma - Auto-filtered
754
+ const users = await database.User.find(); // Mongoose - Auto-filtered
755
+
756
+ // ❌ DON'T: Mix access patterns
757
+ const database = await databaseClass.get();
758
+ const admindatabase = await databaseClass.getTenants();
759
+ const users = await database.user.findMany(); // Which database am I using?
760
+
761
+ // ✅ DO: Clear variable naming
762
+ const database = await databaseClass.get(); // User data
763
+ const dbTenants = await databaseClass.getTenants(); // Admin data
764
+ const users = await database.user.findMany(); // Clear intent (Prisma)
765
+ const users = await database.User.find(); // Clear intent (Mongoose)
766
+ ```
767
+
768
+ ## 🔧 Troubleshooting
769
+
770
+ ### **Database Connection Issues**
771
+
772
+ ```typescript
773
+ // Check configuration
774
+ import { database } from '@bloomneo/appkit/database';
775
+
776
+ const health = await databaseClass.health();
777
+ if (!health.healthy) {
778
+ console.error('Database issue:', health.error);
779
+ }
780
+ ```
781
+
782
+ ### **Missing tenant_id Fields**
783
+
784
+ ```bash
785
+ # Development warning will show:
786
+ # Model 'User' missing required field 'tenant_id'
787
+ # Add: tenant_id String? @map("tenant_id") to your Prisma schema
788
+ ```
789
+
790
+ ### **Environment Validation**
791
+
792
+ ```typescript
793
+ import { getConfigSummary } from '@bloomneo/appkit/database/defaults';
794
+
795
+ console.log(getConfigSummary());
796
+ // Shows current configuration, validation status, and warnings
797
+ ```
798
+
799
+ ## 📈 Roadmap
800
+
801
+ - **Vector Search Support** - Built-in pgvector integration
802
+ - **Read Replicas** - Automatic read/write splitting
803
+ - **Connection Pooling** - Advanced connection management
804
+ - **Schema Migrations** - Automated tenant-aware migrations
805
+ - **Analytics Dashboard** - Built-in multi-tenant analytics
806
+
807
+ ## 📄 License
808
+
809
+ MIT © [Bloomneo](https://github.com/bloomneo)
810
+
811
+ ---
812
+
813
+ <p align="center">
814
+ <strong>Built for developers who value simplicity and future-proof architecture</strong><br>
815
+ <a href="https://github.com/bloomneo/appkit">⭐ Star us on GitHub</a> •
816
+ <a href="https://discord.gg/bloomneo">💬 Join our Discord</a> •
817
+ <a href="https://twitter.com/bloomneo">🐦 Follow on Twitter</a>
818
+ </p>