@voltro/plugin-webhooks 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +670 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +196 -9
- package/dist/index.js +368 -249
- package/dist/mixin.d.ts +48 -0
- package/dist/mixin.js +1 -0
- package/dist/providers/index.js +1 -1
- package/dist/{signing-BOUxHdAo.js → signing-DUG4JWwk.js} +29 -32
- package/package.json +7 -7
package/dist/errors.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare const WebhookDeliveryNotFound_base: Schema.TaggedErrorClass<WebhookDeliv
|
|
|
18
18
|
/**
|
|
19
19
|
* Thrown by `WebhooksService.emit` when the payload does not decode
|
|
20
20
|
* against the outgoing event's declared `payload` schema (from its
|
|
21
|
-
* `
|
|
21
|
+
* declared event's `webhook:` block). The emit is rejected BEFORE any
|
|
22
22
|
* delivery row is written or workflow triggered — a schema-violating
|
|
23
23
|
* payload never reaches a subscriber.
|
|
24
24
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { ColumnDefinition } from '@voltro/database';
|
|
|
4
4
|
import { Context } from 'effect';
|
|
5
5
|
import { DataStore } from '@voltro/database';
|
|
6
6
|
import { Effect } from 'effect';
|
|
7
|
+
import { EventDescriptor } from '@voltro/protocol';
|
|
8
|
+
import { EventWebhookSpec } from '@voltro/protocol';
|
|
7
9
|
import { FieldDefinitions } from '@voltro/database';
|
|
8
10
|
import { Schema } from 'effect';
|
|
9
11
|
import { Table } from '@voltro/database';
|
|
@@ -23,6 +25,24 @@ import { WorkflowInstance } from '@effect/workflow/WorkflowEngine';
|
|
|
23
25
|
*/
|
|
24
26
|
export declare const acquireRateSlots: (store: DataStore, scopes: ReadonlyArray<RateScope>, now: number, windowMs?: number) => Promise<RateAcquireResult>;
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Project a declared event onto the outgoing-event descriptor this plugin
|
|
30
|
+
* already understands.
|
|
31
|
+
*
|
|
32
|
+
* A projection rather than a second registry: every downstream consumer —
|
|
33
|
+
* the delivery workflow, the JSON-Schema export, the dashboard's event list —
|
|
34
|
+
* keeps reading ONE shape. Adding a parallel path for declared events would
|
|
35
|
+
* mean each of them handles two, which is how the two drift.
|
|
36
|
+
*
|
|
37
|
+
* The event's `name` becomes the webhook `id`, so a subscriber that registered
|
|
38
|
+
* for `orders.paid` keeps working across the migration and the dashboard shows
|
|
39
|
+
* one event rather than two spellings of it.
|
|
40
|
+
*/
|
|
41
|
+
export declare const asOutgoingEvent: <Name extends string, Key, Payload>(descriptor: EventDescriptor<Name, never, never> & {
|
|
42
|
+
readonly payload: unknown;
|
|
43
|
+
readonly webhook?: EventWebhookSpec | undefined;
|
|
44
|
+
}) => OutgoingEventDescriptor<Payload> | undefined;
|
|
45
|
+
|
|
26
46
|
/** What `recordDeliveryOutcome` did — surfaced so the workflow can log
|
|
27
47
|
* the auto-disable transition. */
|
|
28
48
|
export declare interface AutoDisableOutcome {
|
|
@@ -82,6 +102,22 @@ export declare interface CustomSignatureScheme {
|
|
|
82
102
|
readonly verify: (rawBody: Uint8Array, secret: string, signatureHeader: string) => boolean;
|
|
83
103
|
}
|
|
84
104
|
|
|
105
|
+
/** The structural slice of a `defineEvent` descriptor this plugin needs.
|
|
106
|
+
* Structural rather than an import so the plugin does not depend on a specific
|
|
107
|
+
* protocol version's class identity. */
|
|
108
|
+
export declare interface DeclaredEventLike {
|
|
109
|
+
readonly kind: 'event';
|
|
110
|
+
readonly name: string;
|
|
111
|
+
readonly payload: unknown;
|
|
112
|
+
readonly webhook?: {
|
|
113
|
+
readonly description?: string;
|
|
114
|
+
readonly version?: number;
|
|
115
|
+
readonly rateLimit?: {
|
|
116
|
+
readonly perMinute: number;
|
|
117
|
+
};
|
|
118
|
+
} | undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
85
121
|
/** Default URL path for an incoming webhook when the descriptor
|
|
86
122
|
* doesn't override. Stage 4's discovery walker uses this to
|
|
87
123
|
* register routes. */
|
|
@@ -97,8 +133,6 @@ export declare const defaultRetryPolicy: () => RetryPolicy;
|
|
|
97
133
|
|
|
98
134
|
export declare const defineIncomingWebhook: <Body>(spec: Omit<IncomingWebhookDescriptor<Body>, "_tag">) => IncomingWebhookDescriptor<Body>;
|
|
99
135
|
|
|
100
|
-
export declare const defineOutgoingEvent: <Payload>(spec: Omit<OutgoingEventDescriptor<Payload>, "_tag">) => OutgoingEventDescriptor<Payload>;
|
|
101
|
-
|
|
102
136
|
export declare const defineWebhookProvider: (spec: Omit<WebhookProviderDescriptor, "_tag">) => WebhookProviderDescriptor;
|
|
103
137
|
|
|
104
138
|
/**
|
|
@@ -125,7 +159,8 @@ export declare const deliverWebhookWorkflow: Workflow.Workflow<"voltro.deliverWe
|
|
|
125
159
|
}>, typeof Schema.Never>;
|
|
126
160
|
|
|
127
161
|
declare interface DeliverWorkflowOptions {
|
|
128
|
-
/** Discovered
|
|
162
|
+
/** Discovered outgoing events (declared events with a `webhook:` block,
|
|
163
|
+
* projected by `asOutgoingEvent`) — the workflow reads
|
|
129
164
|
* the emitted event's `globalRateLimit` from here. Absent events
|
|
130
165
|
* simply have no global limit. */
|
|
131
166
|
readonly events?: ReadonlyArray<OutgoingEventDescriptor<unknown>>;
|
|
@@ -134,6 +169,29 @@ declare interface DeliverWorkflowOptions {
|
|
|
134
169
|
readonly rateWindowMs?: number;
|
|
135
170
|
}
|
|
136
171
|
|
|
172
|
+
/** `getDelivery` adds the two heavy columns `listDeliveries` omits. */
|
|
173
|
+
export declare interface DeliveryDetail extends DeliverySummary {
|
|
174
|
+
readonly payload: unknown;
|
|
175
|
+
readonly responseBody: string | null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** One delivery ATTEMPT, as the management UI reads it. */
|
|
179
|
+
export declare interface DeliverySummary {
|
|
180
|
+
readonly id: string;
|
|
181
|
+
readonly deliveryId: string;
|
|
182
|
+
readonly targetId: string;
|
|
183
|
+
readonly event: string;
|
|
184
|
+
readonly eventId: string | null;
|
|
185
|
+
readonly attempt: number;
|
|
186
|
+
readonly status: 'pending' | 'inFlight' | 'succeeded' | 'failed' | 'retryScheduled';
|
|
187
|
+
readonly responseStatus: number | null;
|
|
188
|
+
readonly errorMessage: string | null;
|
|
189
|
+
readonly latencyMs: number | null;
|
|
190
|
+
readonly scheduledAt: string | null;
|
|
191
|
+
readonly nextAttemptAt: string | null;
|
|
192
|
+
readonly createdAt: string | null;
|
|
193
|
+
}
|
|
194
|
+
|
|
137
195
|
/** Compact duration literal — parsed without a deps-pulling library.
|
|
138
196
|
* Covers ms / s / m / h / d. Default seconds (no suffix). */
|
|
139
197
|
export declare type DurationLiteral = `${number}${'ms' | 's' | 'm' | 'h' | 'd'}` | `${number}`;
|
|
@@ -176,6 +234,11 @@ export declare const getIdempotencyCache: () => IdempotencyCache;
|
|
|
176
234
|
/** GitHub-style: `X-Hub-Signature-256: sha256=hex` (no timestamp). */
|
|
177
235
|
export declare const githubSignature: () => HmacSignatureScheme;
|
|
178
236
|
|
|
237
|
+
/** Does this declared event opt into outbound HTTP delivery? */
|
|
238
|
+
export declare const hasWebhookAudience: (descriptor: {
|
|
239
|
+
readonly webhook?: EventWebhookSpec | undefined;
|
|
240
|
+
}) => boolean;
|
|
241
|
+
|
|
179
242
|
export declare type HmacAlgorithm = 'hmacSha256' | 'hmacSha1';
|
|
180
243
|
|
|
181
244
|
export declare interface HmacSignatureScheme {
|
|
@@ -478,7 +541,7 @@ export declare const releaseRateSlot: (store: DataStore, key: string, bucket: nu
|
|
|
478
541
|
* Default resolution is three-tiered: the subscriber's explicit
|
|
479
542
|
* value wins, then the event descriptor's per-event defaults
|
|
480
543
|
* (`defaultSigning` / `defaultRetry` / `version` from
|
|
481
|
-
* `
|
|
544
|
+
* the declared event's `webhook:` block), then the package-global defaults. */
|
|
482
545
|
export declare const resolveSubscribe: (input: SubscribeInput, event?: OutgoingEventDescriptor<unknown>) => {
|
|
483
546
|
readonly id: string;
|
|
484
547
|
readonly event: string;
|
|
@@ -487,6 +550,7 @@ export declare const resolveSubscribe: (input: SubscribeInput, event?: OutgoingE
|
|
|
487
550
|
readonly signing: SignatureScheme;
|
|
488
551
|
readonly retry: RetryPolicy;
|
|
489
552
|
readonly filter: Readonly<Record<string, unknown>> | null;
|
|
553
|
+
readonly scope: Readonly<Record<string, unknown>> | null;
|
|
490
554
|
readonly headers: Readonly<Record<string, string>> | null;
|
|
491
555
|
readonly rateLimitPerMinute: number | null;
|
|
492
556
|
readonly active: boolean;
|
|
@@ -569,6 +633,15 @@ export declare interface SubscribeInput {
|
|
|
569
633
|
* this predicate fan-out to this target. The shape mirrors the
|
|
570
634
|
* schema-builder's `Predicate` from `@voltro/database`. */
|
|
571
635
|
readonly filter?: Readonly<Record<string, unknown>>;
|
|
636
|
+
/**
|
|
637
|
+
* The APP's own scoping dimension — opaque, stored and returned verbatim.
|
|
638
|
+
*
|
|
639
|
+
* `.with(tenant())` is one level too coarse for real deployments: endpoints
|
|
640
|
+
* are commonly scoped to a team, a project or a workspace, and a tenant has
|
|
641
|
+
* many of those. Pass whatever identifies yours; `listTargets({ scope })`
|
|
642
|
+
* filters on equality against it. The framework never interprets it.
|
|
643
|
+
*/
|
|
644
|
+
readonly scope?: Readonly<Record<string, unknown>>;
|
|
572
645
|
readonly headers?: Readonly<Record<string, string>>;
|
|
573
646
|
readonly rateLimitPerMinute?: number;
|
|
574
647
|
readonly format?: 'json' | 'form' | 'xml';
|
|
@@ -592,10 +665,25 @@ export declare interface SubscribeResult {
|
|
|
592
665
|
readonly retry: RetryPolicy;
|
|
593
666
|
}
|
|
594
667
|
|
|
668
|
+
/** The patchable subset of a target. Absent keys are left alone. */
|
|
669
|
+
export declare interface TargetPatch {
|
|
670
|
+
readonly url?: string;
|
|
671
|
+
readonly description?: string | null;
|
|
672
|
+
readonly filter?: Readonly<Record<string, unknown>> | null;
|
|
673
|
+
readonly scope?: Readonly<Record<string, unknown>> | null;
|
|
674
|
+
readonly headers?: Readonly<Record<string, string>> | null;
|
|
675
|
+
readonly rateLimitPerMinute?: number | null;
|
|
676
|
+
readonly autoDisableAfter?: number | null;
|
|
677
|
+
readonly format?: 'json' | 'form' | 'xml';
|
|
678
|
+
}
|
|
679
|
+
|
|
595
680
|
export declare const TARGETS_TABLE = "_voltro_webhook_targets";
|
|
596
681
|
|
|
597
682
|
export declare interface TargetSummary {
|
|
598
683
|
readonly id: string;
|
|
684
|
+
/** The app's own scoping dimension as written at subscribe time, verbatim.
|
|
685
|
+
* `null` when the app stored none. */
|
|
686
|
+
readonly scope: Readonly<Record<string, unknown>> | null;
|
|
599
687
|
readonly event: string;
|
|
600
688
|
readonly url: string;
|
|
601
689
|
readonly active: boolean;
|
|
@@ -781,6 +869,30 @@ export declare const _voltroWebhookTargetsTable: Table<"_voltro_webhook_targets"
|
|
|
781
869
|
readonly signing: ColumnBuilder<unknown, "json", boolean>;
|
|
782
870
|
/** Serialised `RetryPolicy` — see `retry.ts`. */
|
|
783
871
|
readonly retry: ColumnBuilder<unknown, "json", boolean>;
|
|
872
|
+
/**
|
|
873
|
+
* The APP's own scoping dimension. Opaque to the framework.
|
|
874
|
+
*
|
|
875
|
+
* Stored and returned verbatim, never interpreted — the framework does not
|
|
876
|
+
* know what a team, a project or a workspace is, and does not need to. Reads
|
|
877
|
+
* filter on equality against the json you wrote:
|
|
878
|
+
*
|
|
879
|
+
* subscribe({ event, url, scope: { teamId: 'q970…' } })
|
|
880
|
+
* listTargets({ scope: { teamId } })
|
|
881
|
+
*
|
|
882
|
+
* It exists because `.with(tenant())` is one level too coarse for real
|
|
883
|
+
* deployments. A consumer's endpoints are scoped to a TEAM and a tenant has
|
|
884
|
+
* many teams; every read filters by it and every write guards on it, so
|
|
885
|
+
* without this column the plugin cannot hold their rows at all and they keep
|
|
886
|
+
* a parallel table.
|
|
887
|
+
*
|
|
888
|
+
* The precedent is `_voltro_presence.meta`, and it is worth stating because
|
|
889
|
+
* it decided a migration: that column is json the framework stores and never
|
|
890
|
+
* interprets, and it is the ONLY reason the same consumer's presence
|
|
891
|
+
* migration was lossless — their three denormalised columns went straight in.
|
|
892
|
+
* The general form: a plugin that stores rows in an app's database on the
|
|
893
|
+
* app's behalf needs one place for the app's own dimension.
|
|
894
|
+
*/
|
|
895
|
+
readonly scope: ColumnBuilder<unknown, "json", boolean>;
|
|
784
896
|
/** Optional predicate filter (subset of `Predicate`) — the engine
|
|
785
897
|
* evaluates this against each emit's payload to decide whether
|
|
786
898
|
* this target receives the delivery. */
|
|
@@ -868,7 +980,7 @@ export declare type WebhookId = string;
|
|
|
868
980
|
/**
|
|
869
981
|
* Thrown by `WebhooksService.emit` when the payload does not decode
|
|
870
982
|
* against the outgoing event's declared `payload` schema (from its
|
|
871
|
-
* `
|
|
983
|
+
* declared event's `webhook:` block). The emit is rejected BEFORE any
|
|
872
984
|
* delivery row is written or workflow triggered — a schema-violating
|
|
873
985
|
* payload never reaches a subscriber.
|
|
874
986
|
*/
|
|
@@ -940,7 +1052,7 @@ export declare class WebhooksService extends WebhooksService_base {
|
|
|
940
1052
|
declare const WebhooksService_base: Context.TagClass<WebhooksService, "@voltro/webhooks/WebhooksService", WebhooksServiceShape>;
|
|
941
1053
|
|
|
942
1054
|
export declare interface WebhooksServiceOptions {
|
|
943
|
-
/** Discovered
|
|
1055
|
+
/** Discovered outgoing events (projected from declared events). When present,
|
|
944
1056
|
* `subscribe` resolves the event's `defaultSigning` /
|
|
945
1057
|
* `defaultRetry` / `version` before the package-global defaults,
|
|
946
1058
|
* and `emit` decodes the payload against the event's schema. */
|
|
@@ -950,13 +1062,13 @@ export declare interface WebhooksServiceOptions {
|
|
|
950
1062
|
export declare interface WebhooksServiceShape {
|
|
951
1063
|
readonly subscribe: (input: SubscribeInput) => Promise<SubscribeResult>;
|
|
952
1064
|
/** Emit an event to every subscribed target. Accepts the event id
|
|
953
|
-
* OR the
|
|
1065
|
+
* OR the declared event itself — passing the
|
|
954
1066
|
* descriptor types `payload` against its schema at the call site.
|
|
955
1067
|
* Either way, when the descriptor is known (directly or via the
|
|
956
1068
|
* discovered-events registry) the payload is DECODED against its
|
|
957
1069
|
* schema and a mismatch throws `WebhookPayloadInvalid` before any
|
|
958
1070
|
* delivery is created. */
|
|
959
|
-
readonly emit: <P>(event: string | OutgoingEventDescriptor<P
|
|
1071
|
+
readonly emit: <P>(event: string | OutgoingEventDescriptor<P> | DeclaredEventLike, payload: P) => Promise<EmitResult>;
|
|
960
1072
|
/** Manual re-trigger for the dashboard's "Replay" button on a
|
|
961
1073
|
* failed delivery row. Re-runs the workflow at attempt 1 with
|
|
962
1074
|
* the original payload. */
|
|
@@ -964,7 +1076,12 @@ export declare interface WebhooksServiceShape {
|
|
|
964
1076
|
/** List currently-subscribed targets for an event, or all when
|
|
965
1077
|
* `event` is omitted. The dashboard's "Targets" page consumes
|
|
966
1078
|
* this. */
|
|
967
|
-
readonly listTargets: (event?: string
|
|
1079
|
+
readonly listTargets: (event?: string,
|
|
1080
|
+
/** Filter to targets whose opaque `scope` equals this, key for key. The
|
|
1081
|
+
* match is exact on the keys you pass — a target with extra keys still
|
|
1082
|
+
* matches, so `{ teamId }` finds targets scoped to that team regardless of
|
|
1083
|
+
* what else the app stored beside it. */
|
|
1084
|
+
scope?: Readonly<Record<string, unknown>>) => Promise<ReadonlyArray<TargetSummary>>;
|
|
968
1085
|
/** Soft-disable a target without deleting the row. While paused,
|
|
969
1086
|
* emits against this target queue under
|
|
970
1087
|
* `_voltro_webhook_deliveries` with status='pending' (no POST
|
|
@@ -994,6 +1111,52 @@ export declare interface WebhooksServiceShape {
|
|
|
994
1111
|
* the consumer has updated their handler to accept the new
|
|
995
1112
|
* payload shape. */
|
|
996
1113
|
readonly updateTargetPayloadVersion: (targetId: string, version: number) => Promise<void>;
|
|
1114
|
+
/**
|
|
1115
|
+
* Edit a target in place.
|
|
1116
|
+
*
|
|
1117
|
+
* Without it, changing a URL, description, filter, headers or rate limit
|
|
1118
|
+
* means delete + re-subscribe — which ROTATES the secret (every receiver has
|
|
1119
|
+
* to be reconfigured) and ORPHANS the delivery history (the rows point at a
|
|
1120
|
+
* target id that no longer exists). Neither is what "I fixed a typo in the
|
|
1121
|
+
* URL" should cost.
|
|
1122
|
+
*
|
|
1123
|
+
* `event` and `secret` are deliberately not patchable: changing the event
|
|
1124
|
+
* makes it a different subscription, and the secret has `rotateSecret`, which
|
|
1125
|
+
* returns the new value once.
|
|
1126
|
+
*/
|
|
1127
|
+
readonly updateTarget: (targetId: string, patch: TargetPatch) => Promise<void>;
|
|
1128
|
+
/**
|
|
1129
|
+
* Send one delivery to ONE target, bypassing fan-out and the filter.
|
|
1130
|
+
*
|
|
1131
|
+
* `emit` fans out to every matching target, so there is no way to answer "is
|
|
1132
|
+
* THIS endpoint reachable" — the first button in every webhook UI. The
|
|
1133
|
+
* delivery is real: it is signed, recorded in the delivery log and retried
|
|
1134
|
+
* like any other, so what it proves is what production will do.
|
|
1135
|
+
*/
|
|
1136
|
+
readonly testTarget: (targetId: string, payload?: unknown) => Promise<{
|
|
1137
|
+
readonly deliveryId: string;
|
|
1138
|
+
}>;
|
|
1139
|
+
/**
|
|
1140
|
+
* Read the delivery log.
|
|
1141
|
+
*
|
|
1142
|
+
* There was no service method for it, so the only way to build a management
|
|
1143
|
+
* view was to query `_voltro_webhook_deliveries` directly — and a consumer
|
|
1144
|
+
* declined, correctly: the 0.24.0 `agent_messages` rename taught them what
|
|
1145
|
+
* app code coupled to a framework table name costs. That one was survivable
|
|
1146
|
+
* because it was a rename; a column change would not be.
|
|
1147
|
+
*
|
|
1148
|
+
* The two heavy columns (`payload`, `responseBody`) are omitted here and
|
|
1149
|
+
* available from `getDelivery`, so a list view does not pull response bodies
|
|
1150
|
+
* for 200 rows.
|
|
1151
|
+
*/
|
|
1152
|
+
readonly listDeliveries: (filter?: {
|
|
1153
|
+
readonly targetId?: string;
|
|
1154
|
+
readonly status?: DeliverySummary['status'];
|
|
1155
|
+
readonly since?: Date;
|
|
1156
|
+
readonly limit?: number;
|
|
1157
|
+
}) => Promise<ReadonlyArray<DeliverySummary>>;
|
|
1158
|
+
/** One delivery attempt including its payload and response body. */
|
|
1159
|
+
readonly getDelivery: (id: string) => Promise<DeliveryDetail | null>;
|
|
997
1160
|
}
|
|
998
1161
|
|
|
999
1162
|
/**
|
|
@@ -1042,6 +1205,30 @@ export declare const webhookTables: () => readonly [ Table<"_voltro_webhook_targ
|
|
|
1042
1205
|
readonly signing: ColumnBuilder<unknown, "json", boolean>;
|
|
1043
1206
|
/** Serialised `RetryPolicy` — see `retry.ts`. */
|
|
1044
1207
|
readonly retry: ColumnBuilder<unknown, "json", boolean>;
|
|
1208
|
+
/**
|
|
1209
|
+
* The APP's own scoping dimension. Opaque to the framework.
|
|
1210
|
+
*
|
|
1211
|
+
* Stored and returned verbatim, never interpreted — the framework does not
|
|
1212
|
+
* know what a team, a project or a workspace is, and does not need to. Reads
|
|
1213
|
+
* filter on equality against the json you wrote:
|
|
1214
|
+
*
|
|
1215
|
+
* subscribe({ event, url, scope: { teamId: 'q970…' } })
|
|
1216
|
+
* listTargets({ scope: { teamId } })
|
|
1217
|
+
*
|
|
1218
|
+
* It exists because `.with(tenant())` is one level too coarse for real
|
|
1219
|
+
* deployments. A consumer's endpoints are scoped to a TEAM and a tenant has
|
|
1220
|
+
* many teams; every read filters by it and every write guards on it, so
|
|
1221
|
+
* without this column the plugin cannot hold their rows at all and they keep
|
|
1222
|
+
* a parallel table.
|
|
1223
|
+
*
|
|
1224
|
+
* The precedent is `_voltro_presence.meta`, and it is worth stating because
|
|
1225
|
+
* it decided a migration: that column is json the framework stores and never
|
|
1226
|
+
* interprets, and it is the ONLY reason the same consumer's presence
|
|
1227
|
+
* migration was lossless — their three denormalised columns went straight in.
|
|
1228
|
+
* The general form: a plugin that stores rows in an app's database on the
|
|
1229
|
+
* app's behalf needs one place for the app's own dimension.
|
|
1230
|
+
*/
|
|
1231
|
+
readonly scope: ColumnBuilder<unknown, "json", boolean>;
|
|
1045
1232
|
/** Optional predicate filter (subset of `Predicate`) — the engine
|
|
1046
1233
|
* evaluates this against each emit's payload to decide whether
|
|
1047
1234
|
* this target receives the delivery. */
|