@vritti/api-sdk 0.0.3 → 0.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _nestjs_common from '@nestjs/common';
2
- import { DynamicModule, OnModuleDestroy, OnModuleInit, CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { DynamicModule, OnModuleDestroy, OnModuleInit, Logger, CanActivate, ExecutionContext, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
3
3
  import { ConfigService } from '@nestjs/config';
4
4
  import { Reflector } from '@nestjs/core';
5
5
  import { JwtService } from '@nestjs/jwt';
@@ -376,6 +376,15 @@ declare class TenantDatabaseService implements OnModuleDestroy {
376
376
  /** Cleanup interval timer */
377
377
  private cleanupInterval?;
378
378
  constructor(options: DatabaseModuleOptions, tenantContext: TenantContextService);
379
+ /**
380
+ * Get the Prisma client for the current tenant's database.
381
+ * This returns the tenant-scoped database client.
382
+ *
383
+ * @returns Tenant-scoped database client instance
384
+ * @throws UnauthorizedException if tenant context not set
385
+ * @throws InternalServerErrorException if connection fails
386
+ */
387
+ get prismaClient(): any;
379
388
  /**
380
389
  * Get tenant-scoped database client for the current request/message
381
390
  *
@@ -392,7 +401,7 @@ declare class TenantDatabaseService implements OnModuleDestroy {
392
401
  * const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
393
402
  * const users = await dbClient.user.findMany();
394
403
  */
395
- getDbClient<T = any>(): Promise<T>;
404
+ private getDbClient;
396
405
  /**
397
406
  * Create a new database client for the given tenant
398
407
  */
@@ -488,14 +497,13 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
488
497
  */
489
498
  clearAllCaches(): void;
490
499
  /**
491
- * Get primary database client for direct database access
492
- *
493
- * This is useful for platform admin operations (creating tenants, billing, etc.)
500
+ * Get the Prisma client for the primary database.
501
+ * This is a synchronous property that returns the initialized Prisma client.
494
502
  *
495
503
  * @returns Primary database client instance
496
504
  * @throws Error if primary database client is not initialized
497
505
  */
498
- getPrimaryDbClient<T = any>(): T;
506
+ get prismaClient(): any;
499
507
  /**
500
508
  * Decrypt database credentials
501
509
  *
@@ -508,6 +516,503 @@ declare class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {
508
516
  onModuleDestroy(): Promise<void>;
509
517
  }
510
518
 
519
+ /**
520
+ * Abstract base repository for primary database operations.
521
+ * Provides common CRUD operations with automatic logging.
522
+ *
523
+ * @template TModel - The Prisma model type
524
+ * @template TCreateDTO - DTO type for create operations
525
+ * @template TUpdateDTO - DTO type for update operations
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * // Using the model delegate pattern (RECOMMENDED)
530
+ * // Type-safe, IDE autocomplete, refactor-friendly
531
+ * @Injectable()
532
+ * export class UserRepository extends PrimaryBaseRepository<
533
+ * User,
534
+ * CreateUserDto,
535
+ * UpdateUserDto
536
+ * > {
537
+ * constructor(database: PrimaryDatabaseService) {
538
+ * super(database, (prisma) => prisma.user); // ✅ Type-safe with autocomplete!
539
+ * }
540
+ *
541
+ * // Add custom methods as needed
542
+ * async findByEmail(email: string): Promise<User | null> {
543
+ * return this.model.findUnique({ where: { email } });
544
+ * }
545
+ * }
546
+ *
547
+ * // Short syntax is also supported
548
+ * @Injectable()
549
+ * export class TenantRepository extends PrimaryBaseRepository<Tenant> {
550
+ * constructor(database: PrimaryDatabaseService) {
551
+ * super(database, (p) => p.tenant); // ✅ Concise!
552
+ * }
553
+ * }
554
+ *
555
+ * // Works with complex model names
556
+ * @Injectable()
557
+ * export class EmailVerificationRepository extends PrimaryBaseRepository<EmailVerification> {
558
+ * constructor(database: PrimaryDatabaseService) {
559
+ * super(database, (p) => p.emailVerification); // ✅ Matches Prisma naming
560
+ * }
561
+ * }
562
+ * ```
563
+ */
564
+ declare abstract class PrimaryBaseRepository<TModel, TCreateDTO = any, TUpdateDTO = any> {
565
+ protected readonly database: PrimaryDatabaseService;
566
+ protected readonly logger: Logger;
567
+ private readonly modelGetter;
568
+ /**
569
+ * Lazy getter for Prisma client.
570
+ * Accesses the client from the database service only when needed,
571
+ * avoiding initialization timing issues with NestJS lifecycle.
572
+ */
573
+ protected get prisma(): any;
574
+ /**
575
+ * Lazy getter for the Prisma model delegate.
576
+ * Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.
577
+ */
578
+ protected get model(): any;
579
+ /**
580
+ * Create a new repository instance
581
+ *
582
+ * @param database - The primary database service
583
+ * @param getModel - Function that returns the Prisma model delegate from the client
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * // Standard usage with full parameter name
588
+ * constructor(database: PrimaryDatabaseService) {
589
+ * super(database, (prisma) => prisma.user);
590
+ * }
591
+ *
592
+ * // Short syntax
593
+ * constructor(database: PrimaryDatabaseService) {
594
+ * super(database, (p) => p.user);
595
+ * }
596
+ *
597
+ * // Complex model names
598
+ * constructor(database: PrimaryDatabaseService) {
599
+ * super(database, (p) => p.emailVerification);
600
+ * }
601
+ * ```
602
+ */
603
+ constructor(database: PrimaryDatabaseService, getModel: (prisma: any) => any);
604
+ /**
605
+ * Create a new record
606
+ *
607
+ * @param data - The data to create the record with
608
+ * @returns Promise resolving to the created record
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * const user = await userRepository.create({
613
+ * email: 'user@example.com',
614
+ * name: 'John Doe'
615
+ * });
616
+ * ```
617
+ */
618
+ create(data: TCreateDTO): Promise<TModel>;
619
+ /**
620
+ * Find a single record by ID
621
+ *
622
+ * @param id - The record ID
623
+ * @returns Promise resolving to the record or null if not found
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * const user = await userRepository.findById('user-id-123');
628
+ * ```
629
+ */
630
+ findById(id: string): Promise<TModel | null>;
631
+ /**
632
+ * Find a single record with custom where clause
633
+ *
634
+ * @param where - The where clause or findUnique args
635
+ * @returns Promise resolving to the record or null if not found
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * // Simple where clause
640
+ * const user = await userRepository.findOne({ email: 'user@example.com' });
641
+ *
642
+ * // With include
643
+ * const user = await userRepository.findOne({
644
+ * where: { email: 'user@example.com' },
645
+ * include: { posts: true }
646
+ * });
647
+ * ```
648
+ */
649
+ findOne(where: any): Promise<TModel | null>;
650
+ /**
651
+ * Find multiple records
652
+ *
653
+ * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
654
+ * @returns Promise resolving to an array of records
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * // Find all users
659
+ * const users = await userRepository.findMany();
660
+ *
661
+ * // Find with filtering and pagination
662
+ * const users = await userRepository.findMany({
663
+ * where: { status: 'ACTIVE' },
664
+ * orderBy: { createdAt: 'desc' },
665
+ * take: 10,
666
+ * skip: 0
667
+ * });
668
+ * ```
669
+ */
670
+ findMany(args?: any): Promise<TModel[]>;
671
+ /**
672
+ * Update a record by ID
673
+ *
674
+ * @param id - The record ID
675
+ * @param data - The data to update
676
+ * @returns Promise resolving to the updated record
677
+ *
678
+ * @example
679
+ * ```typescript
680
+ * const user = await userRepository.update('user-id-123', {
681
+ * name: 'Jane Doe'
682
+ * });
683
+ * ```
684
+ */
685
+ update(id: string, data: TUpdateDTO): Promise<TModel>;
686
+ /**
687
+ * Update multiple records
688
+ *
689
+ * @param where - The where clause to match records
690
+ * @param data - The data to update
691
+ * @returns Promise resolving to the count of updated records
692
+ *
693
+ * @example
694
+ * ```typescript
695
+ * const result = await userRepository.updateMany(
696
+ * { status: 'PENDING' },
697
+ * { status: 'ACTIVE' }
698
+ * );
699
+ * console.log(`Updated ${result.count} users`);
700
+ * ```
701
+ */
702
+ updateMany(where: any, data: TUpdateDTO): Promise<{
703
+ count: number;
704
+ }>;
705
+ /**
706
+ * Delete a record by ID
707
+ *
708
+ * @param id - The record ID
709
+ * @returns Promise resolving to the deleted record
710
+ *
711
+ * @example
712
+ * ```typescript
713
+ * const user = await userRepository.delete('user-id-123');
714
+ * ```
715
+ */
716
+ delete(id: string): Promise<TModel>;
717
+ /**
718
+ * Delete multiple records
719
+ *
720
+ * @param where - The where clause to match records
721
+ * @returns Promise resolving to the count of deleted records
722
+ *
723
+ * @example
724
+ * ```typescript
725
+ * const result = await userRepository.deleteMany({
726
+ * status: 'INACTIVE',
727
+ * createdAt: { lt: new Date('2020-01-01') }
728
+ * });
729
+ * console.log(`Deleted ${result.count} users`);
730
+ * ```
731
+ */
732
+ deleteMany(where: any): Promise<{
733
+ count: number;
734
+ }>;
735
+ /**
736
+ * Count records
737
+ *
738
+ * @param where - Optional where clause to filter records
739
+ * @returns Promise resolving to the count of records
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * // Count all users
744
+ * const total = await userRepository.count();
745
+ *
746
+ * // Count active users
747
+ * const activeCount = await userRepository.count({ status: 'ACTIVE' });
748
+ * ```
749
+ */
750
+ count(where?: any): Promise<number>;
751
+ /**
752
+ * Check if a record exists
753
+ *
754
+ * @param where - The where clause to match records
755
+ * @returns Promise resolving to true if at least one record exists, false otherwise
756
+ *
757
+ * @example
758
+ * ```typescript
759
+ * const emailExists = await userRepository.exists({
760
+ * email: 'user@example.com'
761
+ * });
762
+ * ```
763
+ */
764
+ exists(where: any): Promise<boolean>;
765
+ }
766
+
767
+ /**
768
+ * Abstract base repository for tenant-scoped database operations.
769
+ * All operations are automatically scoped to the current tenant.
770
+ *
771
+ * @template TModel - The Prisma model type
772
+ * @template TCreateDTO - DTO type for create operations
773
+ * @template TUpdateDTO - DTO type for update operations
774
+ *
775
+ * @example
776
+ * ```typescript
777
+ * // Using the model delegate pattern (RECOMMENDED)
778
+ * // Type-safe, IDE autocomplete, refactor-friendly
779
+ * @Injectable()
780
+ * export class ProductRepository extends TenantBaseRepository<
781
+ * Product,
782
+ * CreateProductDto,
783
+ * UpdateProductDto
784
+ * > {
785
+ * constructor(database: TenantDatabaseService) {
786
+ * super(database, (prisma) => prisma.product); // ✅ Type-safe with autocomplete!
787
+ * }
788
+ *
789
+ * // Add custom methods as needed
790
+ * async findBySku(sku: string): Promise<Product | null> {
791
+ * return this.model.findUnique({ where: { sku } });
792
+ * }
793
+ * }
794
+ *
795
+ * // Short syntax is also supported
796
+ * @Injectable()
797
+ * export class OrderRepository extends TenantBaseRepository<Order> {
798
+ * constructor(database: TenantDatabaseService) {
799
+ * super(database, (p) => p.order); // ✅ Concise!
800
+ * }
801
+ * }
802
+ *
803
+ * // Works with complex model names
804
+ * @Injectable()
805
+ * export class InventoryItemRepository extends TenantBaseRepository<InventoryItem> {
806
+ * constructor(database: TenantDatabaseService) {
807
+ * super(database, (p) => p.inventoryItem); // ✅ Matches Prisma naming
808
+ * }
809
+ * }
810
+ * ```
811
+ */
812
+ declare abstract class TenantBaseRepository<TModel, TCreateDTO = any, TUpdateDTO = any> {
813
+ protected readonly database: TenantDatabaseService;
814
+ protected readonly logger: Logger;
815
+ private readonly modelGetter;
816
+ /**
817
+ * Lazy getter for Prisma client.
818
+ * Accesses the client from the database service only when needed,
819
+ * avoiding initialization timing issues with NestJS lifecycle.
820
+ */
821
+ protected get prisma(): any;
822
+ /**
823
+ * Lazy getter for the Prisma model delegate.
824
+ * Returns the specific model (e.g., prisma.product, prisma.order) for this repository.
825
+ */
826
+ protected get model(): any;
827
+ /**
828
+ * Create a new repository instance
829
+ *
830
+ * @param database - The tenant database service
831
+ * @param getModel - Function that returns the Prisma model delegate from the client
832
+ *
833
+ * @example
834
+ * ```typescript
835
+ * // Standard usage with full parameter name
836
+ * constructor(database: TenantDatabaseService) {
837
+ * super(database, (prisma) => prisma.product);
838
+ * }
839
+ *
840
+ * // Short syntax
841
+ * constructor(database: TenantDatabaseService) {
842
+ * super(database, (p) => p.product);
843
+ * }
844
+ *
845
+ * // Complex model names
846
+ * constructor(database: TenantDatabaseService) {
847
+ * super(database, (p) => p.inventoryItem);
848
+ * }
849
+ * ```
850
+ */
851
+ constructor(database: TenantDatabaseService, getModel: (prisma: any) => any);
852
+ /**
853
+ * Create a new record
854
+ *
855
+ * @param data - The data to create the record with
856
+ * @returns Promise resolving to the created record
857
+ *
858
+ * @example
859
+ * ```typescript
860
+ * const product = await productRepository.create({
861
+ * name: 'Widget',
862
+ * sku: 'WDG-001',
863
+ * price: 9.99
864
+ * });
865
+ * ```
866
+ */
867
+ create(data: TCreateDTO): Promise<TModel>;
868
+ /**
869
+ * Find a single record by ID
870
+ *
871
+ * @param id - The record ID
872
+ * @returns Promise resolving to the record or null if not found
873
+ *
874
+ * @example
875
+ * ```typescript
876
+ * const product = await productRepository.findById('product-id-123');
877
+ * ```
878
+ */
879
+ findById(id: string): Promise<TModel | null>;
880
+ /**
881
+ * Find a single record with custom where clause
882
+ *
883
+ * @param where - The where clause or findUnique args
884
+ * @returns Promise resolving to the record or null if not found
885
+ *
886
+ * @example
887
+ * ```typescript
888
+ * // Simple where clause
889
+ * const product = await productRepository.findOne({ sku: 'WDG-001' });
890
+ *
891
+ * // With include
892
+ * const product = await productRepository.findOne({
893
+ * where: { sku: 'WDG-001' },
894
+ * include: { category: true }
895
+ * });
896
+ * ```
897
+ */
898
+ findOne(where: any): Promise<TModel | null>;
899
+ /**
900
+ * Find multiple records
901
+ *
902
+ * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
903
+ * @returns Promise resolving to an array of records
904
+ *
905
+ * @example
906
+ * ```typescript
907
+ * // Find all products
908
+ * const products = await productRepository.findMany();
909
+ *
910
+ * // Find with filtering and pagination
911
+ * const products = await productRepository.findMany({
912
+ * where: { status: 'ACTIVE' },
913
+ * orderBy: { createdAt: 'desc' },
914
+ * take: 10,
915
+ * skip: 0
916
+ * });
917
+ * ```
918
+ */
919
+ findMany(args?: any): Promise<TModel[]>;
920
+ /**
921
+ * Update a record by ID
922
+ *
923
+ * @param id - The record ID
924
+ * @param data - The data to update
925
+ * @returns Promise resolving to the updated record
926
+ *
927
+ * @example
928
+ * ```typescript
929
+ * const product = await productRepository.update('product-id-123', {
930
+ * price: 12.99
931
+ * });
932
+ * ```
933
+ */
934
+ update(id: string, data: TUpdateDTO): Promise<TModel>;
935
+ /**
936
+ * Update multiple records
937
+ *
938
+ * @param where - The where clause to match records
939
+ * @param data - The data to update
940
+ * @returns Promise resolving to the count of updated records
941
+ *
942
+ * @example
943
+ * ```typescript
944
+ * const result = await productRepository.updateMany(
945
+ * { status: 'PENDING' },
946
+ * { status: 'ACTIVE' }
947
+ * );
948
+ * console.log(`Updated ${result.count} products`);
949
+ * ```
950
+ */
951
+ updateMany(where: any, data: TUpdateDTO): Promise<{
952
+ count: number;
953
+ }>;
954
+ /**
955
+ * Delete a record by ID
956
+ *
957
+ * @param id - The record ID
958
+ * @returns Promise resolving to the deleted record
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * const product = await productRepository.delete('product-id-123');
963
+ * ```
964
+ */
965
+ delete(id: string): Promise<TModel>;
966
+ /**
967
+ * Delete multiple records
968
+ *
969
+ * @param where - The where clause to match records
970
+ * @returns Promise resolving to the count of deleted records
971
+ *
972
+ * @example
973
+ * ```typescript
974
+ * const result = await productRepository.deleteMany({
975
+ * status: 'INACTIVE',
976
+ * createdAt: { lt: new Date('2020-01-01') }
977
+ * });
978
+ * console.log(`Deleted ${result.count} products`);
979
+ * ```
980
+ */
981
+ deleteMany(where: any): Promise<{
982
+ count: number;
983
+ }>;
984
+ /**
985
+ * Count records
986
+ *
987
+ * @param where - Optional where clause to filter records
988
+ * @returns Promise resolving to the count of records
989
+ *
990
+ * @example
991
+ * ```typescript
992
+ * // Count all products
993
+ * const total = await productRepository.count();
994
+ *
995
+ * // Count active products
996
+ * const activeCount = await productRepository.count({ status: 'ACTIVE' });
997
+ * ```
998
+ */
999
+ count(where?: any): Promise<number>;
1000
+ /**
1001
+ * Check if a record exists
1002
+ *
1003
+ * @param where - The where clause to match records
1004
+ * @returns Promise resolving to true if at least one record exists, false otherwise
1005
+ *
1006
+ * @example
1007
+ * ```typescript
1008
+ * const skuExists = await productRepository.exists({
1009
+ * sku: 'WDG-001'
1010
+ * });
1011
+ * ```
1012
+ */
1013
+ exists(where: any): Promise<boolean>;
1014
+ }
1015
+
511
1016
  declare class RequestService {
512
1017
  private readonly request;
513
1018
  constructor(request: FastifyRequest);
@@ -753,4 +1258,71 @@ declare const Onboarding: () => _nestjs_common.CustomDecorator<string>;
753
1258
  */
754
1259
  declare const Public: () => _nestjs_common.CustomDecorator<string>;
755
1260
 
756
- export { AuthConfigModule, DatabaseModule, type DatabaseModuleOptions, Onboarding, PrimaryDatabaseService, type PrimaryDbConfig, Public, Tenant, TenantContextService, TenantDatabaseService, type TenantInfo, VrittiAuthGuard };
1261
+ /**
1262
+ * HTTP Module
1263
+ *
1264
+ * Provides HTTP utilities including:
1265
+ * - CSRF Guard for request protection
1266
+ * - HTTP Exception Filter for standardized error responses
1267
+ *
1268
+ * Usage:
1269
+ * Import this module to access HTTP guards and filters.
1270
+ * Guards and filters are registered globally in the main application.
1271
+ */
1272
+ declare class HttpModule {
1273
+ }
1274
+
1275
+ /**
1276
+ * CSRF Guard
1277
+ *
1278
+ * Global guard that automatically protects all state-changing requests (POST, PUT, PATCH, DELETE)
1279
+ * from CSRF attacks using Fastify's csrf-protection plugin.
1280
+ *
1281
+ * Flow:
1282
+ * 1. Skip safe methods (GET, HEAD, OPTIONS)
1283
+ * 2. Skip endpoints marked with @Public()
1284
+ * 3. Validate CSRF token for all other requests
1285
+ *
1286
+ * Token Sources (in priority order by @fastify/csrf-protection):
1287
+ * 1. req.headers['csrf-token']
1288
+ * 2. req.headers['xsrf-token']
1289
+ * 3. req.headers['x-csrf-token']
1290
+ * 4. req.headers['x-xsrf-token']
1291
+ * 5. req.body._csrf
1292
+ *
1293
+ * This guard should be registered globally in main.ts after CSRF plugin registration.
1294
+ */
1295
+ declare class CsrfGuard implements CanActivate {
1296
+ private reflector;
1297
+ private readonly logger;
1298
+ constructor(reflector: Reflector);
1299
+ canActivate(context: ExecutionContext): Promise<boolean>;
1300
+ }
1301
+
1302
+ /**
1303
+ * Global HTTP Exception Filter
1304
+ *
1305
+ * Standardizes all error responses in the format:
1306
+ * {
1307
+ * errors: [{ field: string, message: string }],
1308
+ * message?: string,
1309
+ * statusCode: number,
1310
+ * timestamp: string,
1311
+ * path: string
1312
+ * }
1313
+ *
1314
+ * Handles:
1315
+ * - Validation errors (class-validator) - Converts to field-specific errors
1316
+ * - HTTP exceptions - Maps to standardized format
1317
+ * - Unknown errors - Returns generic 500 error
1318
+ */
1319
+ declare class HttpExceptionFilter implements ExceptionFilter {
1320
+ private readonly logger;
1321
+ catch(exception: unknown, host: ArgumentsHost): void;
1322
+ /**
1323
+ * Parse class-validator error messages into field-specific errors
1324
+ */
1325
+ private parseValidationErrors;
1326
+ }
1327
+
1328
+ export { AuthConfigModule, CsrfGuard, DatabaseModule, type DatabaseModuleOptions, HttpExceptionFilter, HttpModule, Onboarding, PrimaryBaseRepository, PrimaryDatabaseService, type PrimaryDbConfig, Public, Tenant, TenantBaseRepository, TenantContextService, TenantDatabaseService, type TenantInfo, VrittiAuthGuard };