@ydbjs/topic
Version:
YDB Topics client for publish-subscribe messaging. Provides at-least-once delivery, exactly-once publishing, FIFO guarantees, and scalable message processing for unstructured data.
102 lines • 4.6 kB
JavaScript
import { setInterval } from 'node:timers/promises';
import { create } from '@bufbuild/protobuf';
import { StatusIds_StatusCode } from '@ydbjs/api/operation';
import { StreamWriteMessage_FromClientSchema, StreamWriteMessage_InitRequestSchema, StreamWriteMessage_WriteRequestSchema, TopicServiceDefinition, UpdateTokenRequestSchema, } from '@ydbjs/api/topic';
import { loggers } from '@ydbjs/debug';
import { YDBError } from '@ydbjs/error';
import { fromCallback } from 'xstate';
import { AsyncPriorityQueue } from "../queue.js";
import { _batch_messages } from "../writer/_batch_messages.js";
import { _send_init_request } from "../writer/_init_request.js";
import { _send_update_token_request } from "../writer/_update_token.js";
import { _emit_write_request } from "../writer/_write_request.js";
const DEFAULT_UPDATE_TOKEN_INTERVAL_MS = 60_000;
export const WriterStream = fromCallback(({ input, sendBack, receive }) => {
let ac = new AbortController();
let queue = new AsyncPriorityQueue();
// Handle incoming commands
receive((event) => {
switch (event.type) {
case 'writer.stream.request.init':
loggers.grpc.log(`%s/%s`, TopicServiceDefinition.streamWrite.path, StreamWriteMessage_InitRequestSchema.typeName);
return queue.push(create(StreamWriteMessage_FromClientSchema, {
clientMessage: {
case: 'initRequest',
value: event.data
}
}), 100);
case 'writer.stream.request.write':
loggers.grpc.log(`%s/%s`, TopicServiceDefinition.streamWrite.path, StreamWriteMessage_WriteRequestSchema.typeName);
return queue.push(create(StreamWriteMessage_FromClientSchema, {
clientMessage: {
case: 'writeRequest',
value: event.data
}
}));
}
});
let stream = async () => {
await input.driver.ready(ac.signal);
let stream = input.driver
.createClient(TopicServiceDefinition)
.streamWrite(queue, { signal: ac.signal });
sendBack({ type: 'writer.stream.start' });
for await (let event of stream) {
loggers.grpc.log(`%s/%s`, TopicServiceDefinition.streamWrite.path, event.serverMessage.value?.$typeName, event.status);
if (ac.signal.aborted) {
break;
}
if (event.status !== StatusIds_StatusCode.SUCCESS) {
throw new YDBError(event.status, event.issues);
}
switch (event.serverMessage.case) {
case 'initResponse':
sendBack({
type: 'writer.stream.response.init',
data: event.serverMessage.value
});
break;
case 'writeResponse':
sendBack({
type: 'writer.stream.response.write',
data: event.serverMessage.value
});
break;
case 'updateTokenResponse':
sendBack({
type: 'writer.stream.update.response.token',
data: event.serverMessage.value
});
break;
default:
sendBack({
type: 'writer.stream.error',
error: new Error('Received unknown message type: ' + event.serverMessage.case)
});
}
}
};
let authorizer = async () => {
await input.driver.ready(ac.signal);
let interval = input.updateTokenIntervalMs ?? DEFAULT_UPDATE_TOKEN_INTERVAL_MS;
for await (let _ of setInterval(interval, null, { signal: ac.signal })) {
let token = await input.driver.token;
queue.push(create(StreamWriteMessage_FromClientSchema, {
clientMessage: {
case: 'updateTokenRequest',
value: create(UpdateTokenRequestSchema, { token })
}
}), 10);
}
};
void stream()
.catch(error => sendBack({ type: 'writer.stream.error', error }))
.finally(() => sendBack({ type: 'writer.stream.close' }));
void authorizer()
.catch(error => sendBack({ type: 'writer.stream.error', error }));
return () => {
queue.close();
ac.abort();
};
});
//# sourceMappingURL=stream.js.map