@classytic/arc 1.0.0 → 1.0.5

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/README.md DELETED
@@ -1,900 +0,0 @@
1
- # @classytic/arc
2
-
3
- **Database-agnostic resource framework for Fastify**
4
-
5
- *Think Rails conventions, Django REST Framework patterns, Laravel's Eloquent — but for Fastify.*
6
-
7
- Arc provides routing, permissions, and resource patterns. **You choose the database:**
8
- - **MongoDB** → `npm install @classytic/mongokit`
9
- - **PostgreSQL/MySQL/SQLite** → `@classytic/prismakit` (coming soon)
10
-
11
- > **⚠️ ESM Only**: Arc requires Node.js 18+ with ES modules (`"type": "module"` in package.json). CommonJS is not supported. [Migration guide →](https://nodejs.org/api/esm.html)
12
-
13
- ---
14
-
15
- ## Why Arc?
16
-
17
- Building REST APIs in Node.js often means making hundreds of small decisions: How do I structure routes? Where does validation go? How do I handle soft deletes consistently? What about multi-tenant isolation?
18
-
19
- **Arc gives you conventions so you can focus on your domain, not boilerplate.**
20
-
21
- | Without Arc | With Arc |
22
- |-------------|----------|
23
- | Write CRUD routes for every model | `defineResource()` generates them |
24
- | Manually wire controllers to routes | Convention-based auto-wiring |
25
- | Copy-paste soft delete logic | `presets: ['softDelete']` |
26
- | Manually filter by tenant on every query | `presets: ['multiTenant']` auto-filters |
27
- | Hand-roll OpenAPI specs | Auto-generated from resources |
28
-
29
- **Arc is opinionated where it matters, flexible where you need it.**
30
-
31
- ---
32
-
33
- ## Installation
34
-
35
- ```bash
36
- # Core framework
37
- npm install @classytic/arc
38
-
39
- # Choose your database kit:
40
- npm install @classytic/mongokit # MongoDB/Mongoose
41
- # npm install @classytic/prismakit # PostgreSQL/MySQL/SQLite (coming soon)
42
- ```
43
-
44
- ### Optional Dependencies
45
-
46
- Arc's security and utility plugins are opt-in via peer dependencies. Install only what you need:
47
-
48
- ```bash
49
- # Security plugins (recommended for production)
50
- npm install @fastify/helmet @fastify/cors @fastify/rate-limit
51
-
52
- # Performance plugins
53
- npm install @fastify/under-pressure
54
-
55
- # Utility plugins
56
- npm install @fastify/sensible @fastify/multipart fastify-raw-body
57
-
58
- # Development logging
59
- npm install pino-pretty
60
- ```
61
-
62
- Or disable plugins you don't need:
63
- ```typescript
64
- createApp({
65
- helmet: false, // Disable if not needed
66
- rateLimit: false, // Disable if not needed
67
- // ...
68
- })
69
- ```
70
-
71
- ## Key Features
72
-
73
- - **Resource-First Architecture** — Define your API as resources with `defineResource()`, not scattered route handlers
74
- - **Presets System** — Composable behaviors like `softDelete`, `slugLookup`, `tree`, `ownedByUser`, `multiTenant`
75
- - **Auto-Generated OpenAPI** — Documentation that stays in sync with your code
76
- - **Database-Agnostic Core** — Works with any database via adapters. MongoDB/Mongoose optimized out of the box, extensible to Prisma, Drizzle, TypeORM, etc.
77
- - **Production Defaults** — Helmet, CORS, rate limiting enabled by default
78
- - **CLI Tooling** — `arc generate resource` scaffolds new resources instantly
79
- - **Environment Presets** — Development, production, and testing configs built-in
80
- - **Type-Safe Presets** — TypeScript interfaces ensure controller methods match preset requirements
81
- - **Ultra-Fast Testing** — In-memory MongoDB support for 10x faster tests
82
-
83
- ## Quick Start
84
-
85
- ### Using ArcFactory (Recommended)
86
-
87
- ```typescript
88
- import mongoose from 'mongoose';
89
- import { createApp } from '@classytic/arc/factory';
90
- import { productResource } from './resources/product.js';
91
- import config from './config/index.js';
92
-
93
- // 1. Connect your database (Arc is database-agnostic)
94
- await mongoose.connect(config.db.uri);
95
-
96
- // 2. Create Arc app
97
- const app = await createApp({
98
- preset: 'production', // or 'development', 'testing'
99
- auth: { jwt: { secret: config.app.jwtSecret } },
100
- cors: { origin: config.cors.origin },
101
-
102
- // Opt-out security (all enabled by default)
103
- helmet: true, // Set false to disable
104
- rateLimit: true, // Set false to disable
105
- underPressure: true, // Set false to disable
106
- });
107
-
108
- // 3. Register your resources
109
- await app.register(productResource.toPlugin());
110
-
111
- await app.listen({ port: 8040, host: '0.0.0.0' });
112
- ```
113
-
114
- ### Multiple Databases
115
-
116
- Arc's adapter pattern lets you connect to multiple databases:
117
-
118
- ```typescript
119
- import mongoose from 'mongoose';
120
-
121
- // Connect to multiple databases
122
- const primaryDb = await mongoose.connect(process.env.PRIMARY_DB);
123
- const analyticsDb = mongoose.createConnection(process.env.ANALYTICS_DB);
124
-
125
- // Each resource uses its own adapter
126
- const orderResource = defineResource({
127
- name: 'order',
128
- adapter: createMongooseAdapter({ model: OrderModel, repository: orderRepo }),
129
- });
130
-
131
- const analyticsResource = defineResource({
132
- name: 'analytics',
133
- adapter: createMongooseAdapter({ model: AnalyticsModel, repository: analyticsRepo }),
134
- });
135
- ```
136
-
137
- ### Manual Setup
138
-
139
- ```typescript
140
- import Fastify from 'fastify';
141
- import mongoose from 'mongoose';
142
- import { defineResource, createMongooseAdapter } from '@classytic/arc';
143
-
144
- // Connect your database
145
- await mongoose.connect('mongodb://localhost:27017/myapp');
146
-
147
- const fastify = Fastify();
148
-
149
- // Define and register resources
150
- import { allowPublic, requireRoles } from '@classytic/arc';
151
-
152
- const productResource = defineResource({
153
- name: 'product',
154
- adapter: createMongooseAdapter({
155
- model: ProductModel,
156
- repository: productRepository,
157
- }),
158
- controller: productController, // optional; auto-created if omitted
159
- presets: ['softDelete', 'slugLookup'],
160
- permissions: {
161
- list: allowPublic(),
162
- get: allowPublic(),
163
- create: requireRoles(['admin']),
164
- update: requireRoles(['admin']),
165
- delete: requireRoles(['admin']),
166
- },
167
- });
168
-
169
- await fastify.register(productResource.toPlugin());
170
- ```
171
-
172
- ## Core Concepts
173
-
174
- ### Authentication
175
-
176
- Arc provides **optional** built-in JWT authentication. You can:
177
-
178
- 1. **Use Arc's JWT auth** (default) - Simple, production-ready
179
- 2. **Replace with OAuth** - Google, Facebook, GitHub, etc.
180
- 3. **Use Passport.js** - 500+ authentication strategies
181
- 4. **Create custom auth** - Full control over authentication logic
182
- 5. **Mix multiple strategies** - JWT + API keys + OAuth
183
-
184
- **Arc's auth is NOT mandatory.** Disable it and use any Fastify auth plugin:
185
-
186
- ```typescript
187
- import { createApp } from '@classytic/arc';
188
-
189
- // Disable Arc's JWT auth
190
- const app = await createApp({
191
- auth: false, // Use your own auth strategy
192
- });
193
-
194
- // Use @fastify/oauth2 for Google login
195
- await app.register(require('@fastify/oauth2'), {
196
- name: 'googleOAuth',
197
- credentials: {
198
- client: {
199
- id: process.env.GOOGLE_CLIENT_ID,
200
- secret: process.env.GOOGLE_CLIENT_SECRET,
201
- },
202
- auth: {
203
- authorizeHost: 'https://accounts.google.com',
204
- authorizePath: '/o/oauth2/v2/auth',
205
- tokenHost: 'https://www.googleapis.com',
206
- tokenPath: '/oauth2/v4/token',
207
- },
208
- },
209
- startRedirectPath: '/auth/google',
210
- callbackUri: 'http://localhost:8080/auth/google/callback',
211
- scope: ['profile', 'email'],
212
- });
213
-
214
- // OAuth callback - issue JWT
215
- app.get('/auth/google/callback', async (request, reply) => {
216
- const { token } = await app.googleOAuth.getAccessTokenFromAuthorizationCodeFlow(request);
217
-
218
- // Fetch user info from Google
219
- const userInfo = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
220
- headers: { Authorization: `Bearer ${token.access_token}` },
221
- }).then(r => r.json());
222
-
223
- // Create user in your database
224
- const user = await User.findOneAndUpdate(
225
- { email: userInfo.email },
226
- { email: userInfo.email, name: userInfo.name, googleId: userInfo.id },
227
- { upsert: true, new: true }
228
- );
229
-
230
- // Issue JWT using Arc's auth (or use sessions/cookies)
231
- const jwtToken = app.jwt.sign({ _id: user._id, email: user.email });
232
-
233
- return reply.send({ token: jwtToken, user });
234
- });
235
- ```
236
-
237
- **See [examples/custom-auth-providers.ts](examples/custom-auth-providers.ts) for:**
238
- - OAuth (Google, Facebook)
239
- - Passport.js integration
240
- - Custom authentication strategies
241
- - SAML/SSO for enterprise
242
- - Hybrid auth (JWT + API keys)
243
-
244
- ### Resources
245
-
246
- A resource encapsulates model, repository, controller, and routes:
247
-
248
- ```typescript
249
- import { defineResource, createMongooseAdapter, allowPublic, requireRoles } from '@classytic/arc';
250
-
251
- export default defineResource({
252
- name: 'product',
253
- adapter: createMongooseAdapter({
254
- model: ProductModel,
255
- repository: productRepository,
256
- }),
257
- controller: productController,
258
-
259
- // Presets add common functionality
260
- presets: [
261
- 'softDelete', // deletedAt field, restore endpoint
262
- 'slugLookup', // GET /products/:slug
263
- 'ownedByUser', // createdBy ownership checks
264
- 'multiTenant', // organizationId isolation
265
- 'tree', // Hierarchical data support
266
- ],
267
-
268
- // Permission functions (NOT string arrays)
269
- permissions: {
270
- list: allowPublic(), // Public
271
- get: allowPublic(), // Public
272
- create: requireRoles(['admin', 'editor']), // Restricted
273
- update: requireRoles(['admin', 'editor']),
274
- delete: requireRoles(['admin']),
275
- },
276
-
277
- // Custom routes beyond CRUD
278
- additionalRoutes: [
279
- {
280
- method: 'GET',
281
- path: '/featured',
282
- handler: 'getFeatured', // Controller method name
283
- permissions: allowPublic(), // Permission function
284
- wrapHandler: true, // Required: true=controller, false=fastify
285
- },
286
- ],
287
- });
288
- ```
289
-
290
- ### Controllers
291
-
292
- Extend BaseController for built-in security and CRUD:
293
-
294
- ```typescript
295
- import { BaseController } from '@classytic/arc';
296
- import type { ISoftDeleteController, ISlugLookupController } from '@classytic/arc/presets';
297
-
298
- // Type-safe controller with preset interfaces
299
- class ProductController
300
- extends BaseController<Product>
301
- implements ISoftDeleteController<Product>, ISlugLookupController<Product>
302
- {
303
- constructor() {
304
- super(productRepository);
305
-
306
- // TypeScript ensures these methods exist (required by presets)
307
- this.getBySlug = this.getBySlug.bind(this);
308
- this.getDeleted = this.getDeleted.bind(this);
309
- this.restore = this.restore.bind(this);
310
- }
311
-
312
- // Custom method
313
- async getFeatured(req, reply) {
314
- // Security checks applied automatically
315
- const products = await this.repository.findAll({
316
- filter: { isFeatured: true },
317
- ...this._applyFilters(req),
318
- });
319
- return reply.send({ success: true, data: products });
320
- }
321
- }
322
- ```
323
-
324
- **Preset Type Interfaces:** Arc exports TypeScript interfaces for each preset that requires controller methods:
325
-
326
- - `ISoftDeleteController` - requires `getDeleted()` and `restore()`
327
- - `ISlugLookupController` - requires `getBySlug()`
328
- - `ITreeController` - requires `getTree()` and `getChildren()`
329
-
330
- **Note:** Presets like `multiTenant`, `ownedByUser`, and `audited` don't require controller methods—they work via middleware.
331
-
332
- ### TypeScript Strict Mode
333
-
334
- For maximum type safety, use strict controller typing:
335
-
336
- ```typescript
337
- import { BaseController } from '@classytic/arc';
338
- import type { Document } from 'mongoose';
339
- import type { ISoftDeleteController, ISlugLookupController } from '@classytic/arc/presets';
340
-
341
- // Define your document type
342
- interface ProductDocument extends Document {
343
- _id: string;
344
- name: string;
345
- slug: string;
346
- price: number;
347
- deletedAt?: Date;
348
- }
349
-
350
- // Strict controller with generics
351
- class ProductController
352
- extends BaseController<ProductDocument>
353
- implements
354
- ISoftDeleteController<ProductDocument>,
355
- ISlugLookupController<ProductDocument>
356
- {
357
- // TypeScript enforces these method signatures
358
- async getBySlug(req, reply): Promise<void> {
359
- const { slug } = req.params;
360
- const product = await this.repository.getBySlug(slug);
361
-
362
- if (!product) {
363
- return reply.code(404).send({ error: 'Product not found' });
364
- }
365
-
366
- return reply.send({ success: true, data: product });
367
- }
368
-
369
- async getDeleted(req, reply): Promise<void> {
370
- const products = await this.repository.findDeleted();
371
- return reply.send({ success: true, data: products });
372
- }
373
-
374
- async restore(req, reply): Promise<void> {
375
- const { id } = req.params;
376
- const product = await this.repository.restore(id);
377
- return reply.send({ success: true, data: product });
378
- }
379
- }
380
- ```
381
-
382
- **Benefits of strict typing:**
383
- - Compile-time checks for preset requirements
384
- - IntelliSense autocomplete for controller methods
385
- - Catch type mismatches before runtime
386
- - Refactoring safety across large codebases
387
-
388
- ### Repositories
389
-
390
- Repositories come from your chosen database kit (Arc is database-agnostic):
391
-
392
- **MongoDB with MongoKit:**
393
- ```typescript
394
- import { Repository, softDeletePlugin } from '@classytic/mongokit';
395
-
396
- class ProductRepository extends Repository {
397
- constructor() {
398
- super(ProductModel, [softDeletePlugin()]);
399
- }
400
-
401
- async getBySlug(slug) {
402
- return this.Model.findOne({ slug }).lean();
403
- }
404
- }
405
- ```
406
-
407
- **Prisma (coming soon):**
408
- ```typescript
409
- import { PrismaRepository } from '@classytic/prismakit';
410
-
411
- class ProductRepository extends PrismaRepository {
412
- // Same interface, different database
413
- }
414
- ```
415
-
416
- ## CLI Commands
417
-
418
- ```bash
419
- # Generate resource scaffold
420
- arc generate resource product --module catalog --presets softDelete,slugLookup
421
-
422
- # Show all registered resources (loads from entry file)
423
- arc introspect --entry ./src/index.js
424
-
425
- # Export OpenAPI spec (loads from entry file)
426
- arc docs ./docs/openapi.json --entry ./src/index.js
427
-
428
- # Note: --entry flag loads your resource definitions into the registry
429
- # Point it to the file that imports all your resources
430
- ```
431
-
432
- ## Environment Presets
433
-
434
- ### Production
435
- - Info-level logging
436
- - Strict CORS (must configure origin)
437
- - Rate limiting: **100 req/min/IP** (configurable via `rateLimit.max` option)
438
- - Helmet with CSP
439
- - Health monitoring (under-pressure)
440
- - All security plugins enabled
441
-
442
- > **💡 Tip**: Default rate limit (100 req/min) may be conservative for high-traffic APIs. Adjust via:
443
- > ```typescript
444
- > createApp({ rateLimit: { max: 300, timeWindow: '1 minute' } })
445
- > ```
446
-
447
- > **Note**: Compression is not included due to known Fastify 5 stream issues. Use a reverse proxy (Nginx, Caddy) or CDN for response compression.
448
-
449
- ### Development
450
- - Debug logging
451
- - Permissive CORS
452
- - Rate limiting: 1000 req/min (development-friendly)
453
- - Relaxed security
454
-
455
- ### Testing
456
- - Silent logging
457
- - No CORS restrictions
458
- - Rate limiting: disabled (test performance)
459
- - Minimal security overhead
460
-
461
- ## Serverless Deployment
462
-
463
- ### AWS Lambda
464
-
465
- ```typescript
466
- import { createLambdaHandler } from './index.factory.js';
467
-
468
- export const handler = await createLambdaHandler();
469
- ```
470
-
471
- ### Google Cloud Run
472
-
473
- ```typescript
474
- import { cloudRunHandler } from './index.factory.js';
475
- import { createServer } from 'http';
476
-
477
- createServer(cloudRunHandler).listen(process.env.PORT || 8080);
478
- ```
479
-
480
- ### Vercel
481
-
482
- ```typescript
483
- import { vercelHandler } from './index.factory.js';
484
-
485
- export default vercelHandler;
486
- ```
487
-
488
- ## Testing Utilities
489
-
490
- ### Test App Creation with In-Memory MongoDB
491
-
492
- Arc's testing utilities now include **in-memory MongoDB by default** for 10x faster tests.
493
-
494
- ```typescript
495
- import { createTestApp } from '@classytic/arc/testing';
496
- import type { TestAppResult } from '@classytic/arc/testing';
497
-
498
- describe('API Tests', () => {
499
- let testApp: TestAppResult;
500
-
501
- beforeAll(async () => {
502
- // Creates app + starts in-memory MongoDB automatically
503
- testApp = await createTestApp({
504
- auth: { jwt: { secret: 'test-secret-32-chars-minimum-len' } },
505
- });
506
-
507
- // Connect your models to the in-memory DB
508
- await mongoose.connect(testApp.mongoUri);
509
- });
510
-
511
- afterAll(async () => {
512
- // Cleans up DB and closes app
513
- await testApp.close();
514
- });
515
-
516
- test('GET /products', async () => {
517
- const response = await testApp.app.inject({
518
- method: 'GET',
519
- url: '/products',
520
- });
521
- expect(response.statusCode).toBe(200);
522
- });
523
- });
524
- ```
525
-
526
- **Performance:** In-memory MongoDB requires `mongodb-memory-server` (dev dependency). Tests run 10x faster than external MongoDB.
527
-
528
- ```bash
529
- npm install -D mongodb-memory-server
530
- ```
531
-
532
- **Using External MongoDB:**
533
-
534
- ```typescript
535
- const testApp = await createTestApp({
536
- auth: { jwt: { secret: 'test-secret-32-chars-minimum-len' } },
537
- useInMemoryDb: false,
538
- mongoUri: 'mongodb://localhost:27017/test-db',
539
- });
540
- ```
541
-
542
- **Note:** Arc's testing preset disables security plugins for faster tests.
543
-
544
- ### Mock Factories
545
-
546
- ```typescript
547
- import { createMockRepository, createDataFactory } from '@classytic/arc/testing';
548
-
549
- // Mock repository
550
- const mockRepo = createMockRepository({
551
- findById: jest.fn().mockResolvedValue({ _id: '123', name: 'Test' }),
552
- });
553
-
554
- // Data factory
555
- const productFactory = createDataFactory({
556
- name: (i) => `Product ${i}`,
557
- price: (i) => 100 + i * 10,
558
- isActive: () => true,
559
- });
560
-
561
- const products = productFactory.buildMany(5);
562
- ```
563
-
564
- ### Database Helpers
565
-
566
- ```typescript
567
- import { withTestDb } from '@classytic/arc/testing';
568
-
569
- describe('Product Repository', () => {
570
- withTestDb((db) => {
571
- it('should create product', async () => {
572
- const product = await Product.create({ name: 'Test' });
573
- expect(product.name).toBe('Test');
574
- });
575
- });
576
- });
577
- ```
578
-
579
- ## State Machine
580
-
581
- ```typescript
582
- import { createStateMachine } from '@classytic/arc/utils';
583
-
584
- const orderStateMachine = createStateMachine('order', {
585
- submit: {
586
- from: ['draft'],
587
- to: 'pending',
588
- guard: ({ data }) => data.items.length > 0,
589
- after: async ({ from, to, data }) => {
590
- await sendNotification(data.userId, 'Order submitted');
591
- },
592
- },
593
- approve: {
594
- from: ['pending'],
595
- to: 'approved',
596
- },
597
- ship: {
598
- from: ['approved'],
599
- to: 'shipped',
600
- },
601
- cancel: {
602
- from: ['draft', 'pending'],
603
- to: 'cancelled',
604
- },
605
- }, { trackHistory: true });
606
-
607
- // Usage
608
- orderStateMachine.can('submit', 'draft'); // true
609
- orderStateMachine.assert('submit', 'draft'); // throws if invalid
610
- orderStateMachine.getAvailableActions('pending'); // ['approve', 'cancel']
611
- orderStateMachine.getHistory(); // Array of transitions
612
- ```
613
-
614
- ## Hooks System
615
-
616
- ```typescript
617
- import { hookRegistry } from '@classytic/arc/hooks';
618
-
619
- // Register hook
620
- hookRegistry.register('product', 'beforeCreate', async (context) => {
621
- context.data.slug = slugify(context.data.name);
622
- });
623
-
624
- // Available hooks
625
- // beforeCreate, afterCreate
626
- // beforeUpdate, afterUpdate
627
- // beforeDelete, afterDelete
628
- // beforeList, afterList
629
- ```
630
-
631
- ## Policies
632
-
633
- ```typescript
634
- import { definePolicy } from '@classytic/arc/policies';
635
-
636
- const ownedByUserPolicy = definePolicy({
637
- name: 'ownedByUser',
638
- apply: async (query, req) => {
639
- if (!req.user) throw new Error('Unauthorized');
640
- query.filter.createdBy = req.user._id;
641
- return query;
642
- },
643
- });
644
-
645
- // Apply in resource
646
- export default defineResource({
647
- name: 'document',
648
- policies: [ownedByUserPolicy],
649
- // ...
650
- });
651
- ```
652
-
653
- ## Events
654
-
655
- ```typescript
656
- import { eventPlugin } from '@classytic/arc/events';
657
-
658
- await fastify.register(eventPlugin);
659
-
660
- // Emit event
661
- await fastify.events.publish('order.created', { orderId: '123', userId: '456' });
662
-
663
- // Subscribe
664
- const unsubscribe = await fastify.events.subscribe('order.created', async (event) => {
665
- await sendConfirmationEmail(event.payload.userId);
666
- });
667
-
668
- // Unsubscribe
669
- unsubscribe();
670
- ```
671
-
672
- ## Introspection
673
-
674
- ```typescript
675
- import { resourceRegistry } from '@classytic/arc/registry';
676
-
677
- // Get all resources
678
- const resources = resourceRegistry.getAll();
679
-
680
- // Get specific resource
681
- const product = resourceRegistry.get('product');
682
-
683
- // Get stats
684
- const stats = resourceRegistry.getStats();
685
- // { total: 15, withPresets: 8, withPolicies: 5 }
686
- ```
687
-
688
- ## Production Features (Meta/Stripe Tier)
689
-
690
- ### OpenTelemetry Distributed Tracing
691
-
692
- ```typescript
693
- import { tracingPlugin } from '@classytic/arc/plugins';
694
-
695
- await fastify.register(tracingPlugin, {
696
- serviceName: 'my-api',
697
- exporterUrl: 'http://localhost:4318/v1/traces',
698
- sampleRate: 0.1, // Trace 10% of requests
699
- });
700
-
701
- // Custom spans
702
- import { createSpan } from '@classytic/arc/plugins';
703
-
704
- return createSpan(req, 'expensiveOperation', async (span) => {
705
- span.setAttribute('userId', req.user._id);
706
- return await processData();
707
- });
708
- ```
709
-
710
- ### Enhanced Health Checks
711
-
712
- ```typescript
713
- import { healthPlugin } from '@classytic/arc/plugins';
714
-
715
- await fastify.register(healthPlugin, {
716
- metrics: true, // Prometheus metrics
717
- checks: [
718
- {
719
- name: 'mongodb',
720
- check: async () => mongoose.connection.readyState === 1,
721
- critical: true,
722
- },
723
- {
724
- name: 'redis',
725
- check: async () => redisClient.ping() === 'PONG',
726
- critical: true,
727
- },
728
- ],
729
- });
730
-
731
- // Endpoints: /_health/live, /_health/ready, /_health/metrics
732
- ```
733
-
734
- ### Circuit Breaker
735
-
736
- ```typescript
737
- import { CircuitBreaker } from '@classytic/arc/utils';
738
-
739
- const stripeBreaker = new CircuitBreaker(
740
- async (amount) => stripe.charges.create({ amount }),
741
- {
742
- failureThreshold: 5,
743
- resetTimeout: 30000,
744
- fallback: async (amount) => queuePayment(amount),
745
- }
746
- );
747
-
748
- const charge = await stripeBreaker.call(1000);
749
- ```
750
-
751
- ### Schema Versioning & Migrations
752
-
753
- ```typescript
754
- import { defineMigration, MigrationRunner } from '@classytic/arc/migrations';
755
-
756
- const productV2 = defineMigration({
757
- version: 2,
758
- resource: 'product',
759
- up: async (db) => {
760
- await db.collection('products').updateMany(
761
- {},
762
- { $rename: { oldField: 'newField' } }
763
- );
764
- },
765
- down: async (db) => {
766
- await db.collection('products').updateMany(
767
- {},
768
- { $rename: { 'newField': 'oldField' } }
769
- );
770
- },
771
- });
772
-
773
- const runner = new MigrationRunner(mongoose.connection.db);
774
- await runner.up([productV2]);
775
- ```
776
-
777
- **See [PRODUCTION_FEATURES.md](../../PRODUCTION_FEATURES.md) for complete guides.**
778
-
779
- ## Battle-Tested Deployments
780
-
781
- Arc has been validated in multiple production environments:
782
-
783
- ### Environment Compatibility
784
-
785
- | Environment | Status | Notes |
786
- |-------------|--------|-------|
787
- | Docker | ✅ Tested | Use Node 18+ Alpine images |
788
- | Kubernetes | ✅ Tested | Health checks + graceful shutdown built-in |
789
- | AWS Lambda | ✅ Tested | Use `@fastify/aws-lambda` adapter |
790
- | Google Cloud Run | ✅ Tested | Auto-scales, health checks work OOTB |
791
- | Vercel Serverless | ✅ Tested | Use serverless functions adapter |
792
- | Bare Metal / VPS | ✅ Tested | PM2 or systemd recommended |
793
- | Railway / Render | ✅ Tested | Works with zero config |
794
-
795
- ### Production Checklist
796
-
797
- Before deploying to production:
798
-
799
- ```typescript
800
- import { createApp, validateEnv } from '@classytic/arc';
801
-
802
- // 1. Validate environment variables at startup
803
- validateEnv({
804
- JWT_SECRET: { required: true, min: 32 },
805
- DATABASE_URL: { required: true },
806
- NODE_ENV: { required: true, values: ['production', 'staging'] },
807
- });
808
-
809
- // 2. Use production environment preset
810
- const app = await createApp({
811
- environment: 'production',
812
-
813
- // 3. Configure CORS properly (never use origin: true)
814
- cors: {
815
- origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
816
- credentials: true,
817
- },
818
-
819
- // 4. Adjust rate limits for your traffic
820
- rateLimit: {
821
- max: 300, // Requests per window
822
- timeWindow: '1 minute',
823
- ban: 10, // Ban after 10 violations
824
- },
825
-
826
- // 5. Enable health checks
827
- healthCheck: true,
828
-
829
- // 6. Configure logging
830
- logger: {
831
- level: 'info',
832
- redact: ['req.headers.authorization'],
833
- },
834
- });
835
-
836
- // 7. Graceful shutdown
837
- process.on('SIGTERM', () => app.close());
838
- process.on('SIGINT', () => app.close());
839
- ```
840
-
841
- ### Multi-Region Deployment
842
-
843
- For globally distributed apps:
844
-
845
- ```typescript
846
- // Use read replicas
847
- const app = await createApp({
848
- mongodb: {
849
- primary: process.env.MONGODB_PRIMARY,
850
- replicas: process.env.MONGODB_REPLICAS?.split(','),
851
- readPreference: 'nearest',
852
- },
853
-
854
- // Distributed tracing for multi-region debugging
855
- tracing: {
856
- enabled: true,
857
- serviceName: `api-${process.env.REGION}`,
858
- exporter: 'zipkin',
859
- },
860
- });
861
- ```
862
-
863
- ### Load Testing Results
864
-
865
- Arc has been load tested with the following results:
866
-
867
- - **Throughput**: 10,000+ req/s (single instance, 4 CPU cores)
868
- - **Latency**: P50: 8ms, P95: 45ms, P99: 120ms
869
- - **Memory**: ~50MB base + ~0.5MB per 1000 requests
870
- - **Connections**: Handles 10,000+ concurrent connections
871
- - **Database**: Tested with 1M+ documents, sub-10ms queries with proper indexes
872
-
873
- *Results vary based on hardware, database, and business logic complexity.*
874
-
875
- ## Performance Tips
876
-
877
- 1. **Use Proxy Compression** - Use Nginx/Caddy or CDN for Brotli/gzip compression
878
- 2. **Enable Memory Monitoring** - Detect leaks early in production
879
- 3. **Use Testing Preset** - Minimal overhead for test suites
880
- 4. **Apply Indexes** - Always index query fields in models
881
- 5. **Use Lean Queries** - Repository returns plain objects by default
882
- 6. **Rate Limiting** - Protect endpoints from abuse
883
- 7. **Validate Early** - Use environment validator at startup
884
- 8. **Distributed Tracing** - Track requests across services (5ms overhead)
885
- 9. **Circuit Breakers** - Prevent cascading failures (<1ms overhead)
886
- 10. **Health Checks** - K8s-compatible liveness/readiness probes
887
-
888
- ## Security Best Practices
889
-
890
- 1. **Opt-out Security** - All plugins enabled by default in production
891
- 2. **Strong Secrets** - Minimum 32 characters for JWT/session secrets
892
- 3. **CORS Configuration** - Never use `origin: true` in production
893
- 4. **Permission Checks** - Always define permissions per operation
894
- 5. **Multi-tenant Isolation** - Use `multiTenant` preset for SaaS apps
895
- 6. **Ownership Checks** - Use `ownedByUser` preset for user data
896
- 7. **Audit Logging** - Track all changes with audit plugin
897
-
898
- ## License
899
-
900
- MIT