node-server-engine 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.commitlintrc.js +10 -10
  2. package/.nycrc +1 -3
  3. package/.releaserc.js +2 -1
  4. package/README.md +194 -138
  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.map +1 -1
  21. package/dist/entities/Sequelize/Sequelize.js.map +1 -1
  22. package/dist/entities/Server/Server.js.map +1 -1
  23. package/dist/entities/Server/Server.types.js.map +1 -1
  24. package/dist/entities/index.js.map +1 -1
  25. package/dist/middleware/checkPermission/checkPermission.js.map +1 -1
  26. package/dist/middleware/checkPermission/index.js.map +1 -1
  27. package/dist/middleware/index.js.map +1 -1
  28. package/dist/middleware/multiPartFileUploader/multiPartFileUploader.js.map +1 -1
  29. package/dist/middleware/requestMetrics/requestMetrics.js.map +1 -1
  30. package/dist/middleware/swaggerDocs/swaggerDocs.js.map +1 -1
  31. package/dist/middleware/verificationToken/verificationToken.js.map +1 -1
  32. package/dist/utils/createSortedObject/createSortedObject.js.map +1 -1
  33. package/dist/utils/dbMigration/dbMigration.d.ts +1 -0
  34. package/dist/utils/dbMigration/dbMigration.js +13 -2
  35. package/dist/utils/dbMigration/dbMigration.js.map +1 -1
  36. package/dist/utils/dbMigration/migrateMetadataTable/migrateMetadataTable.js +29 -3
  37. package/dist/utils/dbMigration/migrateMetadataTable/migrateMetadataTable.js.map +1 -1
  38. package/dist/utils/jwt/jwt.js.map +1 -1
  39. package/dist/utils/jwt/jwt.types.js.map +1 -1
  40. package/dist/utils/report/output.js.map +1 -1
  41. package/dist/utils/report/report.js.map +1 -1
  42. package/dist/utils/semverCompare/semverCompare.js.map +1 -1
  43. package/dist/utils/verificationToken/verificationToken.js.map +1 -1
  44. 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,22 @@ 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 | [] |
532
537
 
533
538
  #### Client Properties & Methods
534
539
 
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 |
540
+ | Property/Method | Type | Description |
541
+ | ----------------- | ----------------------------- | ----------------------------------------------------------------- |
542
+ | id | String | Unique identifier for the connection |
543
+ | establishedAt | Date | Timestamp when connection was established |
544
+ | isAuthenticated() | () => Boolean | Check if client is authenticated |
545
+ | getUser() | () => SocketUser \| undefined | Get authenticated user data (userId, deviceId, tokenId, audience) |
546
+ | sendMessage() | (type, payload, options) | Send a message to the client |
542
547
 
543
548
  #### Client Authentication
544
549
 
@@ -546,10 +551,12 @@ Clients authenticate by sending a message:
546
551
 
547
552
  ```javascript
548
553
  // Client-side
549
- websocket.send(JSON.stringify({
550
- type: 'authenticate',
551
- payload: { token: 'your-jwt-token' }
552
- }));
554
+ websocket.send(
555
+ JSON.stringify({
556
+ type: 'authenticate',
557
+ payload: { token: 'your-jwt-token' }
558
+ })
559
+ );
553
560
 
554
561
  // Server will:
555
562
  // 1. Verify the JWT token
@@ -561,10 +568,10 @@ websocket.send(JSON.stringify({
561
568
 
562
569
  #### Environment Variables
563
570
 
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 |
571
+ | Variable | Description | Default |
572
+ | ----------------------- | ----------------------------------------- | ------- |
573
+ | WS_PING_INTERVAL | Interval between ping checks (seconds) | 30 |
574
+ | WEBSOCKET_CLOSE_TIMEOUT | Timeout for graceful close (milliseconds) | 3000 |
568
575
 
569
576
  ---
570
577
 
@@ -630,13 +637,13 @@ await Redis.shutdown();
630
637
 
631
638
  #### Environment Variables
632
639
 
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 | - | ✗ |
640
+ | Variable | Description | Default | Required |
641
+ | -------------- | ------------------------------------- | ------- | -------- |
642
+ | REDIS_HOST | Redis server hostname or IP | - | ✓ |
643
+ | REDIS_PORT | Redis server port | 6379 | ✗ |
644
+ | REDIS_USERNAME | Username for authentication | - | ✗ |
645
+ | REDIS_PASSWORD | Password for authentication | - | ✗ |
646
+ | TLS_CA | TLS certificate authority for SSL/TLS | - | ✗ |
640
647
 
641
648
  #### Configuration Options
642
649
 
@@ -646,10 +653,11 @@ You can customize Redis client creation:
646
653
  import { createRedisClient } from 'node-server-engine';
647
654
 
648
655
  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
656
+ db: 2, // Database index (default: 0)
657
+ lazyConnect: true, // Don't connect until first command
658
+ enableReadyCheck: false, // Disable ready check
659
+ redis: {
660
+ // Override any ioredis options
653
661
  connectTimeout: 10000,
654
662
  commandTimeout: 5000
655
663
  }
@@ -672,10 +680,10 @@ await SecretManager.init({
672
680
  cache: true,
673
681
  fallbackToEnv: true,
674
682
  secrets: [
675
- 'SQL_PASSWORD', // Simple env variable
683
+ 'SQL_PASSWORD', // Simple env variable
676
684
  'JWT_SECRET',
677
685
  {
678
- name: 'PRIVATE_KEY', // File-based secret
686
+ name: 'PRIVATE_KEY', // File-based secret
679
687
  type: 'file',
680
688
  targetEnvVar: 'PRIVATE_KEY_PATH',
681
689
  filename: 'private-key.pem'
@@ -739,25 +747,27 @@ await server.init(); // Secrets loaded before app starts
739
747
 
740
748
  #### Configuration Options
741
749
 
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 | [] |
750
+ | Property | Type | Description | Default |
751
+ | ------------- | ----------------------------- | ---------------------------------------------------- | ------------- |
752
+ | enabled | boolean | Enable Secret Manager (typically only in production) | false |
753
+ | projectId | string | GCP project ID | Required |
754
+ | cache | boolean | Cache secret values in memory | true |
755
+ | fallbackToEnv | boolean | Fall back to `process.env` if secret loading fails | true |
756
+ | tempDir | string | Directory for file-based secrets | `os.tmpdir()` |
757
+ | secrets | Array<string \| SecretConfig> | List of secrets to load | [] |
750
758
 
751
759
  #### Secret Configuration
752
760
 
753
761
  Secrets can be specified as strings (simple env variables) or objects for advanced configuration:
754
762
 
755
763
  **String format** (simple env variable):
764
+
756
765
  ```javascript
757
- 'SQL_PASSWORD' // Loads GCP secret "SQL_PASSWORD" → process.env.SQL_PASSWORD
766
+ 'SQL_PASSWORD'; // Loads GCP secret "SQL_PASSWORD" → process.env.SQL_PASSWORD
758
767
  ```
759
768
 
760
769
  **Object format** (advanced configuration):
770
+
761
771
  ```javascript
762
772
  {
763
773
  name: 'PRIVATE_KEY', // Secret name in GCP Secret Manager
@@ -771,11 +781,13 @@ Secrets can be specified as strings (simple env variables) or objects for advanc
771
781
  #### Secret Types
772
782
 
773
783
  **Environment Variable Secrets** (`type: 'env'`):
784
+
774
785
  - Loaded directly into `process.env`
775
786
  - Good for passwords, API keys, tokens
776
787
  - Example: `'SQL_PASSWORD'` → `process.env.SQL_PASSWORD`
777
788
 
778
789
  **File-based Secrets** (`type: 'file'`):
790
+
779
791
  - Written to temp files with secure permissions (0o600)
780
792
  - Good for certificates, private keys, JSON credentials
781
793
  - Environment variable points to file path
@@ -783,10 +795,10 @@ Secrets can be specified as strings (simple env variables) or objects for advanc
783
795
 
784
796
  #### Environment Variables
785
797
 
786
- | Variable | Description | Required |
787
- |------------------|---------------------------------------|----------|
788
- | GCP_PROJECT_ID | GCP project containing secrets | ✓ |
789
- | NODE_ENV | Environment (production/development) | - |
798
+ | Variable | Description | Required |
799
+ | -------------- | ------------------------------------ | -------- |
800
+ | GCP_PROJECT_ID | GCP project containing secrets | ✓ |
801
+ | NODE_ENV | Environment (production/development) | - |
790
802
 
791
803
  #### Load Results
792
804
 
@@ -820,11 +832,11 @@ const secretConfig = {
820
832
  // Database credentials
821
833
  'SQL_PASSWORD',
822
834
  'SQL_USER',
823
-
835
+
824
836
  // API keys
825
837
  'STRIPE_API_KEY',
826
838
  'SENDGRID_API_KEY',
827
-
839
+
828
840
  // Certificate files
829
841
  {
830
842
  name: 'TLS_CERT',
@@ -838,7 +850,7 @@ const secretConfig = {
838
850
  targetEnvVar: 'TLS_KEY_PATH',
839
851
  filename: 'tls.key'
840
852
  },
841
-
853
+
842
854
  // Service account key
843
855
  {
844
856
  name: 'GCP_SERVICE_ACCOUNT',
@@ -868,7 +880,7 @@ import fs from 'fs';
868
880
  AWSS3.init({
869
881
  region: 'us-east-1',
870
882
  accessKeyId: 'YOUR_ACCESS_KEY',
871
- secretAccessKey: 'YOUR_SECRET_KEY',
883
+ secretAccessKey: 'YOUR_SECRET_KEY'
872
884
  // For S3-compatible services
873
885
  // endpoint: 'http://localhost:4566',
874
886
  // forcePathStyle: true
@@ -907,7 +919,10 @@ const head = await AWSS3.getMetadata('my-bucket', uploaded.Key);
907
919
  await AWSS3.delete('my-bucket', uploaded.Key);
908
920
 
909
921
  // Generate unique destination path
910
- const key = AWSS3.generateFileDestination({ directory: 'uploads/images', mime: 'image/png' });
922
+ const key = AWSS3.generateFileDestination({
923
+ directory: 'uploads/images',
924
+ mime: 'image/png'
925
+ });
911
926
  ```
912
927
 
913
928
  #### Features
@@ -971,14 +986,14 @@ Generates a unique key using UUID with optional directory and extension (from `m
971
986
 
972
987
  #### Environment Variables
973
988
 
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`) | ✗ |
989
+ | Variable | Description | Required |
990
+ | ------------------------- | ------------------------------------------------ | -------- |
991
+ | `AWS_REGION` | AWS region (e.g., `us-east-1`) | ✗* |
992
+ | `AWS_ACCESS_KEY_ID` | Access key ID | ✗* |
993
+ | `AWS_SECRET_ACCESS_KEY` | Secret access key | ✗* |
994
+ | `AWS_SESSION_TOKEN` | Session token (temporary creds) | ✗ |
995
+ | `AWS_S3_ENDPOINT` | Custom S3-compatible endpoint (MinIO/LocalStack) | ✗ |
996
+ | `AWS_S3_FORCE_PATH_STYLE` | Use path-style addressing (`true`/`false`) | ✗ |
982
997
 
983
998
  \* Not required if you call `init()` with a config object.
984
999
 
@@ -988,6 +1003,7 @@ Generates a unique key using UUID with optional directory and extension (from `m
988
1003
  - Other errors bubble up from AWS SDK v3 commands
989
1004
 
990
1005
  **Example:**
1006
+
991
1007
  ```javascript
992
1008
  try {
993
1009
  await AWSS3.upload(stream, 'bucket', {}, { maxSize: '1MB' });
@@ -1012,11 +1028,10 @@ new Endpoint({
1012
1028
  const file = req.files[0];
1013
1029
  const stream = Readable.from(file.buffer);
1014
1030
 
1015
- const result = await AWSS3.upload(
1016
- stream,
1017
- process.env.S3_UPLOAD_BUCKET,
1018
- { directory: 'user-uploads', mime: file.mimetype }
1019
- );
1031
+ const result = await AWSS3.upload(stream, process.env.S3_UPLOAD_BUCKET, {
1032
+ directory: 'user-uploads',
1033
+ mime: file.mimetype
1034
+ });
1020
1035
 
1021
1036
  res.json({ key: result.Key, url: result.Location });
1022
1037
  }
@@ -1070,14 +1085,14 @@ import { PubSub } from 'node-server-engine';
1070
1085
  PubSub.addPublisher(topic, {
1071
1086
  enableMessageOrdering: false, // Enable ordering (requires orderingKey when publishing)
1072
1087
  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
1088
+ maxMessages: 100, // Max messages per batch
1089
+ maxBytes: 1024 * 1024, // Max bytes per batch (1MB)
1090
+ maxMilliseconds: 100 // Max delay before sending batch
1076
1091
  },
1077
1092
  retry: {
1078
- initialDelayMillis: 100, // Initial retry delay
1079
- maxDelayMillis: 60000, // Max retry delay (60s)
1080
- delayMultiplier: 1.3 // Exponential backoff multiplier
1093
+ initialDelayMillis: 100, // Initial retry delay
1094
+ maxDelayMillis: 60000, // Max retry delay (60s)
1095
+ delayMultiplier: 1.3 // Exponential backoff multiplier
1081
1096
  }
1082
1097
  });
1083
1098
 
@@ -1091,13 +1106,13 @@ PubSub.addPublisher(topic, {
1091
1106
  * @param {Object} [options] - Subscriber configuration options
1092
1107
  */
1093
1108
  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)
1109
+ first: false, // Put handler at beginning of chain
1110
+ isDebezium: false, // Handle Debezium CDC events
1111
+ ackDeadline: 60, // Acknowledgement deadline (10-600 seconds)
1097
1112
  flowControl: {
1098
- maxMessages: 1000, // Max concurrent messages
1113
+ maxMessages: 1000, // Max concurrent messages
1099
1114
  maxBytes: 100 * 1024 * 1024, // Max concurrent bytes (100MB)
1100
- allowExcessMessages: true // Allow excess messages if under maxBytes
1115
+ allowExcessMessages: true // Allow excess messages if under maxBytes
1101
1116
  }
1102
1117
  });
1103
1118
 
@@ -1132,10 +1147,10 @@ async function messageHandler(payload, attributes, publishedAt) {
1132
1147
  // payload: The JSON message content
1133
1148
  // attributes: Message attributes (key-value pairs)
1134
1149
  // publishedAt: Date when message was published
1135
-
1150
+
1136
1151
  // Process the message
1137
1152
  await processData(payload);
1138
-
1153
+
1139
1154
  // Message will be ack'd automatically after successful processing
1140
1155
  // If an error is thrown, message will be nack'd for redelivery
1141
1156
  }
@@ -1203,13 +1218,14 @@ await PushNotification.sendPush(userId, {
1203
1218
 
1204
1219
  #### Environment Variables
1205
1220
 
1206
- | Variable | Description | Required |
1207
- | -------- | ----------- | -------- |
1208
- | `PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC` | Pub/Sub topic name for push notifications | ✓ |
1221
+ | Variable | Description | Required |
1222
+ | -------------------------------------- | ----------------------------------------- | -------- |
1223
+ | `PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC` | Pub/Sub topic name for push notifications | ✓ |
1209
1224
 
1210
1225
  #### Configuration
1211
1226
 
1212
1227
  The PushNotification entity is pre-configured with optimal settings:
1228
+
1213
1229
  - **Batching**: Up to 100 messages, 1MB max, 50ms delay for fast delivery
1214
1230
  - **Retry**: Exponential backoff with 100ms initial delay, up to 60s max delay
1215
1231
  - **No Ordering**: Push notifications don't require strict ordering for better throughput
@@ -1315,6 +1331,7 @@ export async function migrate(client: Client): Promise<void> {
1315
1331
  ```
1316
1332
 
1317
1333
  Migrations are:
1334
+
1318
1335
  - Run automatically on `init()`
1319
1336
  - Tracked in the `migrations` index
1320
1337
  - Executed once per file
@@ -1322,17 +1339,18 @@ Migrations are:
1322
1339
 
1323
1340
  #### Environment Variables
1324
1341
 
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 | ✗ |
1342
+ | Variable | Description | Required |
1343
+ | ----------------------------- | ------------------------------------- | -------- |
1344
+ | ELASTIC_SEARCH_HOST | Elasticsearch cluster URL | ✓ |
1345
+ | ELASTIC_SEARCH_USERNAME | Username for authentication | ✓ |
1346
+ | ELASTIC_SEARCH_PASSWORD | Password for authentication | ✓ |
1347
+ | ELASTIC_SEARCH_MIGRATION_PATH | Absolute path to migrations directory | ✓ |
1348
+ | TLS_CA | TLS certificate authority for SSL/TLS | ✗ |
1332
1349
 
1333
1350
  #### Configuration
1334
1351
 
1335
1352
  The client is configured with:
1353
+
1336
1354
  - **Max Retries**: 3 attempts
1337
1355
  - **Request Timeout**: 30 seconds
1338
1356
  - **Sniff on Start**: Disabled (use with clusters)
@@ -1371,12 +1389,18 @@ const result = await GoogleCloudStorage.upload(
1371
1389
  console.log(result.name); // photos/uuid.jpeg
1372
1390
 
1373
1391
  // Download file as Buffer
1374
- const { data, metadata } = await GoogleCloudStorage.get('my-bucket', 'photos/image.jpg');
1392
+ const { data, metadata } = await GoogleCloudStorage.get(
1393
+ 'my-bucket',
1394
+ 'photos/image.jpg'
1395
+ );
1375
1396
  console.log(metadata.size); // File size in bytes
1376
1397
  fs.writeFileSync('downloaded.jpg', data);
1377
1398
 
1378
1399
  // Stream a file (for large files)
1379
- const { stream, metadata } = await GoogleCloudStorage.download('my-bucket', 'videos/video.mp4');
1400
+ const { stream, metadata } = await GoogleCloudStorage.download(
1401
+ 'my-bucket',
1402
+ 'videos/video.mp4'
1403
+ );
1380
1404
  stream.pipe(res); // Stream to HTTP response
1381
1405
 
1382
1406
  // Get file stream directly (fastest, no metadata)
@@ -1430,6 +1454,7 @@ GoogleCloudStorage.init({
1430
1454
  Upload a file to Google Cloud Storage.
1431
1455
 
1432
1456
  **Parameters:**
1457
+
1433
1458
  - `stream` (Readable): Node.js readable stream of file content
1434
1459
  - `bucket` (string): Bucket name
1435
1460
  - `destinationOptions` (object, optional):
@@ -1444,6 +1469,7 @@ Upload a file to Google Cloud Storage.
1444
1469
  **Returns:** Promise\<StorageUploadedFile> - Metadata of uploaded file
1445
1470
 
1446
1471
  **Example:**
1472
+
1447
1473
  ```javascript
1448
1474
  const fileStream = fs.createReadStream('document.pdf');
1449
1475
  const result = await GoogleCloudStorage.upload(
@@ -1460,14 +1486,19 @@ const result = await GoogleCloudStorage.upload(
1460
1486
  Download a file and return its content as a Buffer along with metadata.
1461
1487
 
1462
1488
  **Parameters:**
1489
+
1463
1490
  - `bucket` (string): Bucket name
1464
1491
  - `path` (string): File path in the bucket
1465
1492
 
1466
1493
  **Returns:** Promise\<{data: Buffer, metadata: StorageUploadedFile}>
1467
1494
 
1468
1495
  **Example:**
1496
+
1469
1497
  ```javascript
1470
- const { data, metadata } = await GoogleCloudStorage.get('my-bucket', 'photos/image.jpg');
1498
+ const { data, metadata } = await GoogleCloudStorage.get(
1499
+ 'my-bucket',
1500
+ 'photos/image.jpg'
1501
+ );
1471
1502
  console.log(metadata.contentType); // 'image/jpeg'
1472
1503
  console.log(data.length); // File size in bytes
1473
1504
  ```
@@ -1477,14 +1508,19 @@ console.log(data.length); // File size in bytes
1477
1508
  Get a readable stream for a file along with its metadata. Use this for large files or when you need to stream content.
1478
1509
 
1479
1510
  **Parameters:**
1511
+
1480
1512
  - `bucket` (string): Bucket name
1481
1513
  - `path` (string): File path in the bucket
1482
1514
 
1483
1515
  **Returns:** Promise\<{stream: Readable, metadata: StorageUploadedFile}>
1484
1516
 
1485
1517
  **Example:**
1518
+
1486
1519
  ```javascript
1487
- const { stream, metadata } = await GoogleCloudStorage.download('my-bucket', 'videos/large-video.mp4');
1520
+ const { stream, metadata } = await GoogleCloudStorage.download(
1521
+ 'my-bucket',
1522
+ 'videos/large-video.mp4'
1523
+ );
1488
1524
  console.log(metadata.size); // File size
1489
1525
  stream.pipe(response); // Stream to HTTP response
1490
1526
  ```
@@ -1494,12 +1530,14 @@ stream.pipe(response); // Stream to HTTP response
1494
1530
  Get a readable stream for a file without fetching metadata. Fastest option when metadata is not needed.
1495
1531
 
1496
1532
  **Parameters:**
1533
+
1497
1534
  - `bucket` (string): Bucket name
1498
1535
  - `path` (string): File path in the bucket
1499
1536
 
1500
1537
  **Returns:** Readable - Node.js readable stream
1501
1538
 
1502
1539
  **Example:**
1540
+
1503
1541
  ```javascript
1504
1542
  const stream = GoogleCloudStorage.getFileStream('my-bucket', 'audio/song.mp3');
1505
1543
  stream.pipe(response); // Direct streaming
@@ -1510,12 +1548,14 @@ stream.pipe(response); // Direct streaming
1510
1548
  Delete a file from Google Cloud Storage.
1511
1549
 
1512
1550
  **Parameters:**
1551
+
1513
1552
  - `bucket` (string): Bucket name
1514
1553
  - `path` (string): File path in the bucket
1515
1554
 
1516
1555
  **Returns:** Promise\<void>
1517
1556
 
1518
1557
  **Example:**
1558
+
1519
1559
  ```javascript
1520
1560
  await GoogleCloudStorage.delete('my-bucket', 'temp/old-file.txt');
1521
1561
  ```
@@ -1525,6 +1565,7 @@ await GoogleCloudStorage.delete('my-bucket', 'temp/old-file.txt');
1525
1565
  Generate a unique file path with optional directory and extension.
1526
1566
 
1527
1567
  **Parameters:**
1568
+
1528
1569
  - `options` (object, optional):
1529
1570
  - `directory` (string): Subdirectory path
1530
1571
  - `mime` (string): MIME type for extension detection
@@ -1533,6 +1574,7 @@ Generate a unique file path with optional directory and extension.
1533
1574
  **Returns:** string - Generated file path
1534
1575
 
1535
1576
  **Examples:**
1577
+
1536
1578
  ```javascript
1537
1579
  // UUID only
1538
1580
  GoogleCloudStorage.generateFileDestination();
@@ -1555,10 +1597,10 @@ GoogleCloudStorage.generateFileDestination({
1555
1597
 
1556
1598
  #### Environment Variables
1557
1599
 
1558
- | Variable | Description | Required |
1559
- | ------------------------------- | ------------------------------------ | -------- |
1560
- | GC_PROJECT | Google Cloud Project ID | ✗* |
1561
- | GOOGLE_APPLICATION_CREDENTIALS | Path to service account key file | ✗* |
1600
+ | Variable | Description | Required |
1601
+ | ------------------------------ | -------------------------------- | -------- |
1602
+ | GC_PROJECT | Google Cloud Project ID | ✗* |
1603
+ | GOOGLE_APPLICATION_CREDENTIALS | Path to service account key file | ✗* |
1562
1604
 
1563
1605
  \* Not required if you call `init()` with a config object
1564
1606
 
@@ -1570,6 +1612,7 @@ The entity throws `WebError` with appropriate status codes:
1570
1612
  - Other errors are passed through from Google Cloud Storage SDK
1571
1613
 
1572
1614
  **Example:**
1615
+
1573
1616
  ```javascript
1574
1617
  try {
1575
1618
  await GoogleCloudStorage.upload(stream, 'bucket', {}, {}, { maxSize: '1MB' });
@@ -1595,13 +1638,13 @@ new Endpoint({
1595
1638
  async handler(req, res) {
1596
1639
  const file = req.files[0];
1597
1640
  const stream = Readable.from(file.buffer);
1598
-
1641
+
1599
1642
  const result = await GoogleCloudStorage.upload(
1600
1643
  stream,
1601
1644
  process.env.UPLOAD_BUCKET,
1602
1645
  { directory: 'user-uploads', mime: file.mimetype }
1603
1646
  );
1604
-
1647
+
1605
1648
  res.json({ path: result.name, url: result.mediaLink });
1606
1649
  }
1607
1650
  });
@@ -1662,6 +1705,7 @@ Errors are a crucial part of the application, they are what helps us to properly
1662
1705
  The engine automatically adapts log output based on the environment:
1663
1706
 
1664
1707
  **Local Development (Readable Format)**
1708
+
1665
1709
  - Colorized output with severity levels
1666
1710
  - Formatted timestamps and file locations
1667
1711
  - Pretty-printed data objects
@@ -1669,6 +1713,7 @@ The engine automatically adapts log output based on the environment:
1669
1713
  - HTTP request context when available
1670
1714
 
1671
1715
  **Production/GCP (JSON Format)**
1716
+
1672
1717
  - Structured JSON for log aggregation
1673
1718
  - Google Cloud Error Reporting integration
1674
1719
  - Kubernetes pod information
@@ -1687,6 +1732,7 @@ LOG_FORMAT=json npm start
1687
1732
  ```
1688
1733
 
1689
1734
  **Example Local Output:**
1735
+
1690
1736
  ```
1691
1737
  [2025-12-10T10:30:45.123Z] ERROR src/endpoints/users.ts:42 User not found
1692
1738
  Error Code: user-not-found
@@ -1943,15 +1989,15 @@ interface User {
1943
1989
 
1944
1990
  ```javascript
1945
1991
  // Case-insensitive matching
1946
- checkPermission('ADMIN') // Matches: 'admin', 'Admin', 'ADMIN'
1947
- checkPermission(['READ', 'write']) // Matches any case variation
1992
+ checkPermission('ADMIN'); // Matches: 'admin', 'Admin', 'ADMIN'
1993
+ checkPermission(['READ', 'write']); // Matches any case variation
1948
1994
 
1949
1995
  // Namespace-style permissions
1950
- checkPermission('users:read')
1951
- checkPermission(['users:write', 'users:delete'])
1996
+ checkPermission('users:read');
1997
+ checkPermission(['users:write', 'users:delete']);
1952
1998
 
1953
1999
  // Role-based permissions
1954
- checkPermission(['admin', 'moderator'])
2000
+ checkPermission(['admin', 'moderator']);
1955
2001
  ```
1956
2002
 
1957
2003
  #### Response Codes
@@ -1975,8 +2021,10 @@ checkPermission(['admin', 'moderator'])
1975
2021
  "message": "Permission denied"
1976
2022
  }
1977
2023
  ```
2024
+
1978
2025
  };
1979
- ```
2026
+
2027
+ ````
1980
2028
 
1981
2029
  ---
1982
2030
 
@@ -2006,7 +2054,7 @@ new Endpoint({
2006
2054
  res.json({ ok: true });
2007
2055
  }
2008
2056
  });
2009
- ```
2057
+ ````
2010
2058
 
2011
2059
  #### Defaults
2012
2060
 
@@ -2147,7 +2195,10 @@ The function returns an object with the following status options:
2147
2195
  Generate and verify a short-lived verification token + OTP for sensitive operations.
2148
2196
 
2149
2197
  ```javascript
2150
- import { createVerificationToken, verifyVerificationToken } from 'node-server-engine';
2198
+ import {
2199
+ createVerificationToken,
2200
+ verifyVerificationToken
2201
+ } from 'node-server-engine';
2151
2202
 
2152
2203
  // Generate token + OTP
2153
2204
  const { token, otp, expiresAt } = createVerificationToken({
@@ -2435,17 +2486,17 @@ npm run coverage
2435
2486
 
2436
2487
  ### Scripts
2437
2488
 
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 |
2489
+ | Script | Description |
2490
+ | ---------------------- | -------------------------------------------------- |
2491
+ | `npm run build` | Compile TypeScript and generate distribution files |
2492
+ | `npm run lint` | Check code for linting errors |
2493
+ | `npm run lint:fix` | Automatically fix linting errors |
2494
+ | `npm run format` | Format code with Prettier |
2495
+ | `npm run format:check` | Check code formatting without modifying files |
2496
+ | `npm test` | Run all tests with coverage |
2497
+ | `npm run test:file` | Run a specific test file |
2498
+ | `npm run coverage` | Generate HTML coverage report |
2499
+ | `npm run coverage:ci` | Generate coverage summary for CI |
2449
2500
 
2450
2501
  ### Commit Convention
2451
2502
 
@@ -2454,6 +2505,7 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/) f
2454
2505
  **Format:** `type(scope): subject`
2455
2506
 
2456
2507
  **Types:**
2508
+
2457
2509
  - `feat`: New feature (triggers minor version bump)
2458
2510
  - `fix`: Bug fix (triggers patch version bump)
2459
2511
  - `docs`: Documentation changes
@@ -2465,6 +2517,7 @@ This project uses [Conventional Commits](https://www.conventionalcommits.org/) f
2465
2517
  - `ci`: CI/CD changes
2466
2518
 
2467
2519
  **Examples:**
2520
+
2468
2521
  ```bash
2469
2522
  git commit -m "feat(endpoints): add support for GraphQL endpoints"
2470
2523
  git commit -m "fix(auth): resolve JWT token validation issue"
@@ -2472,6 +2525,7 @@ git commit -m "docs(readme): update installation instructions"
2472
2525
  ```
2473
2526
 
2474
2527
  **Breaking Changes:** Add `BREAKING CHANGE:` in the commit body to trigger a major version bump:
2528
+
2475
2529
  ```bash
2476
2530
  git commit -m "feat(api): redesign authentication flow" -m "BREAKING CHANGE: AuthType enum values changed"
2477
2531
  ```
@@ -2481,6 +2535,7 @@ git commit -m "feat(api): redesign authentication flow" -m "BREAKING CHANGE: Aut
2481
2535
  This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated version management and package publishing.
2482
2536
 
2483
2537
  **How it works:**
2538
+
2484
2539
  1. Commits are analyzed based on conventional commit format
2485
2540
  2. Version number is automatically determined (major.minor.patch)
2486
2541
  3. Changelog is generated from commit messages
@@ -2489,6 +2544,7 @@ This project uses [semantic-release](https://github.com/semantic-release/semanti
2489
2544
  6. GitHub release is created
2490
2545
 
2491
2546
  **When releases happen:**
2547
+
2492
2548
  - Automatically on every push to `main` branch
2493
2549
  - Only if there are release-worthy commits (feat, fix, perf, BREAKING CHANGE)
2494
2550
  - Releases are skipped for commits with `[skip ci]` in the message