node-server-engine 1.10.12 → 1.10.13

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
@@ -852,7 +852,9 @@ It can be configured through environment variables
852
852
 
853
853
  ### Pub/Sub
854
854
 
855
- The engine exposes a `PubSub` entity that can be used to communicate with Google Cloud Pub/Sub.
855
+ The engine exposes a `PubSub` entity that can be used to communicate with Google Cloud Pub/Sub with production-ready configuration including flow control, retry policies, and batch processing.
856
+
857
+ Based on [@google-cloud/pubsub](https://github.com/googleapis/nodejs-pubsub) v4.11.0.
856
858
 
857
859
  ```javascript
858
860
  import { PubSub } from 'node-server-engine';
@@ -861,32 +863,55 @@ import { PubSub } from 'node-server-engine';
861
863
  * Declare that the service will be publishing to a topic
862
864
  * This must be done before init() is called
863
865
  * Any attempt to publish a message without declaring a topic first will fail
864
- * @property {string|Array.<string>} topic - The topic(s) to which we will be publishing
866
+ * @param {string|Array<string>} topic - The topic(s) to which we will be publishing
867
+ * @param {Object} [options] - Publisher configuration options
865
868
  */
866
- PubSub.addPublisher(topic);
869
+ PubSub.addPublisher(topic, {
870
+ enableMessageOrdering: false, // Enable ordering (requires orderingKey when publishing)
871
+ batching: {
872
+ maxMessages: 100, // Max messages per batch
873
+ maxBytes: 1024 * 1024, // Max bytes per batch (1MB)
874
+ maxMilliseconds: 100 // Max delay before sending batch
875
+ },
876
+ retry: {
877
+ initialDelayMillis: 100, // Initial retry delay
878
+ maxDelayMillis: 60000, // Max retry delay (60s)
879
+ delayMultiplier: 1.3 // Exponential backoff multiplier
880
+ }
881
+ });
867
882
 
868
883
  /**
869
- * Binds a message handle to a subscription
884
+ * Binds a message handler to a subscription
870
885
  * If called multiple times, handlers will be chained
871
886
  * This must be done before init() is called
872
887
  * The subscription will not be consumed until init() is called
873
- * @property {string} subscription - The subscription to consume
874
- * @property {function|Array.<function>} handler - The message handling function(s)
875
- * @property {boolean} [first] - Puts the handler(s) at the beginning of the handling chain (default: false)
888
+ * @param {string} subscription - The subscription to consume
889
+ * @param {function|Array<function>} handler - The message handling function(s)
890
+ * @param {Object} [options] - Subscriber configuration options
876
891
  */
877
- PubSub.addSubscriber(subscription, handler, first);
892
+ PubSub.addSubscriber(subscription, handler, {
893
+ first: false, // Put handler at beginning of chain
894
+ isDebezium: false, // Handle Debezium CDC events
895
+ ackDeadline: 60, // Acknowledgement deadline (10-600 seconds)
896
+ flowControl: {
897
+ maxMessages: 1000, // Max concurrent messages
898
+ maxBytes: 100 * 1024 * 1024, // Max concurrent bytes (100MB)
899
+ allowExcessMessages: true // Allow excess messages if under maxBytes
900
+ }
901
+ });
878
902
 
879
903
  /**
880
904
  * Establish connection with all the declared publishers/subscribers
905
+ * Validates topic/subscription existence and permissions
881
906
  */
882
907
  await PubSub.init();
883
908
 
884
909
  /**
885
910
  * Send a message through a previously declared publisher
886
- * @property {string} topic - The name of the topic to which the message should be pushed
887
- * @property {Object} message - The actual message (will be JSON stringified)
888
- * @property {Object} [attributes] - Message attributes
889
- * @property {string} [orderingKey] - Ordering key
911
+ * @param {string} topic - The name of the topic to which the message should be pushed
912
+ * @param {Object} message - The actual message (will be JSON stringified)
913
+ * @param {Object} [attributes] - Message attributes for filtering
914
+ * @param {string} [orderingKey] - Enforce ordering for messages with same key
890
915
  */
891
916
  await PubSub.publish(topic, message, attributes, orderingKey);
892
917
 
@@ -896,28 +921,102 @@ await PubSub.publish(topic, message, attributes, orderingKey);
896
921
  await PubSub.shutdown();
897
922
  ```
898
923
 
924
+ #### Message Handling
925
+
926
+ Messages are acknowledged after successful processing. If any handler throws an error, the message is nacked and will be redelivered according to the subscription's retry policy.
927
+
928
+ ```javascript
929
+ // Handler signature
930
+ async function messageHandler(payload, attributes, publishedAt) {
931
+ // payload: The JSON message content
932
+ // attributes: Message attributes (key-value pairs)
933
+ // publishedAt: Date when message was published
934
+
935
+ // Process the message
936
+ await processData(payload);
937
+
938
+ // Message will be ack'd automatically after successful processing
939
+ // If an error is thrown, message will be nack'd for redelivery
940
+ }
941
+ ```
942
+
943
+ #### Best Practices
944
+
945
+ 1. **Flow Control**: Configure `flowControl` based on your service's memory and processing capacity
946
+ 2. **Batch Settings**: Tune batching for optimal throughput vs. latency tradeoff
947
+ 3. **Retry Policy**: Use exponential backoff to handle transient failures gracefully
948
+ 4. **Dead Letter Topics**: Configure dead letter topics on your subscriptions in GCP Console for failed messages
949
+ 5. **Exactly-Once Delivery**: Enable exactly-once delivery in GCP Console when creating/updating your subscription (requires ackWithResponse() in handlers)
950
+ 6. **Message Ordering**: Only enable when strict ordering is required (reduces throughput)
951
+ 7. **ACK Deadline**: Set based on your handler's processing time (default: 60s)
952
+
953
+ #### Environment Variables
954
+
955
+ Pub/Sub authentication uses [Google Cloud Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials). Set `GOOGLE_APPLICATION_CREDENTIALS` to your service account key path, or use Workload Identity in GKE.
956
+
899
957
  ---
900
958
 
901
959
  ### PushNotification
902
960
 
903
- Communication interface with the push service.
961
+ Communication interface with the push notification service using Pub/Sub.
904
962
 
905
963
  ```javascript
906
964
  import { PushNotification } from 'node-server-engine';
907
965
 
908
- // The entity needs to initialize it's pub/sub connections
909
- // Handlers must be declared before this function is called
966
+ // Initialize with optimized settings for push notifications
967
+ // Must be called before sending notifications
910
968
  await PushNotification.init();
911
969
 
912
970
  /**
913
971
  * Send a push notification through the push service
914
- * @param {String} userId - ID of the user that should receive the notification
972
+ * @param {string} userId - ID of the user that should receive the notification
915
973
  * @param {Object} notification - Notification that should be sent
916
- * @return {void}
974
+ * @throws {EngineError} If userId is missing or publish fails
917
975
  */
918
- await PushNotification.sendPush(userId, notification);
976
+ await PushNotification.sendPush(userId, {
977
+ title: 'Hello!',
978
+ body: 'You have a new message',
979
+ payload: { type: 'message', messageId: '123' },
980
+ priority: true,
981
+ ttl: 3600 // Time to live in seconds
982
+ });
983
+ ```
984
+
985
+ #### Notification Options
986
+
987
+ ```typescript
988
+ {
989
+ title?: string; // Notification title
990
+ body?: string; // Notification body text
991
+ payload?: unknown; // Custom data payload
992
+ voip?: boolean; // VOIP notification (iOS)
993
+ background?: boolean; // Background/data-only notification
994
+ token?: string; // Specific device token (optional)
995
+ mutable?: boolean; // Content can be mutated by client (iOS)
996
+ contentAvailable?: boolean; // Requires client-side processing (iOS)
997
+ ttl?: number; // Time to live in seconds
998
+ priority?: boolean; // High priority notification
999
+ collapseId?: string; // Group notifications with same ID
1000
+ }
919
1001
  ```
920
1002
 
1003
+ #### Environment Variables
1004
+
1005
+ | Variable | Description | Required |
1006
+ | -------- | ----------- | -------- |
1007
+ | `PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC` | Pub/Sub topic name for push notifications | ✓ |
1008
+
1009
+ #### Configuration
1010
+
1011
+ The PushNotification entity is pre-configured with optimal settings:
1012
+ - **Batching**: Up to 100 messages, 1MB max, 50ms delay for fast delivery
1013
+ - **Retry**: Exponential backoff with 100ms initial delay, up to 60s max delay
1014
+ - **No Ordering**: Push notifications don't require strict ordering for better throughput
1015
+
1016
+ #### Integration
1017
+
1018
+ Your `omg-notification-service` (or similar) should subscribe to the configured topic to process and deliver push notifications to devices via FCM, APNs, or other push providers.
1019
+
921
1020
  ---
922
1021
 
923
1022
  ### Localizator
@@ -1,5 +1,5 @@
1
1
  import { Attributes } from '@google-cloud/pubsub';
2
- import { PubSubAddSubscriberOptions, PubSubMessageHandler } from './PubSub.types';
2
+ import { PubSubAddSubscriberOptions, PubSubMessageHandler, PubSubPublisherOptions } from './PubSub.types';
3
3
  /**
4
4
  * This is a singleton service who's role is to manage pub/sub connections
5
5
  * It is the preferred way to interact with Pub/Sub as it manages the underlying subscription and handler objects
@@ -16,8 +16,10 @@ export declare const PubSub: {
16
16
  shutdown(): Promise<void>;
17
17
  /**
18
18
  * Creates a publisher if it does not exist yet
19
+ * @param topic - Topic name or array of topic names
20
+ * @param options - Publisher configuration options
19
21
  */
20
- addPublisher(topic: string | Array<string>): void;
22
+ addPublisher(topic: string | Array<string>, options?: PubSubPublisherOptions): void;
21
23
  /**
22
24
  * Add message handlers to a subscription
23
25
  * @param subscription - Name of the Pub/Sub subscription
@@ -93,18 +93,43 @@ const PubSub = exports.PubSub = {
93
93
  },
94
94
  /**
95
95
  * Creates a publisher if it does not exist yet
96
+ * @param topic - Topic name or array of topic names
97
+ * @param options - Publisher configuration options
96
98
  */
97
- addPublisher(topic) {
99
+ addPublisher(topic, options) {
98
100
  const topicArray = topic instanceof Array ? topic : [topic];
99
101
  (0, _utils.reportDebug)({
100
102
  namespace,
101
- message: `Registering publisher for topics '${topicArray.join("', '")}'`
103
+ message: `Registering publisher for topics '${topicArray.join("', '")}'`,
104
+ data: options
102
105
  });
103
106
  // Register a new topic if it does not exist yet
104
107
  for (const topic of topicArray) {
105
108
  if (!Object.keys(topics).includes(topic)) {
106
109
  topics[topic] = pubSubClient.topic(topic, {
107
- enableOpenTelemetryTracing: true
110
+ enableOpenTelemetryTracing: true,
111
+ messageOrdering: options?.enableMessageOrdering ?? false,
112
+ batching: options?.batching ? {
113
+ maxMessages: options.batching.maxMessages ?? 100,
114
+ maxBytes: options.batching.maxBytes ?? 1024 * 1024,
115
+ // 1MB
116
+ maxMilliseconds: options.batching.maxMilliseconds ?? 100
117
+ } : undefined,
118
+ gaxOpts: options?.retry ? {
119
+ retry: {
120
+ retryCodes: [10, 14],
121
+ // ABORTED, UNAVAILABLE
122
+ backoffSettings: {
123
+ initialRetryDelayMillis: options.retry.initialDelayMillis ?? 100,
124
+ maxRetryDelayMillis: options.retry.maxDelayMillis ?? 60000,
125
+ retryDelayMultiplier: options.retry.delayMultiplier ?? 1.3,
126
+ initialRpcTimeoutMillis: 60000,
127
+ maxRpcTimeoutMillis: 600000,
128
+ rpcTimeoutMultiplier: 1.0,
129
+ totalTimeoutMillis: 600000
130
+ }
131
+ }
132
+ } : undefined
108
133
  });
109
134
  }
110
135
  }
@@ -119,19 +144,33 @@ const PubSub = exports.PubSub = {
119
144
  const handlers = handler instanceof Array ? handler : [handler];
120
145
  const isDebezium = options?.isDebezium ?? false;
121
146
  const first = options?.first ?? false;
147
+ const flowControl = options?.flowControl;
148
+ const ackDeadline = options?.ackDeadline;
122
149
  (0, _utils.reportDebug)({
123
150
  namespace,
124
151
  message: `Registering subscriber for subscription '${subscription}'`,
125
152
  data: {
126
153
  first,
127
154
  handlersCount: handlers.length,
128
- isDebezium
155
+ isDebezium,
156
+ flowControl
129
157
  }
130
158
  });
131
159
  if (!Object.keys(subscriptions).includes(subscription)) {
132
160
  subscriptions[subscription] = {
133
161
  subscription: pubSubClient.subscription(subscription, {
134
- enableOpenTelemetryTracing: true
162
+ enableOpenTelemetryTracing: true,
163
+ flowControl: flowControl ? {
164
+ maxMessages: flowControl.maxMessages ?? 1000,
165
+ maxBytes: flowControl.maxBytes ?? 100 * 1024 * 1024,
166
+ // 100MB
167
+ allowExcessMessages: flowControl.allowExcessMessages ?? true
168
+ } : {
169
+ maxMessages: 1000,
170
+ maxBytes: 100 * 1024 * 1024,
171
+ allowExcessMessages: true
172
+ },
173
+ ackDeadline: ackDeadline ?? 60
135
174
  }),
136
175
  handlers: []
137
176
  };
@@ -1 +1 @@
1
- {"version":3,"file":"PubSub.js","names":["_pubsub","require","_handleMessage","_EngineError","_LifecycleController","_utils","namespace","subscriptions","topics","pubSubClient","PubsubClient","PubSub","exports","init","reportDebug","message","Promise","all","Object","values","map","topic","exists","EngineError","name","permissions","iam","testPermissions","hasPermissions","every","success","subscription","handlers","isDebezium","on","handleMessage","catch","error","reportError","LifecycleController","register","shutdown","flush","close","keys","forEach","addPublisher","topicArray","Array","join","includes","enableOpenTelemetryTracing","addSubscriber","handler","options","first","data","handlersCount","length","push","unshift","publish","attributes","orderingKey","publishMessage","json"],"sources":["../../../src/entities/PubSub/PubSub.ts"],"sourcesContent":["import {\n Attributes,\n PubSub as PubsubClient,\n Message\n} from '@google-cloud/pubsub';\nimport { handleMessage } from './handleMessage';\nimport {\n PubSubTopicsMap,\n PubSubSubscriptionsMap,\n PubSubAddSubscriberOptions,\n PubSubMessageHandler\n} from './PubSub.types';\nimport { EngineError } from 'entities/EngineError';\nimport { LifecycleController } from 'entities/LifecycleController';\nimport { reportDebug, reportError } from 'utils';\n\nconst namespace = 'engine:PubSub';\n\n/**\n * Associative containing all the subscriptions sorted by topic registered for publishing\n */\nconst subscriptions: PubSubSubscriptionsMap = {};\n\n/**\n * Associative containing all the topics registered for publishing\n */\nconst topics: PubSubTopicsMap = {};\n\nconst pubSubClient = new PubsubClient();\n\n/**\n * This is a singleton service who's role is to manage pub/sub connections\n * It is the preferred way to interact with Pub/Sub as it manages the underlying subscription and handler objects\n * It avoids potential subscription splitting on an instance\n */\nexport const PubSub = {\n /**\n * Initialize the registered publishers and subscribers\n */\n async init(): Promise<void> {\n reportDebug({ namespace, message: `Initializing Pub/Sub` });\n // Check that all topics/permissions exist, and that we have permission on them\n await Promise.all([\n // Test that all topics actually exist and that we have permissions\n ...Object.values(topics).map(async (topic) => {\n const [exists] = await topic.exists();\n if (!exists)\n throw new EngineError(`Topic ${topic.name} does not exist`);\n // Test permission ton publish to topic\n const [permissions] = await topic.iam.testPermissions([\n 'pubsub.topics.publish'\n ]);\n const hasPermissions = Object.values(permissions).every(\n (success) => success\n );\n if (!hasPermissions)\n throw new EngineError(\n `Instance does not have appropriate permissions to publish to topic ${topic.name}`\n );\n }),\n // Test that all subscriptions actually exist and that we have permissions\n ...Object.values(subscriptions).map(async ({ subscription }) => {\n const [exists] = await subscription.exists();\n if (!exists)\n throw new EngineError(\n `Subscription ${subscription.name} does not exist`\n );\n // Test permission ton consume subscription\n const [permissions] = await subscription.iam.testPermissions([\n 'pubsub.subscriptions.consume'\n ]);\n const hasPermissions = Object.values(permissions).every(\n (success) => success\n );\n if (!hasPermissions)\n throw new EngineError(\n `Instance does not have appropriate permissions to subscribe to subscription ${subscription.name}`\n );\n })\n ]);\n\n // Register handlers on the subscriptions to activate them\n for (const { subscription, handlers, isDebezium } of Object.values(\n subscriptions\n )) {\n subscription.on('message', (message) => {\n handleMessage(\n message as Message,\n subscription,\n handlers,\n isDebezium\n ).catch((error) => {\n reportError(error);\n });\n });\n }\n\n LifecycleController.register(PubSub);\n },\n\n /**\n * Shutdown the registered publishers and subscribers\n */\n async shutdown(): Promise<void> {\n reportDebug({ namespace, message: `Shutting down Pub/Sub` });\n // Flush all pending messages and shut down connections\n await Promise.all([\n ...Object.values(topics).map(async (topic) => topic.flush()),\n ...Object.values(subscriptions).map(async ({ subscription }) =>\n subscription.close()\n )\n ]);\n // Delete the stored topics/subscriptions\n Object.keys(topics).forEach((topic) => delete topics[topic]);\n Object.keys(subscriptions).forEach(\n (subscription) => delete subscriptions[subscription]\n );\n // Close the client\n await pubSubClient.close();\n },\n\n /**\n * Creates a publisher if it does not exist yet\n */\n addPublisher(topic: string | Array<string>): void {\n const topicArray = topic instanceof Array ? topic : [topic];\n reportDebug({\n namespace,\n message: `Registering publisher for topics '${topicArray.join(\"', '\")}'`\n });\n // Register a new topic if it does not exist yet\n for (const topic of topicArray) {\n if (!Object.keys(topics).includes(topic)) {\n topics[topic] = pubSubClient.topic(topic, {\n enableOpenTelemetryTracing: true\n });\n }\n }\n },\n\n /**\n * Add message handlers to a subscription\n * @param subscription - Name of the Pub/Sub subscription\n * @param handler - Function or list of functions to set as message handlers\n * @param options - Indicates that the additional options need to handle on Subscriber\n */\n addSubscriber<T>(\n subscription: string,\n handler: PubSubMessageHandler<T> | Array<PubSubMessageHandler<T>>,\n options: PubSubAddSubscriberOptions = {}\n ): void {\n const handlers = handler instanceof Array ? handler : [handler];\n const isDebezium = options?.isDebezium ?? false;\n const first = options?.first ?? false;\n\n reportDebug({\n namespace,\n message: `Registering subscriber for subscription '${subscription}'`,\n data: { first, handlersCount: handlers.length, isDebezium }\n });\n if (!Object.keys(subscriptions).includes(subscription)) {\n subscriptions[subscription] = {\n subscription: pubSubClient.subscription(subscription, {\n enableOpenTelemetryTracing: true\n }),\n handlers: []\n };\n }\n\n if (isDebezium) subscriptions[subscription].isDebezium = true;\n\n if (first) subscriptions[subscription].handlers.push(...handlers);\n else subscriptions[subscription].handlers.unshift(...handlers);\n },\n\n /**\n * Send a message to a Pub/Sub topic\n * @param topic - Name of the topic\n * @param message - Content of the message, must be an object that can be JSON stringified\n * @param attributes - Attributes for this message, can be used to filter messages on the subscription\n * @param orderingKey - Enforce order guarantee of messages sent with the same key\n */\n async publish(\n topic: string,\n message: unknown,\n attributes?: Attributes,\n orderingKey?: string\n ): Promise<void> {\n if (!topics[topic])\n throw new EngineError(`No publisher was registered for topic '${topic}'`);\n reportDebug({\n namespace,\n message: `Sending message to topic '${topic}'`,\n data: { message, attributes, orderingKey }\n });\n await topics[topic].publishMessage({\n json: message,\n attributes,\n orderingKey\n });\n }\n};\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PubSub.js","names":["_pubsub","require","_handleMessage","_EngineError","_LifecycleController","_utils","namespace","subscriptions","topics","pubSubClient","PubsubClient","PubSub","exports","init","reportDebug","message","Promise","all","Object","values","map","topic","exists","EngineError","name","permissions","iam","testPermissions","hasPermissions","every","success","subscription","handlers","isDebezium","on","handleMessage","catch","error","reportError","LifecycleController","register","shutdown","flush","close","keys","forEach","addPublisher","options","topicArray","Array","join","data","includes","enableOpenTelemetryTracing","messageOrdering","enableMessageOrdering","batching","maxMessages","maxBytes","maxMilliseconds","undefined","gaxOpts","retry","retryCodes","backoffSettings","initialRetryDelayMillis","initialDelayMillis","maxRetryDelayMillis","maxDelayMillis","retryDelayMultiplier","delayMultiplier","initialRpcTimeoutMillis","maxRpcTimeoutMillis","rpcTimeoutMultiplier","totalTimeoutMillis","addSubscriber","handler","first","flowControl","ackDeadline","handlersCount","length","allowExcessMessages","push","unshift","publish","attributes","orderingKey","publishMessage","json"],"sources":["../../../src/entities/PubSub/PubSub.ts"],"sourcesContent":["import {\n Attributes,\n PubSub as PubsubClient,\n Message\n} from '@google-cloud/pubsub';\nimport { handleMessage } from './handleMessage';\nimport {\n PubSubTopicsMap,\n PubSubSubscriptionsMap,\n PubSubAddSubscriberOptions,\n PubSubMessageHandler,\n PubSubPublisherOptions\n} from './PubSub.types';\nimport { EngineError } from 'entities/EngineError';\nimport { LifecycleController } from 'entities/LifecycleController';\nimport { reportDebug, reportError } from 'utils';\n\nconst namespace = 'engine:PubSub';\n\n/**\n * Associative containing all the subscriptions sorted by topic registered for publishing\n */\nconst subscriptions: PubSubSubscriptionsMap = {};\n\n/**\n * Associative containing all the topics registered for publishing\n */\nconst topics: PubSubTopicsMap = {};\n\nconst pubSubClient = new PubsubClient();\n\n/**\n * This is a singleton service who's role is to manage pub/sub connections\n * It is the preferred way to interact with Pub/Sub as it manages the underlying subscription and handler objects\n * It avoids potential subscription splitting on an instance\n */\nexport const PubSub = {\n /**\n * Initialize the registered publishers and subscribers\n */\n async init(): Promise<void> {\n reportDebug({ namespace, message: `Initializing Pub/Sub` });\n // Check that all topics/permissions exist, and that we have permission on them\n await Promise.all([\n // Test that all topics actually exist and that we have permissions\n ...Object.values(topics).map(async (topic) => {\n const [exists] = await topic.exists();\n if (!exists)\n throw new EngineError(`Topic ${topic.name} does not exist`);\n // Test permission ton publish to topic\n const [permissions] = await topic.iam.testPermissions([\n 'pubsub.topics.publish'\n ]);\n const hasPermissions = Object.values(permissions).every(\n (success) => success\n );\n if (!hasPermissions)\n throw new EngineError(\n `Instance does not have appropriate permissions to publish to topic ${topic.name}`\n );\n }),\n // Test that all subscriptions actually exist and that we have permissions\n ...Object.values(subscriptions).map(async ({ subscription }) => {\n const [exists] = await subscription.exists();\n if (!exists)\n throw new EngineError(\n `Subscription ${subscription.name} does not exist`\n );\n // Test permission ton consume subscription\n const [permissions] = await subscription.iam.testPermissions([\n 'pubsub.subscriptions.consume'\n ]);\n const hasPermissions = Object.values(permissions).every(\n (success) => success\n );\n if (!hasPermissions)\n throw new EngineError(\n `Instance does not have appropriate permissions to subscribe to subscription ${subscription.name}`\n );\n })\n ]);\n\n // Register handlers on the subscriptions to activate them\n for (const { subscription, handlers, isDebezium } of Object.values(\n subscriptions\n )) {\n subscription.on('message', (message) => {\n handleMessage(\n message as Message,\n subscription,\n handlers,\n isDebezium\n ).catch((error) => {\n reportError(error);\n });\n });\n }\n\n LifecycleController.register(PubSub);\n },\n\n /**\n * Shutdown the registered publishers and subscribers\n */\n async shutdown(): Promise<void> {\n reportDebug({ namespace, message: `Shutting down Pub/Sub` });\n // Flush all pending messages and shut down connections\n await Promise.all([\n ...Object.values(topics).map(async (topic) => topic.flush()),\n ...Object.values(subscriptions).map(async ({ subscription }) =>\n subscription.close()\n )\n ]);\n // Delete the stored topics/subscriptions\n Object.keys(topics).forEach((topic) => delete topics[topic]);\n Object.keys(subscriptions).forEach(\n (subscription) => delete subscriptions[subscription]\n );\n // Close the client\n await pubSubClient.close();\n },\n\n /**\n * Creates a publisher if it does not exist yet\n * @param topic - Topic name or array of topic names\n * @param options - Publisher configuration options\n */\n addPublisher(\n topic: string | Array<string>,\n options?: PubSubPublisherOptions\n ): void {\n const topicArray = topic instanceof Array ? topic : [topic];\n reportDebug({\n namespace,\n message: `Registering publisher for topics '${topicArray.join(\"', '\")}'`,\n data: options\n });\n // Register a new topic if it does not exist yet\n for (const topic of topicArray) {\n if (!Object.keys(topics).includes(topic)) {\n topics[topic] = pubSubClient.topic(topic, {\n enableOpenTelemetryTracing: true,\n messageOrdering: options?.enableMessageOrdering ?? false,\n batching: options?.batching\n ? {\n maxMessages: options.batching.maxMessages ?? 100,\n maxBytes: options.batching.maxBytes ?? 1024 * 1024, // 1MB\n maxMilliseconds: options.batching.maxMilliseconds ?? 100\n }\n : undefined,\n gaxOpts: options?.retry\n ? {\n retry: {\n retryCodes: [10, 14], // ABORTED, UNAVAILABLE\n backoffSettings: {\n initialRetryDelayMillis:\n options.retry.initialDelayMillis ?? 100,\n maxRetryDelayMillis: options.retry.maxDelayMillis ?? 60000,\n retryDelayMultiplier: options.retry.delayMultiplier ?? 1.3,\n initialRpcTimeoutMillis: 60000,\n maxRpcTimeoutMillis: 600000,\n rpcTimeoutMultiplier: 1.0,\n totalTimeoutMillis: 600000\n }\n }\n }\n : undefined\n });\n }\n }\n },\n\n /**\n * Add message handlers to a subscription\n * @param subscription - Name of the Pub/Sub subscription\n * @param handler - Function or list of functions to set as message handlers\n * @param options - Indicates that the additional options need to handle on Subscriber\n */\n addSubscriber<T>(\n subscription: string,\n handler: PubSubMessageHandler<T> | Array<PubSubMessageHandler<T>>,\n options: PubSubAddSubscriberOptions = {}\n ): void {\n const handlers = handler instanceof Array ? handler : [handler];\n const isDebezium = options?.isDebezium ?? false;\n const first = options?.first ?? false;\n const flowControl = options?.flowControl;\n const ackDeadline = options?.ackDeadline;\n\n reportDebug({\n namespace,\n message: `Registering subscriber for subscription '${subscription}'`,\n data: {\n first,\n handlersCount: handlers.length,\n isDebezium,\n flowControl\n }\n });\n if (!Object.keys(subscriptions).includes(subscription)) {\n subscriptions[subscription] = {\n subscription: pubSubClient.subscription(subscription, {\n enableOpenTelemetryTracing: true,\n flowControl: flowControl\n ? {\n maxMessages: flowControl.maxMessages ?? 1000,\n maxBytes: flowControl.maxBytes ?? 100 * 1024 * 1024, // 100MB\n allowExcessMessages: flowControl.allowExcessMessages ?? true\n }\n : {\n maxMessages: 1000,\n maxBytes: 100 * 1024 * 1024,\n allowExcessMessages: true\n },\n ackDeadline: ackDeadline ?? 60\n }),\n handlers: []\n };\n }\n\n if (isDebezium) subscriptions[subscription].isDebezium = true;\n\n if (first) subscriptions[subscription].handlers.push(...handlers);\n else subscriptions[subscription].handlers.unshift(...handlers);\n },\n\n /**\n * Send a message to a Pub/Sub topic\n * @param topic - Name of the topic\n * @param message - Content of the message, must be an object that can be JSON stringified\n * @param attributes - Attributes for this message, can be used to filter messages on the subscription\n * @param orderingKey - Enforce order guarantee of messages sent with the same key\n */\n async publish(\n topic: string,\n message: unknown,\n attributes?: Attributes,\n orderingKey?: string\n ): Promise<void> {\n if (!topics[topic])\n throw new EngineError(`No publisher was registered for topic '${topic}'`);\n reportDebug({\n namespace,\n message: `Sending message to topic '${topic}'`,\n data: { message, attributes, orderingKey }\n });\n await topics[topic].publishMessage({\n json: message,\n attributes,\n orderingKey\n });\n }\n};\n"],"mappings":"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","ignoreList":[]}
@@ -21,6 +21,40 @@ export interface PubSubAddSubscriberOptions {
21
21
  first?: boolean;
22
22
  /** Indicates that the topic is a Debezium CDC topic, additional processing is done on the message */
23
23
  isDebezium?: boolean;
24
+ /** Flow control settings for the subscriber */
25
+ flowControl?: {
26
+ /** Maximum number of messages to process concurrently */
27
+ maxMessages?: number;
28
+ /** Maximum bytes to process concurrently */
29
+ maxBytes?: number;
30
+ /** Allow more messages than maxMessages if total bytes is under maxBytes */
31
+ allowExcessMessages?: boolean;
32
+ };
33
+ /** Acknowledgement deadline in seconds (10-600) */
34
+ ackDeadline?: number;
35
+ }
36
+ /** Options for configuring a publisher */
37
+ export interface PubSubPublisherOptions {
38
+ /** Enable message ordering (requires orderingKey when publishing) */
39
+ enableMessageOrdering?: boolean;
40
+ /** Batching settings */
41
+ batching?: {
42
+ /** Maximum messages to batch */
43
+ maxMessages?: number;
44
+ /** Maximum bytes to batch */
45
+ maxBytes?: number;
46
+ /** Maximum delay in milliseconds */
47
+ maxMilliseconds?: number;
48
+ };
49
+ /** Retry settings */
50
+ retry?: {
51
+ /** Initial retry delay in milliseconds */
52
+ initialDelayMillis?: number;
53
+ /** Maximum retry delay in milliseconds */
54
+ maxDelayMillis?: number;
55
+ /** Retry delay multiplier */
56
+ delayMultiplier?: number;
57
+ };
24
58
  }
25
59
  export type PubSubMessageHandler<T> = (message: T, attributes: Attributes, publishedAt: Date) => void | Promise<void>;
26
60
  /** Debezium create event payload */
@@ -1 +1 @@
1
- {"version":3,"file":"PubSub.types.js","names":[],"sources":["../../../src/entities/PubSub/PubSub.types.ts"],"sourcesContent":["import { Topic, Subscription, Attributes } from '@google-cloud/pubsub';\nimport { DebeziumEvent } from 'const/DebeziumEvent';\n\n/** List of all active topics */\nexport interface PubSubTopicsMap {\n [topic: string]: Topic;\n}\n\n/** List of subscriptions handled for each topic */\nexport interface PubSubSubscriptionsMap {\n [topic: string]: {\n /** The Pub/Sub subscription object */\n subscription: Subscription;\n /** The message handling functions */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handlers: Array<PubSubMessageHandler<any>>;\n /** Indicates that the subscription from Debezium events */\n isDebezium?: boolean;\n };\n}\n\n/** Options to setup a subscription */\nexport interface PubSubAddSubscriberOptions {\n /** Should the handlers declared here be added to the top of the queue, and called first when an new message arrives */\n first?: boolean;\n /** Indicates that the topic is a Debezium CDC topic, additional processing is done on the message */\n isDebezium?: boolean;\n}\n\nexport type PubSubMessageHandler<T> = (\n message: T,\n attributes: Attributes,\n publishedAt: Date\n) => void | Promise<void>;\n\n/** Debezium create event payload */\ninterface PubSubDebeziumCreatePayload<T> {\n /** The instance properties before the operation */\n before: null;\n /** The instance properties after the operation */\n after: T;\n /** The operation that was executed */\n op: DebeziumEvent.Create;\n /** List of fields that have changed */\n diff: Array<string>;\n}\n\n/** Debezium update event payload */\ninterface PubSubDebeziumUpdatePayload<T> {\n /** The instance properties before the operation */\n before: T;\n /** The instance properties after the operation */\n after: T;\n /** The operation that was executed */\n op: DebeziumEvent.Update;\n /** List of fields that have changed */\n diff: Array<string>;\n}\n\n/** Debezium delete event payload */\ninterface PubSubDebeziumDeletePayload<T> {\n /** The instance properties before the operation */\n before: T;\n /** The instance properties after the operation */\n after: null;\n /** The operation that was executed */\n op: DebeziumEvent.Delete;\n /** List of fields that have changed */\n diff: Array<string>;\n}\n\n/** Payload of Debezium messages */\nexport type PubSubDebeziumPayload<T> =\n | PubSubDebeziumCreatePayload<T>\n | PubSubDebeziumUpdatePayload<T>\n | PubSubDebeziumDeletePayload<T>;\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"PubSub.types.js","names":[],"sources":["../../../src/entities/PubSub/PubSub.types.ts"],"sourcesContent":["import { Topic, Subscription, Attributes } from '@google-cloud/pubsub';\nimport { DebeziumEvent } from 'const/DebeziumEvent';\n\n/** List of all active topics */\nexport interface PubSubTopicsMap {\n [topic: string]: Topic;\n}\n\n/** List of subscriptions handled for each topic */\nexport interface PubSubSubscriptionsMap {\n [topic: string]: {\n /** The Pub/Sub subscription object */\n subscription: Subscription;\n /** The message handling functions */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n handlers: Array<PubSubMessageHandler<any>>;\n /** Indicates that the subscription from Debezium events */\n isDebezium?: boolean;\n };\n}\n\n/** Options to setup a subscription */\nexport interface PubSubAddSubscriberOptions {\n /** Should the handlers declared here be added to the top of the queue, and called first when an new message arrives */\n first?: boolean;\n /** Indicates that the topic is a Debezium CDC topic, additional processing is done on the message */\n isDebezium?: boolean;\n /** Flow control settings for the subscriber */\n flowControl?: {\n /** Maximum number of messages to process concurrently */\n maxMessages?: number;\n /** Maximum bytes to process concurrently */\n maxBytes?: number;\n /** Allow more messages than maxMessages if total bytes is under maxBytes */\n allowExcessMessages?: boolean;\n };\n /** Acknowledgement deadline in seconds (10-600) */\n ackDeadline?: number;\n}\n\n/** Options for configuring a publisher */\nexport interface PubSubPublisherOptions {\n /** Enable message ordering (requires orderingKey when publishing) */\n enableMessageOrdering?: boolean;\n /** Batching settings */\n batching?: {\n /** Maximum messages to batch */\n maxMessages?: number;\n /** Maximum bytes to batch */\n maxBytes?: number;\n /** Maximum delay in milliseconds */\n maxMilliseconds?: number;\n };\n /** Retry settings */\n retry?: {\n /** Initial retry delay in milliseconds */\n initialDelayMillis?: number;\n /** Maximum retry delay in milliseconds */\n maxDelayMillis?: number;\n /** Retry delay multiplier */\n delayMultiplier?: number;\n };\n}\n\nexport type PubSubMessageHandler<T> = (\n message: T,\n attributes: Attributes,\n publishedAt: Date\n) => void | Promise<void>;\n\n/** Debezium create event payload */\ninterface PubSubDebeziumCreatePayload<T> {\n /** The instance properties before the operation */\n before: null;\n /** The instance properties after the operation */\n after: T;\n /** The operation that was executed */\n op: DebeziumEvent.Create;\n /** List of fields that have changed */\n diff: Array<string>;\n}\n\n/** Debezium update event payload */\ninterface PubSubDebeziumUpdatePayload<T> {\n /** The instance properties before the operation */\n before: T;\n /** The instance properties after the operation */\n after: T;\n /** The operation that was executed */\n op: DebeziumEvent.Update;\n /** List of fields that have changed */\n diff: Array<string>;\n}\n\n/** Debezium delete event payload */\ninterface PubSubDebeziumDeletePayload<T> {\n /** The instance properties before the operation */\n before: T;\n /** The instance properties after the operation */\n after: null;\n /** The operation that was executed */\n op: DebeziumEvent.Delete;\n /** List of fields that have changed */\n diff: Array<string>;\n}\n\n/** Payload of Debezium messages */\nexport type PubSubDebeziumPayload<T> =\n | PubSubDebeziumCreatePayload<T>\n | PubSubDebeziumUpdatePayload<T>\n | PubSubDebeziumDeletePayload<T>;\n"],"mappings":"","ignoreList":[]}
@@ -21,7 +21,6 @@ async function handleMessage(message, subscription, handlers, isDebezium = false
21
21
  namespace,
22
22
  message: `Received message from Pub/Sub [${subscription.name}]`
23
23
  });
24
- message.ack();
25
24
  let payload;
26
25
  try {
27
26
  payload = JSON.parse(message.data.toString());
@@ -42,22 +41,41 @@ async function handleMessage(message, subscription, handlers, isDebezium = false
42
41
  }
43
42
  });
44
43
  }
45
- } catch {
46
- throw new _EngineError.EngineError({
44
+ } catch (error) {
45
+ (0, _utils.reportError)(new _EngineError.EngineError({
47
46
  severity: _const.LogSeverity.WARNING,
48
47
  message: `Pub/Sub message is not valid JSON ${subscription.name}`,
49
48
  data: {
50
49
  subscription,
51
50
  message: message.data.toString()
52
51
  }
53
- });
52
+ }));
53
+ // Acknowledge invalid messages to prevent redelivery
54
+ message.ack();
55
+ return;
54
56
  }
57
+
58
+ // Process all handlers
59
+ let hasError = false;
55
60
  for (const handler of handlers) {
56
61
  try {
57
62
  await handler(payload, message.attributes, message.publishTime);
58
63
  } catch (error) {
64
+ hasError = true;
59
65
  (0, _utils.reportError)(error);
60
66
  }
61
67
  }
68
+
69
+ // Acknowledge message only after successful processing
70
+ // If there was an error, nack the message so it can be redelivered
71
+ if (hasError) {
72
+ (0, _utils.reportDebug)({
73
+ namespace,
74
+ message: `Nacking message due to handler errors [${subscription.name}]`
75
+ });
76
+ message.nack();
77
+ } else {
78
+ message.ack();
79
+ }
62
80
  }
63
81
  //# sourceMappingURL=handleMessage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"handleMessage.js","names":["_const","require","_EngineError","_utils","namespace","handleMessage","message","subscription","handlers","isDebezium","reportDebug","name","ack","payload","JSON","parse","data","toString","before","after","diff","Object","keys","filter","key","EngineError","severity","LogSeverity","WARNING","handler","attributes","publishTime","error","reportError"],"sources":["../../../../src/entities/PubSub/handleMessage/handleMessage.ts"],"sourcesContent":["import { Subscription, Message } from '@google-cloud/pubsub';\nimport { PubSubMessageHandler, PubSubDebeziumPayload } from '../PubSub.types';\nimport { LogSeverity } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { reportDebug, reportError } from 'utils';\n\nconst namespace = 'engine:PubSub:handleMessage';\n\n/**\n * handle the pubSubClients on message event\n * @param message - Message to parse and handle\n * @param subscription - The related Pub/Sub subscription object\n * @param handlers - Message handlers\n * @param isDebezium - Indicates that the subscription from Debezium events\n */\nexport async function handleMessage(\n message: Message,\n subscription: Subscription,\n handlers: Array<PubSubMessageHandler<unknown>>,\n isDebezium = false\n): Promise<void> {\n reportDebug({\n namespace,\n message: `Received message from Pub/Sub [${subscription.name}]`\n });\n\n message.ack();\n\n let payload;\n try {\n payload = JSON.parse(message.data.toString()) as unknown;\n\n // When handling Debezium message, we calculate automatically the keys that have changed between the previous and current state\n if (isDebezium) {\n payload = payload as PubSubDebeziumPayload<Record<string, unknown>>;\n const { before, after } = payload;\n if (before && after)\n payload.diff = Object.keys(after).filter(\n (key) => after[key] !== before[key]\n );\n else if (before) payload.diff = Object.keys(before);\n else if (after) payload.diff = Object.keys(after);\n else\n throw new EngineError({\n severity: LogSeverity.WARNING,\n message: `Debezium Pub/Sub message did not include before nor after value [${subscription.name}]`,\n data: { subscription, message: message.data.toString() }\n });\n }\n } catch {\n throw new EngineError({\n severity: LogSeverity.WARNING,\n message: `Pub/Sub message is not valid JSON ${subscription.name}`,\n data: { subscription, message: message.data.toString() }\n });\n }\n\n for (const handler of handlers) {\n try {\n await handler(payload, message.attributes, message.publishTime);\n } catch (error) {\n reportError(error);\n }\n }\n}\n"],"mappings":";;;;;;AAEA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA;AAEA,MAAMG,SAAS,GAAG,6BAA6B;;AAE/C;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeC,aAAaA,CACjCC,OAAgB,EAChBC,YAA0B,EAC1BC,QAA8C,EAC9CC,UAAU,GAAG,KAAK,EACH;EACf,IAAAC,kBAAW,EAAC;IACVN,SAAS;IACTE,OAAO,EAAE,kCAAkCC,YAAY,CAACI,IAAI;EAC9D,CAAC,CAAC;EAEFL,OAAO,CAACM,GAAG,CAAC,CAAC;EAEb,IAAIC,OAAO;EACX,IAAI;IACFA,OAAO,GAAGC,IAAI,CAACC,KAAK,CAACT,OAAO,CAACU,IAAI,CAACC,QAAQ,CAAC,CAAC,CAAY;;IAExD;IACA,IAAIR,UAAU,EAAE;MACdI,OAAO,GAAGA,OAAyD;MACnE,MAAM;QAAEK,MAAM;QAAEC;MAAM,CAAC,GAAGN,OAAO;MACjC,IAAIK,MAAM,IAAIC,KAAK,EACjBN,OAAO,CAACO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACH,KAAK,CAAC,CAACI,MAAM,CACrCC,GAAG,IAAKL,KAAK,CAACK,GAAG,CAAC,KAAKN,MAAM,CAACM,GAAG,CACpC,CAAC,CAAC,KACC,IAAIN,MAAM,EAAEL,OAAO,CAACO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACJ,MAAM,CAAC,CAAC,KAC/C,IAAIC,KAAK,EAAEN,OAAO,CAACO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACH,KAAK,CAAC,CAAC,KAEhD,MAAM,IAAIM,wBAAW,CAAC;QACpBC,QAAQ,EAAEC,kBAAW,CAACC,OAAO;QAC7BtB,OAAO,EAAE,oEAAoEC,YAAY,CAACI,IAAI,GAAG;QACjGK,IAAI,EAAE;UAAET,YAAY;UAAED,OAAO,EAAEA,OAAO,CAACU,IAAI,CAACC,QAAQ,CAAC;QAAE;MACzD,CAAC,CAAC;IACN;EACF,CAAC,CAAC,MAAM;IACN,MAAM,IAAIQ,wBAAW,CAAC;MACpBC,QAAQ,EAAEC,kBAAW,CAACC,OAAO;MAC7BtB,OAAO,EAAE,qCAAqCC,YAAY,CAACI,IAAI,EAAE;MACjEK,IAAI,EAAE;QAAET,YAAY;QAAED,OAAO,EAAEA,OAAO,CAACU,IAAI,CAACC,QAAQ,CAAC;MAAE;IACzD,CAAC,CAAC;EACJ;EAEA,KAAK,MAAMY,OAAO,IAAIrB,QAAQ,EAAE;IAC9B,IAAI;MACF,MAAMqB,OAAO,CAAChB,OAAO,EAAEP,OAAO,CAACwB,UAAU,EAAExB,OAAO,CAACyB,WAAW,CAAC;IACjE,CAAC,CAAC,OAAOC,KAAK,EAAE;MACd,IAAAC,kBAAW,EAACD,KAAK,CAAC;IACpB;EACF;AACF","ignoreList":[]}
1
+ {"version":3,"file":"handleMessage.js","names":["_const","require","_EngineError","_utils","namespace","handleMessage","message","subscription","handlers","isDebezium","reportDebug","name","payload","JSON","parse","data","toString","before","after","diff","Object","keys","filter","key","EngineError","severity","LogSeverity","WARNING","error","reportError","ack","hasError","handler","attributes","publishTime","nack"],"sources":["../../../../src/entities/PubSub/handleMessage/handleMessage.ts"],"sourcesContent":["import { Subscription, Message } from '@google-cloud/pubsub';\nimport { PubSubMessageHandler, PubSubDebeziumPayload } from '../PubSub.types';\nimport { LogSeverity } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { reportDebug, reportError } from 'utils';\n\nconst namespace = 'engine:PubSub:handleMessage';\n\n/**\n * handle the pubSubClients on message event\n * @param message - Message to parse and handle\n * @param subscription - The related Pub/Sub subscription object\n * @param handlers - Message handlers\n * @param isDebezium - Indicates that the subscription from Debezium events\n */\nexport async function handleMessage(\n message: Message,\n subscription: Subscription,\n handlers: Array<PubSubMessageHandler<unknown>>,\n isDebezium = false\n): Promise<void> {\n reportDebug({\n namespace,\n message: `Received message from Pub/Sub [${subscription.name}]`\n });\n\n let payload;\n try {\n payload = JSON.parse(message.data.toString()) as unknown;\n\n // When handling Debezium message, we calculate automatically the keys that have changed between the previous and current state\n if (isDebezium) {\n payload = payload as PubSubDebeziumPayload<Record<string, unknown>>;\n const { before, after } = payload;\n if (before && after)\n payload.diff = Object.keys(after).filter(\n (key) => after[key] !== before[key]\n );\n else if (before) payload.diff = Object.keys(before);\n else if (after) payload.diff = Object.keys(after);\n else\n throw new EngineError({\n severity: LogSeverity.WARNING,\n message: `Debezium Pub/Sub message did not include before nor after value [${subscription.name}]`,\n data: { subscription, message: message.data.toString() }\n });\n }\n } catch (error) {\n reportError(\n new EngineError({\n severity: LogSeverity.WARNING,\n message: `Pub/Sub message is not valid JSON ${subscription.name}`,\n data: { subscription, message: message.data.toString() }\n })\n );\n // Acknowledge invalid messages to prevent redelivery\n message.ack();\n return;\n }\n\n // Process all handlers\n let hasError = false;\n for (const handler of handlers) {\n try {\n await handler(payload, message.attributes, message.publishTime);\n } catch (error) {\n hasError = true;\n reportError(error);\n }\n }\n\n // Acknowledge message only after successful processing\n // If there was an error, nack the message so it can be redelivered\n if (hasError) {\n reportDebug({\n namespace,\n message: `Nacking message due to handler errors [${subscription.name}]`\n });\n message.nack();\n } else {\n message.ack();\n }\n}\n"],"mappings":";;;;;;AAEA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA;AAEA,MAAMG,SAAS,GAAG,6BAA6B;;AAE/C;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeC,aAAaA,CACjCC,OAAgB,EAChBC,YAA0B,EAC1BC,QAA8C,EAC9CC,UAAU,GAAG,KAAK,EACH;EACf,IAAAC,kBAAW,EAAC;IACVN,SAAS;IACTE,OAAO,EAAE,kCAAkCC,YAAY,CAACI,IAAI;EAC9D,CAAC,CAAC;EAEF,IAAIC,OAAO;EACX,IAAI;IACFA,OAAO,GAAGC,IAAI,CAACC,KAAK,CAACR,OAAO,CAACS,IAAI,CAACC,QAAQ,CAAC,CAAC,CAAY;;IAExD;IACA,IAAIP,UAAU,EAAE;MACdG,OAAO,GAAGA,OAAyD;MACnE,MAAM;QAAEK,MAAM;QAAEC;MAAM,CAAC,GAAGN,OAAO;MACjC,IAAIK,MAAM,IAAIC,KAAK,EACjBN,OAAO,CAACO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACH,KAAK,CAAC,CAACI,MAAM,CACrCC,GAAG,IAAKL,KAAK,CAACK,GAAG,CAAC,KAAKN,MAAM,CAACM,GAAG,CACpC,CAAC,CAAC,KACC,IAAIN,MAAM,EAAEL,OAAO,CAACO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACJ,MAAM,CAAC,CAAC,KAC/C,IAAIC,KAAK,EAAEN,OAAO,CAACO,IAAI,GAAGC,MAAM,CAACC,IAAI,CAACH,KAAK,CAAC,CAAC,KAEhD,MAAM,IAAIM,wBAAW,CAAC;QACpBC,QAAQ,EAAEC,kBAAW,CAACC,OAAO;QAC7BrB,OAAO,EAAE,oEAAoEC,YAAY,CAACI,IAAI,GAAG;QACjGI,IAAI,EAAE;UAAER,YAAY;UAAED,OAAO,EAAEA,OAAO,CAACS,IAAI,CAACC,QAAQ,CAAC;QAAE;MACzD,CAAC,CAAC;IACN;EACF,CAAC,CAAC,OAAOY,KAAK,EAAE;IACd,IAAAC,kBAAW,EACT,IAAIL,wBAAW,CAAC;MACdC,QAAQ,EAAEC,kBAAW,CAACC,OAAO;MAC7BrB,OAAO,EAAE,qCAAqCC,YAAY,CAACI,IAAI,EAAE;MACjEI,IAAI,EAAE;QAAER,YAAY;QAAED,OAAO,EAAEA,OAAO,CAACS,IAAI,CAACC,QAAQ,CAAC;MAAE;IACzD,CAAC,CACH,CAAC;IACD;IACAV,OAAO,CAACwB,GAAG,CAAC,CAAC;IACb;EACF;;EAEA;EACA,IAAIC,QAAQ,GAAG,KAAK;EACpB,KAAK,MAAMC,OAAO,IAAIxB,QAAQ,EAAE;IAC9B,IAAI;MACF,MAAMwB,OAAO,CAACpB,OAAO,EAAEN,OAAO,CAAC2B,UAAU,EAAE3B,OAAO,CAAC4B,WAAW,CAAC;IACjE,CAAC,CAAC,OAAON,KAAK,EAAE;MACdG,QAAQ,GAAG,IAAI;MACf,IAAAF,kBAAW,EAACD,KAAK,CAAC;IACpB;EACF;;EAEA;EACA;EACA,IAAIG,QAAQ,EAAE;IACZ,IAAArB,kBAAW,EAAC;MACVN,SAAS;MACTE,OAAO,EAAE,0CAA0CC,YAAY,CAACI,IAAI;IACtE,CAAC,CAAC;IACFL,OAAO,CAAC6B,IAAI,CAAC,CAAC;EAChB,CAAC,MAAM;IACL7B,OAAO,CAACwB,GAAG,CAAC,CAAC;EACf;AACF","ignoreList":[]}
@@ -1,11 +1,15 @@
1
1
  import { PushNotificationNotification } from './PushNotification.types';
2
2
  export declare const PushNotification: {
3
- /** Initialize the push notification entity */
4
- init(): void;
3
+ /**
4
+ * Initialize the push notification entity
5
+ * Registers the publisher with optimal settings for push notifications
6
+ */
7
+ init(): Promise<void>;
5
8
  /**
6
9
  * Send a push notification through the push service
7
10
  * @param userId - ID of the user that should receive the notification
8
11
  * @param notification - Notification that should be sent
12
+ * @throws {EngineError} If the topic is not initialized or publish fails
9
13
  */
10
14
  sendPush(userId: string, notification: PushNotificationNotification): Promise<void>;
11
15
  };
@@ -9,8 +9,11 @@ var _PubSub = require("../PubSub");
9
9
  var _utils = require("../../utils");
10
10
  const namespace = 'engine:PushNotification';
11
11
  const PushNotification = exports.PushNotification = {
12
- /** Initialize the push notification entity */
13
- init() {
12
+ /**
13
+ * Initialize the push notification entity
14
+ * Registers the publisher with optimal settings for push notifications
15
+ */
16
+ async init() {
14
17
  (0, _utils.reportDebug)({
15
18
  namespace,
16
19
  message: 'Initializing PushNotification'
@@ -18,27 +21,60 @@ const PushNotification = exports.PushNotification = {
18
21
  if (!process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC) throw new _.EngineError({
19
22
  message: 'Environment variable PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC is not defined'
20
23
  });
21
- // Register publisher
22
- _PubSub.PubSub.addPublisher(process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC);
24
+
25
+ // Register publisher with optimized settings for push notifications
26
+ _PubSub.PubSub.addPublisher(process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC, {
27
+ enableMessageOrdering: false,
28
+ // Push notifications don't need strict ordering
29
+ batching: {
30
+ maxMessages: 100,
31
+ maxBytes: 1024 * 1024,
32
+ // 1MB
33
+ maxMilliseconds: 50 // Fast delivery for notifications
34
+ },
35
+ retry: {
36
+ initialDelayMillis: 100,
37
+ maxDelayMillis: 60000,
38
+ delayMultiplier: 1.3
39
+ }
40
+ });
23
41
  },
24
42
  /**
25
43
  * Send a push notification through the push service
26
44
  * @param userId - ID of the user that should receive the notification
27
45
  * @param notification - Notification that should be sent
46
+ * @throws {EngineError} If the topic is not initialized or publish fails
28
47
  */
29
48
  async sendPush(userId, notification) {
49
+ if (!userId) {
50
+ throw new _.EngineError({
51
+ message: 'userId is required to send push notification'
52
+ });
53
+ }
30
54
  (0, _utils.reportDebug)({
31
55
  namespace,
32
- message: `Received data to send notification to user with Id '${userId}'`,
56
+ message: `Sending push notification to user '${userId}'`,
33
57
  data: {
34
58
  userId,
35
59
  notification
36
60
  }
37
61
  });
38
- await _PubSub.PubSub.publish(process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC, {
39
- ...notification,
40
- userId
41
- });
62
+ try {
63
+ await _PubSub.PubSub.publish(process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC, {
64
+ ...notification,
65
+ userId
66
+ });
67
+ } catch (error) {
68
+ (0, _utils.reportError)(new _.EngineError({
69
+ message: `Failed to send push notification to user '${userId}'`,
70
+ data: {
71
+ userId,
72
+ notification,
73
+ error
74
+ }
75
+ }));
76
+ throw error;
77
+ }
42
78
  }
43
79
  };
44
80
  //# sourceMappingURL=PushNotification.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PushNotification.js","names":["_","require","_PubSub","_utils","namespace","PushNotification","exports","init","reportDebug","message","process","env","PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC","EngineError","PubSub","addPublisher","sendPush","userId","notification","data","publish"],"sources":["../../../src/entities/PushNotification/PushNotification.ts"],"sourcesContent":["import { PushNotificationNotification } from './PushNotification.types';\nimport { EngineError } from 'entities';\nimport { PubSub } from 'entities/PubSub';\nimport { reportDebug } from 'utils';\n\nconst namespace = 'engine:PushNotification';\n\nexport const PushNotification = {\n /** Initialize the push notification entity */\n init(): void {\n reportDebug({ namespace, message: 'Initializing PushNotification' });\n if (!process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC)\n throw new EngineError({\n message:\n 'Environment variable PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC is not defined'\n });\n // Register publisher\n PubSub.addPublisher(process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC);\n },\n\n /**\n * Send a push notification through the push service\n * @param userId - ID of the user that should receive the notification\n * @param notification - Notification that should be sent\n */\n async sendPush(\n userId: string,\n notification: PushNotificationNotification\n ): Promise<void> {\n reportDebug({\n namespace,\n message: `Received data to send notification to user with Id '${userId}'`,\n data: { userId, notification }\n });\n\n await PubSub.publish(\n process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC as string,\n {\n ...notification,\n userId\n }\n );\n }\n};\n"],"mappings":";;;;;;AACA,IAAAA,CAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA;AAEA,MAAMG,SAAS,GAAG,yBAAyB;AAEpC,MAAMC,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,GAAG;EAC9B;EACAE,IAAIA,CAAA,EAAS;IACX,IAAAC,kBAAW,EAAC;MAAEJ,SAAS;MAAEK,OAAO,EAAE;IAAgC,CAAC,CAAC;IACpE,IAAI,CAACC,OAAO,CAACC,GAAG,CAACC,oCAAoC,EACnD,MAAM,IAAIC,aAAW,CAAC;MACpBJ,OAAO,EACL;IACJ,CAAC,CAAC;IACJ;IACAK,cAAM,CAACC,YAAY,CAACL,OAAO,CAACC,GAAG,CAACC,oCAAoC,CAAC;EACvE,CAAC;EAED;AACF;AACA;AACA;AACA;EACE,MAAMI,QAAQA,CACZC,MAAc,EACdC,YAA0C,EAC3B;IACf,IAAAV,kBAAW,EAAC;MACVJ,SAAS;MACTK,OAAO,EAAE,uDAAuDQ,MAAM,GAAG;MACzEE,IAAI,EAAE;QAAEF,MAAM;QAAEC;MAAa;IAC/B,CAAC,CAAC;IAEF,MAAMJ,cAAM,CAACM,OAAO,CAClBV,OAAO,CAACC,GAAG,CAACC,oCAAoC,EAChD;MACE,GAAGM,YAAY;MACfD;IACF,CACF,CAAC;EACH;AACF,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"PushNotification.js","names":["_","require","_PubSub","_utils","namespace","PushNotification","exports","init","reportDebug","message","process","env","PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC","EngineError","PubSub","addPublisher","enableMessageOrdering","batching","maxMessages","maxBytes","maxMilliseconds","retry","initialDelayMillis","maxDelayMillis","delayMultiplier","sendPush","userId","notification","data","publish","error","reportError"],"sources":["../../../src/entities/PushNotification/PushNotification.ts"],"sourcesContent":["import { PushNotificationNotification } from './PushNotification.types';\nimport { EngineError } from 'entities';\nimport { PubSub } from 'entities/PubSub';\nimport { reportDebug, reportError } from 'utils';\n\nconst namespace = 'engine:PushNotification';\n\nexport const PushNotification = {\n /**\n * Initialize the push notification entity\n * Registers the publisher with optimal settings for push notifications\n */\n async init(): Promise<void> {\n reportDebug({ namespace, message: 'Initializing PushNotification' });\n if (!process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC)\n throw new EngineError({\n message:\n 'Environment variable PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC is not defined'\n });\n \n // Register publisher with optimized settings for push notifications\n PubSub.addPublisher(process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC, {\n enableMessageOrdering: false, // Push notifications don't need strict ordering\n batching: {\n maxMessages: 100,\n maxBytes: 1024 * 1024, // 1MB\n maxMilliseconds: 50 // Fast delivery for notifications\n },\n retry: {\n initialDelayMillis: 100,\n maxDelayMillis: 60000,\n delayMultiplier: 1.3\n }\n });\n },\n\n /**\n * Send a push notification through the push service\n * @param userId - ID of the user that should receive the notification\n * @param notification - Notification that should be sent\n * @throws {EngineError} If the topic is not initialized or publish fails\n */\n async sendPush(\n userId: string,\n notification: PushNotificationNotification\n ): Promise<void> {\n if (!userId) {\n throw new EngineError({\n message: 'userId is required to send push notification'\n });\n }\n\n reportDebug({\n namespace,\n message: `Sending push notification to user '${userId}'`,\n data: { userId, notification }\n });\n\n try {\n await PubSub.publish(\n process.env.PUBSUB_PUSH_NOTIFICATION_QUEUE_TOPIC as string,\n {\n ...notification,\n userId\n }\n );\n } catch (error) {\n reportError(\n new EngineError({\n message: `Failed to send push notification to user '${userId}'`,\n data: { userId, notification, error }\n })\n );\n throw error;\n }\n }\n};\n"],"mappings":";;;;;;AACA,IAAAA,CAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AACA,IAAAE,MAAA,GAAAF,OAAA;AAEA,MAAMG,SAAS,GAAG,yBAAyB;AAEpC,MAAMC,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,GAAG;EAC9B;AACF;AACA;AACA;EACE,MAAME,IAAIA,CAAA,EAAkB;IAC1B,IAAAC,kBAAW,EAAC;MAAEJ,SAAS;MAAEK,OAAO,EAAE;IAAgC,CAAC,CAAC;IACpE,IAAI,CAACC,OAAO,CAACC,GAAG,CAACC,oCAAoC,EACnD,MAAM,IAAIC,aAAW,CAAC;MACpBJ,OAAO,EACL;IACJ,CAAC,CAAC;;IAEJ;IACAK,cAAM,CAACC,YAAY,CAACL,OAAO,CAACC,GAAG,CAACC,oCAAoC,EAAE;MACpEI,qBAAqB,EAAE,KAAK;MAAE;MAC9BC,QAAQ,EAAE;QACRC,WAAW,EAAE,GAAG;QAChBC,QAAQ,EAAE,IAAI,GAAG,IAAI;QAAE;QACvBC,eAAe,EAAE,EAAE,CAAC;MACtB,CAAC;MACDC,KAAK,EAAE;QACLC,kBAAkB,EAAE,GAAG;QACvBC,cAAc,EAAE,KAAK;QACrBC,eAAe,EAAE;MACnB;IACF,CAAC,CAAC;EACJ,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;EACE,MAAMC,QAAQA,CACZC,MAAc,EACdC,YAA0C,EAC3B;IACf,IAAI,CAACD,MAAM,EAAE;MACX,MAAM,IAAIb,aAAW,CAAC;QACpBJ,OAAO,EAAE;MACX,CAAC,CAAC;IACJ;IAEA,IAAAD,kBAAW,EAAC;MACVJ,SAAS;MACTK,OAAO,EAAE,sCAAsCiB,MAAM,GAAG;MACxDE,IAAI,EAAE;QAAEF,MAAM;QAAEC;MAAa;IAC/B,CAAC,CAAC;IAEF,IAAI;MACF,MAAMb,cAAM,CAACe,OAAO,CAClBnB,OAAO,CAACC,GAAG,CAACC,oCAAoC,EAChD;QACE,GAAGe,YAAY;QACfD;MACF,CACF,CAAC;IACH,CAAC,CAAC,OAAOI,KAAK,EAAE;MACd,IAAAC,kBAAW,EACT,IAAIlB,aAAW,CAAC;QACdJ,OAAO,EAAE,6CAA6CiB,MAAM,GAAG;QAC/DE,IAAI,EAAE;UAAEF,MAAM;UAAEC,YAAY;UAAEG;QAAM;MACtC,CAAC,CACH,CAAC;MACD,MAAMA,KAAK;IACb;EACF;AACF,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-server-engine",
3
- "version": "1.10.12",
3
+ "version": "1.10.13",
4
4
  "description": "Framework used to develop backend services. This package ships with a lot of features to standardize the creation of services, letting you focus on the business logic.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {