node-server-engine 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.commitlintrc.js +10 -10
  2. package/.nycrc +1 -3
  3. package/.releaserc.js +2 -1
  4. package/README.md +206 -146
  5. package/dist/const/metrics.js.map +1 -1
  6. package/dist/db/models/index.js.map +1 -1
  7. package/dist/entities/AWSS3/AWSS3.js +12 -12
  8. package/dist/entities/AWSS3/AWSS3.js.map +1 -1
  9. package/dist/entities/ElasticSearch/ElasticSearch.js.map +1 -1
  10. package/dist/entities/Endpoint/Endpoint.types.js.map +1 -1
  11. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.js +11 -11
  12. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.js.map +1 -1
  13. package/dist/entities/KeySet/KeySet.js.map +1 -1
  14. package/dist/entities/PubSub/PubSub.js.map +1 -1
  15. package/dist/entities/PushNotification/PushNotification.js.map +1 -1
  16. package/dist/entities/Redis/Redis.js +3 -3
  17. package/dist/entities/Redis/Redis.js.map +1 -1
  18. package/dist/entities/SecretManager/SecretManager.js +3 -3
  19. package/dist/entities/SecretManager/SecretManager.js.map +1 -1
  20. package/dist/entities/Sequelize/Sequelize.config.js +11 -7
  21. package/dist/entities/Sequelize/Sequelize.config.js.map +1 -1
  22. package/dist/entities/Sequelize/Sequelize.js.map +1 -1
  23. package/dist/entities/Server/Server.js.map +1 -1
  24. package/dist/entities/Server/Server.types.js.map +1 -1
  25. package/dist/entities/SocketClient/SocketClient.d.ts +12 -0
  26. package/dist/entities/SocketClient/SocketClient.js +34 -3
  27. package/dist/entities/SocketClient/SocketClient.js.map +1 -1
  28. package/dist/entities/SocketClient/SocketClient.types.d.ts +6 -0
  29. package/dist/entities/SocketClient/SocketClient.types.js.map +1 -1
  30. package/dist/entities/index.js.map +1 -1
  31. package/dist/middleware/checkPermission/checkPermission.js.map +1 -1
  32. package/dist/middleware/checkPermission/index.js.map +1 -1
  33. package/dist/middleware/index.js.map +1 -1
  34. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js.map +1 -1
  35. package/dist/middleware/requestMetrics/requestMetrics.js.map +1 -1
  36. package/dist/middleware/swaggerDocs/swaggerDocs.js.map +1 -1
  37. package/dist/middleware/verificationToken/verificationToken.js.map +1 -1
  38. package/dist/utils/createSortedObject/createSortedObject.js.map +1 -1
  39. package/dist/utils/dbMigration/dbMigration.js.map +1 -1
  40. package/dist/utils/dbMigration/migrateMetadataTable/migrateMetadataTable.js.map +1 -1
  41. package/dist/utils/jwt/jwt.js.map +1 -1
  42. package/dist/utils/jwt/jwt.types.js.map +1 -1
  43. package/dist/utils/report/output.js.map +1 -1
  44. package/dist/utils/report/report.js.map +1 -1
  45. package/dist/utils/semverCompare/semverCompare.js.map +1 -1
  46. package/dist/utils/verificationToken/verificationToken.js.map +1 -1
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -126,20 +126,22 @@ The server provides structured logging with automatic format detection. In local
126
126
  ### Log Format
127
127
 
128
128
  Logs automatically detect the environment:
129
+
129
130
  - **Local Development**: Colorized, concise format with time (HH:MM:SS)
130
131
  - **Production**: JSON structured logs for cloud log aggregation
131
132
 
132
133
  ### Environment Variables
133
134
 
134
- | Variable | Values | Description | Default |
135
- |----------|--------|-------------|---------|
136
- | `LOG_FORMAT` | `local`, `json` | Force specific log format | Auto-detect |
137
- | `DETAILED_LOGS` | `true`, `false` | Show stack traces and verbose details | `false` |
138
- | `DEBUG` | `namespace:*` | Enable debug logs for specific namespaces | Off |
135
+ | Variable | Values | Description | Default |
136
+ | --------------- | --------------- | ----------------------------------------- | ----------- |
137
+ | `LOG_FORMAT` | `local`, `json` | Force specific log format | Auto-detect |
138
+ | `DETAILED_LOGS` | `true`, `false` | Show stack traces and verbose details | `false` |
139
+ | `DEBUG` | `namespace:*` | Enable debug logs for specific namespaces | Off |
139
140
 
140
141
  ### Examples
141
142
 
142
143
  **Default Local Format** (clean, concise):
144
+
143
145
  ```
144
146
  [21:16:15] INFO SERVER_RUNNING
145
147
  [21:16:15] INFO Connected to database successfully
@@ -149,6 +151,7 @@ Logs automatically detect the environment:
149
151
  ```
150
152
 
151
153
  **Detailed Logs** (`DETAILED_LOGS=true`):
154
+
152
155
  ```
153
156
  [21:16:15] DEBUG POST /auth/login [200] 154ms
154
157
  Data:
@@ -167,6 +170,7 @@ Error: No bearer token found
167
170
  ```
168
171
 
169
172
  **Production Format** (`LOG_FORMAT=json`):
173
+
170
174
  ```json
171
175
  {"severity":"INFO","message":"SERVER_RUNNING","timestamp":"2025-12-09T21:16:15.028Z","serviceContext":{"service":"my-service","version":"1.0.0"}}
172
176
  {"severity":"DEBUG","message":"POST /auth/login [200] 154ms","timestamp":"2025-12-09T21:16:15.182Z"}
@@ -378,22 +382,22 @@ new Endpoint({
378
382
  The middleware adds a `files` object to the `req` object, which contains information about the uploaded file.
379
383
 
380
384
  ```json
381
- [
382
- {
383
- "fieldname": "avatar",
384
- "originalname": "profile.png",
385
- "mimetype": "image/png",
386
- "size": 204800,
387
- "buffer":[]
388
- },
389
- {
390
- "fieldname": "document",
391
- "originalname": "resume.pdf",
392
- "mimetype": "application/pdf",
393
- "size": 512000,
394
- "buffer":[]
395
- }
396
- ]
385
+ [
386
+ {
387
+ "fieldname": "avatar",
388
+ "originalname": "profile.png",
389
+ "mimetype": "image/png",
390
+ "size": 204800,
391
+ "buffer": []
392
+ },
393
+ {
394
+ "fieldname": "document",
395
+ "originalname": "resume.pdf",
396
+ "mimetype": "application/pdf",
397
+ "size": 512000,
398
+ "buffer": []
399
+ }
400
+ ]
397
401
  ```
398
402
 
399
403
  ##### Features
@@ -481,6 +485,7 @@ new Endpoint({
481
485
  }
482
486
  });
483
487
  ```
488
+
484
489
  ---
485
490
 
486
491
  ### Socket Client
@@ -523,22 +528,24 @@ const server = new Server({
523
528
  });
524
529
  ```
525
530
 
526
- | Parameter | Type | Description | Default |
527
- | ----------------- | ------------------------------------------ | ----------------------------------------------------------------------- | ------------ |
528
- | handlers | Array\<[MessageHandler](#message-handler)> | A list of message handlers to use | **required** |
529
- | authCallbacks | Function \| Array\<Function> | Callbacks called when a client successfully authenticates | [] |
530
- | initCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is created | [] |
531
- | shutdownCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is destroyed | [] |
531
+ | Parameter | Type | Description | Default |
532
+ | ----------------- | ------------------------------------------ | --------------------------------------------------------------------------------- | ------------ |
533
+ | handlers | Array\<[MessageHandler](#message-handler)> | A list of message handlers to use | **required** |
534
+ | authCallbacks | Function \| Array\<Function> | Callbacks called when a client successfully authenticates | [] |
535
+ | initCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is created | [] |
536
+ | shutdownCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is destroyed | [] |
537
+ | authTimeoutMs | Number | Close the socket with code 4401 if it has not authenticated within this time (ms) | never |
532
538
 
533
539
  #### Client Properties & Methods
534
540
 
535
- | Property/Method | Type | Description |
536
- | ----------------- | --------------------------- | ------------------------------------------------------------------------- |
537
- | id | String | Unique identifier for the connection |
538
- | establishedAt | Date | Timestamp when connection was established |
539
- | isAuthenticated() | () => Boolean | Check if client is authenticated |
540
- | getUser() | () => SocketUser \| undefined | Get authenticated user data (userId, deviceId, tokenId, audience) |
541
- | sendMessage() | (type, payload, options) | Send a message to the client |
541
+ | Property/Method | Type | Description |
542
+ | ----------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- |
543
+ | id | String | Unique identifier for the connection |
544
+ | establishedAt | Date | Timestamp when connection was established |
545
+ | isAuthenticated() | () => Boolean | Check if client is authenticated |
546
+ | getUser() | () => SocketUser \| undefined | Get authenticated user data (userId, deviceId, tokenId, audience, the token's `user` claim, `expiresAt`) |
547
+ | sendMessage() | (type, payload, options) | Send a message to the client |
548
+ | close() | (code?, reason?) | Close the connection, e.g. `close(4403, 'Not allowed')` from an `authCallbacks` check |
542
549
 
543
550
  #### Client Authentication
544
551
 
@@ -546,10 +553,12 @@ Clients authenticate by sending a message:
546
553
 
547
554
  ```javascript
548
555
  // Client-side
549
- websocket.send(JSON.stringify({
550
- type: 'authenticate',
551
- payload: { token: 'your-jwt-token' }
552
- }));
556
+ websocket.send(
557
+ JSON.stringify({
558
+ type: 'authenticate',
559
+ payload: { token: 'your-jwt-token' }
560
+ })
561
+ );
553
562
 
554
563
  // Server will:
555
564
  // 1. Verify the JWT token
@@ -561,10 +570,10 @@ websocket.send(JSON.stringify({
561
570
 
562
571
  #### Environment Variables
563
572
 
564
- | Variable | Description | Default |
565
- | ------------------- | ----------------------------------------------- | ------- |
566
- | WS_PING_INTERVAL | Interval between ping checks (seconds) | 30 |
567
- | WEBSOCKET_CLOSE_TIMEOUT | Timeout for graceful close (milliseconds) | 3000 |
573
+ | Variable | Description | Default |
574
+ | ----------------------- | ----------------------------------------- | ------- |
575
+ | WS_PING_INTERVAL | Interval between ping checks (seconds) | 30 |
576
+ | WEBSOCKET_CLOSE_TIMEOUT | Timeout for graceful close (milliseconds) | 3000 |
568
577
 
569
578
  ---
570
579
 
@@ -630,13 +639,13 @@ await Redis.shutdown();
630
639
 
631
640
  #### Environment Variables
632
641
 
633
- | Variable | Description | Default | Required |
634
- | --------------- | -------------------------------------- | ------- | -------- |
635
- | REDIS_HOST | Redis server hostname or IP | - | ✓ |
636
- | REDIS_PORT | Redis server port | 6379 | ✗ |
637
- | REDIS_USERNAME | Username for authentication | - | ✗ |
638
- | REDIS_PASSWORD | Password for authentication | - | ✗ |
639
- | TLS_CA | TLS certificate authority for SSL/TLS | - | ✗ |
642
+ | Variable | Description | Default | Required |
643
+ | -------------- | ------------------------------------- | ------- | -------- |
644
+ | REDIS_HOST | Redis server hostname or IP | - | ✓ |
645
+ | REDIS_PORT | Redis server port | 6379 | ✗ |
646
+ | REDIS_USERNAME | Username for authentication | - | ✗ |
647
+ | REDIS_PASSWORD | Password for authentication | - | ✗ |
648
+ | TLS_CA | TLS certificate authority for SSL/TLS | - | ✗ |
640
649
 
641
650
  #### Configuration Options
642
651
 
@@ -646,10 +655,11 @@ You can customize Redis client creation:
646
655
  import { createRedisClient } from 'node-server-engine';
647
656
 
648
657
  const client = createRedisClient({
649
- db: 2, // Database index (default: 0)
650
- lazyConnect: true, // Don't connect until first command
651
- enableReadyCheck: false, // Disable ready check
652
- redis: { // Override any ioredis options
658
+ db: 2, // Database index (default: 0)
659
+ lazyConnect: true, // Don't connect until first command
660
+ enableReadyCheck: false, // Disable ready check
661
+ redis: {
662
+ // Override any ioredis options
653
663
  connectTimeout: 10000,
654
664
  commandTimeout: 5000
655
665
  }
@@ -672,10 +682,10 @@ await SecretManager.init({
672
682
  cache: true,
673
683
  fallbackToEnv: true,
674
684
  secrets: [
675
- 'SQL_PASSWORD', // Simple env variable
685
+ 'SQL_PASSWORD', // Simple env variable
676
686
  'JWT_SECRET',
677
687
  {
678
- name: 'PRIVATE_KEY', // File-based secret
688
+ name: 'PRIVATE_KEY', // File-based secret
679
689
  type: 'file',
680
690
  targetEnvVar: 'PRIVATE_KEY_PATH',
681
691
  filename: 'private-key.pem'
@@ -739,25 +749,27 @@ await server.init(); // Secrets loaded before app starts
739
749
 
740
750
  #### Configuration Options
741
751
 
742
- | Property | Type | Description | Default |
743
- |---------------|-------------------------------|----------------------------------------------------------------|---------------|
744
- | enabled | boolean | Enable Secret Manager (typically only in production) | false |
745
- | projectId | string | GCP project ID | Required |
746
- | cache | boolean | Cache secret values in memory | true |
747
- | fallbackToEnv | boolean | Fall back to `process.env` if secret loading fails | true |
748
- | tempDir | string | Directory for file-based secrets | `os.tmpdir()` |
749
- | secrets | Array<string \| SecretConfig> | List of secrets to load | [] |
752
+ | Property | Type | Description | Default |
753
+ | ------------- | ----------------------------- | ---------------------------------------------------- | ------------- |
754
+ | enabled | boolean | Enable Secret Manager (typically only in production) | false |
755
+ | projectId | string | GCP project ID | Required |
756
+ | cache | boolean | Cache secret values in memory | true |
757
+ | fallbackToEnv | boolean | Fall back to `process.env` if secret loading fails | true |
758
+ | tempDir | string | Directory for file-based secrets | `os.tmpdir()` |
759
+ | secrets | Array<string \| SecretConfig> | List of secrets to load | [] |
750
760
 
751
761
  #### Secret Configuration
752
762
 
753
763
  Secrets can be specified as strings (simple env variables) or objects for advanced configuration:
754
764
 
755
765
  **String format** (simple env variable):
766
+
756
767
  ```javascript
757
- 'SQL_PASSWORD' // Loads GCP secret "SQL_PASSWORD" → process.env.SQL_PASSWORD
768
+ 'SQL_PASSWORD'; // Loads GCP secret "SQL_PASSWORD" → process.env.SQL_PASSWORD
758
769
  ```
759
770
 
760
771
  **Object format** (advanced configuration):
772
+
761
773
  ```javascript
762
774
  {
763
775
  name: 'PRIVATE_KEY', // Secret name in GCP Secret Manager
@@ -771,11 +783,13 @@ Secrets can be specified as strings (simple env variables) or objects for advanc
771
783
  #### Secret Types
772
784
 
773
785
  **Environment Variable Secrets** (`type: 'env'`):
786
+
774
787
  - Loaded directly into `process.env`
775
788
  - Good for passwords, API keys, tokens
776
789
  - Example: `'SQL_PASSWORD'` → `process.env.SQL_PASSWORD`
777
790
 
778
791
  **File-based Secrets** (`type: 'file'`):
792
+
779
793
  - Written to temp files with secure permissions (0o600)
780
794
  - Good for certificates, private keys, JSON credentials
781
795
  - Environment variable points to file path
@@ -783,10 +797,10 @@ Secrets can be specified as strings (simple env variables) or objects for advanc
783
797
 
784
798
  #### Environment Variables
785
799
 
786
- | Variable | Description | Required |
787
- |------------------|---------------------------------------|----------|
788
- | GCP_PROJECT_ID | GCP project containing secrets | ✓ |
789
- | NODE_ENV | Environment (production/development) | - |
800
+ | Variable | Description | Required |
801
+ | -------------- | ------------------------------------ | -------- |
802
+ | GCP_PROJECT_ID | GCP project containing secrets | ✓ |
803
+ | NODE_ENV | Environment (production/development) | - |
790
804
 
791
805
  #### Load Results
792
806
 
@@ -820,11 +834,11 @@ const secretConfig = {
820
834
  // Database credentials
821
835
  'SQL_PASSWORD',
822
836
  'SQL_USER',
823
-
837
+
824
838
  // API keys
825
839
  'STRIPE_API_KEY',
826
840
  'SENDGRID_API_KEY',
827
-
841
+
828
842
  // Certificate files
829
843
  {
830
844
  name: 'TLS_CERT',
@@ -838,7 +852,7 @@ const secretConfig = {
838
852
  targetEnvVar: 'TLS_KEY_PATH',
839
853
  filename: 'tls.key'
840
854
  },
841
-
855
+
842
856
  // Service account key
843
857
  {
844
858
  name: 'GCP_SERVICE_ACCOUNT',
@@ -868,7 +882,7 @@ import fs from 'fs';
868
882
  AWSS3.init({
869
883
  region: 'us-east-1',
870
884
  accessKeyId: 'YOUR_ACCESS_KEY',
871
- secretAccessKey: 'YOUR_SECRET_KEY',
885
+ secretAccessKey: 'YOUR_SECRET_KEY'
872
886
  // For S3-compatible services
873
887
  // endpoint: 'http://localhost:4566',
874
888
  // forcePathStyle: true
@@ -907,7 +921,10 @@ const head = await AWSS3.getMetadata('my-bucket', uploaded.Key);
907
921
  await AWSS3.delete('my-bucket', uploaded.Key);
908
922
 
909
923
  // Generate unique destination path
910
- const key = AWSS3.generateFileDestination({ directory: 'uploads/images', mime: 'image/png' });
924
+ const key = AWSS3.generateFileDestination({
925
+ directory: 'uploads/images',
926
+ mime: 'image/png'
927
+ });
911
928
  ```
912
929
 
913
930
  #### Features
@@ -971,14 +988,14 @@ Generates a unique key using UUID with optional directory and extension (from `m
971
988
 
972
989
  #### Environment Variables
973
990
 
974
- | Variable | Description | Required |
975
- | --- | --- | --- |
976
- | `AWS_REGION` | AWS region (e.g., `us-east-1`) | ✗* |
977
- | `AWS_ACCESS_KEY_ID` | Access key ID | ✗* |
978
- | `AWS_SECRET_ACCESS_KEY` | Secret access key | ✗* |
979
- | `AWS_SESSION_TOKEN` | Session token (temporary creds) | ✗ |
980
- | `AWS_S3_ENDPOINT` | Custom S3-compatible endpoint (MinIO/LocalStack) | ✗ |
981
- | `AWS_S3_FORCE_PATH_STYLE` | Use path-style addressing (`true`/`false`) | ✗ |
991
+ | Variable | Description | Required |
992
+ | ------------------------- | ------------------------------------------------ | -------- |
993
+ | `AWS_REGION` | AWS region (e.g., `us-east-1`) | ✗* |
994
+ | `AWS_ACCESS_KEY_ID` | Access key ID | ✗* |
995
+ | `AWS_SECRET_ACCESS_KEY` | Secret access key | ✗* |
996
+ | `AWS_SESSION_TOKEN` | Session token (temporary creds) | ✗ |
997
+ | `AWS_S3_ENDPOINT` | Custom S3-compatible endpoint (MinIO/LocalStack) | ✗ |
998
+ | `AWS_S3_FORCE_PATH_STYLE` | Use path-style addressing (`true`/`false`) | ✗ |
982
999
 
983
1000
  \* Not required if you call `init()` with a config object.
984
1001
 
@@ -988,6 +1005,7 @@ Generates a unique key using UUID with optional directory and extension (from `m
988
1005
  - Other errors bubble up from AWS SDK v3 commands
989
1006
 
990
1007
  **Example:**
1008
+
991
1009
  ```javascript
992
1010
  try {
993
1011
  await AWSS3.upload(stream, 'bucket', {}, { maxSize: '1MB' });
@@ -1012,11 +1030,10 @@ new Endpoint({
1012
1030
  const file = req.files[0];
1013
1031
  const stream = Readable.from(file.buffer);
1014
1032
 
1015
- const result = await AWSS3.upload(
1016
- stream,
1017
- process.env.S3_UPLOAD_BUCKET,
1018
- { directory: 'user-uploads', mime: file.mimetype }
1019
- );
1033
+ const result = await AWSS3.upload(stream, process.env.S3_UPLOAD_BUCKET, {
1034
+ directory: 'user-uploads',
1035
+ mime: file.mimetype
1036
+ });
1020
1037
 
1021
1038
  res.json({ key: result.Key, url: result.Location });
1022
1039
  }
@@ -1040,14 +1057,16 @@ Sequelize.closeSequelize();
1040
1057
 
1041
1058
  It can be configured through environment variables
1042
1059
 
1043
- | env | description | default |
1044
- | ------------ | ------------------------------------------------- | -------- |
1045
- | SQL_HOST | Host to which connect to | |
1046
- | SQL_PORT | Port on which SQL is served | 5432 |
1047
- | SQL_PASSWORD | Password used to authenticate with the SQL server | |
1048
- | SQL_USER | User used to authenticate with the SQL server | |
1049
- | SQL_DB | Database to which connect to | |
1050
- | SQL_TYPE | SQL type which connect to | postgres |
1060
+ | env | description | default |
1061
+ | --------------------- | ------------------------------------------------------------------------------------------ | -------- |
1062
+ | SQL_HOST | Host to which connect to | |
1063
+ | SQL_PORT | Port on which SQL is served | 5432 |
1064
+ | SQL_PASSWORD | Password used to authenticate with the SQL server | |
1065
+ | SQL_USER | User used to authenticate with the SQL server | |
1066
+ | SQL_DB | Database to which connect to | |
1067
+ | SQL_TYPE | SQL type which connect to | postgres |
1068
+ | SQL_ENCRYPT | `mssql` only: set to `false` to disable TLS | true |
1069
+ | SQL_TRUST_SERVER_CERT | `mssql` only: `true` skips certificate verification (self-signed development servers only) | false |
1051
1070
 
1052
1071
  ---
1053
1072
 
@@ -1070,14 +1089,14 @@ import { PubSub } from 'node-server-engine';
1070
1089
  PubSub.addPublisher(topic, {
1071
1090
  enableMessageOrdering: false, // Enable ordering (requires orderingKey when publishing)
1072
1091
  batching: {
1073
- maxMessages: 100, // Max messages per batch
1074
- maxBytes: 1024 * 1024, // Max bytes per batch (1MB)
1075
- maxMilliseconds: 100 // Max delay before sending batch
1092
+ maxMessages: 100, // Max messages per batch
1093
+ maxBytes: 1024 * 1024, // Max bytes per batch (1MB)
1094
+ maxMilliseconds: 100 // Max delay before sending batch
1076
1095
  },
1077
1096
  retry: {
1078
- initialDelayMillis: 100, // Initial retry delay
1079
- maxDelayMillis: 60000, // Max retry delay (60s)
1080
- delayMultiplier: 1.3 // Exponential backoff multiplier
1097
+ initialDelayMillis: 100, // Initial retry delay
1098
+ maxDelayMillis: 60000, // Max retry delay (60s)
1099
+ delayMultiplier: 1.3 // Exponential backoff multiplier
1081
1100
  }
1082
1101
  });
1083
1102
 
@@ -1091,13 +1110,13 @@ PubSub.addPublisher(topic, {
1091
1110
  * @param {Object} [options] - Subscriber configuration options
1092
1111
  */
1093
1112
  PubSub.addSubscriber(subscription, handler, {
1094
- first: false, // Put handler at beginning of chain
1095
- isDebezium: false, // Handle Debezium CDC events
1096
- ackDeadline: 60, // Acknowledgement deadline (10-600 seconds)
1113
+ first: false, // Put handler at beginning of chain
1114
+ isDebezium: false, // Handle Debezium CDC events
1115
+ ackDeadline: 60, // Acknowledgement deadline (10-600 seconds)
1097
1116
  flowControl: {
1098
- maxMessages: 1000, // Max concurrent messages
1117
+ maxMessages: 1000, // Max concurrent messages
1099
1118
  maxBytes: 100 * 1024 * 1024, // Max concurrent bytes (100MB)
1100
- allowExcessMessages: true // Allow excess messages if under maxBytes
1119
+ allowExcessMessages: true // Allow excess messages if under maxBytes
1101
1120
  }
1102
1121
  });
1103
1122
 
@@ -1132,10 +1151,10 @@ async function messageHandler(payload, attributes, publishedAt) {
1132
1151
  // payload: The JSON message content
1133
1152
  // attributes: Message attributes (key-value pairs)
1134
1153
  // publishedAt: Date when message was published
1135
-
1154
+
1136
1155
  // Process the message
1137
1156
  await processData(payload);
1138
-
1157
+
1139
1158
  // Message will be ack'd automatically after successful processing
1140
1159
  // If an error is thrown, message will be nack'd for redelivery
1141
1160
  }
@@ -1203,13 +1222,14 @@ await PushNotification.sendPush(userId, {
1203
1222
 
1204
1223
  #### Environment Variables
1205
1224
 
1206
- | Variable | Description | Required |
1207
- | -------- | ----------- | -------- |
1208
- | `PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC` | Pub/Sub topic name for push notifications | ✓ |
1225
+ | Variable | Description | Required |
1226
+ | -------------------------------------- | ----------------------------------------- | -------- |
1227
+ | `PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC` | Pub/Sub topic name for push notifications | ✓ |
1209
1228
 
1210
1229
  #### Configuration
1211
1230
 
1212
1231
  The PushNotification entity is pre-configured with optimal settings:
1232
+
1213
1233
  - **Batching**: Up to 100 messages, 1MB max, 50ms delay for fast delivery
1214
1234
  - **Retry**: Exponential backoff with 100ms initial delay, up to 60s max delay
1215
1235
  - **No Ordering**: Push notifications don't require strict ordering for better throughput
@@ -1315,6 +1335,7 @@ export async function migrate(client: Client): Promise<void> {
1315
1335
  ```
1316
1336
 
1317
1337
  Migrations are:
1338
+
1318
1339
  - Run automatically on `init()`
1319
1340
  - Tracked in the `migrations` index
1320
1341
  - Executed once per file
@@ -1322,17 +1343,18 @@ Migrations are:
1322
1343
 
1323
1344
  #### Environment Variables
1324
1345
 
1325
- | Variable | Description | Required |
1326
- | ------------------------------ | ------------------------------------------ | -------- |
1327
- | ELASTIC_SEARCH_HOST | Elasticsearch cluster URL | ✓ |
1328
- | ELASTIC_SEARCH_USERNAME | Username for authentication | ✓ |
1329
- | ELASTIC_SEARCH_PASSWORD | Password for authentication | ✓ |
1330
- | ELASTIC_SEARCH_MIGRATION_PATH | Absolute path to migrations directory | ✓ |
1331
- | TLS_CA | TLS certificate authority for SSL/TLS | ✗ |
1346
+ | Variable | Description | Required |
1347
+ | ----------------------------- | ------------------------------------- | -------- |
1348
+ | ELASTIC_SEARCH_HOST | Elasticsearch cluster URL | ✓ |
1349
+ | ELASTIC_SEARCH_USERNAME | Username for authentication | ✓ |
1350
+ | ELASTIC_SEARCH_PASSWORD | Password for authentication | ✓ |
1351
+ | ELASTIC_SEARCH_MIGRATION_PATH | Absolute path to migrations directory | ✓ |
1352
+ | TLS_CA | TLS certificate authority for SSL/TLS | ✗ |
1332
1353
 
1333
1354
  #### Configuration
1334
1355
 
1335
1356
  The client is configured with:
1357
+
1336
1358
  - **Max Retries**: 3 attempts
1337
1359
  - **Request Timeout**: 30 seconds
1338
1360
  - **Sniff on Start**: Disabled (use with clusters)
@@ -1371,12 +1393,18 @@ const result = await GoogleCloudStorage.upload(
1371
1393
  console.log(result.name); // photos/uuid.jpeg
1372
1394
 
1373
1395
  // Download file as Buffer
1374
- const { data, metadata } = await GoogleCloudStorage.get('my-bucket', 'photos/image.jpg');
1396
+ const { data, metadata } = await GoogleCloudStorage.get(
1397
+ 'my-bucket',
1398
+ 'photos/image.jpg'
1399
+ );
1375
1400
  console.log(metadata.size); // File size in bytes
1376
1401
  fs.writeFileSync('downloaded.jpg', data);
1377
1402
 
1378
1403
  // Stream a file (for large files)
1379
- const { stream, metadata } = await GoogleCloudStorage.download('my-bucket', 'videos/video.mp4');
1404
+ const { stream, metadata } = await GoogleCloudStorage.download(
1405
+ 'my-bucket',
1406
+ 'videos/video.mp4'
1407
+ );
1380
1408
  stream.pipe(res); // Stream to HTTP response
1381
1409
 
1382
1410
  // Get file stream directly (fastest, no metadata)
@@ -1430,6 +1458,7 @@ GoogleCloudStorage.init({
1430
1458
  Upload a file to Google Cloud Storage.
1431
1459
 
1432
1460
  **Parameters:**
1461
+
1433
1462
  - `stream` (Readable): Node.js readable stream of file content
1434
1463
  - `bucket` (string): Bucket name
1435
1464
  - `destinationOptions` (object, optional):
@@ -1444,6 +1473,7 @@ Upload a file to Google Cloud Storage.
1444
1473
  **Returns:** Promise\<StorageUploadedFile> - Metadata of uploaded file
1445
1474
 
1446
1475
  **Example:**
1476
+
1447
1477
  ```javascript
1448
1478
  const fileStream = fs.createReadStream('document.pdf');
1449
1479
  const result = await GoogleCloudStorage.upload(
@@ -1460,14 +1490,19 @@ const result = await GoogleCloudStorage.upload(
1460
1490
  Download a file and return its content as a Buffer along with metadata.
1461
1491
 
1462
1492
  **Parameters:**
1493
+
1463
1494
  - `bucket` (string): Bucket name
1464
1495
  - `path` (string): File path in the bucket
1465
1496
 
1466
1497
  **Returns:** Promise\<{data: Buffer, metadata: StorageUploadedFile}>
1467
1498
 
1468
1499
  **Example:**
1500
+
1469
1501
  ```javascript
1470
- const { data, metadata } = await GoogleCloudStorage.get('my-bucket', 'photos/image.jpg');
1502
+ const { data, metadata } = await GoogleCloudStorage.get(
1503
+ 'my-bucket',
1504
+ 'photos/image.jpg'
1505
+ );
1471
1506
  console.log(metadata.contentType); // 'image/jpeg'
1472
1507
  console.log(data.length); // File size in bytes
1473
1508
  ```
@@ -1477,14 +1512,19 @@ console.log(data.length); // File size in bytes
1477
1512
  Get a readable stream for a file along with its metadata. Use this for large files or when you need to stream content.
1478
1513
 
1479
1514
  **Parameters:**
1515
+
1480
1516
  - `bucket` (string): Bucket name
1481
1517
  - `path` (string): File path in the bucket
1482
1518
 
1483
1519
  **Returns:** Promise\<{stream: Readable, metadata: StorageUploadedFile}>
1484
1520
 
1485
1521
  **Example:**
1522
+
1486
1523
  ```javascript
1487
- const { stream, metadata } = await GoogleCloudStorage.download('my-bucket', 'videos/large-video.mp4');
1524
+ const { stream, metadata } = await GoogleCloudStorage.download(
1525
+ 'my-bucket',
1526
+ 'videos/large-video.mp4'
1527
+ );
1488
1528
  console.log(metadata.size); // File size
1489
1529
  stream.pipe(response); // Stream to HTTP response
1490
1530
  ```
@@ -1494,12 +1534,14 @@ stream.pipe(response); // Stream to HTTP response
1494
1534
  Get a readable stream for a file without fetching metadata. Fastest option when metadata is not needed.
1495
1535
 
1496
1536
  **Parameters:**
1537
+
1497
1538
  - `bucket` (string): Bucket name
1498
1539
  - `path` (string): File path in the bucket
1499
1540
 
1500
1541
  **Returns:** Readable - Node.js readable stream
1501
1542
 
1502
1543
  **Example:**
1544
+
1503
1545
  ```javascript
1504
1546
  const stream = GoogleCloudStorage.getFileStream('my-bucket', 'audio/song.mp3');
1505
1547
  stream.pipe(response); // Direct streaming
@@ -1510,12 +1552,14 @@ stream.pipe(response); // Direct streaming
1510
1552
  Delete a file from Google Cloud Storage.
1511
1553
 
1512
1554
  **Parameters:**
1555
+
1513
1556
  - `bucket` (string): Bucket name
1514
1557
  - `path` (string): File path in the bucket
1515
1558
 
1516
1559
  **Returns:** Promise\<void>
1517
1560
 
1518
1561
  **Example:**
1562
+
1519
1563
  ```javascript
1520
1564
  await GoogleCloudStorage.delete('my-bucket', 'temp/old-file.txt');
1521
1565
  ```
@@ -1525,6 +1569,7 @@ await GoogleCloudStorage.delete('my-bucket', 'temp/old-file.txt');
1525
1569
  Generate a unique file path with optional directory and extension.
1526
1570
 
1527
1571
  **Parameters:**
1572
+
1528
1573
  - `options` (object, optional):
1529
1574
  - `directory` (string): Subdirectory path
1530
1575
  - `mime` (string): MIME type for extension detection
@@ -1533,6 +1578,7 @@ Generate a unique file path with optional directory and extension.
1533
1578
  **Returns:** string - Generated file path
1534
1579
 
1535
1580
  **Examples:**
1581
+
1536
1582
  ```javascript
1537
1583
  // UUID only
1538
1584
  GoogleCloudStorage.generateFileDestination();
@@ -1555,10 +1601,10 @@ GoogleCloudStorage.generateFileDestination({
1555
1601
 
1556
1602
  #### Environment Variables
1557
1603
 
1558
- | Variable | Description | Required |
1559
- | ------------------------------- | ------------------------------------ | -------- |
1560
- | GC_PROJECT | Google Cloud Project ID | ✗* |
1561
- | GOOGLE_APPLICATION_CREDENTIALS | Path to service account key file | ✗* |
1604
+ | Variable | Description | Required |
1605
+ | ------------------------------ | -------------------------------- | -------- |
1606
+ | GC_PROJECT | Google Cloud Project ID | ✗* |
1607
+ | GOOGLE_APPLICATION_CREDENTIALS | Path to service account key file | ✗* |
1562
1608
 
1563
1609
  \* Not required if you call `init()` with a config object
1564
1610
 
@@ -1570,6 +1616,7 @@ The entity throws `WebError` with appropriate status codes:
1570
1616
  - Other errors are passed through from Google Cloud Storage SDK
1571
1617
 
1572
1618
  **Example:**
1619
+
1573
1620
  ```javascript
1574
1621
  try {
1575
1622
  await GoogleCloudStorage.upload(stream, 'bucket', {}, {}, { maxSize: '1MB' });
@@ -1595,13 +1642,13 @@ new Endpoint({
1595
1642
  async handler(req, res) {
1596
1643
  const file = req.files[0];
1597
1644
  const stream = Readable.from(file.buffer);
1598
-
1645
+
1599
1646
  const result = await GoogleCloudStorage.upload(
1600
1647
  stream,
1601
1648
  process.env.UPLOAD_BUCKET,
1602
1649
  { directory: 'user-uploads', mime: file.mimetype }
1603
1650
  );
1604
-
1651
+
1605
1652
  res.json({ path: result.name, url: result.mediaLink });
1606
1653
  }
1607
1654
  });
@@ -1662,6 +1709,7 @@ Errors are a crucial part of the application, they are what helps us to properly
1662
1709
  The engine automatically adapts log output based on the environment:
1663
1710
 
1664
1711
  **Local Development (Readable Format)**
1712
+
1665
1713
  - Colorized output with severity levels
1666
1714
  - Formatted timestamps and file locations
1667
1715
  - Pretty-printed data objects
@@ -1669,6 +1717,7 @@ The engine automatically adapts log output based on the environment:
1669
1717
  - HTTP request context when available
1670
1718
 
1671
1719
  **Production/GCP (JSON Format)**
1720
+
1672
1721
  - Structured JSON for log aggregation
1673
1722
  - Google Cloud Error Reporting integration
1674
1723
  - Kubernetes pod information
@@ -1687,6 +1736,7 @@ LOG_FORMAT=json npm start
1687
1736
  ```
1688
1737
 
1689
1738
  **Example Local Output:**
1739
+
1690
1740
  ```
1691
1741
  [2025-12-10T10:30:45.123Z] ERROR src/endpoints/users.ts:42 User not found
1692
1742
  Error Code: user-not-found
@@ -1943,15 +1993,15 @@ interface User {
1943
1993
 
1944
1994
  ```javascript
1945
1995
  // Case-insensitive matching
1946
- checkPermission('ADMIN') // Matches: 'admin', 'Admin', 'ADMIN'
1947
- checkPermission(['READ', 'write']) // Matches any case variation
1996
+ checkPermission('ADMIN'); // Matches: 'admin', 'Admin', 'ADMIN'
1997
+ checkPermission(['READ', 'write']); // Matches any case variation
1948
1998
 
1949
1999
  // Namespace-style permissions
1950
- checkPermission('users:read')
1951
- checkPermission(['users:write', 'users:delete'])
2000
+ checkPermission('users:read');
2001
+ checkPermission(['users:write', 'users:delete']);
1952
2002
 
1953
2003
  // Role-based permissions
1954
- checkPermission(['admin', 'moderator'])
2004
+ checkPermission(['admin', 'moderator']);
1955
2005
  ```
1956
2006
 
1957
2007
  #### Response Codes
@@ -1975,8 +2025,10 @@ checkPermission(['admin', 'moderator'])
1975
2025
  "message": "Permission denied"
1976
2026
  }
1977
2027
  ```
2028
+
1978
2029
  };
1979
- ```
2030
+
2031
+ ````
1980
2032
 
1981
2033
  ---
1982
2034
 
@@ -2006,7 +2058,7 @@ new Endpoint({
2006
2058
  res.json({ ok: true });
2007
2059
  }
2008
2060
  });
2009
- ```
2061
+ ````
2010
2062
 
2011
2063
  #### Defaults
2012
2064
 
@@ -2147,7 +2199,10 @@ The function returns an object with the following status options:
2147
2199
  Generate and verify a short-lived verification token + OTP for sensitive operations.
2148
2200
 
2149
2201
  ```javascript
2150
- import { createVerificationToken, verifyVerificationToken } from 'node-server-engine';
2202
+ import {
2203
+ createVerificationToken,
2204
+ verifyVerificationToken
2205
+ } from 'node-server-engine';
2151
2206
 
2152
2207
  // Generate token + OTP
2153
2208
  const { token, otp, expiresAt } = createVerificationToken({
@@ -2435,17 +2490,17 @@ npm run coverage
2435
2490
 
2436
2491
  ### Scripts
2437
2492
 
2438
- | Script | Description |
2439
- |--------|-------------|
2440
- | `npm run build` | Compile TypeScript and generate distribution files |
2441
- | `npm run lint` | Check code for linting errors |
2442
- | `npm run lint:fix` | Automatically fix linting errors |
2443
- | `npm run format` | Format code with Prettier |
2444
- | `npm run format:check` | Check code formatting without modifying files |
2445
- | `npm test` | Run all tests with coverage |
2446
- | `npm run test:file` | Run a specific test file |
2447
- | `npm run coverage` | Generate HTML coverage report |
2448
- | `npm run coverage:ci` | Generate coverage summary for CI |
2493
+ | Script | Description |
2494
+ | ---------------------- | -------------------------------------------------- |
2495
+ | `npm run build` | Compile TypeScript and generate distribution files |
2496
+ | `npm run lint` | Check code for linting errors |
2497
+ | `npm run lint:fix` | Automatically fix linting errors |
2498
+ | `npm run format` | Format code with Prettier |
2499
+ | `npm run format:check` | Check code formatting without modifying files |
2500
+ | `npm test` | Run all tests with coverage |
2501
+ | `npm run test:file` | Run a specific test file |
2502
+ | `npm run coverage` | Generate HTML coverage report |
2503
+ | `npm run coverage:ci` | Generate coverage summary for CI |
2449
2504
 
2450
2505
  ### Commit Convention
2451
2506
 
@@ -2454,6 +2509,7 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/) f
2454
2509
  **Format:** `type(scope): subject`
2455
2510
 
2456
2511
  **Types:**
2512
+
2457
2513
  - `feat`: New feature (triggers minor version bump)
2458
2514
  - `fix`: Bug fix (triggers patch version bump)
2459
2515
  - `docs`: Documentation changes
@@ -2465,6 +2521,7 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/) f
2465
2521
  - `ci`: CI/CD changes
2466
2522
 
2467
2523
  **Examples:**
2524
+
2468
2525
  ```bash
2469
2526
  git commit -m "feat(endpoints): add support for GraphQL endpoints"
2470
2527
  git commit -m "fix(auth): resolve JWT token validation issue"
@@ -2472,6 +2529,7 @@ git commit -m "docs(readme): update installation instructions"
2472
2529
  ```
2473
2530
 
2474
2531
  **Breaking Changes:** Add `BREAKING CHANGE:` in the commit body to trigger a major version bump:
2532
+
2475
2533
  ```bash
2476
2534
  git commit -m "feat(api): redesign authentication flow" -m "BREAKING CHANGE: AuthType enum values changed"
2477
2535
  ```
@@ -2481,6 +2539,7 @@ git commit -m "feat(api): redesign authentication flow" -m "BREAKING CHANGE: Aut
2481
2539
  This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated version management and package publishing.
2482
2540
 
2483
2541
  **How it works:**
2542
+
2484
2543
  1. Commits are analyzed based on conventional commit format
2485
2544
  2. Version number is automatically determined (major.minor.patch)
2486
2545
  3. Changelog is generated from commit messages
@@ -2489,6 +2548,7 @@ This project uses [semantic-release](https://github.com/semantic-release/semanti
2489
2548
  6. GitHub release is created
2490
2549
 
2491
2550
  **When releases happen:**
2551
+
2492
2552
  - Automatically on every push to `main` branch
2493
2553
  - Only if there are release-worthy commits (feat, fix, perf, BREAKING CHANGE)
2494
2554
  - Releases are skipped for commits with `[skip ci]` in the message