@haathie/pgmb 0.2.0 → 0.2.1

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.
@@ -124,6 +124,19 @@ await pgmb.registerReliableHandler(
124
124
  retryOpts: {
125
125
  // will retry after 1 minute, then after 5 minutes
126
126
  retriesS: [60, 5 * 60]
127
+ },
128
+ // optionally provide a splitBy function to split
129
+ // event to be processed differently based on some attribute.
130
+ splitBy(ev) {
131
+ return Object.values(
132
+ // group events by their topic
133
+ ev.items.reduce((acc, item) => {
134
+ const key = item.topic
135
+ acc[key] ||= { items: [] }
136
+ acc[key].items.push(item)
137
+ return acc
138
+ }, {})
139
+ )
127
140
  }
128
141
  },
129
142
  async({ items }, { logger }) => {
package/lib/client.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { type Logger } from 'pino';
2
2
  import { PGMBEventBatcher } from './batcher.ts';
3
3
  import type { PgClientLike } from './query-types.ts';
4
- import type { GetWebhookInfoFn, IEphemeralListener, IEventData, IEventHandler, IReadEvent, Pgmb2ClientOpts, registerReliableHandlerParams, RegisterSubscriptionParams } from './types.ts';
4
+ import type { GetWebhookInfoFn, IEphemeralListener, IEventData, IEventHandler, IReadEvent, IReadNextEventsFn, ISplitFn, Pgmb2ClientOpts, registerReliableHandlerParams, RegisterSubscriptionParams } from './types.ts';
5
5
  type IReliableListener<T extends IEventData> = {
6
6
  type: 'reliable';
7
7
  handler: IEventHandler<T>;
8
8
  removeOnEmpty?: boolean;
9
9
  extra?: unknown;
10
+ splitBy?: ISplitFn<T>;
10
11
  queue: {
11
12
  item: IReadEvent<T>;
12
13
  checkpoint: Checkpoint;
@@ -37,12 +38,13 @@ export declare class PgmbClient<T extends IEventData = IEventData> extends PGMBE
37
38
  readonly subscriptionMaintenanceMs: number;
38
39
  readonly tableMaintenanceMs: number;
39
40
  readonly maxActiveCheckpoints: number;
41
+ readonly readNextEvents: IReadNextEventsFn;
40
42
  readonly getWebhookInfo: GetWebhookInfoFn;
41
- readonly webhookHandler: IEventHandler;
43
+ readonly webhookHandler: IEventHandler<T>;
42
44
  readonly listeners: {
43
45
  [subId: string]: IListenerStore<T>;
44
46
  };
45
- constructor({ client, groupId, logger, sleepDurationMs, readChunkSize, maxActiveCheckpoints, poll, subscriptionMaintenanceMs, webhookHandlerOpts, getWebhookInfo, tableMaintainanceMs, ...batcherOpts }: Pgmb2ClientOpts);
47
+ constructor({ client, groupId, logger, sleepDurationMs, readChunkSize, maxActiveCheckpoints, poll, subscriptionMaintenanceMs, webhookHandlerOpts: { splitBy: whSplitBy, ...whHandlerOpts }, getWebhookInfo, tableMaintainanceMs, readNextEvents, ...batcherOpts }: Pgmb2ClientOpts<T>);
46
48
  init(): Promise<void>;
47
49
  end(): Promise<void>;
48
50
  publish(events: T[], client?: PgClientLike): Promise<import("./queries.ts").IWriteEventsResult[]>;
@@ -63,7 +65,7 @@ export declare class PgmbClient<T extends IEventData = IEventData> extends PGMBE
63
65
  * to retry failed events by the handler itself, allowing for delayed retries
64
66
  * with backoff, and without disrupting the overall event flow.
65
67
  */
66
- registerReliableHandler({ retryOpts, name, ...opts }: registerReliableHandlerParams, handler: IEventHandler<T>): Promise<{
68
+ registerReliableHandler({ retryOpts, name, splitBy, ...opts }: registerReliableHandlerParams<T>, handler: IEventHandler<T>): Promise<{
67
69
  subscriptionId: string;
68
70
  cancel: () => void;
69
71
  }>;
package/lib/client.js CHANGED
@@ -21,9 +21,11 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
21
21
  subscriptionMaintenanceMs;
22
22
  tableMaintenanceMs;
23
23
  maxActiveCheckpoints;
24
+ readNextEvents;
24
25
  getWebhookInfo;
25
26
  webhookHandler;
26
27
  listeners = {};
28
+ #webhookHandlerOpts;
27
29
  #readClient;
28
30
  #endAc = new AbortController();
29
31
  #shouldPoll;
@@ -33,11 +35,11 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
33
35
  #tableMaintainTask;
34
36
  #inMemoryCursor = null;
35
37
  #activeCheckpoints = [];
36
- constructor({ client, groupId, logger = (0, pino_1.pino)(), sleepDurationMs = 750, readChunkSize = 1000, maxActiveCheckpoints = 10, poll, subscriptionMaintenanceMs = 60 * 1000, webhookHandlerOpts = {}, getWebhookInfo = () => ({}), tableMaintainanceMs = 5 * 60 * 1000, ...batcherOpts }) {
38
+ constructor({ client, groupId, logger = (0, pino_1.pino)(), sleepDurationMs = 750, readChunkSize = 1000, maxActiveCheckpoints = 10, poll, subscriptionMaintenanceMs = 60 * 1000, webhookHandlerOpts: { splitBy: whSplitBy, ...whHandlerOpts } = {}, getWebhookInfo = () => ({}), tableMaintainanceMs = 5 * 60 * 1000, readNextEvents = queries_ts_1.readNextEvents.run.bind(queries_ts_1.readNextEvents), ...batcherOpts }) {
37
39
  super({
38
40
  ...batcherOpts,
39
41
  logger,
40
- publish: (...e) => this.publish(e)
42
+ publish: (...e) => this.publish(e),
41
43
  });
42
44
  this.client = client;
43
45
  this.logger = logger;
@@ -47,9 +49,11 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
47
49
  this.#shouldPoll = !!poll;
48
50
  this.subscriptionMaintenanceMs = subscriptionMaintenanceMs;
49
51
  this.maxActiveCheckpoints = maxActiveCheckpoints;
50
- this.webhookHandler = (0, webhook_handler_ts_1.createWebhookHandler)(webhookHandlerOpts);
52
+ this.webhookHandler = (0, webhook_handler_ts_1.createWebhookHandler)(whHandlerOpts);
53
+ this.#webhookHandlerOpts = { splitBy: whSplitBy };
51
54
  this.getWebhookInfo = getWebhookInfo;
52
55
  this.tableMaintenanceMs = tableMaintainanceMs;
56
+ this.readNextEvents = readNextEvents;
53
57
  }
54
58
  async init() {
55
59
  this.#endAc = new AbortController();
@@ -62,11 +66,9 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
62
66
  await queries_ts_1.assertGroup.run({ id: this.groupId }, this.client);
63
67
  this.logger.debug({ groupId: this.groupId }, 'asserted group exists');
64
68
  // clean up expired subscriptions on start
65
- const [{ deleted }] = await queries_ts_1.removeExpiredSubscriptions
66
- .run({ groupId: this.groupId, activeIds: [] }, this.client);
69
+ const [{ deleted }] = await queries_ts_1.removeExpiredSubscriptions.run({ groupId: this.groupId, activeIds: [] }, this.client);
67
70
  this.logger.debug({ deleted }, 'removed expired subscriptions');
68
- this.#readTask
69
- = this.#startLoop(this.readChanges.bind(this), this.sleepDurationMs);
71
+ this.#readTask = this.#startLoop(this.readChanges.bind(this), this.sleepDurationMs);
70
72
  if (this.#shouldPoll) {
71
73
  this.#pollTask = this.#startLoop(queries_ts_1.pollForEvents.run.bind(queries_ts_1.pollForEvents, undefined, this.client), this.sleepDurationMs);
72
74
  }
@@ -74,8 +76,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
74
76
  this.#subMaintainTask = this.#startLoop(this.#maintainSubscriptions, this.subscriptionMaintenanceMs);
75
77
  }
76
78
  if (this.tableMaintenanceMs) {
77
- this.#tableMaintainTask = this.#startLoop(queries_ts_1.maintainEventsTable.run
78
- .bind(queries_ts_1.maintainEventsTable, undefined, this.client), this.tableMaintenanceMs);
79
+ this.#tableMaintainTask = this.#startLoop(queries_ts_1.maintainEventsTable.run.bind(queries_ts_1.maintainEventsTable, undefined, this.client), this.tableMaintenanceMs);
79
80
  }
80
81
  }
81
82
  async end() {
@@ -91,7 +92,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
91
92
  this.#readTask,
92
93
  this.#pollTask,
93
94
  this.#subMaintainTask,
94
- this.#tableMaintainTask
95
+ this.#tableMaintainTask,
95
96
  ]);
96
97
  await this.#unlockAndReleaseReadClient();
97
98
  this.#readTask = undefined;
@@ -101,14 +102,13 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
101
102
  }
102
103
  publish(events, client = this.client) {
103
104
  return queries_ts_1.writeEvents.run({
104
- topics: events.map(e => e.topic),
105
- payloads: events.map(e => e.payload),
106
- metadatas: events.map(e => e.metadata || null),
105
+ topics: events.map((e) => e.topic),
106
+ payloads: events.map((e) => e.payload),
107
+ metadatas: events.map((e) => e.metadata || null),
107
108
  }, client);
108
109
  }
109
110
  async assertSubscription(opts, client = this.client) {
110
- const [rslt] = await queries_ts_1.assertSubscription
111
- .run({ ...opts, groupId: this.groupId }, client);
111
+ const [rslt] = await queries_ts_1.assertSubscription.run({ ...opts, groupId: this.groupId }, client);
112
112
  this.logger.debug({ ...opts, ...rslt }, 'asserted subscription');
113
113
  return rslt;
114
114
  }
@@ -131,18 +131,23 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
131
131
  * to retry failed events by the handler itself, allowing for delayed retries
132
132
  * with backoff, and without disrupting the overall event flow.
133
133
  */
134
- async registerReliableHandler({ retryOpts, name = createListenerId(), ...opts }, handler) {
134
+ async registerReliableHandler({ retryOpts, name = createListenerId(), splitBy, ...opts }, handler) {
135
135
  const { id: subId } = await this.assertSubscription(opts);
136
136
  if (retryOpts) {
137
137
  handler = (0, retry_handler_ts_1.createRetryHandler)(retryOpts, handler);
138
138
  }
139
139
  const lts = (this.listeners[subId] ||= { values: {} });
140
- (0, assert_1.default)(!lts.values[name], `Handler with id ${name} already registered for subscription ${subId}.`
141
- + ' Cancel the existing one or use a different id.');
142
- this.listeners[subId].values[name] = { type: 'reliable', handler, queue: [] };
140
+ (0, assert_1.default)(!lts.values[name], `Handler with id ${name} already registered for subscription ${subId}.` +
141
+ ' Cancel the existing one or use a different id.');
142
+ this.listeners[subId].values[name] = {
143
+ type: 'reliable',
144
+ handler,
145
+ splitBy,
146
+ queue: [],
147
+ };
143
148
  return {
144
149
  subscriptionId: subId,
145
- cancel: () => this.#removeListener(subId, name)
150
+ cancel: () => this.#removeListener(subId, name),
146
151
  };
147
152
  }
148
153
  async removeSubscription(subId) {
@@ -153,8 +158,8 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
153
158
  if (!existingSubs) {
154
159
  return;
155
160
  }
156
- await Promise.allSettled(Object.values(existingSubs).map(e => (e.type === 'fire-and-forget'
157
- && e.stream.throw(new Error('subscription removed')))));
161
+ await Promise.allSettled(Object.values(existingSubs).map((e) => e.type === 'fire-and-forget' &&
162
+ e.stream.throw(new Error('subscription removed'))));
158
163
  }
159
164
  #listenForEvents(subId) {
160
165
  const lid = createListenerId();
@@ -178,8 +183,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
178
183
  const activeIds = Object.keys(this.listeners);
179
184
  await queries_ts_1.markSubscriptionsActive.run({ ids: activeIds }, this.client);
180
185
  this.logger.trace({ activeSubscriptions: activeIds.length }, 'marked subscriptions as active');
181
- const [{ deleted }] = await queries_ts_1.removeExpiredSubscriptions
182
- .run({ groupId: this.groupId, activeIds }, this.client);
186
+ const [{ deleted }] = await queries_ts_1.removeExpiredSubscriptions.run({ groupId: this.groupId, activeIds }, this.client);
183
187
  this.logger.trace({ deleted }, 'removed expired subscriptions');
184
188
  }
185
189
  async readChanges() {
@@ -188,10 +192,10 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
188
192
  }
189
193
  const now = Date.now();
190
194
  await this.#connectReadClient();
191
- const rows = await queries_ts_1.readNextEvents.run({
195
+ const rows = await this.readNextEvents({
192
196
  groupId: this.groupId,
193
197
  cursor: this.#inMemoryCursor,
194
- chunkSize: this.readChunkSize
198
+ chunkSize: this.readChunkSize,
195
199
  }, this.#readClient || this.client)
196
200
  .catch(async (err) => {
197
201
  if (err instanceof Error && err.message.includes('connection error')) {
@@ -207,7 +211,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
207
211
  }
208
212
  return 0;
209
213
  }
210
- const uqSubIds = Array.from(new Set(rows.flatMap(r => r.subscriptionIds)));
214
+ const uqSubIds = Array.from(new Set(rows.flatMap((r) => r.subscriptionIds)));
211
215
  const webhookSubs = await this.getWebhookInfo(uqSubIds);
212
216
  let webhookCount = 0;
213
217
  for (const sid in webhookSubs) {
@@ -220,14 +224,18 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
220
224
  queue: [],
221
225
  extra: wh,
222
226
  removeOnEmpty: true,
223
- handler: this.webhookHandler
227
+ handler: this.webhookHandler,
228
+ ...this.#webhookHandlerOpts
224
229
  };
225
230
  webhookCount++;
226
231
  }
227
232
  }
228
- const { map: subToEventMap, retryEvents, retryItemCount } = await (0, retry_handler_ts_1.normaliseRetryEventsInReadEventMap)(rows, this.client);
233
+ const { map: subToEventMap, retryEvents, retryItemCount, } = await (0, retry_handler_ts_1.normaliseRetryEventsInReadEventMap)(rows, this.client);
229
234
  const subs = Object.entries(subToEventMap);
230
- const checkpoint = { activeTasks: 0, nextCursor: rows[0].nextCursor };
235
+ const checkpoint = {
236
+ activeTasks: 0,
237
+ nextCursor: rows[0].nextCursor,
238
+ };
231
239
  for (const [subId, evs] of subs) {
232
240
  const listeners = this.listeners[subId]?.values;
233
241
  if (!listeners) {
@@ -257,7 +265,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
257
265
  activeCheckpoints: this.#activeCheckpoints.length,
258
266
  webhookCount,
259
267
  retryEvents,
260
- retryItemCount
268
+ retryItemCount,
261
269
  }, 'read rows');
262
270
  if (!checkpoint.activeTasks && this.#activeCheckpoints.length === 1) {
263
271
  await this.#updateCursorFromCompletedCheckpoints();
@@ -272,7 +280,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
272
280
  async #enqueueEventInReliableListener(subId, lid, item, checkpoint) {
273
281
  const lt = this.listeners[subId]?.values?.[lid];
274
282
  (0, assert_1.default)(lt?.type === 'reliable', 'invalid listener type: ' + lt.type);
275
- const { handler, queue, removeOnEmpty, extra } = lt;
283
+ const { handler, queue, removeOnEmpty, extra, splitBy = defaultSplitBy } = lt;
276
284
  queue.push({ item, checkpoint });
277
285
  checkpoint.activeTasks++;
278
286
  if (queue.length > 1) {
@@ -286,7 +294,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
286
294
  }
287
295
  const logger = this.logger.child({
288
296
  subId,
289
- items: item.items.map(i => i.id),
297
+ items: item.items.map((i) => i.id),
290
298
  extra,
291
299
  retryNumber: item.retry?.retryNumber,
292
300
  });
@@ -295,13 +303,20 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
295
303
  queue: queue.length,
296
304
  }, 'processing handler queue');
297
305
  try {
298
- await handler(item, {
299
- client: this.client,
300
- logger,
301
- subscriptionId: subId,
302
- extra,
303
- name: lid,
304
- });
306
+ for (const batch of splitBy(item)) {
307
+ await handler(batch, {
308
+ client: this.client,
309
+ logger: this.logger.child({
310
+ subId,
311
+ items: batch.items.map(i => i.id),
312
+ extra,
313
+ retryNumber: item.retry?.retryNumber,
314
+ }),
315
+ subscriptionId: subId,
316
+ extra,
317
+ name: lid,
318
+ });
319
+ }
305
320
  checkpoint.activeTasks--;
306
321
  (0, assert_1.default)(checkpoint.activeTasks >= 0, 'internal: checkpoint.activeTasks < 0');
307
322
  if (!checkpoint.activeTasks) {
@@ -313,9 +328,9 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
313
328
  }, 'completed handler task');
314
329
  }
315
330
  catch (err) {
316
- logger.error({ err }, 'error in handler,'
317
- + 'cancelling all active checkpoints'
318
- + '. Restarting from last known good cursor.');
331
+ logger.error({ err }, 'error in handler,' +
332
+ 'cancelling all active checkpoints' +
333
+ '. Restarting from last known good cursor.');
319
334
  this.#cancelAllActiveCheckpoints();
320
335
  }
321
336
  finally {
@@ -349,11 +364,11 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
349
364
  await queries_ts_1.setGroupCursor.run({
350
365
  groupId: this.groupId,
351
366
  cursor: latestMaxCursor,
352
- releaseLock: releaseLock
367
+ releaseLock: releaseLock,
353
368
  }, this.#readClient || this.client);
354
369
  this.logger.debug({
355
370
  cursor: latestMaxCursor,
356
- activeCheckpoints: this.#activeCheckpoints.length
371
+ activeCheckpoints: this.#activeCheckpoints.length,
357
372
  }, 'set cursor');
358
373
  // if there are no more active checkpoints,
359
374
  // clear in-memory cursor, so in case another process takes
@@ -375,8 +390,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
375
390
  return;
376
391
  }
377
392
  try {
378
- await queries_ts_1.releaseGroupLock
379
- .run({ groupId: this.groupId }, this.#readClient);
393
+ await queries_ts_1.releaseGroupLock.run({ groupId: this.groupId }, this.#readClient);
380
394
  }
381
395
  catch (err) {
382
396
  this.logger.error({ err }, 'error releasing read client');
@@ -400,8 +414,7 @@ class PgmbClient extends batcher_ts_1.PGMBEventBatcher {
400
414
  if (cl !== this.#readClient) {
401
415
  return;
402
416
  }
403
- this.logger
404
- .info('dedicated read client disconnected, may have dup event processing');
417
+ this.logger.info('dedicated read client disconnected, may have dup event processing');
405
418
  };
406
419
  #releaseReadClient() {
407
420
  try {
@@ -430,3 +443,6 @@ exports.PgmbClient = PgmbClient;
430
443
  function createListenerId() {
431
444
  return Math.random().toString(16).slice(2, 10);
432
445
  }
446
+ function defaultSplitBy(e) {
447
+ return [e];
448
+ }
package/lib/types.d.ts CHANGED
@@ -1,9 +1,11 @@
1
+ import type { IDatabaseConnection } from '@pgtyped/runtime';
1
2
  import type { IncomingMessage } from 'node:http';
2
3
  import type { Logger } from 'pino';
3
4
  import type { HeaderRecord } from 'undici-types/header.js';
4
5
  import type { AbortableAsyncIterator } from './abortable-async-iterator.ts';
5
- import type { IAssertSubscriptionParams } from './queries.ts';
6
+ import type { IAssertSubscriptionParams, IReadNextEventsParams, IReadNextEventsResult } from './queries.ts';
6
7
  import type { PgClientLike } from './query-types.ts';
8
+ export type ISplitFn<T extends IEventData> = (event: IReadEvent<T>) => IReadEvent<T>[];
7
9
  export type SerialisedEvent = {
8
10
  body: Buffer | string;
9
11
  contentType: string;
@@ -17,7 +19,7 @@ export type GetWebhookInfoFn = (subscriptionIds: string[]) => Promise<{
17
19
  }> | {
18
20
  [id: string]: WebhookInfo[];
19
21
  };
20
- export type PgmbWebhookOpts = {
22
+ export type PgmbWebhookOpts<T extends IEventData> = {
21
23
  /**
22
24
  * Maximum time to wait for webhook request to complete
23
25
  * @default 5 seconds
@@ -29,6 +31,7 @@ export type PgmbWebhookOpts = {
29
31
  * If null, a failed handler will fail the event processor. Use carefully.
30
32
  */
31
33
  retryOpts?: IRetryHandlerOpts | null;
34
+ splitBy?: ISplitFn<T>;
32
35
  jsonifier?: JSONifier;
33
36
  serialiseEvent?(ev: IReadEvent): SerialisedEvent;
34
37
  };
@@ -64,7 +67,8 @@ export type PGMBEventBatcherOpts<T extends IEventData> = {
64
67
  */
65
68
  maxBatchSize?: number;
66
69
  };
67
- export type Pgmb2ClientOpts = {
70
+ export type IReadNextEventsFn = (parmas: IReadNextEventsParams, db: IDatabaseConnection) => Promise<IReadNextEventsResult[]>;
71
+ export type Pgmb2ClientOpts<T extends IEventData> = {
68
72
  client: PgClientLike;
69
73
  /**
70
74
  * Globally unique identifier for this Pgmb2Client instance. All subs
@@ -100,22 +104,31 @@ export type Pgmb2ClientOpts = {
100
104
  * @default true
101
105
  */
102
106
  poll?: boolean;
103
- webhookHandlerOpts?: Partial<PgmbWebhookOpts>;
107
+ webhookHandlerOpts?: Partial<PgmbWebhookOpts<T>>;
104
108
  getWebhookInfo?: GetWebhookInfoFn;
109
+ /**
110
+ * Override the default readNextEvents implementation
111
+ */
112
+ readNextEvents?: IReadNextEventsFn;
105
113
  } & Pick<PGMBEventBatcherOpts<IEventData>, 'flushIntervalMs' | 'maxBatchSize' | 'shouldLog'>;
106
114
  export type IReadEvent<T extends IEventData = IEventData> = {
107
115
  items: IEvent<T>[];
108
116
  retry?: IRetryEventPayload;
109
117
  };
110
118
  export type RegisterSubscriptionParams = Omit<IAssertSubscriptionParams, 'groupId'>;
111
- export type registerReliableHandlerParams = RegisterSubscriptionParams & {
119
+ export type registerReliableHandlerParams<T extends IEventData = IEventData> = RegisterSubscriptionParams & {
112
120
  /**
113
121
  * Name for the retry handler, used to ensure retries for a particular
114
122
  * handler are not mixed with another handler. This name need only be
115
123
  * unique for a particular subscription.
116
- */
124
+ */
117
125
  name?: string;
118
126
  retryOpts?: IRetryHandlerOpts;
127
+ /**
128
+ * If provided, will split an incoming event into multiple events
129
+ * as determined by the function.
130
+ */
131
+ splitBy?: ISplitFn<T>;
119
132
  };
120
133
  export type CreateTopicalSubscriptionOpts<T extends IEventData> = {
121
134
  /**
@@ -126,6 +139,9 @@ export type CreateTopicalSubscriptionOpts<T extends IEventData> = {
126
139
  * To scale out processing, you can partition the subscriptions.
127
140
  * For example, with `current: 0, total: 3`, only messages
128
141
  * where `hashtext(e.id) % 3 == 0` will be received by this subscription.
142
+ * This will result in an approximate even split for all processors, the only
143
+ * caveat being it requires knowing the number of event processors on this
144
+ * subscription beforehand.
129
145
  */
130
146
  partition?: {
131
147
  current: number;
@@ -1,6 +1,6 @@
1
- import type { IEventHandler, PgmbWebhookOpts } from './types.ts';
1
+ import type { IEventData, IEventHandler, PgmbWebhookOpts } from './types.ts';
2
2
  /**
3
3
  * Create a handler that sends events to a webhook URL via HTTP POST.
4
4
  * @param url Where to send the webhook requests
5
5
  */
6
- export declare function createWebhookHandler({ timeoutMs, headers, retryOpts, jsonifier, serialiseEvent }: Partial<PgmbWebhookOpts>): IEventHandler;
6
+ export declare function createWebhookHandler<T extends IEventData>({ timeoutMs, headers, retryOpts, jsonifier, serialiseEvent }: Partial<PgmbWebhookOpts<T>>): IEventHandler;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@haathie/pgmb",
3
- "version": "0.2.0",
4
- "description": "Postgres Message Broker",
3
+ "version": "0.2.1",
4
+ "description": "PG message broker, with a type-safe typescript client with built-in webhook & SSE support.",
5
5
  "main": "lib/index.js",
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org",