onecore 0.6.5 → 0.6.7

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 CHANGED
@@ -16,14 +16,19 @@ npm install onecore
16
16
 
17
17
  Large applications commonly depend on many infrastructure libraries:
18
18
 
19
- * Database
20
- * Validation
21
19
  * Cache
22
- * Message Queue
23
20
  * Logging
24
21
  * Health Check
25
- * HTTP clients
22
+ * Validation
23
+ * Database
24
+ * DB
25
+ * Transaction
26
+ * Statement
27
+ * Repository
28
+ * Service
29
+ * Message Queue
26
30
  * Import / Export
31
+ * HTTP clients
27
32
  * Localization
28
33
 
29
34
  Without a shared foundation, each library tends to define its own interfaces and metadata.
@@ -38,11 +43,11 @@ Without a shared foundation, each library tends to define its own interfaces and
38
43
  ┌─────────┴─────────┐
39
44
  │ onecore │
40
45
  │ │
41
- │ Interfaces │
42
- │ Attribute │
43
- │ Repository │
44
- │ Transaction │
45
- │ Use Cases │
46
+ │ Interfaces │
47
+ │ Attribute │
48
+ │ Transaction │
49
+ │ Repository │
50
+ │ Use Cases │
46
51
  └─────────┬─────────┘
47
52
  │
48
53
  ┌──────────┬──────────┼──────────┬──────────┐
@@ -52,20 +57,37 @@ Without a shared foundation, each library tends to define its own interfaces and
52
57
 
53
58
  The implementation packages depend on these contracts, while application code can depend on the contracts rather than concrete infrastructure implementations.
54
59
 
60
+ ```text
61
+ Application
62
+ │
63
+ ▼
64
+ UseCase / Service
65
+ │
66
+ ▼
67
+ Repository
68
+ │
69
+ ▼
70
+ Executor
71
+ │
72
+ ├── DB
73
+ └── Transaction
74
+ ```
75
+
55
76
  ## Features
56
77
 
57
78
  * Shared TypeScript interfaces for the Core TS ecosystem
58
- * Unified `Attribute` metadata model
79
+ * Unified `Attribute` metadata model
80
+ * `ErrorMessage` for data validation
81
+ * Generic `Filter` and `SearchResult` models
59
82
  * Database executor and transaction abstractions
60
- * Repository abstractions for CRUD and search
83
+ * Type-safe database parameter abstraction
84
+ * Repository abstractions for CRUD and search
61
85
  * Reusable CRUD and Search use-case classes
62
- * Generic filtering and search result models
63
- * Type-safe database parameter abstraction
64
- * No framework dependency
65
- * Interfaces with no runtime representation after TypeScript compilation
66
- * Suitable for both backend and frontend projects
67
- * Tree-shakeable runtime code
68
86
 
87
+ * Interfaces with no runtime representation after TypeScript compilation
88
+ * No framework dependency
89
+ * Suitable for both backend and frontend projects
90
+ * Tree-shakeable runtime code
69
91
  ## Unified metadata
70
92
 
71
93
  The central concept in `onecore` is `Attribute`.
@@ -124,13 +146,13 @@ Individual libraries consume only the properties relevant to them.
124
146
  │
125
147
  ┌───────────────┼────────────────┐
126
148
  │ │ │
127
- Validation Database Import/Export
149
+ Validation Database Import/Export
128
150
  │ │ │
129
- validation-core sql-core import-service
130
- mysql2-core export-kit
131
- mssql-core
151
+ validation-core sql-core import-service
152
+ postgres-kit export-kit
153
+ mysql2-core
132
154
  oracle-core
133
- postgres-kit
155
+ mssql-core
134
156
  cassandra-core
135
157
  mongodb-kit
136
158
  ```
@@ -222,9 +244,9 @@ export type FormatType =
222
244
  export interface Executor {
223
245
  driver: string
224
246
  param(i: number): string
247
+ query<T>(sql: string, args?: any[], m?: StringMap, bools?: Attribute[]): Promise<T[]>
225
248
  execute(sql: string, args?: any[]): Promise<number>
226
249
  executeBatch(statements: Statement[], requireFirstAffected?: boolean): Promise<number>
227
- query<T>(sql: string, args?: any[], m?: StringMap, bools?: Attribute[]): Promise<T[]>
228
250
  }
229
251
  ```
230
252
 
@@ -301,7 +323,7 @@ The transaction abstraction is particularly useful when several repositories par
301
323
 
302
324
  `onecore` defines generic repository contracts for CRUD and search operations.
303
325
 
304
- ### CRUD
326
+ ### CRUDRepository
305
327
 
306
328
  ```ts
307
329
  export interface CRUDRepository<T, ID> {
@@ -327,12 +349,12 @@ Transactional operation:
327
349
  await repository.create(user, tx)
328
350
  ```
329
351
 
330
- ### Search
352
+ ### SearchRepository
331
353
 
332
354
  ```ts
333
355
  export interface SearchRepository<T, F extends Filter> {
334
356
  search(
335
- s: F,
357
+ filter: F,
336
358
  limit: number,
337
359
  offset?: number | string,
338
360
  fields?: string[],
@@ -365,7 +387,7 @@ export interface SearchResult<T> {
365
387
 
366
388
  This allows implementations to support result counts as well as cursor-style pagination.
367
389
 
368
- ## Combined repositories
390
+ ## Repository
369
391
 
370
392
  CRUD and search repositories can be combined:
371
393
 
@@ -429,7 +451,7 @@ export class CRUDUseCase<T, ID> {
429
451
  }
430
452
  ```
431
453
 
432
- ### Combined UseCase
454
+ ### UseCase
433
455
 
434
456
  For applications that need both CRUD and search operations:
435
457
 
@@ -503,10 +525,7 @@ export interface ErrorMessage {
503
525
  with:
504
526
 
505
527
  ```ts
506
- export type Result<T> =
507
- | number
508
- | T
509
- | ErrorMessage[]
528
+ export type Result<T> = number | T | ErrorMessage[]
510
529
  ```
511
530
 
512
531
  This allows an application layer to return structured field-level errors without coupling the service interface to a particular validation library.
@@ -520,10 +539,11 @@ Examples include:
520
539
  ```text
521
540
  Database
522
541
  ├── sql-core
523
- ├── mysql2-core
524
542
  ├── postgres-kit
543
+ ├── mysql2-core
525
544
  ├── oracle-core
526
545
  ├── mssql-core
546
+ ├── cassandra-core
527
547
  └── mongodb-kit
528
548
 
529
549
  Validation
@@ -535,6 +555,7 @@ Messaging
535
555
  ├── redis-messaging
536
556
  ├── rabbitmq-transport
537
557
  ├── activemq
558
+ ├── kafka-plus
538
559
  └── ibmmq-plus
539
560
 
540
561
  Cache
@@ -542,12 +563,12 @@ Cache
542
563
  └── redis-plus
543
564
 
544
565
  Other
566
+ ├── import-service
567
+ ├── export-kit
545
568
  ├── logger-core
546
569
  ├── health-service
547
- ├── web-clients
548
570
  ├── locale-service
549
- ├── import-service
550
- └── export-kit
571
+ └── web-clients
551
572
  ```
552
573
 
553
574
  These packages can share the same contracts and metadata model while remaining independently implemented.
@@ -558,12 +579,12 @@ These packages can share the same contracts and metadata model while remaining i
558
579
  `onecore` defines the common contracts used by the ecosystem.
559
580
 
560
581
  ### Database
582
+ * Statement
583
+ * DB
584
+ * Transaction
561
585
  * SearchRepository
562
586
  * CRUDRepository
563
587
  * Repository
564
- * Transaction
565
- * Statement
566
- * QueryBuilder
567
588
 
568
589
  Implemented by
569
590
 
@@ -575,8 +596,6 @@ Implemented by
575
596
  * cassandra-core
576
597
  * mongodb-kit
577
598
 
578
- ---
579
-
580
599
  ### Validation
581
600
 
582
601
  * Validator<T>
@@ -585,8 +604,6 @@ Implemented by
585
604
 
586
605
  * validation-core
587
606
 
588
- ---
589
-
590
607
  ### Cache
591
608
 
592
609
  * CachePort
@@ -596,8 +613,6 @@ Implemented by
596
613
  * cache-plus
597
614
  * redis-plus
598
615
 
599
- ---
600
-
601
616
  ### Message Queue
602
617
 
603
618
  * Producer
@@ -611,9 +626,9 @@ Implemented by
611
626
  * redis-messaging
612
627
  * nats-plus
613
628
  * rabbitmq-transport
614
- * ActiveMQ libraries
615
-
616
- ---
629
+ * activemq
630
+ * kafka-plus
631
+ * ibmmq-plus
617
632
 
618
633
  ### Logging
619
634
 
@@ -623,8 +638,6 @@ Implemented by
623
638
 
624
639
  * logger-core
625
640
 
626
- ---
627
-
628
641
  ### Health Check
629
642
 
630
643
  * HealthChecker
@@ -633,8 +646,6 @@ Implemented by
633
646
 
634
647
  * health-service
635
648
 
636
- ---
637
-
638
649
  ### HTTP
639
650
 
640
651
  * HttpRequest
@@ -644,8 +655,6 @@ Implemented by
644
655
 
645
656
  * web-clients
646
657
 
647
- ---
648
-
649
658
  ### Localization
650
659
 
651
660
  * Locale
@@ -674,17 +683,34 @@ Reusable classes such as `UseCase` are only included when they are actually impo
674
683
 
675
684
  This makes the package suitable for both backend and frontend projects.
676
685
 
677
- ## Backend and frontend
686
+ ## Backend Support
678
687
 
679
- Backend applications can use `onecore` for:
688
+ Infrastructure services often depend only on interfaces.
689
+
690
+ Examples include
680
691
 
681
- * Repository contracts
682
- * Database transactions
683
692
  * Shared metadata
693
+ * Database transactions
694
+ * Repository contracts
684
695
  * Application use cases
685
- * Infrastructure interfaces
696
+ * Message Queue services
697
+ * Import services / Export services
698
+ * Infrastructure interfaces: Logging, Cache, Validation...
699
+
700
+ Since these projects do not use application-layer base classes, the generated JavaScript contains virtually no runtime code from `onecore`.
701
+
702
+ ## Frontend Support
703
+
704
+ `onecore` works in frontend applications.
705
+
706
+ React applications commonly use
707
+
708
+ * Attribute
709
+ * Locale
710
+ * Currency
711
+ * Validator interfaces
686
712
 
687
- Frontend applications can use shared models and metadata without needing to import backend implementations.
713
+ without importing any backend service classes.
688
714
 
689
715
  For example:
690
716
 
@@ -698,6 +724,8 @@ import type {
698
724
 
699
725
  This allows a frontend and backend to share the same domain metadata and contracts.
700
726
 
727
+ The final JavaScript bundle remains minimal because interfaces are removed during compilation and unused code is tree-shaken.
728
+
701
729
  ## Design principles
702
730
 
703
731
  ### One common contract
@@ -720,51 +748,16 @@ Transactions are passed explicitly to repository operations when multiple operat
720
748
 
721
749
  Keep shared contracts in TypeScript interfaces and keep runtime implementations small and reusable.
722
750
 
723
- ## License
724
-
725
- MIT
726
-
727
- # Frontend Support
728
-
729
- `onecore` works in frontend applications.
730
-
731
- React applications commonly use
732
-
733
- * Attribute
734
- * Locale
735
- * Currency
736
- * Validator interfaces
737
-
738
- without importing any backend service classes.
739
-
740
- The final JavaScript bundle remains minimal because interfaces are removed during compilation and unused code is tree-shaken.
741
-
742
- ---
743
-
744
- # Backend Support
745
-
746
- Infrastructure services often depend only on interfaces.
747
-
748
- Examples include
749
-
750
- * Message Queue services
751
- * Import services
752
- * Export services
753
- * Background workers
754
-
755
- Since these projects do not use application-layer base classes, the generated JavaScript contains virtually no runtime code from `onecore`.
756
-
757
- ---
758
751
 
759
- # Sample Projects
752
+ ## Sample Projects
760
753
 
761
754
  The following sample applications demonstrate different ways of using `onecore`.
762
755
 
763
756
  ### CRUD Applications
764
757
 
765
- * [**sql-modular-sample**](https://github.com/source-code-template/sql-modular-sample) — SQL modular microservice using MySQL
766
- * [**sql-simple-modular-sample**](https://github.com/source-code-template/sql-simple-modular-sample) — SQL modular microservice using PosgreSQL
767
- * [**mongo-simple-modular-sample**](https://github.com/source-code-template/mongo-simple-modular-sample) — MongoDB modular microservice
758
+ * [sql-modular-sample](https://github.com/source-code-template/sql-modular-sample) — SQL modular microservice using MySQL
759
+ * [sql-simple-modular-sample](https://github.com/source-code-template/sql-simple-modular-sample) — SQL modular microservice using PosgreSQL
760
+ * [mongo-simple-modular-sample](https://github.com/source-code-template/mongo-simple-modular-sample) — MongoDB modular microservice
768
761
 
769
762
  These projects extend the reusable CRUD and Search use-case classes.
770
763
 
@@ -794,21 +787,1623 @@ These projects depend only on interfaces and the shared `Attribute` metadata mod
794
787
 
795
788
  These projects depend only on interfaces and the shared `Attribute` metadata model.
796
789
 
797
- ---
790
+ ## License
798
791
 
799
- # Design Principles
792
+ MIT
800
793
 
801
- * Interface-first architecture
802
- * Dependency inversion
803
- * Clean Architecture
804
- * Hexagonal Architecture
805
- * Tree-shakeable design
806
- * Shared metadata model
807
- * Framework independent
808
- * Enterprise ready
809
794
 
810
- ---
795
+ # TypeScript Repository & Service Abstraction
796
+
797
+ A lightweight TypeScript abstraction for building database-backed applications using repositories, services/use cases, transactions, and metadata-driven attributes.
798
+
799
+ The library separates database execution from persistence and application-facing operations while keeping the API generic across database implementations.
800
+
801
+ ## Architecture
802
+
803
+ ```text
804
+ ┌──────────────────────────────┐
805
+ │ Service / UseCase │
806
+ │ SearchUseCase │
807
+ │ CRUDUseCase │
808
+ │ UseCase │
809
+ └──────────────┬───────────────┘
810
+ │
811
+ ▼
812
+ ┌──────────────────────────────┐
813
+ │ Repository │
814
+ │ SearchRepository │
815
+ │ CRUDRepository │
816
+ │ Repository │
817
+ └──────────────┬───────────────┘
818
+ │
819
+ ▼
820
+ ┌──────────────────────────────┐
821
+ │ Database Layer │
822
+ │ Executor │
823
+ │ Transaction │
824
+ │ DB │
825
+ └──────────────┬───────────────┘
826
+ │
827
+ ▼
828
+ Database Driver
829
+ ```
830
+
831
+ ## Core Concepts
832
+
833
+ ### Attribute metadata
834
+
835
+ `Attribute` describes how an application field should be mapped, validated, searched, formatted, or persisted.
836
+
837
+ ```ts
838
+ const attributes: Attributes = {
839
+ id: {
840
+ field: 'id',
841
+ column: 'user_id',
842
+ type: 'ObjectId',
843
+ key: true,
844
+ },
845
+
846
+ email: {
847
+ field: 'email',
848
+ column: 'email_address',
849
+ type: 'string',
850
+ format: 'email',
851
+ required: true,
852
+ unique: true,
853
+ },
854
+
855
+ age: {
856
+ field: 'age',
857
+ type: 'integer',
858
+ min: 0,
859
+ max: 150,
860
+ },
861
+ };
862
+ ```
863
+
864
+ Supported data types include:
865
+
866
+ ```ts
867
+ type DataType =
868
+ | 'ObjectId'
869
+ | 'date'
870
+ | 'datetime'
871
+ | 'time'
872
+ | 'boolean'
873
+ | 'number'
874
+ | 'integer'
875
+ | 'string'
876
+ | 'text'
877
+ | 'object'
878
+ | 'array'
879
+ | 'binary'
880
+ | 'primitives'
881
+ | 'booleans'
882
+ | 'numbers'
883
+ | 'integers'
884
+ | 'strings'
885
+ | 'dates'
886
+ | 'datetimes'
887
+ | 'times';
888
+ ```
889
+
890
+ Supported formats include:
891
+
892
+ ```ts
893
+ type FormatType =
894
+ | 'currency'
895
+ | 'percentage'
896
+ | 'email'
897
+ | 'url'
898
+ | 'phone'
899
+ | 'fax'
900
+ | 'ipv4'
901
+ | 'ipv6';
902
+ ```
903
+
904
+ ## Database Executor
905
+
906
+ `Executor` is the low-level database abstraction.
907
+
908
+ ```ts
909
+ export interface Executor {
910
+ driver: string;
911
+
912
+ param(i: number): string;
913
+
914
+ execute(
915
+ sql: string,
916
+ args?: any[]
917
+ ): Promise<number>;
918
+
919
+ executeBatch(
920
+ statements: Statement[],
921
+ firstSuccess?: boolean
922
+ ): Promise<number>;
923
+
924
+ query<T>(
925
+ sql: string,
926
+ args?: any[],
927
+ m?: StringMap,
928
+ bools?: Attribute[]
929
+ ): Promise<T[]>;
930
+ }
931
+ ```
932
+
933
+ The application can therefore depend on `Executor` rather than directly depending on a specific database driver.
934
+
935
+ ### Statements
936
+
937
+ A batch statement is represented by:
938
+
939
+ ```ts
940
+ export interface Statement {
941
+ query: string;
942
+ params?: any[];
943
+ }
944
+ ```
945
+
946
+ Example:
947
+
948
+ ```ts
949
+ const statements: Statement[] = [
950
+ {
951
+ query: 'UPDATE users SET active = ? WHERE id = ?',
952
+ params: [true, 1],
953
+ },
954
+ {
955
+ query: 'INSERT INTO audit_log(action) VALUES (?)',
956
+ params: ['activate-user'],
957
+ },
958
+ ];
959
+
960
+ await executor.executeBatch(statements);
961
+ ```
962
+
963
+ ## Transactions
964
+
965
+ A transaction extends `Executor`:
966
+
967
+ ```ts
968
+ export interface Transaction extends Executor {
969
+ commit(): Promise<void>;
970
+ rollback(): Promise<void>;
971
+ }
972
+ ```
973
+
974
+ Transactions can be passed explicitly to repository operations.
975
+
976
+ ```ts
977
+ const tx = await db.beginTransaction();
978
+
979
+ try {
980
+ await userRepository.create(user, tx);
981
+ await auditRepository.create(audit, tx);
982
+
983
+ await tx.commit();
984
+ } catch (error) {
985
+ await tx.rollback();
986
+ throw error;
987
+ }
988
+ ```
989
+
990
+ ### Transaction ownership
991
+
992
+ The recommended ownership model is:
993
+
994
+ > The caller that creates the transaction owns the transaction and is responsible for committing or rolling it back.
995
+
996
+ A repository should not commit or roll back a transaction supplied by its caller.
997
+
998
+ ## Repository Layer
999
+
1000
+ ### CRUD repository
1001
+
1002
+ ```ts
1003
+ export interface CRUDRepository<T, ID> {
1004
+ load(id: ID, tx?: Transaction): Promise<T | null>;
1005
+ create(obj: T, tx?: Transaction): Promise<number>;
1006
+ update(obj: T, tx?: Transaction): Promise<number>;
1007
+ patch(obj: Partial<T>, tx?: Transaction): Promise<number>;
1008
+ delete(id: ID, tx?: Transaction): Promise<number>;
1009
+ }
1010
+ ```
1011
+
1012
+ Typical usage:
1013
+
1014
+ ```ts
1015
+ const user = await repository.load(userId);
1016
+
1017
+ if (!user) {
1018
+ // not found
1019
+ }
1020
+
1021
+ await repository.create(user);
1022
+
1023
+ await repository.update(user);
1024
+
1025
+ await repository.patch({
1026
+ id: user.id,
1027
+ name: 'Updated name',
1028
+ });
1029
+
1030
+ await repository.delete(user.id);
1031
+ ```
1032
+
1033
+ The optional `Transaction` allows multiple repository operations to participate in the same transaction.
1034
+
1035
+ ## Search Repository
1036
+
1037
+ Search is separated from CRUD:
1038
+
1039
+ ```ts
1040
+ export interface SearchRepository<T, F extends Filter> {
1041
+ search(
1042
+ s: F,
1043
+ limit: number,
1044
+ offset?: number | string,
1045
+ fields?: string[],
1046
+ tx?: Transaction
1047
+ ): Promise<SearchResult<T>>;
1048
+ }
1049
+ ```
1050
+
1051
+ A typical filter contains:
1052
+
1053
+ ```ts
1054
+ export interface Filter {
1055
+ page?: number;
1056
+ limit: number;
1057
+ fields?: string[];
1058
+ sort?: string;
1059
+ q?: string;
1060
+ }
1061
+ ```
1062
+
1063
+ Search results are represented by:
1064
+
1065
+ ```ts
1066
+ export interface SearchResult<T> {
1067
+ list: T[];
1068
+ total?: number;
1069
+ next?: string;
1070
+ }
1071
+ ```
1072
+
1073
+ Example:
1074
+
1075
+ ```ts
1076
+ const result = await userRepository.search(
1077
+ {
1078
+ q: 'john',
1079
+ limit: 20,
1080
+ page: 1,
1081
+ sort: 'name',
1082
+ },
1083
+ 20,
1084
+ );
1085
+
1086
+ console.log(result.list);
1087
+ console.log(result.total);
1088
+ console.log(result.next);
1089
+ ```
1090
+
1091
+ ## Combined Repository
1092
+
1093
+ For entities that support both CRUD and search:
1094
+
1095
+ ```ts
1096
+ export interface Repository<T, ID, F extends Filter>
1097
+ extends CRUDRepository<T, ID>,
1098
+ SearchRepository<T, F> {
1099
+ }
1100
+ ```
1101
+
1102
+ Example:
1103
+
1104
+ ```ts
1105
+ interface UserFilter extends Filter {
1106
+ active?: boolean;
1107
+ }
1108
+
1109
+ interface User {
1110
+ id: string;
1111
+ name: string;
1112
+ email: string;
1113
+ active: boolean;
1114
+ }
1115
+
1116
+ class UserRepository
1117
+ implements Repository<User, string, UserFilter> {
1118
+
1119
+ // implementation
1120
+ }
1121
+ ```
1122
+
1123
+ ## Service / Use Case Layer
1124
+
1125
+ The service layer exposes repository operations without exposing transactions or database details to the caller.
1126
+
1127
+ ### CRUD service
1128
+
1129
+ ```ts
1130
+ export interface CRUDService<T, ID> {
1131
+ load(id: ID): Promise<T | null>;
1132
+ create(obj: T): Promise<Result<T>>;
1133
+ update(obj: T): Promise<Result<T>>;
1134
+ patch(obj: Partial<T>): Promise<Result<T>>;
1135
+ delete(id: ID): Promise<number>;
1136
+ }
1137
+ ```
1138
+
1139
+ ### Search service
1140
+
1141
+ ```ts
1142
+ export interface SearchService<T, F extends Filter> {
1143
+ search(
1144
+ s: F,
1145
+ limit: number,
1146
+ page?: number | string,
1147
+ fields?: string[]
1148
+ ): Promise<SearchResult<T>>;
1149
+ }
1150
+ ```
1151
+
1152
+ ### Combined service
1153
+
1154
+ ```ts
1155
+ export interface Service<T, ID, F extends Filter>
1156
+ extends CRUDService<T, ID>,
1157
+ SearchService<T, F> {
1158
+ }
1159
+ ```
1160
+
1161
+ ## Use Case Implementations
1162
+
1163
+ `SearchUseCase` delegates search operations:
1164
+
1165
+ ```ts
1166
+ const useCase = new SearchUseCase(userRepository);
1167
+
1168
+ const result = await useCase.search(
1169
+ {
1170
+ q: 'john',
1171
+ limit: 20,
1172
+ },
1173
+ 20,
1174
+ );
1175
+ ```
1176
+
1177
+ `CRUDUseCase` delegates CRUD operations:
1178
+
1179
+ ```ts
1180
+ const useCase = new CRUDUseCase(userRepository);
1181
+
1182
+ const user = await useCase.load(id);
1183
+
1184
+ await useCase.create(userData);
1185
+ await useCase.update(userData);
1186
+ await useCase.patch({ id, name: 'New name' });
1187
+ await useCase.delete(id);
1188
+ ```
1189
+
1190
+ `UseCase` combines both APIs:
1191
+
1192
+ ```ts
1193
+ const useCase = new UseCase(userRepository);
1194
+
1195
+ const result = await useCase.search(
1196
+ {
1197
+ q: 'john',
1198
+ limit: 20,
1199
+ },
1200
+ 20,
1201
+ );
1202
+
1203
+ const user = await useCase.load(id);
1204
+ ```
1205
+
1206
+ These classes are intentionally thin. They provide an application-facing boundary where business rules can be added without coupling controllers or handlers directly to repositories.
1207
+
1208
+ ## Error Model
1209
+
1210
+ Validation/application errors are represented by:
1211
+
1212
+ ```ts
1213
+ export interface ErrorMessage {
1214
+ field: string;
1215
+ code: string;
1216
+ param?: string | number | Date;
1217
+ message?: string;
1218
+ }
1219
+ ```
1220
+
1221
+ The current result type is:
1222
+
1223
+ ```ts
1224
+ export type Result<T> =
1225
+ | number
1226
+ | T
1227
+ | ErrorMessage[];
1228
+ ```
1229
+
1230
+ For example, an implementation may return:
1231
+
1232
+ ```ts
1233
+ [
1234
+ {
1235
+ field: 'email',
1236
+ code: 'invalid',
1237
+ message: 'Invalid email address',
1238
+ },
1239
+ ]
1240
+ ```
1241
+
1242
+ For larger applications, a discriminated result type is recommended because it makes success and failure states easier to distinguish.
1243
+
1244
+ ## Security Considerations
1245
+
1246
+ ### Always parameterize values
1247
+
1248
+ Do not construct SQL by interpolating user-provided values.
1249
+
1250
+ Prefer:
1251
+
1252
+ ```ts
1253
+ await executor.execute(
1254
+ 'UPDATE users SET name = ? WHERE id = ?',
1255
+ [name, id],
1256
+ );
1257
+ ```
1258
+
1259
+ Avoid:
1260
+
1261
+ ```ts
1262
+ await executor.execute(
1263
+ `UPDATE users SET name = '${name}' WHERE id = '${id}'`,
1264
+ );
1265
+ ```
1266
+
1267
+ ### Validate SQL identifiers
1268
+
1269
+ Fields, columns, and sort expressions are different from ordinary values.
1270
+
1271
+ For example:
1272
+
1273
+ ```ts
1274
+ filter.sort
1275
+ filter.fields
1276
+ attribute.column
1277
+ ```
1278
+
1279
+ should not be inserted into SQL without validation or an allowlist.
1280
+
1281
+ Prefer mapping application-level names to trusted database columns:
1282
+
1283
+ ```ts
1284
+ const columns = {
1285
+ id: 'user_id',
1286
+ name: 'user_name',
1287
+ createdAt: 'created_at',
1288
+ };
1289
+ ```
1290
+
1291
+ Never treat an arbitrary `sort` or `fields` value as trusted SQL.
1292
+
1293
+ ## Design Guidelines
1294
+
1295
+ ### Repository responsibilities
1296
+
1297
+ Repositories should focus on:
1298
+
1299
+ * persistence
1300
+ * SQL generation/execution
1301
+ * mapping database rows to application objects
1302
+ * transaction participation
1303
+
1304
+ They should not normally contain HTTP concerns or presentation logic.
1305
+
1306
+ ### Use case responsibilities
1307
+
1308
+ Use cases are the preferred location for:
1309
+
1310
+ * business rules
1311
+ * orchestration across multiple repositories
1312
+ * authorization decisions
1313
+ * transaction coordination
1314
+ * application-level validation
1315
+
1316
+ ### Transaction coordination
1317
+
1318
+ For operations spanning multiple repositories:
1319
+
1320
+ ```ts
1321
+ const tx = await db.beginTransaction();
1322
+
1323
+ try {
1324
+ await repositoryA.update(a, tx);
1325
+ await repositoryB.create(b, tx);
1326
+
1327
+ await tx.commit();
1328
+ } catch (error) {
1329
+ await tx.rollback();
1330
+ throw error;
1331
+ }
1332
+ ```
1333
+
1334
+ This keeps transaction lifecycle management outside individual repositories.
1335
+
1336
+ ## Known API Considerations
1337
+
1338
+ The current API has several areas that should be kept in mind when extending the library.
1339
+
1340
+ ### Filter and search pagination
1341
+
1342
+ `Filter` already contains:
1343
+
1344
+ ```ts
1345
+ page
1346
+ limit
1347
+ fields
1348
+ ```
1349
+
1350
+ while `SearchRepository.search()` also accepts:
1351
+
1352
+ ```ts
1353
+ limit
1354
+ offset
1355
+ fields
1356
+ ```
1357
+
1358
+ These represent overlapping concepts. A future API revision should establish a single source of truth for pagination and selected fields.
1359
+
1360
+ ### Attribute responsibilities
1361
+
1362
+ `Attribute` currently combines:
1363
+
1364
+ * database mapping
1365
+ * validation
1366
+ * formatting
1367
+ * search metadata
1368
+ * CRUD restrictions
1369
+ * serialization
1370
+ * relationship metadata
1371
+
1372
+ This is convenient for a small framework but may become difficult to maintain as the number of features grows.
1373
+
1374
+ ### Result type
1375
+
1376
+ ```ts
1377
+ number | T | ErrorMessage[]
1378
+ ```
1379
+
1380
+ is intentionally flexible but can be difficult for callers to discriminate safely. A future version may use discriminated result objects.
1381
+
1382
+ ### Patch behavior
1383
+
1384
+ `patch()` uses `Partial<T>`. This is convenient, but it does not express rules such as:
1385
+
1386
+ * immutable fields
1387
+ * required primary keys
1388
+ * fields that cannot be patched
1389
+ * fields that are read-only
1390
+
1391
+ Those rules currently need to be enforced through metadata or repository logic.
1392
+
1393
+ ## Recommended Improvements
1394
+
1395
+ For future versions, consider:
1396
+
1397
+ 1. Use `unknown[]` instead of `any[]` for SQL parameters.
1398
+ 2. Define explicit search pagination semantics.
1399
+ 3. Replace free-form `sort` strings with validated sort definitions.
1400
+ 4. Separate validation, persistence, search, and formatting metadata from `Attribute`.
1401
+ 5. Replace `Result<T>` with a discriminated result type.
1402
+ 6. Remove defensive checks for methods that are already required by TypeScript interfaces.
1403
+ 7. Clearly document transaction ownership.
1404
+ 8. Consider separate types for database entities, create inputs, update inputs, and patch inputs.
1405
+ 9. Export `StringMap` if it is intended to be part of the public API.
1406
+ 10. Use descriptive parameter/property names instead of ambiguous names such as `bools`, `true`, `false`, and `typeof`.
1407
+
1408
+ ## Summary
1409
+
1410
+ This library provides a clean foundation for a TypeScript application using:
1411
+
1412
+ ```text
1413
+ Database
1414
+ ↓
1415
+ Executor
1416
+ ↓
1417
+ Transaction / Repository
1418
+ ↓
1419
+ Use Case / Service
1420
+ ↓
1421
+ Application
1422
+ ```
1423
+
1424
+ Its main strengths are its small API surface, generic repository interfaces, explicit transaction support, and separation between persistence and application layers.
1425
+
1426
+ The most important areas to control as the framework evolves are SQL identifier safety, search/pagination consistency, transaction ownership, the overloaded `Attribute` metadata model, and the `Result<T>` error contract.
1427
+
1428
+ MIT
1429
+
1430
+ # TypeScript Application Core
1431
+
1432
+ A set of TypeScript interfaces, types, and lightweight use-case implementations for building applications around repositories, databases, validation, caching, logging, and messaging.
1433
+
1434
+ The library is designed around **interfaces and dependency inversion**, allowing application code to depend on abstractions rather than concrete infrastructure implementations.
1435
+
1436
+ ## Features
1437
+
1438
+ * Generic CRUD and search repositories
1439
+ * Database executor and transaction abstractions
1440
+ * Application service/use-case contracts
1441
+ * Validation contracts and structured validation errors
1442
+ * Cache port abstraction
1443
+ * Structured logger abstraction
1444
+ * Generic messaging and message-envelope types
1445
+ * Shared schema and attribute metadata
1446
+ * Strong generic typing for entities, IDs, filters, and messages
1447
+
1448
+ ## Architecture
1449
+
1450
+ The main dependency flow is:
1451
+
1452
+ ```text
1453
+ Application
1454
+ │
1455
+ ▼
1456
+ UseCase / Service
1457
+ │
1458
+ ▼
1459
+ Repository
1460
+ │
1461
+ ▼
1462
+ Executor
1463
+ │
1464
+ ├── DB
1465
+ └── Transaction
1466
+ ```
1467
+
1468
+ Infrastructure services are exposed through ports:
1469
+
1470
+ ```text
1471
+ Application
1472
+ ├── CachePort
1473
+ ├── Logger
1474
+ └── Messaging ports
1475
+ ```
1476
+
1477
+ This keeps business/application code independent from concrete database, cache, logging, and messaging implementations.
1478
+
1479
+ ---
1480
+
1481
+ # Core Types
1482
+
1483
+ ## Data types
1484
+
1485
+ `DataType` describes supported field types:
1486
+
1487
+ ```ts
1488
+ type DataType =
1489
+ | 'ObjectId'
1490
+ | 'date'
1491
+ | 'datetime'
1492
+ | 'time'
1493
+ | 'boolean'
1494
+ | 'number'
1495
+ | 'integer'
1496
+ | 'string'
1497
+ | 'text'
1498
+ | 'object'
1499
+ | 'array'
1500
+ | 'binary'
1501
+ | 'primitives'
1502
+ | 'booleans'
1503
+ | 'numbers'
1504
+ | 'integers'
1505
+ | 'strings'
1506
+ | 'dates'
1507
+ | 'datetimes'
1508
+ | 'times';
1509
+ ```
1510
+
1511
+ `FormatType` provides common semantic formats:
1512
+
1513
+ ```ts
1514
+ type FormatType =
1515
+ | 'currency'
1516
+ | 'percentage'
1517
+ | 'email'
1518
+ | 'url'
1519
+ | 'phone'
1520
+ | 'fax'
1521
+ | 'ipv4'
1522
+ | 'ipv6';
1523
+ ```
1524
+
1525
+ `Operator` defines supported query operations:
1526
+
1527
+ ```ts
1528
+ type Operator =
1529
+ | '='
1530
+ | 'like'
1531
+ | '!='
1532
+ | '<>'
1533
+ | '>'
1534
+ | '>='
1535
+ | '<'
1536
+ | '<=';
1537
+ ```
1538
+
1539
+ ## Attribute metadata
1540
+
1541
+ `Attribute` describes a model field and can contain database, validation, search, formatting, and persistence metadata.
1542
+
1543
+ ```ts
1544
+ const attributes: Attributes = {
1545
+ email: {
1546
+ field: 'email',
1547
+ type: 'string',
1548
+ format: 'email',
1549
+ required: true,
1550
+ unique: true
1551
+ },
1552
+
1553
+ age: {
1554
+ field: 'age',
1555
+ type: 'integer',
1556
+ min: 0
1557
+ }
1558
+ };
1559
+ ```
1560
+
1561
+ Common attributes include:
1562
+
1563
+ | Property | Purpose |
1564
+ | ------------------------- | ----------------------------- |
1565
+ | `field` | Application field name |
1566
+ | `column` | Database column |
1567
+ | `type` | Field data type |
1568
+ | `format` | Semantic/display format |
1569
+ | `required` | Required-field validation |
1570
+ | `operator` | Default query operator |
1571
+ | `enum` | Allowed values |
1572
+ | `min` / `max` | Range validation |
1573
+ | `gt` / `lt` | Exclusive range validation |
1574
+ | `length` | Length constraint |
1575
+ | `precision` / `scale` | Numeric precision |
1576
+ | `exp` | Regular-expression validation |
1577
+ | `key` | Primary/key metadata |
1578
+ | `unique` | Uniqueness metadata |
1579
+ | `version` | Version field |
1580
+ | `createdAt` / `updatedAt` | Audit timestamps |
1581
+ | `noinsert` | Exclude from insert |
1582
+ | `noupdate` | Exclude from update |
1583
+ | `nopatch` | Exclude from patch |
1584
+ | `ignored` | Ignore the field |
1585
+ | `jsonField` | JSON field mapping |
1586
+ | `link` | Related-resource metadata |
1587
+ | `getString` | Custom string conversion |
1588
+
1589
+ ---
1590
+
1591
+ # Filtering and Search
1592
+
1593
+ `Filter` represents a generic search filter:
1594
+
1595
+ ```ts
1596
+ interface Filter {
1597
+ page?: number;
1598
+ limit: number;
1599
+ fields?: string[];
1600
+ sort?: string;
1601
+ q?: string;
1602
+ }
1603
+ ```
1604
+
1605
+ A search returns:
1606
+
1607
+ ```ts
1608
+ interface SearchResult<T> {
1609
+ list: T[];
1610
+ total?: number;
1611
+ next?: string;
1612
+ }
1613
+ ```
1614
+
1615
+ Example:
1616
+
1617
+ ```ts
1618
+ interface UserFilter extends Filter {
1619
+ status?: string;
1620
+ role?: string;
1621
+ }
1622
+ ```
1623
+
1624
+ Repositories can then provide strongly typed search:
1625
+
1626
+ ```ts
1627
+ interface UserRepository
1628
+ extends Repository<User, string, UserFilter> {}
1629
+ ```
1630
+
1631
+ ---
1632
+
1633
+ # Database Abstractions
1634
+
1635
+ ## Executor
1636
+
1637
+ `Executor` abstracts database operations:
1638
+
1639
+ ```ts
1640
+ interface Executor {
1641
+ driver: string;
1642
+
1643
+ param(i: number): string;
1644
+
1645
+ execute(
1646
+ sql: string,
1647
+ args?: any[]
1648
+ ): Promise<number>;
1649
+
1650
+ executeBatch(
1651
+ statements: Statement[],
1652
+ firstSuccess?: boolean
1653
+ ): Promise<number>;
1654
+
1655
+ query<T>(
1656
+ sql: string,
1657
+ args?: any[],
1658
+ m?: StringMap,
1659
+ bools?: Attribute[]
1660
+ ): Promise<T[]>;
1661
+ }
1662
+ ```
1663
+
1664
+ A repository does not need to know which database driver is being used.
1665
+
1666
+ ## Transaction
1667
+
1668
+ `Transaction` extends `Executor`:
1669
+
1670
+ ```ts
1671
+ interface Transaction extends Executor {
1672
+ commit(): Promise<void>;
1673
+ rollback(): Promise<void>;
1674
+ }
1675
+ ```
1676
+
1677
+ Typical usage:
1678
+
1679
+ ```ts
1680
+ const tx = await db.beginTransaction();
1681
+
1682
+ try {
1683
+ await tx.execute(...);
1684
+ await tx.execute(...);
1685
+
1686
+ await tx.commit();
1687
+ } catch (error) {
1688
+ await tx.rollback();
1689
+ throw error;
1690
+ }
1691
+ ```
1692
+
1693
+ Transaction state management remains the responsibility of the concrete implementation.
1694
+
1695
+ ## DB
1696
+
1697
+ ```ts
1698
+ interface DB extends Executor {
1699
+ beginTransaction(): Promise<Transaction>;
1700
+ }
1701
+ ```
1702
+
1703
+ A concrete database adapter can implement `DB` while repositories depend only on the interface.
1704
+
1705
+ ---
1706
+
1707
+ # Repositories
1708
+
1709
+ The repository layer separates persistence operations from application logic.
1710
+
1711
+ ## CRUD repository
1712
+
1713
+ ```ts
1714
+ interface CRUDRepository<T, ID> {
1715
+ load(id: ID, tx?: Transaction): Promise<T | null>;
1716
+ create(obj: T, tx?: Transaction): Promise<number>;
1717
+ update(obj: T, tx?: Transaction): Promise<number>;
1718
+ patch(obj: Partial<T>, tx?: Transaction): Promise<number>;
1719
+ delete(id: ID, tx?: Transaction): Promise<number>;
1720
+ }
1721
+ ```
1722
+
1723
+ ## Search repository
1724
+
1725
+ ```ts
1726
+ interface SearchRepository<T, F extends Filter> {
1727
+ search(
1728
+ s: F,
1729
+ limit: number,
1730
+ offset?: number | string,
1731
+ fields?: string[],
1732
+ tx?: Transaction
1733
+ ): Promise<SearchResult<T>>;
1734
+ }
1735
+ ```
1736
+
1737
+ ## Combined repository
1738
+
1739
+ ```ts
1740
+ interface Repository<T, ID, F extends Filter>
1741
+ extends CRUDRepository<T, ID>,
1742
+ SearchRepository<T, F> {}
1743
+ ```
1744
+
1745
+ This allows CRUD and search capabilities to be combined without forcing unrelated implementations into one interface.
1746
+
1747
+ ---
1748
+
1749
+ # Use Cases and Services
1750
+
1751
+ The library provides thin application-layer implementations.
1752
+
1753
+ ## SearchUseCase
1754
+
1755
+ ```ts
1756
+ class SearchUseCase<T, F extends Filter>
1757
+ implements SearchService<T, F> {
1758
+
1759
+ constructor(
1760
+ protected repository: SearchRepository<T, F>
1761
+ ) {}
1762
+
1763
+ search(
1764
+ filter: F,
1765
+ limit: number,
1766
+ page?: number | string,
1767
+ fields?: string[]
1768
+ ): Promise<SearchResult<T>> {
1769
+ return this.repository.search(
1770
+ filter,
1771
+ limit,
1772
+ page,
1773
+ fields
1774
+ );
1775
+ }
1776
+ }
1777
+ ```
1778
+
1779
+ ## CRUDUseCase
1780
+
1781
+ ```ts
1782
+ class CRUDUseCase<T, ID>
1783
+ implements CRUDService<T, ID> {
1784
+
1785
+ constructor(
1786
+ protected repository: CRUDRepository<T, ID>
1787
+ ) {}
1788
+
1789
+ load(id: ID) {
1790
+ return this.repository.load(id);
1791
+ }
1792
+
1793
+ create(obj: T) {
1794
+ return this.repository.create(obj);
1795
+ }
1796
+
1797
+ update(obj: T) {
1798
+ return this.repository.update(obj);
1799
+ }
1800
+
1801
+ patch(obj: Partial<T>) {
1802
+ return this.repository.patch(obj);
1803
+ }
1804
+
1805
+ delete(id: ID) {
1806
+ return this.repository.delete(id);
1807
+ }
1808
+ }
1809
+ ```
1810
+
1811
+ ## Combined UseCase
1812
+
1813
+ ```ts
1814
+ class UseCase<T, ID, F extends Filter>
1815
+ implements Service<T, ID, F> {
1816
+
1817
+ constructor(
1818
+ protected repository: Repository<T, ID, F>
1819
+ ) {}
1820
+
1821
+ // CRUD + search operations
1822
+ }
1823
+ ```
1824
+
1825
+ These classes are intentionally thin. Business rules can be added at the use-case layer without coupling them to database implementations.
1826
+
1827
+ ---
1828
+
1829
+ # Services
1830
+
1831
+ The service contracts provide an application-facing API:
1832
+
1833
+ ```ts
1834
+ interface SearchService<T, F extends Filter> {
1835
+ search(
1836
+ s: F,
1837
+ limit: number,
1838
+ page?: number | string,
1839
+ fields?: string[]
1840
+ ): Promise<SearchResult<T>>;
1841
+ }
1842
+
1843
+ interface CRUDService<T, ID> {
1844
+ load(id: ID): Promise<T | null>;
1845
+ create(obj: T): Promise<Result<T>>;
1846
+ update(obj: T): Promise<Result<T>>;
1847
+ patch(obj: Partial<T>): Promise<Result<T>>;
1848
+ delete(id: ID): Promise<number>;
1849
+ }
1850
+ ```
1851
+
1852
+ The combined service is:
1853
+
1854
+ ```ts
1855
+ interface Service<T, ID, F extends Filter>
1856
+ extends CRUDService<T, ID>,
1857
+ SearchService<T, F> {}
1858
+ ```
1859
+
1860
+ ---
1861
+
1862
+ # Validation
1863
+
1864
+ Validation is abstracted behind:
1865
+
1866
+ ```ts
1867
+ interface Validator<T> {
1868
+ validate(
1869
+ model: T,
1870
+ resource?: StringMap,
1871
+ patch?: boolean
1872
+ ): Promise<ErrorMessage[]>;
1873
+ }
1874
+ ```
1875
+
1876
+ Validation errors use:
1877
+
1878
+ ```ts
1879
+ interface ErrorMessage {
1880
+ field: string;
1881
+ code: string;
1882
+ param?: string | number | Date;
1883
+ message?: string;
1884
+ }
1885
+ ```
1886
+
1887
+ Example:
1888
+
1889
+ ```ts
1890
+ [
1891
+ {
1892
+ field: 'email',
1893
+ code: 'format',
1894
+ message: 'Invalid email address'
1895
+ },
1896
+ {
1897
+ field: 'age',
1898
+ code: 'min',
1899
+ param: 18,
1900
+ message: 'Age must be at least 18'
1901
+ }
1902
+ ]
1903
+ ```
1904
+
1905
+ ---
1906
+
1907
+ # Result Type
1908
+
1909
+ Service methods use:
1910
+
1911
+ ```ts
1912
+ type Result<T> =
1913
+ | number
1914
+ | T
1915
+ | ErrorMessage[];
1916
+ ```
1917
+
1918
+ This allows a service to represent an operation result, returned model, or validation errors.
1919
+
1920
+ Because this is a broad union, consumers should define clear runtime handling conventions. A discriminated result type is preferable for new APIs where backwards compatibility is not required.
1921
+
1922
+ ---
1923
+
1924
+ # Caching
1925
+
1926
+ `CachePort<K, V>` provides a database-independent cache abstraction:
1927
+
1928
+ ```ts
1929
+ interface CachePort<K, V> {
1930
+ isEnabled?(): boolean;
1931
+
1932
+ put(
1933
+ key: K,
1934
+ obj: V,
1935
+ expiresInSeconds?: number
1936
+ ): Promise<boolean>;
1937
+
1938
+ expire(
1939
+ key: K,
1940
+ timeToLive: number
1941
+ ): Promise<boolean>;
1942
+
1943
+ get(key: K): Promise<V>;
1944
+
1945
+ getMany(keys: K[]): Promise<V[]>;
1946
+
1947
+ containsKey(key: K): Promise<boolean>;
1948
+
1949
+ remove(key: K): Promise<boolean>;
1950
+
1951
+ clear(): Promise<boolean>;
1952
+
1953
+ keys(): Promise<string[]>;
1954
+
1955
+ count(): Promise<number>;
1956
+
1957
+ size(): Promise<number>;
1958
+ }
1959
+ ```
1960
+
1961
+ This allows implementations for Redis, in-memory caches, distributed caches, or other storage systems without changing application code.
1962
+
1963
+ ---
1964
+
1965
+ # Logging
1966
+
1967
+ The logging layer consists of configuration types and a `Logger` interface.
1968
+
1969
+ ## Logger
1970
+
1971
+ ```ts
1972
+ interface Logger {
1973
+ name: Name;
1974
+ level: number;
1975
+ map: LogMap;
1976
+ constants?: SimpleMap;
1977
+
1978
+ trace(
1979
+ msg: string,
1980
+ m?: SimpleMap,
1981
+ ctx?: any
1982
+ ): void;
1983
+
1984
+ debug(
1985
+ msg: string,
1986
+ m?: SimpleMap,
1987
+ ctx?: any
1988
+ ): void;
1989
+
1990
+ info(
1991
+ msg: string,
1992
+ m?: SimpleMap,
1993
+ ctx?: any
1994
+ ): void;
1995
+
1996
+ warn(
1997
+ msg: string,
1998
+ m?: SimpleMap,
1999
+ ctx?: any
2000
+ ): void;
2001
+
2002
+ error(
2003
+ msg: string,
2004
+ m?: SimpleMap,
2005
+ ctx?: any
2006
+ ): void;
2007
+
2008
+ panic(
2009
+ msg: string,
2010
+ m?: SimpleMap,
2011
+ ctx?: any
2012
+ ): void;
2013
+
2014
+ fatal(
2015
+ msg: string,
2016
+ m?: SimpleMap,
2017
+ ctx?: any
2018
+ ): void;
2019
+
2020
+ isLevelEnabled(level: number): boolean;
2021
+ isTraceEnabled(): boolean;
2022
+ isDebugEnabled(): boolean;
2023
+ isInfoEnabled(): boolean;
2024
+ isWarnEnabled(): boolean;
2025
+ isErrorEnabled(): boolean;
2026
+ isPanicEnabled(): boolean;
2027
+ isFatalEnabled(): boolean;
2028
+ }
2029
+ ```
2030
+
2031
+ Logging configuration supports level names, field mappings, and constant metadata.
2032
+
2033
+ Example:
2034
+
2035
+ ```ts
2036
+ const config: LogConfig = {
2037
+ log: {
2038
+ level: 'info',
2039
+ map: {
2040
+ time: 'timestamp',
2041
+ level: 'level',
2042
+ msg: 'message'
2043
+ }
2044
+ }
2045
+ };
2046
+ ```
2047
+
2048
+ ---
2049
+
2050
+ # Messaging
2051
+
2052
+ The library provides generic producer and consumer contracts.
2053
+
2054
+ ## Processing functions
2055
+
2056
+ ```ts
2057
+ type Process<T, R, M> =
2058
+ (
2059
+ data: T,
2060
+ attributes?: StringMap,
2061
+ msg?: M
2062
+ ) => Promise<R>;
2063
+
2064
+ type Handle<T, R, M> =
2065
+ (
2066
+ data: T,
2067
+ attributes?: StringMap,
2068
+ msg?: M
2069
+ ) => Promise<R>;
2070
+ ```
2071
+
2072
+ ## Produce / write operations
2073
+
2074
+ ```ts
2075
+ type Produce<T, R> =
2076
+ (data: T) => Promise<R>;
2077
+ ```
2078
+
2079
+ Aliases are provided for common terminology:
2080
+
2081
+ ```ts
2082
+ type Write<T, R> = Produce<T, R>;
2083
+ type Publish<T, R> = Produce<T, R>;
2084
+ type Send<T, R> = Produce<T, R>;
2085
+ type Put<T, R> = Produce<T, R>;
2086
+ type Set<T, R> = Produce<T, R>;
2087
+ ```
2088
+
2089
+ ## Consume operations
2090
+
2091
+ ```ts
2092
+ type Consume<T> =
2093
+ (
2094
+ handle: (
2095
+ data: T,
2096
+ header?: StringMap
2097
+ ) => Promise<number>
2098
+ ) => void;
2099
+ ```
2100
+
2101
+ Aliases include:
2102
+
2103
+ ```ts
2104
+ type Read<T> = Consume<T>;
2105
+ type Subscribe<T> = Consume<T>;
2106
+ type Receive<T> = Consume<T>;
2107
+ type Get<T> = Consume<T>;
2108
+ type Fetch<T> = Consume<T>;
2109
+ ```
2110
+
2111
+ ---
2112
+
2113
+ # Message Envelope
2114
+
2115
+ `Message<T, ID, R>` represents a generic transport message:
2116
+
2117
+ ```ts
2118
+ interface Message<T, ID, R> {
2119
+ id?: ID;
2120
+ data?: T;
2121
+ attributes?: StringMap;
2122
+ timestamp?: Date;
2123
+ raw?: R;
2124
+ }
2125
+ ```
2126
+
2127
+ This allows application code to work with a normalized message representation while retaining the original transport payload through `raw`.
2128
+
2129
+ ---
2130
+
2131
+ # Producers
2132
+
2133
+ ```ts
2134
+ interface Producer<T, R, ID> {
2135
+ produce(
2136
+ to: string,
2137
+ data: T,
2138
+ attributes?: StringMap,
2139
+ id?: ID
2140
+ ): Promise<R>;
2141
+ }
2142
+ ```
2143
+
2144
+ Equivalent concepts are provided for publishing and sending:
2145
+
2146
+ ```ts
2147
+ interface Publisher<T, R, ID> {
2148
+ publish(
2149
+ to: string,
2150
+ data: T,
2151
+ attributes?: StringMap,
2152
+ id?: ID
2153
+ ): Promise<R>;
2154
+ }
2155
+
2156
+ interface Sender<T, R, ID> {
2157
+ send(
2158
+ to: string,
2159
+ data: T,
2160
+ attributes?: StringMap,
2161
+ id?: ID
2162
+ ): Promise<R>;
2163
+ }
2164
+ ```
2165
+
2166
+ ---
2167
+
2168
+ # Consumers
2169
+
2170
+ Several consumer styles are available:
2171
+
2172
+ ```ts
2173
+ interface SimpleConsumer<T, R> {
2174
+ consume(
2175
+ process: (
2176
+ data: T,
2177
+ attributes?: StringMap,
2178
+ raw?: R
2179
+ ) => Promise<number>
2180
+ ): void;
2181
+ }
2182
+
2183
+ interface SimpleSubscriber<T, R> {
2184
+ subscribe(
2185
+ process: (
2186
+ data: T,
2187
+ attributes?: StringMap,
2188
+ raw?: R
2189
+ ) => Promise<number>
2190
+ ): void;
2191
+ }
2192
+ ```
2193
+
2194
+ Message-envelope based consumers are also available:
2195
+
2196
+ ```ts
2197
+ interface MessageSubscriber<T, ID, R> {
2198
+ subscribe(
2199
+ process: (
2200
+ data: Message<T, ID, R>
2201
+ ) => Promise<number>
2202
+ ): void;
2203
+ }
2204
+ ```
2205
+
2206
+ ---
2207
+
2208
+ # Retry
2209
+
2210
+ Retry configuration is represented by:
2211
+
2212
+ ```ts
2213
+ interface RetryConfig {
2214
+ name: string;
2215
+ limit: number;
2216
+ }
2217
+ ```
2218
+
2219
+ Concrete messaging implementations can use this configuration to determine retry policies.
2220
+
2221
+ ---
2222
+
2223
+ # Design Principles
2224
+
2225
+ ## Dependency inversion
2226
+
2227
+ Application code should depend on interfaces such as:
2228
+
2229
+ ```text
2230
+ Repository
2231
+ CachePort
2232
+ Validator
2233
+ Logger
2234
+ Producer
2235
+ Consumer
2236
+ ```
2237
+
2238
+ rather than concrete infrastructure implementations.
2239
+
2240
+ ## Generic programming
2241
+
2242
+ Most contracts are generic:
2243
+
2244
+ ```text
2245
+ T = entity/message data
2246
+ ID = entity/message identifier
2247
+ F = search-filter type
2248
+ R = raw/transport/result type
2249
+ ```
2250
+
2251
+ This allows the same abstractions to be reused across different domains.
2252
+
2253
+ ## Separation of concerns
2254
+
2255
+ Persistence, validation, application logic, infrastructure, and messaging are deliberately represented as separate contracts.
2256
+
2257
+ ---
2258
+
2259
+ # Important Implementation Considerations
2260
+
2261
+ ## SQL safety
2262
+
2263
+ Dynamic SQL identifiers such as:
2264
+
2265
+ ```text
2266
+ field
2267
+ column
2268
+ sort
2269
+ fields
2270
+ ```
2271
+
2272
+ must be validated against an allowlist before being included in SQL.
2273
+
2274
+ Query values should always be passed as parameters.
2275
+
2276
+ Do not build SQL by directly concatenating untrusted values.
2277
+
2278
+ ## Transaction lifecycle
2279
+
2280
+ A failed `commit()` does not necessarily mean that a subsequent `rollback()` can safely or successfully complete the transaction. Concrete transaction implementations should explicitly manage transaction state and connection cleanup.
2281
+
2282
+ ## Cache misses
2283
+
2284
+ Concrete `CachePort` implementations should define and document how `get()` behaves when a key does not exist.
2285
+
2286
+ ## Message lifecycle
2287
+
2288
+ Messaging implementations should document:
2289
+
2290
+ * acknowledgment behavior
2291
+ * retry behavior
2292
+ * concurrency
2293
+ * ordering guarantees
2294
+ * error handling
2295
+ * cancellation/shutdown
2296
+ * backpressure
2297
+
2298
+ The interfaces themselves intentionally leave these transport-specific details open.
2299
+
2300
+ ---
2301
+
2302
+ # Example Domain
2303
+
2304
+ A simple user repository can be modeled as:
2305
+
2306
+ ```ts
2307
+ interface User {
2308
+ id: string;
2309
+ name: string;
2310
+ email: string;
2311
+ }
2312
+
2313
+ interface UserFilter extends Filter {
2314
+ role?: string;
2315
+ active?: boolean;
2316
+ }
2317
+
2318
+ interface UserRepository
2319
+ extends Repository<User, string, UserFilter> {}
2320
+ ```
2321
+
2322
+ The application layer can then depend on:
2323
+
2324
+ ```ts
2325
+ class UserUseCase
2326
+ extends UseCase<User, string, UserFilter> {}
2327
+ ```
2328
+
2329
+ A concrete infrastructure adapter implements `UserRepository` using the database implementation behind `Executor`.
2330
+
2331
+ ---
2332
+
2333
+ # Recommended Project Structure
2334
+
2335
+ A project using this library can be organized as:
2336
+
2337
+ ```text
2338
+ src/
2339
+ ├── domain/
2340
+ │ └── user/
2341
+ │ ├── user.ts
2342
+ │ └── user-filter.ts
2343
+ │
2344
+ ├── application/
2345
+ │ └── user/
2346
+ │ └── user-use-case.ts
2347
+ │
2348
+ ├── repository/
2349
+ │ └── user-repository.ts
2350
+ │
2351
+ ├── infrastructure/
2352
+ │ ├── database/
2353
+ │ │ ├── executor.ts
2354
+ │ │ └── transaction.ts
2355
+ │ ├── cache/
2356
+ │ ├── logging/
2357
+ │ └── messaging/
2358
+ │
2359
+ └── validation/
2360
+ └── user-validator.ts
2361
+ ```
2362
+
2363
+ ---
2364
+
2365
+ # Summary
2366
+
2367
+ This package provides a reusable set of TypeScript contracts for applications that follow a layered or ports-and-adapters architecture.
2368
+
2369
+ The central flow is:
2370
+
2371
+ ```text
2372
+ ┌──────────────┐
2373
+ │ Controller │
2374
+ └──────┬───────┘
2375
+ │
2376
+ ▼
2377
+ ┌──────────────┐
2378
+ │ UseCase │
2379
+ └──────┬───────┘
2380
+ │
2381
+ ▼
2382
+ ┌──────────────┐
2383
+ │ Repository │
2384
+ └──────┬───────┘
2385
+ │
2386
+ ▼
2387
+ ┌──────────────┐
2388
+ │ Executor │
2389
+ └──────┬───────┘
2390
+ │
2391
+ ▼
2392
+ Database
2393
+ ```
2394
+
2395
+ Supporting infrastructure is exposed through independent ports:
2396
+
2397
+ ```text
2398
+ Application
2399
+ / | \
2400
+ / | \
2401
+ Validator Cache Logger
2402
+ \
2403
+ Messaging
2404
+ ```
2405
+
2406
+ The interfaces are intentionally implementation-independent, making the package suitable as a shared foundation for multiple services, databases, cache systems, and messaging transports.
811
2407
 
812
- # License
813
2408
 
814
2409
  MIT