@laserdata/laser-sdk 0.5.3 → 0.6.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/README.md +105 -79
- package/dist/agent/agdx.d.ts +8 -3
- package/dist/agent/agdx.d.ts.map +1 -1
- package/dist/agent/agdx.js +39 -22
- package/dist/agent/agdx.js.map +1 -1
- package/dist/agent/builder.d.ts +25 -15
- package/dist/agent/builder.d.ts.map +1 -1
- package/dist/agent/builder.js +103 -59
- package/dist/agent/builder.js.map +1 -1
- package/dist/agent/context.d.ts +3 -6
- package/dist/agent/context.d.ts.map +1 -1
- package/dist/agent/context.js +17 -9
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/contract.d.ts +2 -2
- package/dist/agent/contract.d.ts.map +1 -1
- package/dist/agent/contract.js +10 -6
- package/dist/agent/contract.js.map +1 -1
- package/dist/agent/memory-handler.d.ts +25 -0
- package/dist/agent/memory-handler.d.ts.map +1 -0
- package/dist/agent/memory-handler.js +43 -0
- package/dist/agent/memory-handler.js.map +1 -0
- package/dist/agent/registry.d.ts +3 -10
- package/dist/agent/registry.d.ts.map +1 -1
- package/dist/agent/registry.js +22 -19
- package/dist/agent/registry.js.map +1 -1
- package/dist/agent/reliable-consumer.d.ts +19 -9
- package/dist/agent/reliable-consumer.d.ts.map +1 -1
- package/dist/agent/reliable-consumer.js +193 -54
- package/dist/agent/reliable-consumer.js.map +1 -1
- package/dist/agent/replies.d.ts +6 -0
- package/dist/agent/replies.d.ts.map +1 -1
- package/dist/agent/replies.js +55 -4
- package/dist/agent/replies.js.map +1 -1
- package/dist/agent/router.d.ts +1 -1
- package/dist/agent/router.d.ts.map +1 -1
- package/dist/agent/router.js +5 -19
- package/dist/agent/router.js.map +1 -1
- package/dist/agent/scope.d.ts +7 -3
- package/dist/agent/scope.d.ts.map +1 -1
- package/dist/agent/scope.js +11 -1
- package/dist/agent/scope.js.map +1 -1
- package/dist/agent/timer.d.ts +6 -0
- package/dist/agent/timer.d.ts.map +1 -0
- package/dist/agent/timer.js +21 -0
- package/dist/agent/timer.js.map +1 -0
- package/dist/agent/workflow.d.ts +16 -19
- package/dist/agent/workflow.d.ts.map +1 -1
- package/dist/agent/workflow.js +17 -6
- package/dist/agent/workflow.js.map +1 -1
- package/dist/blob.d.ts +2 -3
- package/dist/blob.d.ts.map +1 -1
- package/dist/blob.js.map +1 -1
- package/dist/bridges/a2a.d.ts +34 -23
- package/dist/bridges/a2a.d.ts.map +1 -1
- package/dist/bridges/a2a.js +29 -19
- package/dist/bridges/a2a.js.map +1 -1
- package/dist/bridges/hops.d.ts +1 -1
- package/dist/bridges/hops.d.ts.map +1 -1
- package/dist/bridges/hops.js +1 -1
- package/dist/bridges/hops.js.map +1 -1
- package/dist/bridges/mcp.d.ts +27 -7
- package/dist/bridges/mcp.d.ts.map +1 -1
- package/dist/bridges/mcp.js +24 -9
- package/dist/bridges/mcp.js.map +1 -1
- package/dist/client/bytes.d.ts +5 -0
- package/dist/client/bytes.d.ts.map +1 -1
- package/dist/client/bytes.js +15 -0
- package/dist/client/bytes.js.map +1 -1
- package/dist/client/capabilities.d.ts +25 -13
- package/dist/client/capabilities.d.ts.map +1 -1
- package/dist/client/capabilities.js +145 -123
- package/dist/client/capabilities.js.map +1 -1
- package/dist/client/error-classify.d.ts +57 -0
- package/dist/client/error-classify.d.ts.map +1 -0
- package/dist/client/error-classify.js +347 -0
- package/dist/client/error-classify.js.map +1 -0
- package/dist/client/errors.d.ts +113 -40
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +191 -53
- package/dist/client/errors.js.map +1 -1
- package/dist/client/internals.d.ts +1 -0
- package/dist/client/internals.d.ts.map +1 -1
- package/dist/client/internals.js +1 -0
- package/dist/client/internals.js.map +1 -1
- package/dist/client/laser.d.ts +62 -46
- package/dist/client/laser.d.ts.map +1 -1
- package/dist/client/laser.js +206 -120
- package/dist/client/laser.js.map +1 -1
- package/dist/client/managed.d.ts +3 -0
- package/dist/client/managed.d.ts.map +1 -1
- package/dist/client/managed.js +70 -3
- package/dist/client/managed.js.map +1 -1
- package/dist/context-scope.d.ts +25 -9
- package/dist/context-scope.d.ts.map +1 -1
- package/dist/context-scope.js +36 -9
- package/dist/context-scope.js.map +1 -1
- package/dist/context.d.ts +33 -16
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +174 -95
- package/dist/context.js.map +1 -1
- package/dist/conversation-state.d.ts +1 -1
- package/dist/conversation-state.d.ts.map +1 -1
- package/dist/conversation-state.js +35 -16
- package/dist/conversation-state.js.map +1 -1
- package/dist/govern.d.ts +24 -5
- package/dist/govern.d.ts.map +1 -1
- package/dist/govern.js +116 -28
- package/dist/govern.js.map +1 -1
- package/dist/iggy/apache-iggy.d.ts +59 -56
- package/dist/iggy/apache-iggy.d.ts.map +1 -1
- package/dist/iggy/apache-iggy.js +300 -137
- package/dist/iggy/apache-iggy.js.map +1 -1
- package/dist/index.d.ts +97 -65
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -30
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +16 -2
- package/dist/intent.d.ts.map +1 -1
- package/dist/intent.js +50 -18
- package/dist/intent.js.map +1 -1
- package/dist/managed/coordination.d.ts +82 -0
- package/dist/managed/coordination.d.ts.map +1 -0
- package/dist/managed/coordination.js +367 -0
- package/dist/managed/coordination.js.map +1 -0
- package/dist/managed/destinations.d.ts +4 -3
- package/dist/managed/destinations.d.ts.map +1 -1
- package/dist/managed/destinations.js +29 -13
- package/dist/managed/destinations.js.map +1 -1
- package/dist/managed/filters.d.ts +5 -5
- package/dist/managed/filters.d.ts.map +1 -1
- package/dist/managed/filters.js +39 -22
- package/dist/managed/filters.js.map +1 -1
- package/dist/managed/forks.d.ts +8 -11
- package/dist/managed/forks.d.ts.map +1 -1
- package/dist/managed/forks.js +33 -13
- package/dist/managed/forks.js.map +1 -1
- package/dist/managed/graph.d.ts +1 -4
- package/dist/managed/graph.d.ts.map +1 -1
- package/dist/managed/graph.js +5 -3
- package/dist/managed/graph.js.map +1 -1
- package/dist/managed/kv.d.ts +26 -14
- package/dist/managed/kv.d.ts.map +1 -1
- package/dist/managed/kv.js +88 -61
- package/dist/managed/kv.js.map +1 -1
- package/dist/managed/projections.d.ts +5 -8
- package/dist/managed/projections.d.ts.map +1 -1
- package/dist/managed/projections.js +22 -2
- package/dist/managed/projections.js.map +1 -1
- package/dist/managed/query.d.ts +20 -6
- package/dist/managed/query.d.ts.map +1 -1
- package/dist/managed/query.js +97 -24
- package/dist/managed/query.js.map +1 -1
- package/dist/managed/runs.d.ts +2 -4
- package/dist/managed/runs.d.ts.map +1 -1
- package/dist/managed/runs.js +9 -1
- package/dist/managed/runs.js.map +1 -1
- package/dist/managed/watch.d.ts +2 -4
- package/dist/managed/watch.d.ts.map +1 -1
- package/dist/managed/watch.js +9 -1
- package/dist/managed/watch.js.map +1 -1
- package/dist/memory/handle.d.ts +75 -22
- package/dist/memory/handle.d.ts.map +1 -1
- package/dist/memory/handle.js +232 -58
- package/dist/memory/handle.js.map +1 -1
- package/dist/memory/log-memory.d.ts +24 -12
- package/dist/memory/log-memory.d.ts.map +1 -1
- package/dist/memory/log-memory.js +214 -100
- package/dist/memory/log-memory.js.map +1 -1
- package/dist/memory/topic.d.ts +2 -2
- package/dist/memory/topic.d.ts.map +1 -1
- package/dist/memory/topic.js +7 -5
- package/dist/memory/topic.js.map +1 -1
- package/dist/memory/types.d.ts +41 -5
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +44 -18
- package/dist/memory/types.js.map +1 -1
- package/dist/memory/vector-memory.d.ts +8 -6
- package/dist/memory/vector-memory.d.ts.map +1 -1
- package/dist/memory/vector-memory.js +65 -34
- package/dist/memory/vector-memory.js.map +1 -1
- package/dist/memory.d.ts +6 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +4 -4
- package/dist/memory.js.map +1 -1
- package/dist/provenance/agent-topic.d.ts +3 -1
- package/dist/provenance/agent-topic.d.ts.map +1 -1
- package/dist/provenance/agent-topic.js +3 -1
- package/dist/provenance/agent-topic.js.map +1 -1
- package/dist/provenance/provenance.d.ts +3 -3
- package/dist/provenance/provenance.d.ts.map +1 -1
- package/dist/provenance/provenance.js +30 -18
- package/dist/provenance/provenance.js.map +1 -1
- package/dist/runtime/clock.d.ts +3 -0
- package/dist/runtime/clock.d.ts.map +1 -1
- package/dist/runtime/clock.js +16 -3
- package/dist/runtime/clock.js.map +1 -1
- package/dist/schema-codecs.d.ts +7 -3
- package/dist/schema-codecs.d.ts.map +1 -1
- package/dist/schema-codecs.js +80 -12
- package/dist/schema-codecs.js.map +1 -1
- package/dist/session.d.ts +27 -18
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +95 -37
- package/dist/session.js.map +1 -1
- package/dist/signing.d.ts +11 -5
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +64 -21
- package/dist/signing.js.map +1 -1
- package/dist/snapshot.d.ts +10 -2
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +62 -28
- package/dist/snapshot.js.map +1 -1
- package/dist/state-store.d.ts +1 -1
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js.map +1 -1
- package/dist/stream/batching.d.ts +81 -0
- package/dist/stream/batching.d.ts.map +1 -0
- package/dist/stream/batching.js +210 -0
- package/dist/stream/batching.js.map +1 -0
- package/dist/stream/bson.d.ts +3 -0
- package/dist/stream/bson.d.ts.map +1 -0
- package/dist/stream/bson.js +245 -0
- package/dist/stream/bson.js.map +1 -0
- package/dist/stream/codecs.d.ts +58 -6
- package/dist/stream/codecs.d.ts.map +1 -1
- package/dist/stream/codecs.js +143 -59
- package/dist/stream/codecs.js.map +1 -1
- package/dist/stream/consumer-group.d.ts +13 -10
- package/dist/stream/consumer-group.d.ts.map +1 -1
- package/dist/stream/consumer-group.js +91 -31
- package/dist/stream/consumer-group.js.map +1 -1
- package/dist/stream/consumer-start.d.ts +17 -0
- package/dist/stream/consumer-start.d.ts.map +1 -0
- package/dist/stream/consumer-start.js +2 -0
- package/dist/stream/consumer-start.js.map +1 -0
- package/dist/stream/consumer.d.ts +166 -24
- package/dist/stream/consumer.d.ts.map +1 -1
- package/dist/stream/consumer.js +451 -68
- package/dist/stream/consumer.js.map +1 -1
- package/dist/stream/cursor.d.ts +5 -12
- package/dist/stream/cursor.d.ts.map +1 -1
- package/dist/stream/cursor.js +34 -8
- package/dist/stream/cursor.js.map +1 -1
- package/dist/stream/header-value.d.ts +61 -15
- package/dist/stream/header-value.d.ts.map +1 -1
- package/dist/stream/header-value.js.map +1 -1
- package/dist/stream/message.d.ts +17 -0
- package/dist/stream/message.d.ts.map +1 -0
- package/dist/stream/message.js +9 -0
- package/dist/stream/message.js.map +1 -0
- package/dist/stream/producer.d.ts +127 -11
- package/dist/stream/producer.d.ts.map +1 -1
- package/dist/stream/producer.js +401 -36
- package/dist/stream/producer.js.map +1 -1
- package/dist/stream/publish.d.ts +6 -15
- package/dist/stream/publish.d.ts.map +1 -1
- package/dist/stream/publish.js +18 -28
- package/dist/stream/publish.js.map +1 -1
- package/dist/stream/record.d.ts +4 -6
- package/dist/stream/record.d.ts.map +1 -1
- package/dist/stream/record.js +4 -3
- package/dist/stream/record.js.map +1 -1
- package/dist/stream/routing.d.ts +12 -0
- package/dist/stream/routing.d.ts.map +1 -1
- package/dist/stream/routing.js +6 -1
- package/dist/stream/routing.js.map +1 -1
- package/dist/stream/stream.d.ts +1 -4
- package/dist/stream/stream.d.ts.map +1 -1
- package/dist/stream/stream.js +5 -1
- package/dist/stream/stream.js.map +1 -1
- package/dist/stream/topic.d.ts +22 -23
- package/dist/stream/topic.d.ts.map +1 -1
- package/dist/stream/topic.js +90 -30
- package/dist/stream/topic.js.map +1 -1
- package/dist/stream/typed-topic.d.ts +9 -13
- package/dist/stream/typed-topic.d.ts.map +1 -1
- package/dist/stream/typed-topic.js +25 -10
- package/dist/stream/typed-topic.js.map +1 -1
- package/dist/swarm.d.ts +0 -1
- package/dist/swarm.d.ts.map +1 -1
- package/dist/swarm.js +1 -0
- package/dist/swarm.js.map +1 -1
- package/dist/testing.d.ts +3 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +3 -3
- package/dist/testing.js.map +1 -1
- package/dist/types/ids.d.ts +17 -2
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +34 -14
- package/dist/types/ids.js.map +1 -1
- package/dist/wire/agent.d.ts +22 -0
- package/dist/wire/agent.d.ts.map +1 -1
- package/dist/wire/agent.js +80 -25
- package/dist/wire/agent.js.map +1 -1
- package/dist/wire/authz.d.ts +35 -1
- package/dist/wire/authz.d.ts.map +1 -1
- package/dist/wire/authz.js +45 -15
- package/dist/wire/authz.js.map +1 -1
- package/dist/wire/cbor.d.ts.map +1 -1
- package/dist/wire/cbor.js +13 -2
- package/dist/wire/cbor.js.map +1 -1
- package/dist/wire/checkpoint.d.ts +17 -12
- package/dist/wire/checkpoint.d.ts.map +1 -1
- package/dist/wire/checkpoint.js +28 -3
- package/dist/wire/checkpoint.js.map +1 -1
- package/dist/wire/control.d.ts +82 -0
- package/dist/wire/control.d.ts.map +1 -1
- package/dist/wire/control.js +219 -0
- package/dist/wire/control.js.map +1 -1
- package/dist/wire/filter-eval.d.ts +41 -7
- package/dist/wire/filter-eval.d.ts.map +1 -1
- package/dist/wire/filter-eval.js +26 -28
- package/dist/wire/filter-eval.js.map +1 -1
- package/dist/wire/filter.d.ts +51 -17
- package/dist/wire/filter.d.ts.map +1 -1
- package/dist/wire/filter.js +99 -10
- package/dist/wire/filter.js.map +1 -1
- package/dist/wire/fork.d.ts +14 -1
- package/dist/wire/fork.d.ts.map +1 -1
- package/dist/wire/fork.js +19 -8
- package/dist/wire/fork.js.map +1 -1
- package/dist/wire/graph.d.ts +3 -0
- package/dist/wire/graph.d.ts.map +1 -1
- package/dist/wire/graph.js +18 -4
- package/dist/wire/graph.js.map +1 -1
- package/dist/wire/hello.d.ts +4 -1
- package/dist/wire/hello.d.ts.map +1 -1
- package/dist/wire/hello.js +13 -3
- package/dist/wire/hello.js.map +1 -1
- package/dist/wire/index.d.ts +4 -1
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +4 -1
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/kv.d.ts +37 -2
- package/dist/wire/kv.d.ts.map +1 -1
- package/dist/wire/kv.js +36 -18
- package/dist/wire/kv.js.map +1 -1
- package/dist/wire/query.d.ts +6 -0
- package/dist/wire/query.d.ts.map +1 -1
- package/dist/wire/query.js +38 -12
- package/dist/wire/query.js.map +1 -1
- package/dist/wire/schema.d.ts +29 -6
- package/dist/wire/schema.d.ts.map +1 -1
- package/dist/wire/schema.js +61 -26
- package/dist/wire/schema.js.map +1 -1
- package/dist/wire/snapshot.js +1 -1
- package/dist/wire/snapshot.js.map +1 -1
- package/dist/wire/value.d.ts +10 -2
- package/dist/wire/value.d.ts.map +1 -1
- package/dist/wire/value.js +29 -3
- package/dist/wire/value.js.map +1 -1
- package/package.json +2 -3
- package/dist/stream/polling-strategy.d.ts +0 -14
- package/dist/stream/polling-strategy.d.ts.map +0 -1
- package/dist/stream/polling-strategy.js +0 -2
- package/dist/stream/polling-strategy.js.map +0 -1
package/dist/stream/consumer.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { INTERNAL_RETURN_DELIVERY } from "../client/internals.js";
|
|
1
2
|
import { mintUlidValue } from "../runtime/ulid.js";
|
|
2
|
-
import { CancelledError, FilterExecutionError, InvalidError, TransportError, UnsupportedError } from "../client/errors.js";
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
3
|
+
import { CancelledError, FilterExecutionError, InvalidError, TimeoutError, TransportError, UnsupportedError } from "../client/errors.js";
|
|
4
|
+
import { Json } from "./codecs.js";
|
|
5
|
+
const DEFAULT_BATCH_LENGTH = 1000;
|
|
6
|
+
const DEFAULT_POLL_INTERVAL_MS = 0;
|
|
7
|
+
const DEFAULT_POLLING_RETRY_INTERVAL_MS = 1_000;
|
|
8
|
+
const DEFAULT_START_AT = { kind: "next" };
|
|
6
9
|
function throwIfAborted(signal, message) {
|
|
7
10
|
if (signal?.aborted === true)
|
|
8
11
|
throw new CancelledError(message, { cause: signal.reason });
|
|
@@ -24,22 +27,52 @@ function delay(ms, signal) {
|
|
|
24
27
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
25
28
|
});
|
|
26
29
|
}
|
|
30
|
+
function commitInterval(policy) {
|
|
31
|
+
return "intervalMs" in policy ? policy.intervalMs : undefined;
|
|
32
|
+
}
|
|
33
|
+
function validateCommitPolicy(policy) {
|
|
34
|
+
const interval = commitInterval(policy);
|
|
35
|
+
if (interval !== undefined && (!Number.isFinite(interval) || interval <= 0)) {
|
|
36
|
+
throw new InvalidError("commit policy intervalMs must be a positive finite number");
|
|
37
|
+
}
|
|
38
|
+
if ("count" in policy && (!Number.isSafeInteger(policy.count) || policy.count < 1)) {
|
|
39
|
+
throw new InvalidError("commit policy count must be a positive safe integer");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
27
42
|
/** Apply the defaults and reject values no consumer can run with. */
|
|
28
43
|
export function resolveConsumerOptions(options, anonymous) {
|
|
44
|
+
const commitPolicy = options.commitPolicy ?? (anonymous ? { kind: "disabled" } : { kind: "polling" });
|
|
45
|
+
validateCommitPolicy(commitPolicy);
|
|
46
|
+
const initRetries = options.initRetries ?? { retries: 0, intervalMs: 0 };
|
|
29
47
|
const resolved = {
|
|
30
48
|
batchLength: options.batchLength ?? DEFAULT_BATCH_LENGTH,
|
|
31
|
-
autoCommit:
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
autoCommit: commitPolicy.kind !== "disabled",
|
|
50
|
+
commitPolicy,
|
|
51
|
+
startAt: options.startAt ?? DEFAULT_START_AT,
|
|
52
|
+
pollIntervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
53
|
+
autoJoinGroup: options.autoJoinGroup ?? true,
|
|
54
|
+
createGroup: options.createGroup ?? true,
|
|
55
|
+
pollingRetryIntervalMs: options.pollingRetryIntervalMs ?? DEFAULT_POLLING_RETRY_INTERVAL_MS,
|
|
56
|
+
initRetries,
|
|
57
|
+
allowReplay: options.allowReplay ?? false
|
|
34
58
|
};
|
|
59
|
+
if (!Number.isFinite(resolved.pollingRetryIntervalMs) || resolved.pollingRetryIntervalMs <= 0) {
|
|
60
|
+
throw new InvalidError("consumer pollingRetryIntervalMs must be a positive finite number");
|
|
61
|
+
}
|
|
62
|
+
if (!Number.isSafeInteger(initRetries.retries) ||
|
|
63
|
+
initRetries.retries < 0 ||
|
|
64
|
+
!Number.isFinite(initRetries.intervalMs) ||
|
|
65
|
+
(options.initRetries === undefined ? initRetries.intervalMs < 0 : initRetries.intervalMs <= 0)) {
|
|
66
|
+
throw new InvalidError("consumer initRetries needs non-negative retries and a positive intervalMs");
|
|
67
|
+
}
|
|
35
68
|
if (!Number.isSafeInteger(resolved.batchLength) || resolved.batchLength < 1) {
|
|
36
69
|
throw new InvalidError("consumer batchLength must be a positive safe integer");
|
|
37
70
|
}
|
|
38
71
|
if (!Number.isFinite(resolved.pollIntervalMs) || resolved.pollIntervalMs < 0) {
|
|
39
72
|
throw new InvalidError("consumer pollIntervalMs must be a non-negative finite number");
|
|
40
73
|
}
|
|
41
|
-
if ((resolved.
|
|
42
|
-
resolved.
|
|
74
|
+
if ((resolved.startAt.kind === "offset" || resolved.startAt.kind === "timestampMicros") &&
|
|
75
|
+
resolved.startAt.value < 0n) {
|
|
43
76
|
throw new InvalidError("consumer start value must be non-negative");
|
|
44
77
|
}
|
|
45
78
|
return resolved;
|
|
@@ -59,20 +92,38 @@ export class Consumer {
|
|
|
59
92
|
topicName;
|
|
60
93
|
target;
|
|
61
94
|
reader;
|
|
95
|
+
pollFailures;
|
|
62
96
|
batchLength;
|
|
63
97
|
autoCommit;
|
|
64
|
-
|
|
98
|
+
commitPolicy;
|
|
99
|
+
startAt;
|
|
65
100
|
pollIntervalMs;
|
|
101
|
+
pollingRetryIntervalMs;
|
|
102
|
+
allowReplay;
|
|
66
103
|
buffer = [];
|
|
104
|
+
// Native deliveries handed back, yielded again before the buffer.
|
|
105
|
+
returned = [];
|
|
106
|
+
storedOffsets = new Map();
|
|
107
|
+
yieldedSinceStore = 0;
|
|
108
|
+
commitTimer;
|
|
109
|
+
latestDelivered;
|
|
67
110
|
// Group reads: the records of the current page, the record each delivered
|
|
68
111
|
// message came from, and whether a delivery waits to be stored.
|
|
69
112
|
records = [];
|
|
70
113
|
delivered = new WeakMap();
|
|
71
|
-
handledSincePoll = false;
|
|
72
114
|
lastDelivery;
|
|
115
|
+
// When a group consumer's commit policy last stored the handled prefix, the
|
|
116
|
+
// records yielded since, and the store decision a failed store left due.
|
|
117
|
+
yieldedSinceFlush = 0;
|
|
118
|
+
lastFlushAt = Date.now();
|
|
119
|
+
pendingCommit;
|
|
73
120
|
moreScanned = false;
|
|
74
121
|
consumedOffsets = new Map();
|
|
75
122
|
explicitOffsets = new Map();
|
|
123
|
+
// The consumed offset a partition had when its server offset was deleted.
|
|
124
|
+
// No commit policy stores that offset again, so the delete stands until a
|
|
125
|
+
// newer record is delivered.
|
|
126
|
+
deletedAt = new Map();
|
|
76
127
|
nextOffsets = new Map();
|
|
77
128
|
assignedPartitions = new Set();
|
|
78
129
|
assignmentSeen = false;
|
|
@@ -80,12 +131,16 @@ export class Consumer {
|
|
|
80
131
|
gainedPartitions = new Set();
|
|
81
132
|
started = false;
|
|
82
133
|
shuttingDown = false;
|
|
83
|
-
|
|
134
|
+
polling;
|
|
135
|
+
// A native group consumer leaves at shutdown only the group it joined.
|
|
136
|
+
leavesGroup;
|
|
137
|
+
constructor(transport, streamName, topicName, target, options = {}, reader, pollFailures = "retry") {
|
|
84
138
|
this.transport = transport;
|
|
85
139
|
this.streamName = streamName;
|
|
86
140
|
this.topicName = topicName;
|
|
87
141
|
this.target = target;
|
|
88
142
|
this.reader = reader;
|
|
143
|
+
this.pollFailures = pollFailures;
|
|
89
144
|
const anonymous = target.kind === "single" && target.name === undefined;
|
|
90
145
|
if (anonymous)
|
|
91
146
|
this.target = { ...target, name: `anonymous-${mintUlidValue().toString(16)}` };
|
|
@@ -96,9 +151,22 @@ export class Consumer {
|
|
|
96
151
|
const resolved = resolveConsumerOptions(options, anonymous);
|
|
97
152
|
this.batchLength = resolved.batchLength;
|
|
98
153
|
this.autoCommit = resolved.autoCommit;
|
|
99
|
-
this.
|
|
154
|
+
this.commitPolicy = resolved.commitPolicy;
|
|
155
|
+
this.startAt = resolved.startAt;
|
|
100
156
|
this.pollIntervalMs = resolved.pollIntervalMs;
|
|
157
|
+
this.pollingRetryIntervalMs = resolved.pollingRetryIntervalMs;
|
|
158
|
+
this.allowReplay = resolved.allowReplay;
|
|
159
|
+
this.leavesGroup = this.target.kind === "group" && resolved.autoJoinGroup;
|
|
101
160
|
}
|
|
161
|
+
/** @internal */
|
|
162
|
+
static create(transport, streamName, topicName, target, options = {}, reader, pollFailures = "retry") {
|
|
163
|
+
return new Consumer(transport, streamName, topicName, target, options, reader, pollFailures);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Waits for the next record, bounding how long the caller sits idle. Fails
|
|
167
|
+
* with `TimeoutError` past `timeoutMs` and with `InvalidError` once the
|
|
168
|
+
* consumer was shut down, like the Rust `next_within`.
|
|
169
|
+
*/
|
|
102
170
|
async nextWithin(timeoutMs, options = {}) {
|
|
103
171
|
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
104
172
|
throw new InvalidError("nextWithin timeout must be a non-negative finite number");
|
|
@@ -106,21 +174,23 @@ export class Consumer {
|
|
|
106
174
|
const deadline = Date.now() + timeoutMs;
|
|
107
175
|
for (;;) {
|
|
108
176
|
if (this.stopped())
|
|
109
|
-
|
|
177
|
+
throw new InvalidError("the live consumer stream ended");
|
|
110
178
|
throwIfAborted(options.signal, "nextWithin aborted");
|
|
111
|
-
this.
|
|
179
|
+
await this.finishDelivery();
|
|
112
180
|
const message = this.take();
|
|
113
|
-
if (message !== undefined)
|
|
181
|
+
if (message !== undefined) {
|
|
182
|
+
await this.afterYield(message);
|
|
114
183
|
return message;
|
|
115
|
-
|
|
184
|
+
}
|
|
185
|
+
await this.pollOnce(options.signal, deadline);
|
|
116
186
|
throwIfAborted(options.signal, "nextWithin aborted");
|
|
117
187
|
if (this.stopped())
|
|
118
|
-
|
|
188
|
+
throw new InvalidError("the live consumer stream ended");
|
|
119
189
|
if (this.pending())
|
|
120
190
|
continue;
|
|
121
191
|
const remaining = deadline - Date.now();
|
|
122
192
|
if (remaining <= 0)
|
|
123
|
-
|
|
193
|
+
throw new TimeoutError("the live consumer to yield a record");
|
|
124
194
|
await delay(this.moreScanned ? 0 : Math.min(this.pollIntervalMs, remaining), options.signal);
|
|
125
195
|
}
|
|
126
196
|
}
|
|
@@ -130,13 +200,14 @@ export class Consumer {
|
|
|
130
200
|
async *stream(options = {}) {
|
|
131
201
|
while (!this.stopped()) {
|
|
132
202
|
throwIfAborted(options.signal, "consumer stream aborted");
|
|
133
|
-
this.
|
|
203
|
+
await this.finishDelivery();
|
|
134
204
|
const message = this.take();
|
|
135
205
|
if (message !== undefined) {
|
|
206
|
+
await this.afterYield(message);
|
|
136
207
|
yield message;
|
|
137
208
|
continue;
|
|
138
209
|
}
|
|
139
|
-
await this.
|
|
210
|
+
await this.pollOnce(options.signal);
|
|
140
211
|
throwIfAborted(options.signal, "consumer stream aborted");
|
|
141
212
|
if (this.stopped())
|
|
142
213
|
return;
|
|
@@ -145,6 +216,29 @@ export class Consumer {
|
|
|
145
216
|
}
|
|
146
217
|
}
|
|
147
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Hands back the latest delivery that the caller could not process, so the
|
|
221
|
+
* next read yields it again. Only the most recent delivery can be returned.
|
|
222
|
+
*/
|
|
223
|
+
[INTERNAL_RETURN_DELIVERY](message) {
|
|
224
|
+
if (this.stopped())
|
|
225
|
+
throw new InvalidError("consumer has been shut down");
|
|
226
|
+
if (message !== this.latestDelivered) {
|
|
227
|
+
throw new InvalidError("the delivery is not the latest one from this consumer");
|
|
228
|
+
}
|
|
229
|
+
this.latestDelivered = undefined;
|
|
230
|
+
if (this.reader !== undefined) {
|
|
231
|
+
const record = this.delivered.get(message);
|
|
232
|
+
if (record === undefined) {
|
|
233
|
+
throw new InvalidError("the record was not delivered by this group consumer");
|
|
234
|
+
}
|
|
235
|
+
if (this.lastDelivery?.record === record)
|
|
236
|
+
this.lastDelivery = undefined;
|
|
237
|
+
this.records.unshift(record);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this.returned.unshift(message);
|
|
241
|
+
}
|
|
148
242
|
/**
|
|
149
243
|
* Store a handled message's offset on the server. A group consumer on a
|
|
150
244
|
* server that resolves group policies stores the contiguous prefix of the
|
|
@@ -157,18 +251,65 @@ export class Consumer {
|
|
|
157
251
|
throw new InvalidError("commit the message object this group consumer delivered");
|
|
158
252
|
}
|
|
159
253
|
await this.reader.ackThrough(record);
|
|
160
|
-
this.explicitOffsets.set(message.partitionId, message.offset);
|
|
254
|
+
this.explicitOffsets.set(message.partitionId, message.position.offset);
|
|
255
|
+
this.storedOffsets.set(message.partitionId, message.position.offset);
|
|
161
256
|
return;
|
|
162
257
|
}
|
|
163
|
-
await this.transport.storeOffset(this.streamName, this.topicName, this.target, message.partitionId, message.offset);
|
|
164
|
-
this.explicitOffsets.set(message.partitionId, message.offset);
|
|
258
|
+
await this.transport.storeOffset(this.streamName, this.topicName, this.target, message.partitionId, message.position.offset);
|
|
259
|
+
this.explicitOffsets.set(message.partitionId, message.position.offset);
|
|
260
|
+
this.storedOffsets.set(message.partitionId, message.position.offset);
|
|
165
261
|
}
|
|
166
262
|
lastConsumedOffset(partitionId) {
|
|
167
263
|
return this.consumedOffsets.get(partitionId);
|
|
168
264
|
}
|
|
265
|
+
/** The last offset this consumer stored for `partitionId`, by `commit`,
|
|
266
|
+
* `storeOffset`, or its commit policy. Local bookkeeping: for server-side
|
|
267
|
+
* resume, start from `next`. */
|
|
268
|
+
lastStoredOffset(partitionId) {
|
|
269
|
+
return this.storedOffsets.get(partitionId);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Stores an explicit server offset for `partitionId`, or for the partition
|
|
273
|
+
* of the latest delivery when omitted. Native only: a policy-aware group
|
|
274
|
+
* consumer refuses it, because an arbitrary offset bypasses the group's
|
|
275
|
+
* acknowledgment contract. Commit a delivered record instead.
|
|
276
|
+
*/
|
|
277
|
+
async storeOffset(offset, partitionId) {
|
|
278
|
+
this.requireNative("storeOffset");
|
|
279
|
+
if (offset < 0n)
|
|
280
|
+
throw new InvalidError("offset must be non-negative");
|
|
281
|
+
const partition = this.currentPartition(partitionId);
|
|
282
|
+
await this.transport.storeOffset(this.streamName, this.topicName, this.target, partition, offset);
|
|
283
|
+
this.explicitOffsets.set(partition, offset);
|
|
284
|
+
this.storedOffsets.set(partition, offset);
|
|
285
|
+
}
|
|
286
|
+
/** Deletes the server offset for `partitionId`, or for the partition of the
|
|
287
|
+
* latest delivery when omitted. Native only, like `storeOffset`. */
|
|
288
|
+
async deleteOffset(partitionId) {
|
|
289
|
+
this.requireNative("deleteOffset");
|
|
290
|
+
if (this.transport.deleteOffset === undefined) {
|
|
291
|
+
throw new UnsupportedError("the Apache Iggy client does not expose offset deletion", {
|
|
292
|
+
surface: "stream"
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const partition = this.currentPartition(partitionId);
|
|
296
|
+
await this.transport.deleteOffset(this.streamName, this.topicName, this.target, partition);
|
|
297
|
+
this.explicitOffsets.delete(partition);
|
|
298
|
+
this.storedOffsets.delete(partition);
|
|
299
|
+
const consumed = this.consumedOffsets.get(partition);
|
|
300
|
+
if (consumed === undefined)
|
|
301
|
+
this.deletedAt.delete(partition);
|
|
302
|
+
else
|
|
303
|
+
this.deletedAt.set(partition, consumed);
|
|
304
|
+
}
|
|
305
|
+
/** The offset the server stores for this consumer, its group or its
|
|
306
|
+
* name, on `partitionId`, with the partition head. `undefined` when nothing
|
|
307
|
+
* is stored. */
|
|
169
308
|
async storedOffset(partitionId) {
|
|
170
309
|
if (this.transport.getConsumerOffset === undefined) {
|
|
171
|
-
throw new UnsupportedError("the Apache Iggy client does not expose consumer offsets"
|
|
310
|
+
throw new UnsupportedError("the Apache Iggy client does not expose consumer offsets", {
|
|
311
|
+
surface: "stream"
|
|
312
|
+
});
|
|
172
313
|
}
|
|
173
314
|
const offsetTarget = this.target.kind === "group"
|
|
174
315
|
? { kind: "group", name: this.target.name }
|
|
@@ -188,24 +329,35 @@ export class Consumer {
|
|
|
188
329
|
if (this.shuttingDown)
|
|
189
330
|
return;
|
|
190
331
|
this.shuttingDown = true;
|
|
332
|
+
if (this.commitTimer !== undefined) {
|
|
333
|
+
clearInterval(this.commitTimer);
|
|
334
|
+
this.commitTimer = undefined;
|
|
335
|
+
}
|
|
191
336
|
if (this.reader !== undefined) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
337
|
+
try {
|
|
338
|
+
await this.finishDelivery();
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
this.records.length = 0;
|
|
342
|
+
await this.reader.close();
|
|
343
|
+
}
|
|
195
344
|
return;
|
|
196
345
|
}
|
|
197
346
|
try {
|
|
198
347
|
if (this.autoCommit) {
|
|
199
348
|
for (const [partition, offset] of this.consumedOffsets) {
|
|
200
349
|
const explicit = this.explicitOffsets.get(partition);
|
|
350
|
+
if (this.deletedAt.get(partition) === offset)
|
|
351
|
+
continue;
|
|
201
352
|
if (explicit === undefined || explicit < offset) {
|
|
202
353
|
await this.transport.storeOffset(this.streamName, this.topicName, this.target, partition, offset);
|
|
354
|
+
this.storedOffsets.set(partition, offset);
|
|
203
355
|
}
|
|
204
356
|
}
|
|
205
357
|
}
|
|
206
358
|
}
|
|
207
359
|
finally {
|
|
208
|
-
if (this.target.kind === "group") {
|
|
360
|
+
if (this.leavesGroup && this.target.kind === "group") {
|
|
209
361
|
await this.transport.leaveConsumerGroup(this.streamName, this.topicName, this.target.name);
|
|
210
362
|
}
|
|
211
363
|
}
|
|
@@ -218,9 +370,17 @@ export class Consumer {
|
|
|
218
370
|
// the prefix through it handled when the consumer commits on its own.
|
|
219
371
|
take() {
|
|
220
372
|
if (this.reader === undefined) {
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
this.
|
|
373
|
+
const returned = this.returned.shift();
|
|
374
|
+
if (returned !== undefined) {
|
|
375
|
+
this.latestDelivered = returned;
|
|
376
|
+
return returned;
|
|
377
|
+
}
|
|
378
|
+
const polled = this.buffer.shift();
|
|
379
|
+
if (polled === undefined)
|
|
380
|
+
return undefined;
|
|
381
|
+
this.consumedOffsets.set(polled.partitionId, polled.offset);
|
|
382
|
+
const message = withJson(polled, polled.headersMalformed !== undefined);
|
|
383
|
+
this.latestDelivered = message;
|
|
224
384
|
return message;
|
|
225
385
|
}
|
|
226
386
|
let record = this.records.shift();
|
|
@@ -229,51 +389,213 @@ export class Consumer {
|
|
|
229
389
|
if (record === undefined)
|
|
230
390
|
return undefined;
|
|
231
391
|
if (this.autoCommit)
|
|
232
|
-
this.lastDelivery = record;
|
|
392
|
+
this.lastDelivery = { record, lastOfPage: this.records.length === 0 };
|
|
233
393
|
this.consumedOffsets.set(record.partitionId, record.offset);
|
|
234
|
-
const message =
|
|
235
|
-
payload: record.payload,
|
|
236
|
-
partitionId: record.partitionId,
|
|
237
|
-
offset: record.offset,
|
|
238
|
-
...(record.timestampMicros !== undefined ? { timestampMicros: record.timestampMicros } : {}),
|
|
239
|
-
headers: record.headers
|
|
240
|
-
};
|
|
394
|
+
const message = record.message;
|
|
241
395
|
this.delivered.set(message, record);
|
|
396
|
+
this.latestDelivered = message;
|
|
242
397
|
return message;
|
|
243
398
|
}
|
|
399
|
+
// The native commit policies that store after yielding. `polling` stores on
|
|
400
|
+
// the server during the poll itself, and a group reader stores its handled
|
|
401
|
+
// prefix on its own. A failed store never costs the caller the record: the
|
|
402
|
+
// offset stays pending, and the next yield, timer tick, or shutdown stores
|
|
403
|
+
// it, like the Rust consumer.
|
|
404
|
+
async afterYield(message) {
|
|
405
|
+
if (this.reader !== undefined)
|
|
406
|
+
return;
|
|
407
|
+
const policy = this.commitPolicy;
|
|
408
|
+
this.yieldedSinceStore += 1;
|
|
409
|
+
const store = policy.kind === "each" ||
|
|
410
|
+
policy.kind === "intervalOrEach" ||
|
|
411
|
+
((policy.kind === "all" || policy.kind === "intervalOrAll") && this.buffer.length === 0) ||
|
|
412
|
+
((policy.kind === "every" || policy.kind === "intervalOrEvery") &&
|
|
413
|
+
this.yieldedSinceStore >= policy.count);
|
|
414
|
+
if (!store)
|
|
415
|
+
return;
|
|
416
|
+
try {
|
|
417
|
+
await this.storeConsumed(message.partitionId);
|
|
418
|
+
this.yieldedSinceStore = 0;
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// Stored on a later attempt.
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async storeConsumed(only) {
|
|
425
|
+
for (const [partition, offset] of this.consumedOffsets) {
|
|
426
|
+
if (only !== undefined && partition !== only)
|
|
427
|
+
continue;
|
|
428
|
+
if ((this.storedOffsets.get(partition) ?? -1n) >= offset)
|
|
429
|
+
continue;
|
|
430
|
+
if (this.deletedAt.get(partition) === offset)
|
|
431
|
+
continue;
|
|
432
|
+
await this.transport.storeOffset(this.streamName, this.topicName, this.target, partition, offset);
|
|
433
|
+
this.storedOffsets.set(partition, offset);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
startCommitTimer() {
|
|
437
|
+
const interval = commitInterval(this.commitPolicy);
|
|
438
|
+
if (interval === undefined || this.commitTimer !== undefined || this.reader !== undefined) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
this.commitTimer = setInterval(() => {
|
|
442
|
+
this.storeConsumed().catch(() => undefined);
|
|
443
|
+
}, interval);
|
|
444
|
+
this.commitTimer.unref();
|
|
445
|
+
}
|
|
446
|
+
requireNative(operation) {
|
|
447
|
+
if (this.reader !== undefined) {
|
|
448
|
+
throw new InvalidError(`${operation}() is native only: a policy-aware group consumer commits delivered records`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
currentPartition(partitionId) {
|
|
452
|
+
const partition = partitionId ??
|
|
453
|
+
(this.target.kind === "single" ? this.target.partitionId : undefined) ??
|
|
454
|
+
this.latestDelivered?.partitionId;
|
|
455
|
+
if (partition === undefined || !Number.isSafeInteger(partition) || partition < 0) {
|
|
456
|
+
throw new InvalidError("name a partition: this consumer has no current partition yet");
|
|
457
|
+
}
|
|
458
|
+
return partition;
|
|
459
|
+
}
|
|
460
|
+
// One poll. A retryable transport failure waits the polling retry interval
|
|
461
|
+
// and reports an empty poll, so the read loop keeps going after a server
|
|
462
|
+
// restart, like the Rust consumer. The wait never runs past `deadline`.
|
|
463
|
+
async pollOnce(signal, deadline) {
|
|
464
|
+
this.startCommitTimer();
|
|
465
|
+
try {
|
|
466
|
+
this.polling ??= this.fillBuffer();
|
|
467
|
+
await this.waitForPoll(this.polling, signal, deadline);
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
if (this.pollFailures === "propagate" ||
|
|
471
|
+
!(error instanceof TransportError) ||
|
|
472
|
+
!error.retryable ||
|
|
473
|
+
this.stopped()) {
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
const wait = deadline === undefined
|
|
477
|
+
? this.pollingRetryIntervalMs
|
|
478
|
+
: Math.min(this.pollingRetryIntervalMs, Math.max(0, deadline - Date.now()));
|
|
479
|
+
await delay(wait, signal);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
waitForPoll(poll, signal, deadline) {
|
|
483
|
+
return new Promise((resolve, reject) => {
|
|
484
|
+
let waiting = true;
|
|
485
|
+
let timer;
|
|
486
|
+
const finish = (complete, observed) => {
|
|
487
|
+
if (!waiting)
|
|
488
|
+
return;
|
|
489
|
+
waiting = false;
|
|
490
|
+
clearTimeout(timer);
|
|
491
|
+
signal?.removeEventListener("abort", onAbort);
|
|
492
|
+
if (observed && this.polling === poll)
|
|
493
|
+
this.polling = undefined;
|
|
494
|
+
complete();
|
|
495
|
+
};
|
|
496
|
+
const onAbort = () => {
|
|
497
|
+
finish(() => {
|
|
498
|
+
reject(new CancelledError("consumer poll aborted", { cause: signal?.reason }));
|
|
499
|
+
}, false);
|
|
500
|
+
};
|
|
501
|
+
poll.then(() => {
|
|
502
|
+
if (deadline !== undefined && Date.now() > deadline) {
|
|
503
|
+
finish(() => {
|
|
504
|
+
reject(new TimeoutError("the live consumer to yield a record"));
|
|
505
|
+
}, true);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
finish(resolve, true);
|
|
509
|
+
}
|
|
510
|
+
}, (error) => {
|
|
511
|
+
finish(() => {
|
|
512
|
+
reject(error instanceof Error
|
|
513
|
+
? error
|
|
514
|
+
: new TransportError("consumer poll failed", true, { cause: error }));
|
|
515
|
+
}, true);
|
|
516
|
+
});
|
|
517
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
518
|
+
if (signal?.aborted === true) {
|
|
519
|
+
onAbort();
|
|
520
|
+
}
|
|
521
|
+
else if (deadline !== undefined) {
|
|
522
|
+
const tick = () => {
|
|
523
|
+
if (!waiting)
|
|
524
|
+
return;
|
|
525
|
+
const remaining = deadline - Date.now();
|
|
526
|
+
if (remaining <= 0) {
|
|
527
|
+
finish(() => {
|
|
528
|
+
reject(new TimeoutError("the live consumer to yield a record"));
|
|
529
|
+
}, false);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
timer = setTimeout(tick, Math.min(remaining, 2_147_483_647));
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
tick();
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
244
539
|
pending() {
|
|
245
|
-
return this.reader === undefined
|
|
540
|
+
return this.reader === undefined
|
|
541
|
+
? this.buffer.length > 0 || this.returned.length > 0
|
|
542
|
+
: this.records.length > 0;
|
|
246
543
|
}
|
|
247
544
|
stopped() {
|
|
248
545
|
return this.shuttingDown;
|
|
249
546
|
}
|
|
250
547
|
// Asking for the next record or shutting down confirms that the previous
|
|
251
|
-
// delivery reached the caller
|
|
548
|
+
// delivery reached the caller, and the commit policy decides whether the
|
|
549
|
+
// handled prefix is stored now. A cancelled poll cannot complete a record
|
|
252
550
|
// that the caller never received.
|
|
253
|
-
|
|
254
|
-
if (this.
|
|
551
|
+
async finishDelivery() {
|
|
552
|
+
if (this.reader === undefined)
|
|
553
|
+
return;
|
|
554
|
+
const delivery = this.lastDelivery;
|
|
555
|
+
if (delivery !== undefined) {
|
|
556
|
+
this.lastDelivery = undefined;
|
|
557
|
+
this.reader.handled(delivery.record);
|
|
558
|
+
this.yieldedSinceFlush += 1;
|
|
559
|
+
this.pendingCommit = { kind: "delivered", lastOfPage: delivery.lastOfPage };
|
|
560
|
+
}
|
|
561
|
+
const point = this.pendingCommit;
|
|
562
|
+
if (point === undefined)
|
|
255
563
|
return;
|
|
256
|
-
this.
|
|
257
|
-
this.
|
|
258
|
-
this.handledSincePoll = true;
|
|
564
|
+
await this.flushIfDue(this.reader, point);
|
|
565
|
+
this.pendingCommit = undefined;
|
|
259
566
|
}
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
await reader.flushCompleted();
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
// A policy change fences the prefix read under the old policy. Those
|
|
269
|
-
// records are delivered again from the stored offset, so nothing is
|
|
270
|
-
// lost and the consumer goes on without an error.
|
|
271
|
-
if (!(error instanceof FilterExecutionError) || error.reason !== "conflict")
|
|
272
|
-
throw error;
|
|
273
|
-
this.records.length = 0;
|
|
274
|
-
}
|
|
275
|
-
this.handledSincePoll = false;
|
|
567
|
+
// Stores the handled prefix when the commit policy says so at this point.
|
|
568
|
+
async flushIfDue(reader, point) {
|
|
569
|
+
if (!commitDue(this.commitPolicy, this.yieldedSinceFlush, Date.now() - this.lastFlushAt, point)) {
|
|
570
|
+
return;
|
|
276
571
|
}
|
|
572
|
+
try {
|
|
573
|
+
await reader.flushCompleted();
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
const reason = error instanceof FilterExecutionError ? error.detail.reason : undefined;
|
|
577
|
+
if (reason !== "conflict" && reason !== "source_changed")
|
|
578
|
+
throw error;
|
|
579
|
+
const owned = this.records.filter((record) => reader.owns(record));
|
|
580
|
+
this.records.splice(0, this.records.length, ...owned);
|
|
581
|
+
this.pendingCommit = undefined;
|
|
582
|
+
this.yieldedSinceFlush = 0;
|
|
583
|
+
// A policy change fences the prefix read under the old policy. Those
|
|
584
|
+
// records are delivered again from the stored offset, so nothing is
|
|
585
|
+
// lost and the consumer goes on without an error.
|
|
586
|
+
if (reason !== "conflict")
|
|
587
|
+
throw error;
|
|
588
|
+
this.lastFlushAt = Date.now();
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.yieldedSinceFlush = 0;
|
|
592
|
+
this.lastFlushAt = Date.now();
|
|
593
|
+
}
|
|
594
|
+
// One group poll. A policy that stores at the poll stores the delivered
|
|
595
|
+
// prefix first, so a crash redelivers the current batch instead of
|
|
596
|
+
// skipping it.
|
|
597
|
+
async fillFromGroup(reader) {
|
|
598
|
+
await this.flushIfDue(reader, { kind: "poll" });
|
|
277
599
|
const [page, more] = await reader.readRound();
|
|
278
600
|
this.moreScanned = more;
|
|
279
601
|
if (page !== undefined)
|
|
@@ -290,7 +612,7 @@ export class Consumer {
|
|
|
290
612
|
}
|
|
291
613
|
let target = this.target;
|
|
292
614
|
if (target.kind === "group" &&
|
|
293
|
-
(this.
|
|
615
|
+
(this.startAt.kind !== "next" || !this.autoCommit) &&
|
|
294
616
|
this.transport.syncConsumerGroup !== undefined) {
|
|
295
617
|
const assignment = await this.transport.syncConsumerGroup(this.streamName, this.topicName, target.name);
|
|
296
618
|
if (assignment === undefined)
|
|
@@ -328,15 +650,76 @@ export class Consumer {
|
|
|
328
650
|
: partition !== undefined
|
|
329
651
|
? this.gainedPartitions.has(partition)
|
|
330
652
|
? { kind: "next" }
|
|
331
|
-
: this.
|
|
653
|
+
: this.startAt
|
|
332
654
|
: this.started
|
|
333
655
|
? { kind: "next" }
|
|
334
|
-
: this.
|
|
335
|
-
const
|
|
656
|
+
: this.startAt;
|
|
657
|
+
const commitOnPoll = this.commitPolicy.kind === "polling" || this.commitPolicy.kind === "intervalOrPolling";
|
|
658
|
+
const polled = await this.transport.pollMessages(this.streamName, this.topicName, target, strategy, this.batchLength, commitOnPoll);
|
|
336
659
|
this.started = true;
|
|
337
660
|
for (const message of polled)
|
|
338
661
|
this.nextOffsets.set(message.partitionId, message.offset + 1n);
|
|
339
|
-
this.
|
|
662
|
+
const fresh = this.allowReplay
|
|
663
|
+
? polled
|
|
664
|
+
: polled.filter((message) => {
|
|
665
|
+
const consumed = this.consumedOffsets.get(message.partitionId);
|
|
666
|
+
return consumed === undefined || message.offset > consumed;
|
|
667
|
+
});
|
|
668
|
+
if (commitOnPoll) {
|
|
669
|
+
for (const message of fresh)
|
|
670
|
+
this.storedOffsets.set(message.partitionId, message.offset);
|
|
671
|
+
}
|
|
672
|
+
this.buffer.push(...fresh);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function withJson(message, headersMalformed) {
|
|
676
|
+
const json = (decodeValue) => new Json(decodeValue).decode(message.payload);
|
|
677
|
+
const position = { partitionId: message.partitionId, offset: message.offset };
|
|
678
|
+
const delivered = {
|
|
679
|
+
payload: message.payload,
|
|
680
|
+
id: position,
|
|
681
|
+
partitionId: message.partitionId,
|
|
682
|
+
headers: message.headers,
|
|
683
|
+
position,
|
|
684
|
+
messageId: message.messageId ?? 0n,
|
|
685
|
+
checksum: message.checksum ?? 0n,
|
|
686
|
+
currentOffset: message.currentOffset ?? 0n,
|
|
687
|
+
timestampMicros: message.timestampMicros ?? 0n,
|
|
688
|
+
originTimestampMicros: message.originTimestampMicros ?? 0n,
|
|
689
|
+
...(message.userHeaders !== undefined ? { userHeaders: message.userHeaders } : {}),
|
|
690
|
+
headersMalformed
|
|
691
|
+
};
|
|
692
|
+
// Not enumerable, so a delivered message still compares equal to its plain
|
|
693
|
+
// fields.
|
|
694
|
+
return Object.defineProperty(delivered, "json", { value: json });
|
|
695
|
+
}
|
|
696
|
+
// Whether an automatic commit policy stores the handled prefix now, like the
|
|
697
|
+
// Rust `commit_due`. `yielded` counts records delivered since the last store
|
|
698
|
+
// and `elapsedMs` the time since it.
|
|
699
|
+
function commitDue(policy, yielded, elapsedMs, point) {
|
|
700
|
+
const polling = point.kind === "poll" && yielded > 0;
|
|
701
|
+
const lastOfPage = point.kind === "delivered" && point.lastOfPage;
|
|
702
|
+
const delivered = point.kind === "delivered";
|
|
703
|
+
switch (policy.kind) {
|
|
704
|
+
case "disabled":
|
|
705
|
+
return false;
|
|
706
|
+
case "polling":
|
|
707
|
+
return polling;
|
|
708
|
+
case "interval":
|
|
709
|
+
return elapsedMs >= policy.intervalMs;
|
|
710
|
+
case "intervalOrPolling":
|
|
711
|
+
return polling || elapsedMs >= policy.intervalMs;
|
|
712
|
+
case "all":
|
|
713
|
+
return lastOfPage;
|
|
714
|
+
case "intervalOrAll":
|
|
715
|
+
return lastOfPage || elapsedMs >= policy.intervalMs;
|
|
716
|
+
case "each":
|
|
717
|
+
case "intervalOrEach":
|
|
718
|
+
return delivered && yielded > 0;
|
|
719
|
+
case "every":
|
|
720
|
+
return delivered && yielded >= policy.count;
|
|
721
|
+
case "intervalOrEvery":
|
|
722
|
+
return (delivered && yielded >= policy.count) || elapsedMs >= policy.intervalMs;
|
|
340
723
|
}
|
|
341
724
|
}
|
|
342
725
|
//# sourceMappingURL=consumer.js.map
|