tina4-nodejs 3.13.43 → 3.13.44
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/CLAUDE.md +2 -1
- package/package.json +1 -1
- package/packages/core/src/graphql.ts +23 -14
- package/packages/core/src/mcp.ts +21 -2
- package/packages/core/src/queueBackends/kafkaBackend.ts +303 -167
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +97 -31
- package/packages/core/src/server.ts +12 -5
- package/packages/core/src/session.ts +11 -95
- package/packages/core/src/sessionHandlers/mongoClient.ts +238 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +25 -204
- package/packages/core/src/sessionHandlers/redisHandler.ts +69 -114
- package/packages/core/src/sessionHandlers/respClient.ts +171 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +11 -95
- package/packages/orm/src/adapters/firebird.ts +20 -2
- package/packages/orm/src/adapters/mssql.ts +24 -2
- package/packages/orm/src/adapters/mysql.ts +20 -2
- package/packages/orm/src/adapters/postgres.ts +40 -12
- package/packages/orm/src/adapters/sqlite.ts +16 -2
- package/packages/orm/src/autoCrud.ts +13 -0
- package/packages/orm/src/baseModel.ts +3 -1
- package/packages/orm/src/cachedDatabase.ts +1 -1
- package/packages/orm/src/database.ts +42 -11
- package/packages/orm/src/index.ts +1 -1
- package/packages/orm/src/migration.ts +124 -45
- package/packages/orm/src/types.ts +5 -3
|
@@ -193,6 +193,13 @@ export class KafkaBackend implements QueueBackend {
|
|
|
193
193
|
|
|
194
194
|
/**
|
|
195
195
|
* Execute a Kafka operation synchronously via a child process.
|
|
196
|
+
*
|
|
197
|
+
* The wire protocol is hand-rolled (Tina4 is zero-dependency — no npm Kafka
|
|
198
|
+
* library). Produce uses Produce **v3** carrying a Kafka **v2 RecordBatch**
|
|
199
|
+
* (magic byte 2) with a **CRC-32C** (Castagnoli) checksum; Fetch uses Fetch
|
|
200
|
+
* **v4** and parses the v2 RecordBatch out of the response. Both formats are
|
|
201
|
+
* what a modern KRaft broker (apache/kafka 3.7.0) requires — the old
|
|
202
|
+
* Produce-v0 / message-format-v0 / CRC=0 batch is rejected by such brokers.
|
|
196
203
|
*/
|
|
197
204
|
private execSync(operation: string, topic: string, data?: string): string {
|
|
198
205
|
// execFileSync imported at top level
|
|
@@ -206,213 +213,342 @@ export class KafkaBackend implements QueueBackend {
|
|
|
206
213
|
const topic = ${JSON.stringify(topic)};
|
|
207
214
|
const groupId = ${JSON.stringify(this.groupId)};
|
|
208
215
|
const data = ${JSON.stringify(data ?? "")};
|
|
216
|
+
const API_PRODUCE = ${API_PRODUCE};
|
|
217
|
+
const API_FETCH = ${API_FETCH};
|
|
218
|
+
const PRODUCE_VERSION = 3; // v3+ requires the v2 RecordBatch format
|
|
219
|
+
const FETCH_VERSION = 4; // v4 returns v2 RecordBatches + isolation_level
|
|
209
220
|
let correlationId = 0;
|
|
210
221
|
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
// ── CRC-32C (Castagnoli, polynomial 0x1EDC6F41), table-based ──────────
|
|
223
|
+
// Node has no built-in CRC-32C; the v2 RecordBatch mandates it over the
|
|
224
|
+
// bytes from \`attributes\` to the end of the batch. A wrong CRC makes the
|
|
225
|
+
// broker drop the connection, so this must be exact.
|
|
226
|
+
const CRC32C_TABLE = (() => {
|
|
227
|
+
const table = new Uint32Array(256);
|
|
228
|
+
for (let n = 0; n < 256; n++) {
|
|
229
|
+
let c = n;
|
|
230
|
+
for (let k = 0; k < 8; k++) {
|
|
231
|
+
c = (c & 1) ? (0x82f63b78 ^ (c >>> 1)) : (c >>> 1);
|
|
232
|
+
}
|
|
233
|
+
table[n] = c >>> 0;
|
|
234
|
+
}
|
|
235
|
+
return table;
|
|
236
|
+
})();
|
|
237
|
+
function crc32c(buf) {
|
|
238
|
+
let crc = 0xffffffff;
|
|
239
|
+
for (let i = 0; i < buf.length; i++) {
|
|
240
|
+
crc = (CRC32C_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8)) >>> 0;
|
|
241
|
+
}
|
|
242
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
215
243
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
244
|
+
|
|
245
|
+
// ── Kafka protocol varints (zigzag-encoded signed varints) ────────────
|
|
246
|
+
function encodeZigZag(n) {
|
|
247
|
+
// 32-bit zigzag: (n << 1) ^ (n >> 31)
|
|
248
|
+
return ((n << 1) ^ (n >> 31)) >>> 0;
|
|
219
249
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
250
|
+
function encodeVarintUnsigned(u) {
|
|
251
|
+
const bytes = [];
|
|
252
|
+
let v = u >>> 0;
|
|
253
|
+
while (true) {
|
|
254
|
+
if ((v & ~0x7f) === 0) { bytes.push(v); break; }
|
|
255
|
+
bytes.push((v & 0x7f) | 0x80);
|
|
256
|
+
v >>>= 7;
|
|
224
257
|
}
|
|
225
|
-
|
|
226
|
-
buf.writeInt16BE(len, offset);
|
|
227
|
-
buf.write(str, offset + 2, len, "utf-8");
|
|
228
|
-
return offset + 2 + len;
|
|
258
|
+
return Buffer.from(bytes);
|
|
229
259
|
}
|
|
230
|
-
function
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
260
|
+
function encodeVarint(n) {
|
|
261
|
+
// signed varint = zigzag then unsigned-varint
|
|
262
|
+
return encodeVarintUnsigned(encodeZigZag(n));
|
|
263
|
+
}
|
|
264
|
+
function decodeVarint(buf, pos) {
|
|
265
|
+
// returns { value, next } — signed (zigzag-decoded)
|
|
266
|
+
let result = 0, shift = 0, b;
|
|
267
|
+
do {
|
|
268
|
+
b = buf[pos++];
|
|
269
|
+
result |= (b & 0x7f) << shift;
|
|
270
|
+
shift += 7;
|
|
271
|
+
} while (b & 0x80);
|
|
272
|
+
result = result >>> 0;
|
|
273
|
+
// zigzag decode
|
|
274
|
+
const value = (result >>> 1) ^ -(result & 1);
|
|
275
|
+
return { value, next: pos };
|
|
238
276
|
}
|
|
239
277
|
|
|
240
|
-
|
|
278
|
+
// ── v2 RecordBatch builder ────────────────────────────────────────────
|
|
279
|
+
function buildRecordBatch(valueBytes) {
|
|
280
|
+
const now = BigInt(Date.now());
|
|
281
|
+
|
|
282
|
+
// Build the single record body.
|
|
283
|
+
// record = attributes:int8(0), timestampDelta:varint(0),
|
|
284
|
+
// offsetDelta:varint(0), keyLen:varint(-1 null),
|
|
285
|
+
// valueLen:varint(len), value, headerCount:varint(0)
|
|
286
|
+
const recBody = Buffer.concat([
|
|
287
|
+
Buffer.from([0]), // attributes (int8)
|
|
288
|
+
encodeVarint(0), // timestampDelta
|
|
289
|
+
encodeVarint(0), // offsetDelta
|
|
290
|
+
encodeVarint(-1), // keyLen (-1 = null key)
|
|
291
|
+
encodeVarint(valueBytes.length), // valueLen
|
|
292
|
+
valueBytes, // value (JSON payload bytes)
|
|
293
|
+
encodeVarint(0), // headerCount
|
|
294
|
+
]);
|
|
295
|
+
// record length prefix = signed varint of recBody length
|
|
296
|
+
const record = Buffer.concat([encodeVarint(recBody.length), recBody]);
|
|
297
|
+
const recordsCount = 1;
|
|
298
|
+
|
|
299
|
+
// The portion the CRC covers starts at \`attributes\` and runs to end.
|
|
300
|
+
// crcBody = attributes:int16(0), lastOffsetDelta:int32(count-1),
|
|
301
|
+
// firstTimestamp:int64, maxTimestamp:int64,
|
|
302
|
+
// producerId:int64(-1), producerEpoch:int16(-1),
|
|
303
|
+
// baseSequence:int32(-1), recordsCount:int32, records
|
|
304
|
+
const crcBody = Buffer.alloc(2 + 4 + 8 + 8 + 8 + 2 + 4 + 4 + record.length);
|
|
305
|
+
let c = 0;
|
|
306
|
+
crcBody.writeInt16BE(0, c); c += 2; // attributes
|
|
307
|
+
crcBody.writeInt32BE(recordsCount - 1, c); c += 4; // lastOffsetDelta
|
|
308
|
+
crcBody.writeBigInt64BE(now, c); c += 8; // firstTimestamp
|
|
309
|
+
crcBody.writeBigInt64BE(now, c); c += 8; // maxTimestamp
|
|
310
|
+
crcBody.writeBigInt64BE(-1n, c); c += 8; // producerId
|
|
311
|
+
crcBody.writeInt16BE(-1, c); c += 2; // producerEpoch
|
|
312
|
+
crcBody.writeInt32BE(-1, c); c += 4; // baseSequence
|
|
313
|
+
crcBody.writeInt32BE(recordsCount, c); c += 4; // recordsCount
|
|
314
|
+
record.copy(crcBody, c);
|
|
315
|
+
|
|
316
|
+
const crc = crc32c(crcBody);
|
|
317
|
+
|
|
318
|
+
// Header before the CRC-covered region:
|
|
319
|
+
// baseOffset:int64(0), batchLength:int32, partitionLeaderEpoch:int32(-1),
|
|
320
|
+
// magic:int8(2), crc:uint32. batchLength counts everything AFTER itself
|
|
321
|
+
// (partitionLeaderEpoch through end of records) = 4 + 1 + 4 + crcBody.
|
|
322
|
+
const batchLength = 4 + 1 + 4 + crcBody.length;
|
|
323
|
+
const head = Buffer.alloc(8 + 4 + 4 + 1 + 4);
|
|
324
|
+
let h = 0;
|
|
325
|
+
head.writeBigInt64BE(0n, h); h += 8; // baseOffset
|
|
326
|
+
head.writeInt32BE(batchLength, h); h += 4; // batchLength
|
|
327
|
+
head.writeInt32BE(-1, h); h += 4; // partitionLeaderEpoch
|
|
328
|
+
head.writeInt8(2, h); h += 1; // magic = 2
|
|
329
|
+
head.writeUInt32BE(crc, h); h += 4; // crc (CRC-32C)
|
|
330
|
+
|
|
331
|
+
return Buffer.concat([head, crcBody]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── Produce v3 request ─────────────────────────────────────────────────
|
|
335
|
+
function buildProduceRequest(topicName, valueBytes) {
|
|
241
336
|
correlationId++;
|
|
242
|
-
const
|
|
337
|
+
const clientBuf = Buffer.from("tina4", "utf-8");
|
|
243
338
|
const topicBuf = Buffer.from(topicName, "utf-8");
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
let pos = 0;
|
|
270
|
-
req.writeInt32BE(reqSize, pos); pos += 4;
|
|
271
|
-
// API key (Produce = 0)
|
|
272
|
-
req.writeInt16BE(API_PRODUCE, pos); pos += 2;
|
|
273
|
-
// API version
|
|
274
|
-
req.writeInt16BE(0, pos); pos += 2;
|
|
275
|
-
// Correlation ID
|
|
276
|
-
req.writeInt32BE(correlationId, pos); pos += 4;
|
|
277
|
-
// Client ID
|
|
278
|
-
req.writeInt16BE(clientBuf.length, pos); pos += 2;
|
|
279
|
-
clientBuf.copy(req, pos); pos += clientBuf.length;
|
|
280
|
-
// Required acks
|
|
281
|
-
req.writeInt16BE(1, pos); pos += 2;
|
|
282
|
-
// Timeout
|
|
283
|
-
req.writeInt32BE(5000, pos); pos += 4;
|
|
284
|
-
// Topic count
|
|
285
|
-
req.writeInt32BE(1, pos); pos += 4;
|
|
286
|
-
// Topic name
|
|
287
|
-
req.writeInt16BE(topicBuf.length, pos); pos += 2;
|
|
288
|
-
topicBuf.copy(req, pos); pos += topicBuf.length;
|
|
289
|
-
// Partition count
|
|
290
|
-
req.writeInt32BE(1, pos); pos += 4;
|
|
291
|
-
// Partition index
|
|
292
|
-
req.writeInt32BE(0, pos); pos += 4;
|
|
293
|
-
// Message set size
|
|
294
|
-
req.writeInt32BE(msgBuf.length, pos); pos += 4;
|
|
295
|
-
msgBuf.copy(req, pos);
|
|
296
|
-
|
|
297
|
-
return req;
|
|
339
|
+
const recordBatch = buildRecordBatch(valueBytes);
|
|
340
|
+
|
|
341
|
+
const body = Buffer.alloc(
|
|
342
|
+
2 + // transactionalId (-1 null nullable_string)
|
|
343
|
+
2 + // acks
|
|
344
|
+
4 + // timeoutMs
|
|
345
|
+
4 + // topics array count
|
|
346
|
+
2 + topicBuf.length + // topic name
|
|
347
|
+
4 + // partitions array count
|
|
348
|
+
4 + // partition index
|
|
349
|
+
4 + recordBatch.length // recordSetBytes (int32 size) + batch
|
|
350
|
+
);
|
|
351
|
+
let p = 0;
|
|
352
|
+
body.writeInt16BE(-1, p); p += 2; // transactionalId = null
|
|
353
|
+
body.writeInt16BE(1, p); p += 2; // acks = 1
|
|
354
|
+
body.writeInt32BE(10000, p); p += 4; // timeoutMs
|
|
355
|
+
body.writeInt32BE(1, p); p += 4; // topics count
|
|
356
|
+
body.writeInt16BE(topicBuf.length, p); p += 2;
|
|
357
|
+
topicBuf.copy(body, p); p += topicBuf.length;
|
|
358
|
+
body.writeInt32BE(1, p); p += 4; // partitions count
|
|
359
|
+
body.writeInt32BE(0, p); p += 4; // partition index 0
|
|
360
|
+
body.writeInt32BE(recordBatch.length, p); p += 4; // recordSetBytes size
|
|
361
|
+
recordBatch.copy(body, p);
|
|
362
|
+
|
|
363
|
+
return frameRequest(API_PRODUCE, PRODUCE_VERSION, clientBuf, body);
|
|
298
364
|
}
|
|
299
365
|
|
|
366
|
+
// ── Fetch v4 request ────────────────────────────────────────────────────
|
|
300
367
|
function buildFetchRequest(topicName, fetchOffset) {
|
|
301
368
|
correlationId++;
|
|
302
|
-
const
|
|
369
|
+
const clientBuf = Buffer.from("tina4", "utf-8");
|
|
303
370
|
const topicBuf = Buffer.from(topicName, "utf-8");
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
371
|
+
|
|
372
|
+
// Fetch v4 request body (NO logStartOffset — that arrives in v5+):
|
|
373
|
+
// replicaId(4), maxWaitMs(4), minBytes(4), maxBytes(4 v3+),
|
|
374
|
+
// isolationLevel(1 v4+), topics[count]: name, partitions[count]:
|
|
375
|
+
// partition(4), fetchOffset(8), partitionMaxBytes(4).
|
|
376
|
+
const buf = Buffer.alloc(4 + 4 + 4 + 4 + 1 + 4 + (2 + topicBuf.length) + 4 + 4 + 8 + 4);
|
|
377
|
+
let p = 0;
|
|
378
|
+
buf.writeInt32BE(-1, p); p += 4; // replicaId (-1 consumer)
|
|
379
|
+
buf.writeInt32BE(1000, p); p += 4; // maxWaitMs
|
|
380
|
+
buf.writeInt32BE(1, p); p += 4; // minBytes
|
|
381
|
+
buf.writeInt32BE(1048576, p); p += 4; // maxBytes
|
|
382
|
+
buf.writeInt8(0, p); p += 1; // isolationLevel = READ_UNCOMMITTED
|
|
383
|
+
buf.writeInt32BE(1, p); p += 4; // topics count
|
|
384
|
+
buf.writeInt16BE(topicBuf.length, p); p += 2;
|
|
385
|
+
topicBuf.copy(buf, p); p += topicBuf.length;
|
|
386
|
+
buf.writeInt32BE(1, p); p += 4; // partitions count
|
|
387
|
+
buf.writeInt32BE(0, p); p += 4; // partition 0
|
|
388
|
+
buf.writeBigInt64BE(BigInt(fetchOffset), p); p += 8; // fetchOffset
|
|
389
|
+
buf.writeInt32BE(1048576, p); p += 4; // partitionMaxBytes
|
|
390
|
+
|
|
391
|
+
return frameRequest(API_FETCH, FETCH_VERSION, clientBuf, buf);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Frame a request with header v1: apiKey, apiVersion, correlationId,
|
|
395
|
+
// clientId (nullable string) + body.
|
|
396
|
+
function frameRequest(apiKey, apiVersion, clientBuf, body) {
|
|
397
|
+
const header = Buffer.alloc(2 + 2 + 4 + 2 + clientBuf.length);
|
|
398
|
+
let p = 0;
|
|
399
|
+
header.writeInt16BE(apiKey, p); p += 2;
|
|
400
|
+
header.writeInt16BE(apiVersion, p); p += 2;
|
|
401
|
+
header.writeInt32BE(correlationId, p); p += 4;
|
|
402
|
+
header.writeInt16BE(clientBuf.length, p); p += 2;
|
|
403
|
+
clientBuf.copy(header, p);
|
|
404
|
+
const payload = Buffer.concat([header, body]);
|
|
405
|
+
const framed = Buffer.alloc(4 + payload.length);
|
|
406
|
+
framed.writeInt32BE(payload.length, 0);
|
|
407
|
+
payload.copy(framed, 4);
|
|
408
|
+
return framed;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ── Parse a v2 RecordBatch region, return the FIRST record value ───────
|
|
412
|
+
// \`buf\` is the whole fetch response; [start, end) bounds the partition's
|
|
413
|
+
// record-set bytes (may contain one or more concatenated batches).
|
|
414
|
+
function firstRecordValue(buf, start, end) {
|
|
415
|
+
let pos = start;
|
|
416
|
+
while (pos + 12 <= end) {
|
|
417
|
+
// baseOffset(8) + batchLength(4) then batchLength bytes follow.
|
|
418
|
+
const batchLength = buf.readInt32BE(pos + 8);
|
|
419
|
+
const batchStart = pos + 12; // first byte of partitionLeaderEpoch
|
|
420
|
+
const batchEnd = batchStart + batchLength;
|
|
421
|
+
if (batchLength <= 0 || batchEnd > end) break;
|
|
422
|
+
// partitionLeaderEpoch(4), magic(1), crc(4), attributes(2),
|
|
423
|
+
// lastOffsetDelta(4), firstTimestamp(8), maxTimestamp(8),
|
|
424
|
+
// producerId(8), producerEpoch(2), baseSequence(4), recordsCount(4)
|
|
425
|
+
const magic = buf.readInt8(batchStart + 4);
|
|
426
|
+
if (magic !== 2) { pos = batchEnd; continue; }
|
|
427
|
+
let r = batchStart + 4 + 1 + 4 + 2 + 4 + 8 + 8 + 8 + 2 + 4;
|
|
428
|
+
const recordsCount = buf.readInt32BE(r); r += 4;
|
|
429
|
+
for (let i = 0; i < recordsCount && r < batchEnd; i++) {
|
|
430
|
+
const recLen = decodeVarint(buf, r); // record length (signed varint)
|
|
431
|
+
let rp = recLen.next;
|
|
432
|
+
const recordEnd = rp + recLen.value;
|
|
433
|
+
rp += 1; // attributes (int8)
|
|
434
|
+
const tsDelta = decodeVarint(buf, rp); rp = tsDelta.next;
|
|
435
|
+
const offDelta = decodeVarint(buf, rp); rp = offDelta.next;
|
|
436
|
+
const keyLen = decodeVarint(buf, rp); rp = keyLen.next;
|
|
437
|
+
if (keyLen.value >= 0) rp += keyLen.value;
|
|
438
|
+
const valLen = decodeVarint(buf, rp); rp = valLen.next;
|
|
439
|
+
if (valLen.value >= 0) {
|
|
440
|
+
return buf.subarray(rp, rp + valLen.value).toString("utf-8");
|
|
441
|
+
}
|
|
442
|
+
r = recordEnd;
|
|
443
|
+
}
|
|
444
|
+
pos = batchEnd;
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let finished = false;
|
|
450
|
+
function finish(out, code) {
|
|
451
|
+
if (finished) return;
|
|
452
|
+
finished = true;
|
|
453
|
+
clearTimeout(timer);
|
|
454
|
+
try { sock.destroy(); } catch (e) { /* ignore */ }
|
|
455
|
+
if (out) process.stdout.write(out);
|
|
456
|
+
// Flush stdout before exiting so execFileSync captures it.
|
|
457
|
+
process.stdout.write("", () => process.exit(code));
|
|
336
458
|
}
|
|
337
459
|
|
|
338
460
|
const sock = net.createConnection({ host, port }, () => {
|
|
339
461
|
if (operation === "publish") {
|
|
340
|
-
const
|
|
341
|
-
const req = buildProduceRequest(topic, msgBytes);
|
|
462
|
+
const req = buildProduceRequest(topic, Buffer.from(data, "utf-8"));
|
|
342
463
|
sock.write(req);
|
|
343
464
|
} else if (operation === "get") {
|
|
344
|
-
|
|
345
|
-
sock.write(req);
|
|
465
|
+
sock.write(buildFetchRequest(topic, 0));
|
|
346
466
|
} else {
|
|
347
|
-
|
|
348
|
-
sock.destroy();
|
|
467
|
+
finish("__UNSUPPORTED__", 0);
|
|
349
468
|
}
|
|
350
469
|
});
|
|
351
470
|
|
|
352
471
|
let buffer = Buffer.alloc(0);
|
|
353
472
|
sock.on("data", (chunk) => {
|
|
354
473
|
buffer = Buffer.concat([buffer, chunk]);
|
|
474
|
+
if (buffer.length < 4) return;
|
|
475
|
+
const respSize = buffer.readInt32BE(0);
|
|
476
|
+
if (buffer.length < 4 + respSize) return;
|
|
355
477
|
|
|
356
|
-
if (
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
478
|
+
if (operation === "publish") {
|
|
479
|
+
// Produce v3 response (after the 4-byte frame size): correlationId(4),
|
|
480
|
+
// topics[count]: name, partitions[count]: partition(4), errorCode(2),
|
|
481
|
+
// baseOffset(8), logAppendTime(8); then throttleTimeMs(4) at the END.
|
|
482
|
+
// Only the per-partition error code matters here.
|
|
483
|
+
try {
|
|
484
|
+
let pos = 4 + 4; // skip frame size + correlationId
|
|
485
|
+
const topicCount = buffer.readInt32BE(pos); pos += 4;
|
|
486
|
+
let errCode = 0;
|
|
487
|
+
for (let t = 0; t < topicCount; t++) {
|
|
488
|
+
const tl = buffer.readInt16BE(pos); pos += 2 + tl;
|
|
489
|
+
const pc = buffer.readInt32BE(pos); pos += 4;
|
|
490
|
+
for (let pi = 0; pi < pc; pi++) {
|
|
491
|
+
pos += 4; // partition index
|
|
492
|
+
errCode = buffer.readInt16BE(pos); pos += 2; // error code
|
|
493
|
+
pos += 8; // baseOffset
|
|
494
|
+
pos += 8; // logAppendTime (v2+)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (errCode === 0) {
|
|
498
|
+
finish("__PUBLISHED__", 0);
|
|
499
|
+
} else {
|
|
500
|
+
process.stderr.write("Produce error code " + errCode);
|
|
501
|
+
finish("__ERROR__" + errCode, 0);
|
|
502
|
+
}
|
|
503
|
+
} catch (e) {
|
|
504
|
+
process.stderr.write("produce parse: " + e.message);
|
|
505
|
+
finish("__ERROR__", 0);
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
} else if (operation === "get") {
|
|
509
|
+
// Fetch v4 response (after the 4-byte frame size): correlationId(4),
|
|
510
|
+
// throttleTimeMs(4), topics[count]: name, partitions[count]:
|
|
511
|
+
// partition(4), errorCode(2), highWatermark(8), lastStableOffset(8),
|
|
512
|
+
// abortedTxns[count](producerId(8)+firstOffset(8)),
|
|
513
|
+
// recordSetBytes(int32)+batch.
|
|
514
|
+
try {
|
|
515
|
+
let pos = 4 + 4; // frame size + correlationId
|
|
516
|
+
pos += 4; // throttleTimeMs (v1+)
|
|
517
|
+
const topicCount = buffer.readInt32BE(pos); pos += 4;
|
|
518
|
+
let out = "__EMPTY__";
|
|
519
|
+
for (let t = 0; t < topicCount; t++) {
|
|
520
|
+
const tl = buffer.readInt16BE(pos); pos += 2 + tl;
|
|
521
|
+
const pc = buffer.readInt32BE(pos); pos += 4;
|
|
522
|
+
for (let pi = 0; pi < pc; pi++) {
|
|
523
|
+
pos += 4; // partition index
|
|
524
|
+
const errCode = buffer.readInt16BE(pos); pos += 2;
|
|
525
|
+
pos += 8; // highWatermark
|
|
526
|
+
pos += 8; // lastStableOffset (v4+)
|
|
527
|
+
const abortedCount = buffer.readInt32BE(pos); pos += 4;
|
|
528
|
+
if (abortedCount > 0) pos += abortedCount * 16; // (-1 => none, skip)
|
|
529
|
+
const recSetSize = buffer.readInt32BE(pos); pos += 4;
|
|
530
|
+
if (errCode === 0 && recSetSize > 0) {
|
|
531
|
+
const val = firstRecordValue(buffer, pos, pos + recSetSize);
|
|
532
|
+
if (val !== null) out = val;
|
|
400
533
|
}
|
|
401
|
-
|
|
402
|
-
process.stdout.write("__EMPTY__");
|
|
534
|
+
pos += recSetSize > 0 ? recSetSize : 0;
|
|
403
535
|
}
|
|
404
536
|
}
|
|
405
|
-
|
|
537
|
+
finish(out, 0);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
process.stderr.write("fetch parse: " + e.message);
|
|
540
|
+
finish("__EMPTY__", 0);
|
|
406
541
|
}
|
|
542
|
+
return;
|
|
407
543
|
}
|
|
408
544
|
});
|
|
409
545
|
|
|
410
546
|
sock.on("error", (err) => {
|
|
411
547
|
process.stderr.write(err.message);
|
|
412
|
-
|
|
548
|
+
finish("", 1);
|
|
413
549
|
});
|
|
414
550
|
|
|
415
|
-
setTimeout(() => {
|
|
551
|
+
var timer = setTimeout(() => { finish("", 1); }, 10000);
|
|
416
552
|
`;
|
|
417
553
|
|
|
418
554
|
try {
|