@secondlayer/sdk 6.39.0 → 6.40.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.
@@ -230,6 +230,12 @@ type StreamsEventBase = {
230
230
  * Optional for back-compat; the API always sets it on Streams responses.
231
231
  */
232
232
  finalized?: boolean
233
+ /**
234
+ * Labels whose filter group this event satisfied, present only when the
235
+ * request used a labelled `filters` map. The SDK dispatches on it for you
236
+ * (`on.<label>`), so handlers rarely read it directly.
237
+ */
238
+ matched?: string[]
233
239
  };
234
240
  type StreamsEventOf<
235
241
  T extends StreamsEventType,
@@ -299,6 +305,34 @@ type StreamsReorgsListEnvelope = {
299
305
  };
300
306
  /** A filter that matches a single value or any value in a list. */
301
307
  type StreamsFilterValue = string | readonly string[];
308
+ /**
309
+ * One labelled filter group. Fields inside a group AND together; groups in a
310
+ * map OR together server-side.
311
+ */
312
+ type StreamsLabelledFilter = {
313
+ types?: readonly StreamsEventType[]
314
+ contractId?: StreamsFilterValue
315
+ sender?: StreamsFilterValue
316
+ recipient?: StreamsFilterValue
317
+ assetIdentifier?: string
318
+ };
319
+ /** A map of label → filter group. Labels are yours; the server echoes them. */
320
+ type StreamsFilterMap = Record<string, StreamsLabelledFilter>;
321
+ /**
322
+ * The events one label can yield. A group that declares `types` narrows to
323
+ * exactly those variants, so a per-label handler needs no `event_type` guard;
324
+ * a group without `types` keeps the full union.
325
+ */
326
+ type StreamsEventForFilter<
327
+ F,
328
+ D extends boolean = false
329
+ > = F extends {
330
+ types: readonly (infer T extends StreamsEventType)[]
331
+ } ? D extends true ? Extract<IndexEvent, {
332
+ event_type: T
333
+ }> : Extract<StreamsEvent, {
334
+ event_type: T
335
+ }> : D extends true ? IndexEvent : StreamsEvent;
302
336
  type StreamsEventsListParams = {
303
337
  cursor?: string | null
304
338
  fromHeight?: number
@@ -310,6 +344,12 @@ type StreamsEventsListParams = {
310
344
  sender?: StreamsFilterValue
311
345
  recipient?: StreamsFilterValue
312
346
  assetIdentifier?: string
347
+ /**
348
+ * Labelled filter groups. The groups OR together in one scan and each
349
+ * returned event carries the labels it matched (`event.matched`) — two
350
+ * unrelated concerns share one page, one cursor, one checkpoint.
351
+ */
352
+ filters?: StreamsFilterMap
313
353
  limit?: number
314
354
  };
315
355
  type StreamsEventsStreamParams = {
@@ -320,6 +360,8 @@ type StreamsEventsStreamParams = {
320
360
  sender?: StreamsFilterValue
321
361
  recipient?: StreamsFilterValue
322
362
  assetIdentifier?: string
363
+ /** Labelled filter groups; see {@link StreamsEventsListParams.filters}. */
364
+ filters?: StreamsFilterMap
323
365
  batchSize?: number
324
366
  emptyBackoffMs?: number
325
367
  maxPages?: number
@@ -335,6 +377,8 @@ type StreamsEventsSubscribeParams = {
335
377
  sender?: StreamsFilterValue
336
378
  recipient?: StreamsFilterValue
337
379
  assetIdentifier?: string
380
+ /** Labelled filter groups; see {@link StreamsEventsListParams.filters}. */
381
+ filters?: StreamsFilterMap
338
382
  /** Abort to unsubscribe (the returned function does the same). */
339
383
  signal?: AbortSignal
340
384
  /** Called for each pushed event, in order. */
@@ -386,7 +430,8 @@ type StreamsReorgContext = {
386
430
  };
387
431
  type StreamsEventsConsumeParams<
388
432
  TTx = never,
389
- D extends boolean = false
433
+ D extends boolean = false,
434
+ F extends StreamsFilterMap = Record<never, never>
390
435
  > = {
391
436
  fromCursor?: string | null
392
437
  /**
@@ -420,13 +465,33 @@ type StreamsEventsConsumeParams<
420
465
  sender?: StreamsFilterValue
421
466
  recipient?: StreamsFilterValue
422
467
  assetIdentifier?: string
468
+ /**
469
+ * Labelled filter groups. The groups OR together in ONE server-side scan,
470
+ * so two unrelated concerns share one page, one cursor, and one checkpoint
471
+ * instead of two consume loops. Pair with `on` to handle each label.
472
+ */
473
+ filters?: F
474
+ /**
475
+ * One handler per label in `filters`. Every label must be handled, so
476
+ * adding a label is a compile error until its handler exists.
477
+ *
478
+ * A label's declared `types` narrows its handler's events, so `payload` is
479
+ * typed without an `event_type` guard. Labels are dispatched in declaration
480
+ * order: a label's own events stay in cursor order, but events of different
481
+ * labels do not interleave within a page — add `onBatch` when you need
482
+ * strict global order across labels.
483
+ */
484
+ on?: { [K in keyof F] : (events: StreamsEventForFilter<F[K], D>[], ctx: StreamsBatchContext & WithSinkTx<TTx>) => void | Promise<void> }
423
485
  batchSize?: number
424
486
  /**
425
487
  * Apply a page of canonical events. Persist `ctx.cursor` in the same
426
488
  * transaction as your writes. Returning a cursor overrides `ctx.cursor` as
427
489
  * the resume point (advanced manual control); returning nothing uses it.
490
+ *
491
+ * Optional only when `on` handles the page instead; a consume call with
492
+ * neither throws.
428
493
  */
429
- onBatch: (events: D extends true ? IndexEvent[] : StreamsEvent[], envelope: StreamsEventsEnvelope, ctx: StreamsBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>
494
+ onBatch?: (events: D extends true ? IndexEvent[] : StreamsEvent[], envelope: StreamsEventsEnvelope, ctx: StreamsBatchContext & WithSinkTx<TTx>) => void | string | null | undefined | Promise<void> | Promise<string | null | undefined>
430
495
  /**
431
496
  * Roll your projection back to `reorg.fork_point_height`, persisting
432
497
  * `ctx.cursor` in the same transaction. Called once per *new* reorg
@@ -585,9 +650,10 @@ type StreamsClient = {
585
650
  * stops it.
586
651
  */
587
652
  consume<
653
+ const F extends StreamsFilterMap = Record<never, never>,
588
654
  TTx = never,
589
655
  D extends boolean = false
590
- >(params: StreamsEventsConsumeParams<TTx, D> & {
656
+ >(params: StreamsEventsConsumeParams<TTx, D, F> & {
591
657
  sink?: ConsumerSink<TTx>
592
658
  }): Promise<StreamsEventsConsumeResult>
593
659
  /**
@@ -399,7 +399,8 @@ async function consumeStreamsEvents(opts) {
399
399
  const emptyBackoffMs = opts.emptyBackoffMs ?? 500;
400
400
  const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
401
401
  const maxEmptyPolls = opts.maxEmptyPolls ?? Number.POSITIVE_INFINITY;
402
- let cursor = opts.fromCursor ?? await opts.sink?.loadCursor() ?? null;
402
+ const committedCursor = await opts.sink?.loadCursor() ?? null;
403
+ let cursor = opts.fromCursor ?? committedCursor;
403
404
  const handledReorgs = new Set;
404
405
  let pages = 0;
405
406
  let emptyPolls = 0;
@@ -413,7 +414,8 @@ async function consumeStreamsEvents(opts) {
413
414
  contractId: opts.contractId,
414
415
  sender: opts.sender,
415
416
  recipient: opts.recipient,
416
- assetIdentifier: opts.assetIdentifier
417
+ assetIdentifier: opts.assetIdentifier,
418
+ filters: opts.filters
417
419
  }), {
418
420
  retryCount: opts.retryCount,
419
421
  retryDelay: opts.retryDelay,
@@ -490,7 +492,8 @@ async function* iterateStreamsBatches(opts) {
490
492
  contractId: opts.contractId,
491
493
  sender: opts.sender,
492
494
  recipient: opts.recipient,
493
- assetIdentifier: opts.assetIdentifier
495
+ assetIdentifier: opts.assetIdentifier,
496
+ filters: opts.filters
494
497
  });
495
498
  const checkpoint = envelope.next_cursor ?? cursor;
496
499
  if (envelope.events.length > 0 || envelope.reorgs.length > 0) {
@@ -527,7 +530,8 @@ async function* streamStreamsEvents(opts) {
527
530
  contractId: opts.contractId,
528
531
  sender: opts.sender,
529
532
  recipient: opts.recipient,
530
- assetIdentifier: opts.assetIdentifier
533
+ assetIdentifier: opts.assetIdentifier,
534
+ filters: opts.filters
531
535
  });
532
536
  pages++;
533
537
  for (const event of envelope.events) {
@@ -802,7 +806,8 @@ function subscribeStreamsEvents(opts) {
802
806
  contract_id: params.contractId,
803
807
  sender: params.sender,
804
808
  recipient: params.recipient,
805
- asset_identifier: params.assetIdentifier
809
+ asset_identifier: params.assetIdentifier,
810
+ filters: params.filters ? JSON.stringify(params.filters) : undefined
806
811
  })}`;
807
812
  const res = await opts.fetchImpl(url, {
808
813
  headers: {
@@ -1046,7 +1051,8 @@ function createStreamsClient(options) {
1046
1051
  contractId,
1047
1052
  sender,
1048
1053
  recipient,
1049
- assetIdentifier
1054
+ assetIdentifier,
1055
+ filters
1050
1056
  }) => {
1051
1057
  return listEvents({
1052
1058
  cursor,
@@ -1056,9 +1062,13 @@ function createStreamsClient(options) {
1056
1062
  contractId,
1057
1063
  sender,
1058
1064
  recipient,
1059
- assetIdentifier
1065
+ assetIdentifier,
1066
+ filters
1060
1067
  });
1061
1068
  };
1069
+ function encodeFilters(filters) {
1070
+ return filters ? JSON.stringify(filters) : undefined;
1071
+ }
1062
1072
  async function listEvents(params = {}) {
1063
1073
  return request(`/v1/streams/events${buildQuery({
1064
1074
  cursor: params.cursor,
@@ -1070,9 +1080,56 @@ function createStreamsClient(options) {
1070
1080
  recipient: params.recipient,
1071
1081
  asset_identifier: params.assetIdentifier,
1072
1082
  types: params.types,
1073
- not_types: params.notTypes
1083
+ not_types: params.notTypes,
1084
+ filters: encodeFilters(params.filters)
1074
1085
  })}`);
1075
1086
  }
1087
+ function consumeEvents(params) {
1088
+ const labels = params.filters ? Object.keys(params.filters) : [];
1089
+ if (labels.length === 0 && !params.onBatch) {
1090
+ throw new ValidationError("consume requires onBatch, or filters + on handlers.", 400);
1091
+ }
1092
+ if (labels.length > 0 && !params.on && !params.onBatch) {
1093
+ throw new ValidationError("consume with filters requires on handlers (or onBatch).", 400);
1094
+ }
1095
+ const dispatch = params.on;
1096
+ const project = (events) => params.decoded ? events.map(decode) : events;
1097
+ const onBatch = async (events, envelope, ctx) => {
1098
+ for (const label of labels) {
1099
+ const forLabel = events.filter((event) => event.matched?.includes(label));
1100
+ if (forLabel.length === 0)
1101
+ continue;
1102
+ await dispatch?.[label]?.(project(forLabel), ctx);
1103
+ }
1104
+ const userOnBatch = params.onBatch;
1105
+ return await userOnBatch?.(project(events), envelope, ctx);
1106
+ };
1107
+ return consumeStreamsEvents({
1108
+ fromCursor: params.fromCursor,
1109
+ sink: params.sink,
1110
+ onProgress: params.onProgress,
1111
+ mode: params.mode,
1112
+ finalizedOnly: params.finalizedOnly,
1113
+ types: params.types,
1114
+ notTypes: params.notTypes,
1115
+ contractId: params.contractId,
1116
+ sender: params.sender,
1117
+ recipient: params.recipient,
1118
+ assetIdentifier: params.assetIdentifier,
1119
+ filters: params.filters,
1120
+ batchSize: params.batchSize ?? 100,
1121
+ fetchEvents,
1122
+ onBatch,
1123
+ onReorg: params.onReorg,
1124
+ emptyBackoffMs: params.emptyBackoffMs,
1125
+ maxPages: params.maxPages,
1126
+ maxEmptyPolls: params.maxEmptyPolls,
1127
+ signal: params.signal,
1128
+ retryCount: params.retryCount,
1129
+ retryDelay: params.retryDelay,
1130
+ onError: params.onError
1131
+ });
1132
+ }
1076
1133
  return {
1077
1134
  consume(params = {}) {
1078
1135
  return iterateStreamsBatches({
@@ -1094,33 +1151,7 @@ function createStreamsClient(options) {
1094
1151
  byTxId(txId) {
1095
1152
  return request(`/v1/streams/events/${encodeURIComponent(txId)}`);
1096
1153
  },
1097
- consume(params) {
1098
- const onBatch = params.decoded ? (events, envelope, ctx) => params.onBatch(events.map(decode), envelope, ctx) : params.onBatch;
1099
- return consumeStreamsEvents({
1100
- fromCursor: params.fromCursor,
1101
- sink: params.sink,
1102
- onProgress: params.onProgress,
1103
- mode: params.mode,
1104
- finalizedOnly: params.finalizedOnly,
1105
- types: params.types,
1106
- notTypes: params.notTypes,
1107
- contractId: params.contractId,
1108
- sender: params.sender,
1109
- recipient: params.recipient,
1110
- assetIdentifier: params.assetIdentifier,
1111
- batchSize: params.batchSize ?? 100,
1112
- fetchEvents,
1113
- onBatch,
1114
- onReorg: params.onReorg,
1115
- emptyBackoffMs: params.emptyBackoffMs,
1116
- maxPages: params.maxPages,
1117
- maxEmptyPolls: params.maxEmptyPolls,
1118
- signal: params.signal,
1119
- retryCount: params.retryCount,
1120
- retryDelay: params.retryDelay,
1121
- onError: params.onError
1122
- });
1123
- },
1154
+ consume: consumeEvents,
1124
1155
  stream(params = {}) {
1125
1156
  return streamStreamsEvents({
1126
1157
  fromCursor: params.fromCursor,
@@ -1453,5 +1484,5 @@ export {
1453
1484
  AuthError
1454
1485
  };
1455
1486
 
1456
- //# debugId=5CDDE8F307DF3D9164756E2164756E21
1487
+ //# debugId=93D7CDA8BB38740964756E2164756E21
1457
1488
  //# sourceMappingURL=index.js.map