blob: 030d9f0fef196c5d5ae66c953444157165d7939b [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
/**
* `Operation` values and classification, ported from
* `core/binary_protocol/src/consensus/operation.rs` and the command table in
* `core/binary_protocol/src/dispatch.rs`.
*/
import { COMMAND_CODE } from '../command.code.js';
/** `Operation` discriminants a client sends or receives. */
export const Operation = {
Reserved: 0,
Register: 1,
NonReplicated: 2,
Logout: 3,
CreateTopicWithAssignments: 64,
CreatePartitionsWithAssignments: 65,
RemoveConsumerGroupMember: 66,
CompleteConsumerGroupRevocation: 67,
TruncatePartition: 68,
CreateStream: 128,
UpdateStream: 129,
DeleteStream: 130,
PurgeStream: 131,
CreateTopic: 132,
UpdateTopic: 133,
DeleteTopic: 134,
PurgeTopic: 135,
CreatePartitions: 136,
DeletePartitions: 137,
DeleteSegments: 138,
CreateConsumerGroup: 139,
DeleteConsumerGroup: 140,
CreateUser: 141,
UpdateUser: 142,
DeleteUser: 143,
ChangePassword: 144,
UpdatePermissions: 145,
CreatePersonalAccessToken: 146,
DeletePersonalAccessToken: 147,
JoinConsumerGroup: 148,
LeaveConsumerGroup: 149,
SendMessages: 160,
StoreConsumerOffset: 161,
DeleteConsumerOffset: 162
} as const;
const INTERNAL_START = 64;
const METADATA_START = 128;
/**
* Replicated command code to `Operation` mapping, the client half of the
* dispatch table. Codes absent here are sent as non-replicated so the server
* remains authoritative for extension commands unknown to this SDK build.
*/
const REPLICATED_OPERATION: ReadonlyMap<number, number> = new Map([
[COMMAND_CODE.CreateUser, Operation.CreateUser],
[COMMAND_CODE.DeleteUser, Operation.DeleteUser],
[COMMAND_CODE.UpdateUser, Operation.UpdateUser],
[COMMAND_CODE.UpdatePermissions, Operation.UpdatePermissions],
[COMMAND_CODE.ChangePassword, Operation.ChangePassword],
[COMMAND_CODE.CreateAccessToken, Operation.CreatePersonalAccessToken],
[COMMAND_CODE.DeleteAccessToken, Operation.DeletePersonalAccessToken],
[COMMAND_CODE.SendMessages, Operation.SendMessages],
[COMMAND_CODE.StoreOffset, Operation.StoreConsumerOffset],
[COMMAND_CODE.DeleteConsumerOffset, Operation.DeleteConsumerOffset],
[COMMAND_CODE.CreateStream, Operation.CreateStream],
[COMMAND_CODE.DeleteStream, Operation.DeleteStream],
[COMMAND_CODE.UpdateStream, Operation.UpdateStream],
[COMMAND_CODE.PurgeStream, Operation.PurgeStream],
[COMMAND_CODE.CreateTopic, Operation.CreateTopic],
[COMMAND_CODE.DeleteTopic, Operation.DeleteTopic],
[COMMAND_CODE.UpdateTopic, Operation.UpdateTopic],
[COMMAND_CODE.PurgeTopic, Operation.PurgeTopic],
[COMMAND_CODE.CreatePartitions, Operation.CreatePartitions],
[COMMAND_CODE.DeletePartitions, Operation.DeletePartitions],
[COMMAND_CODE.DeleteSegments, Operation.DeleteSegments],
[COMMAND_CODE.CreateGroup, Operation.CreateConsumerGroup],
[COMMAND_CODE.DeleteGroup, Operation.DeleteConsumerGroup],
[COMMAND_CODE.JoinGroup, Operation.JoinConsumerGroup],
[COMMAND_CODE.LeaveGroup, Operation.LeaveConsumerGroup]
]);
const KNOWN_OPERATIONS: ReadonlySet<number> =
new Set(Object.values(Operation));
/**
* Whether the byte is a declared `Operation` discriminant. Replies carry a
* server-controlled operation byte; Rust validates it with a checked cast,
* so an undeclared value must be rejected, not classified by range.
*/
export const isKnownOperation = (operation: number): boolean =>
KNOWN_OPERATIONS.has(operation);
/** Internal band, never client-sent. */
export const isInternal = (operation: number): boolean =>
operation >= INTERNAL_START && operation < METADATA_START;
/**
* Metadata classification is an explicit allowlist, not a range:
* `DeleteSegments` (138) sits inside the band but resolves to a partition
* truncation server-side, so it is deliberately excluded. Port of
* `Operation::is_metadata`.
*/
export const isMetadata = (operation: number): boolean => {
if (isInternal(operation)) return true;
if (operation === Operation.DeleteSegments) return false;
return operation >= METADATA_START &&
operation <= Operation.LeaveConsumerGroup;
};
/** Whether a reply body leads with a committed result section. */
export const isResultFramed = (operation: number): boolean =>
isMetadata(operation) ||
operation === Operation.StoreConsumerOffset ||
operation === Operation.DeleteConsumerOffset;
/**
* Picks the header operation for a command code. Unknown extension codes are
* sent as non-replicated with their command code in the reserved header field,
* allowing the server to classify or reject them.
*/
export const operationForCode = (code: number): number => {
// The dispatch table files logout as non-replicated, but VSR routes it
// through its own consensus operation; the raw API blocks code 39 before
// classification, so only the typed logout path reaches this branch.
if (code === COMMAND_CODE.LogoutUser)
return Operation.Logout;
return REPLICATED_OPERATION.get(code) ?? Operation.NonReplicated;
};