apache-iggy 0.10.0-edge.6 → 0.10.0-edge.7
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/dist/wire/message/send-messages.command.d.ts +2 -3
- package/dist/wire/options.utils.test.js +9 -6
- package/dist/wire/topic/create-topic.command.d.ts +5 -4
- package/dist/wire/topic/create-topic.command.js +15 -6
- package/dist/wire/topic/create-topic.command.test.js +30 -10
- package/dist/wire/topic/index.d.ts +1 -1
- package/dist/wire/topic/index.js +1 -1
- package/dist/wire/topic/topic.utils.d.ts +5 -0
- package/dist/wire/topic/topic.utils.js +4 -0
- package/package.json +1 -1
|
@@ -28,9 +28,8 @@ export type SendMessagesConfirmation = {
|
|
|
28
28
|
*
|
|
29
29
|
* Delivery is at-least-once, so an earlier retry of the same batch may
|
|
30
30
|
* already have committed at a lower offset: this never identifies a batch
|
|
31
|
-
* uniquely.
|
|
32
|
-
*
|
|
33
|
-
* client has already recorded.
|
|
31
|
+
* uniquely. Confirmation follows VSR quorum commit. Persisted message
|
|
32
|
+
* durability also requires recoverable stable-storage copies on the quorum.
|
|
34
33
|
*/
|
|
35
34
|
baseOffset: bigint;
|
|
36
35
|
};
|
|
@@ -28,22 +28,25 @@ const prefixed = (block) => {
|
|
|
28
28
|
*
|
|
29
29
|
* Rust pins the identical bytes in `core/binary_protocol/src/primitives/options.rs`,
|
|
30
30
|
* as do the Go and Java SDKs. Round-tripping through this SDK's own decoder proves
|
|
31
|
-
* nothing about interoperability
|
|
31
|
+
* nothing about interoperability. This insertion-order vector covers Bool, Uint64
|
|
32
|
+
* and String values, independently of Rust's sorted map order.
|
|
32
33
|
*/
|
|
33
34
|
const GOLDEN_OPTIONS_BLOCK = Buffer.from([
|
|
34
|
-
2,
|
|
35
|
-
...Buffer.from('
|
|
35
|
+
2, 20, 0, 0, 0,
|
|
36
|
+
...Buffer.from('preallocate_segments'),
|
|
36
37
|
3, 1, 0, 0, 0, 1,
|
|
37
38
|
2, 12, 0, 0, 0,
|
|
38
39
|
...Buffer.from('segment_size'),
|
|
39
40
|
12, 8, 0, 0, 0,
|
|
40
|
-
0, 0, 0, 64, 0, 0, 0, 0
|
|
41
|
+
0, 0, 0, 64, 0, 0, 0, 0,
|
|
42
|
+
2, 10, 0, 0, 0, 100, 117, 114, 97, 98, 105, 108, 105, 116, 121, 2, 9, 0, 0, 0, 112, 101, 114, 115, 105, 115, 116, 101, 100
|
|
41
43
|
]);
|
|
42
44
|
describe('serializeOptions', () => {
|
|
43
45
|
it('encodes the cross-SDK golden vector byte for byte', () => {
|
|
44
46
|
const encoded = serializeOptions([
|
|
45
|
-
{ key: '
|
|
46
|
-
{ key: 'segment_size', value: HeaderValue.Uint64(1073741824n) }
|
|
47
|
+
{ key: 'preallocate_segments', value: HeaderValue.Bool(true) },
|
|
48
|
+
{ key: 'segment_size', value: HeaderValue.Uint64(1073741824n) },
|
|
49
|
+
{ key: 'durability', value: HeaderValue.String('persisted') }
|
|
47
50
|
]);
|
|
48
51
|
assert.deepEqual(encoded, GOLDEN_OPTIONS_BLOCK);
|
|
49
52
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CommandResponse } from '../../client/client.type.js';
|
|
2
2
|
import { type Id } from '../identifier.utils.js';
|
|
3
3
|
import { type OptionEntry } from '../options.utils.js';
|
|
4
|
-
import { type Topic, type CompressionAlgorithm as CompressionAlgorithmT } from './topic.utils.js';
|
|
4
|
+
import { Durability, type Topic, type CompressionAlgorithm as CompressionAlgorithmT } from './topic.utils.js';
|
|
5
5
|
/**
|
|
6
6
|
* Parameters for the create topic command.
|
|
7
7
|
*/
|
|
@@ -28,8 +28,9 @@ export type CreateTopic = {
|
|
|
28
28
|
maxTopicSize?: bigint;
|
|
29
29
|
/** Segment size in bytes: 512-byte multiple between 1 MiB and 1 GiB */
|
|
30
30
|
segmentSize?: bigint;
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/** Message completion policy. Defaults to replicated, independently of offsets. */
|
|
32
|
+
durability?: Durability;
|
|
33
|
+
consumerOffsetDurability?: Durability;
|
|
33
34
|
/** Message count that triggers a save (must be non-zero) */
|
|
34
35
|
messagesRequiredToSave?: number;
|
|
35
36
|
/** Accumulated message bytes that trigger a save */
|
|
@@ -52,7 +53,7 @@ export type CreateTopic = {
|
|
|
52
53
|
*/
|
|
53
54
|
export declare const CREATE_TOPIC: {
|
|
54
55
|
code: number;
|
|
55
|
-
serialize: ({ streamId, name, partitionCount, compressionAlgorithm, messageExpiry, maxTopicSize, segmentSize,
|
|
56
|
+
serialize: ({ streamId, name, partitionCount, compressionAlgorithm, messageExpiry, maxTopicSize, segmentSize, durability, consumerOffsetDurability, messagesRequiredToSave, sizeOfMessagesRequiredToSave, preallocateSegments, options: extraOptions }: CreateTopic) => Buffer<ArrayBuffer>;
|
|
56
57
|
deserialize: (r: CommandResponse) => Topic;
|
|
57
58
|
};
|
|
58
59
|
/**
|
|
@@ -19,7 +19,7 @@ import { wrapCommand } from '../command.utils.js';
|
|
|
19
19
|
import { COMMAND_CODE } from '../command.code.js';
|
|
20
20
|
import { dedupeOptions, serializeOptions } from '../options.utils.js';
|
|
21
21
|
import { HeaderValue } from '../message/header.utils.js';
|
|
22
|
-
import { isValidCompressionAlgorithm, CompressionAlgorithm, compressionAlgorithmName, deserializeTopic } from './topic.utils.js';
|
|
22
|
+
import { isValidCompressionAlgorithm, CompressionAlgorithm, Durability, compressionAlgorithmName, deserializeTopic } from './topic.utils.js';
|
|
23
23
|
/**
|
|
24
24
|
* Create topic command definition.
|
|
25
25
|
* Creates a new topic within a stream.
|
|
@@ -28,7 +28,7 @@ import { isValidCompressionAlgorithm, CompressionAlgorithm, compressionAlgorithm
|
|
|
28
28
|
*/
|
|
29
29
|
export const CREATE_TOPIC = {
|
|
30
30
|
code: COMMAND_CODE.CreateTopic,
|
|
31
|
-
serialize: ({ streamId, name, partitionCount, compressionAlgorithm = CompressionAlgorithm.None, messageExpiry = 0n, maxTopicSize = 0n, segmentSize,
|
|
31
|
+
serialize: ({ streamId, name, partitionCount, compressionAlgorithm = CompressionAlgorithm.None, messageExpiry = 0n, maxTopicSize = 0n, segmentSize, durability = Durability.Replicated, consumerOffsetDurability = Durability.Replicated, messagesRequiredToSave, sizeOfMessagesRequiredToSave, preallocateSegments, options: extraOptions = [] }) => {
|
|
32
32
|
// Topic ID is now auto-assigned by the server, not sent in the protocol
|
|
33
33
|
const streamIdentifier = serializeIdentifier(streamId);
|
|
34
34
|
const bName = Buffer.from(name);
|
|
@@ -60,10 +60,19 @@ export const CREATE_TOPIC = {
|
|
|
60
60
|
options.push({
|
|
61
61
|
key: 'segment_size', value: HeaderValue.Uint64(segmentSize)
|
|
62
62
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
for (const [key, policy] of [
|
|
64
|
+
['durability', durability],
|
|
65
|
+
['consumer_offset_durability', consumerOffsetDurability]
|
|
66
|
+
]) {
|
|
67
|
+
if (policy !== Durability.Replicated && policy !== Durability.Persisted)
|
|
68
|
+
throw new Error(`Invalid ${key}: ${policy}`);
|
|
69
|
+
const expected = HeaderValue.String(policy);
|
|
70
|
+
for (const entry of extraOptions) {
|
|
71
|
+
if (entry.key === key && (entry.value.kind !== expected.kind || entry.value.value !== expected.value))
|
|
72
|
+
throw new Error(`Conflicting ${key}`);
|
|
73
|
+
}
|
|
74
|
+
options.push({ key, value: expected });
|
|
75
|
+
}
|
|
67
76
|
if (messagesRequiredToSave !== undefined)
|
|
68
77
|
options.push({
|
|
69
78
|
key: 'messages_required_to_save',
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// under the License.
|
|
17
17
|
import { describe, it } from 'node:test';
|
|
18
18
|
import assert from 'node:assert/strict';
|
|
19
|
+
import { Durability } from './topic.utils.js';
|
|
19
20
|
import { CREATE_TOPIC } from './create-topic.command.js';
|
|
20
21
|
import { deserializeOptions } from '../options.utils.js';
|
|
21
22
|
import { HeaderValue } from '../message/header.utils.js';
|
|
@@ -32,17 +33,19 @@ describe('CreateTopic', () => {
|
|
|
32
33
|
// TLV field: [kind:u8][len:u32_le][bytes]
|
|
33
34
|
const tlvSize = (bytes) => 1 + 4 + bytes;
|
|
34
35
|
const identifierSize = 1 + 1 + 4; // numeric stream id
|
|
36
|
+
const defaultPolicySize = tlvSize('durability'.length) + tlvSize('replicated'.length)
|
|
37
|
+
+ tlvSize('consumer_offset_durability'.length) + tlvSize('replicated'.length);
|
|
35
38
|
const fixedSize = identifierSize + 4 + 1; // + partitions_count + name_len
|
|
36
39
|
it('serialize name and default options into buffer', () => {
|
|
37
|
-
//
|
|
38
|
-
assert.deepEqual(CREATE_TOPIC.serialize(t1).length, fixedSize + t1.name.length);
|
|
40
|
+
// Durability defaults are explicit, while the other sentinels are omitted.
|
|
41
|
+
assert.deepEqual(CREATE_TOPIC.serialize(t1).length, fixedSize + t1.name.length + defaultPolicySize);
|
|
39
42
|
});
|
|
40
43
|
it('serialize partitionCount as a fixed u32 before the name', () => {
|
|
41
44
|
const t = { ...t1, partitionCount: 7 };
|
|
42
45
|
const b = CREATE_TOPIC.serialize(t);
|
|
43
46
|
assert.equal(b.readUInt32LE(identifierSize), 7);
|
|
44
47
|
assert.equal(b.readUInt8(identifierSize + 4), t.name.length);
|
|
45
|
-
assert.equal(b.subarray(fixedSize).toString(), t.name);
|
|
48
|
+
assert.equal(b.subarray(fixedSize, fixedSize + t.name.length).toString(), t.name);
|
|
46
49
|
});
|
|
47
50
|
it('serialize non-default options into buffer', () => {
|
|
48
51
|
const t = {
|
|
@@ -51,7 +54,7 @@ describe('CreateTopic', () => {
|
|
|
51
54
|
messageExpiry: 42n,
|
|
52
55
|
maxTopicSize: 1024n
|
|
53
56
|
};
|
|
54
|
-
assert.deepEqual(CREATE_TOPIC.serialize(t).length, fixedSize + t1.name.length
|
|
57
|
+
assert.deepEqual(CREATE_TOPIC.serialize(t).length, fixedSize + t1.name.length + defaultPolicySize
|
|
55
58
|
+ tlvSize('compression_algorithm'.length) + tlvSize('gzip'.length)
|
|
56
59
|
+ tlvSize('message_expiry'.length) + tlvSize(8)
|
|
57
60
|
+ tlvSize('max_topic_size'.length) + tlvSize(8));
|
|
@@ -60,14 +63,14 @@ describe('CreateTopic', () => {
|
|
|
60
63
|
const t = {
|
|
61
64
|
...t1,
|
|
62
65
|
segmentSize: 1048576n,
|
|
63
|
-
|
|
66
|
+
durability: Durability.Persisted,
|
|
64
67
|
messagesRequiredToSave: 1000,
|
|
65
68
|
sizeOfMessagesRequiredToSave: 4096n,
|
|
66
69
|
preallocateSegments: false
|
|
67
70
|
};
|
|
68
|
-
assert.deepEqual(CREATE_TOPIC.serialize(t).length, fixedSize + t1.name.length
|
|
71
|
+
assert.deepEqual(CREATE_TOPIC.serialize(t).length, fixedSize + t1.name.length + defaultPolicySize
|
|
69
72
|
+ tlvSize('segment_size'.length) + tlvSize(8)
|
|
70
|
-
+
|
|
73
|
+
+ 'persisted'.length - 'replicated'.length
|
|
71
74
|
+ tlvSize('messages_required_to_save'.length) + tlvSize(4)
|
|
72
75
|
+ tlvSize('size_of_messages_required_to_save'.length) + tlvSize(8)
|
|
73
76
|
+ tlvSize('preallocate_segments'.length) + tlvSize(1));
|
|
@@ -77,7 +80,7 @@ describe('CreateTopic', () => {
|
|
|
77
80
|
...t1,
|
|
78
81
|
maxTopicSize: 4096n,
|
|
79
82
|
options: [
|
|
80
|
-
{ key: '
|
|
83
|
+
{ key: 'preallocate_segments', value: HeaderValue.Bool(true) },
|
|
81
84
|
// The typed field covers this key, so the caller's entry is dropped:
|
|
82
85
|
// a duplicate key makes the server refuse the whole block.
|
|
83
86
|
{ key: 'max_topic_size', value: HeaderValue.String('1 GiB') }
|
|
@@ -86,10 +89,27 @@ describe('CreateTopic', () => {
|
|
|
86
89
|
const b = CREATE_TOPIC.serialize(t);
|
|
87
90
|
// The create payload runs its options block to the end, unprefixed.
|
|
88
91
|
const options = deserializeOptions(b, fixedSize + t.name.length);
|
|
89
|
-
assert.deepEqual(Object.keys(options).sort(), ['
|
|
90
|
-
assert.equal(options.
|
|
92
|
+
assert.deepEqual(Object.keys(options).sort(), ['consumer_offset_durability', 'durability', 'max_topic_size', 'preallocate_segments']);
|
|
93
|
+
assert.equal(options.preallocate_segments, true);
|
|
91
94
|
assert.equal(options.max_topic_size, 4096n);
|
|
92
95
|
});
|
|
96
|
+
it('keeps each omitted durability policy replicated', () => {
|
|
97
|
+
for (const selected of [
|
|
98
|
+
{ durability: Durability.Persisted },
|
|
99
|
+
{ consumerOffsetDurability: Durability.Persisted }
|
|
100
|
+
]) {
|
|
101
|
+
const input = { ...t1, ...selected };
|
|
102
|
+
const encoded = CREATE_TOPIC.serialize(input);
|
|
103
|
+
const options = deserializeOptions(encoded, fixedSize + input.name.length);
|
|
104
|
+
assert.equal(options.durability, selected.durability ?? 'replicated');
|
|
105
|
+
assert.equal(options.consumer_offset_durability, selected.consumerOffsetDurability ?? 'replicated');
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
it('rejects a conflicting raw durability instead of weakening it', () => {
|
|
109
|
+
assert.throws(() => CREATE_TOPIC.serialize({ ...t1, options: [
|
|
110
|
+
{ key: 'durability', value: HeaderValue.String('persisted') }
|
|
111
|
+
] }));
|
|
112
|
+
});
|
|
93
113
|
it('throw on name < 1', () => {
|
|
94
114
|
const t = { ...t1, name: '' };
|
|
95
115
|
assert.throws(() => CREATE_TOPIC.serialize(t));
|
|
@@ -5,5 +5,5 @@ export * from './get-topics.command.js';
|
|
|
5
5
|
export * from './purge-topic.command.js';
|
|
6
6
|
export * from './update-topic.command.js';
|
|
7
7
|
export * from './ensure-topic.virtual.command.js';
|
|
8
|
-
export { CompressionAlgorithm } from './topic.utils.js';
|
|
8
|
+
export { CompressionAlgorithm, Durability } from './topic.utils.js';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/wire/topic/index.js
CHANGED
|
@@ -21,5 +21,5 @@ export * from './get-topics.command.js';
|
|
|
21
21
|
export * from './purge-topic.command.js';
|
|
22
22
|
export * from './update-topic.command.js';
|
|
23
23
|
export * from './ensure-topic.virtual.command.js';
|
|
24
|
-
export { CompressionAlgorithm } from './topic.utils.js';
|
|
24
|
+
export { CompressionAlgorithm, Durability } from './topic.utils.js';
|
|
25
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -138,5 +138,10 @@ export declare const deserializeTopic: (p: Buffer, pos?: number) => TopicSeriali
|
|
|
138
138
|
* @returns Array of deserialized topics
|
|
139
139
|
*/
|
|
140
140
|
export declare const deserializeTopics: (p: Buffer, pos?: number) => Topic[];
|
|
141
|
+
export declare const Durability: {
|
|
142
|
+
readonly Replicated: "replicated";
|
|
143
|
+
readonly Persisted: "persisted";
|
|
144
|
+
};
|
|
145
|
+
export type Durability = typeof Durability[keyof typeof Durability];
|
|
141
146
|
export {};
|
|
142
147
|
//# sourceMappingURL=topic.utils.d.ts.map
|