@vlasky/zongji 0.7.1 → 0.9.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/index.d.ts CHANGED
@@ -11,8 +11,27 @@ export interface ZongJiOptions {
11
11
  filename?: string;
12
12
  /** Binlog position to start from */
13
13
  position?: number;
14
+ /**
15
+ * Executed GTIDs to resume from (e.g. a persisted zongji.gtidSet): a
16
+ * MySQL GTID set ('uuid:1-5,...', tagged intervals as
17
+ * 'uuid:1-5:tag_a:1,...' on MySQL 8.3+) or a MariaDB GTID position
18
+ * ('0-1-1234,...'), matching the server flavour. The server locates
19
+ * the right binlog file and skips transactions already processed
20
+ * (MySQL: COM_BINLOG_DUMP_GTID, requires gtid_mode=ON; MariaDB:
21
+ * @slave_connect_state, always available), so this works across
22
+ * failover to another server in the same topology. '' streams the
23
+ * server's entire available history. Takes precedence over
24
+ * filename/position. A set containing tags uses a wire encoding only
25
+ * MySQL 8.3+ understands; older servers reject it.
26
+ */
27
+ gtidSet?: string;
14
28
  /** If true, use non-blocking mode */
15
29
  nonBlock?: boolean;
30
+ /**
31
+ * MariaDB only: request ANNOTATE_ROWS events (the SQL statement text
32
+ * preceding each row operation's TableMap events). Ignored on MySQL.
33
+ */
34
+ requestAnnotateRows?: boolean;
16
35
  /** List of event types to include (e.g., ['tablemap', 'writerows']) */
17
36
  includeEvents?: string[];
18
37
  /** List of event types to exclude */
@@ -23,13 +42,22 @@ export interface ZongJiOptions {
23
42
  excludeSchema?: Record<string, string[] | true>;
24
43
  }
25
44
 
26
- // Column metadata from INFORMATION_SCHEMA
45
+ // Column metadata, either fetched from INFORMATION_SCHEMA or synthesised
46
+ // from the binlog's own TableMap metadata (binlog_row_metadata=FULL).
47
+ // In the synthesised form COLUMN_TYPE is approximate for character columns
48
+ // (no display width) and COLLATION_NAME/COLUMN_COMMENT are not available.
27
49
  export interface ColumnSchema {
28
50
  COLUMN_NAME: string;
29
51
  COLLATION_NAME: string | null;
30
52
  CHARACTER_SET_NAME: string | null;
31
53
  COLUMN_COMMENT: string;
32
54
  COLUMN_TYPE: string;
55
+ /** Exact signedness from binlog metadata (MySQL 8.0+); numeric columns only */
56
+ UNSIGNED?: boolean;
57
+ /** ENUM value list from binlog metadata (binlog_row_metadata=FULL) */
58
+ ENUM_VALUES?: string[];
59
+ /** SET value list from binlog metadata (binlog_row_metadata=FULL) */
60
+ SET_VALUES?: string[];
33
61
  }
34
62
 
35
63
  // Column information from TableMap event
@@ -95,9 +123,15 @@ export interface GtidEvent extends BinlogEvent {
95
123
  flags: number;
96
124
  /** Source UUID */
97
125
  sid: string;
98
- /** Group number */
99
- gno: number;
100
- /** Full GTID string (sid:gno) */
126
+ /** Group number (exact string beyond Number.MAX_SAFE_INTEGER) */
127
+ gno: number | string;
128
+ /**
129
+ * GTID tag (MySQL 8.3+, set via GTID_NEXT='AUTOMATIC:tag'); '' for
130
+ * untagged transactions. Only present on events that arrived as
131
+ * GTID_TAGGED_LOG_EVENT; classic GTID events leave it undefined.
132
+ */
133
+ tag?: string;
134
+ /** Full GTID string (sid:gno, or sid:tag:gno when tagged) */
101
135
  gtid: string;
102
136
  }
103
137
 
@@ -109,20 +143,34 @@ export interface AnonymousGtidEvent extends BinlogEvent {
109
143
  flags: number;
110
144
  /** Source UUID */
111
145
  sid: string;
112
- /** Group number */
113
- gno: number;
146
+ /** Group number (exact string beyond Number.MAX_SAFE_INTEGER) */
147
+ gno: number | string;
114
148
  /** Full GTID string (sid:gno) */
115
149
  gtid: string;
116
150
  }
117
151
 
118
152
  // Previous GTIDs event
119
153
  export interface GtidInterval {
120
- start: number;
121
- end: number;
154
+ /**
155
+ * Interval start GNO, inclusive (exact string above
156
+ * Number.MAX_SAFE_INTEGER)
157
+ */
158
+ start: number | string;
159
+ /**
160
+ * Interval end GNO, exclusive as on the wire (exact string above
161
+ * Number.MAX_SAFE_INTEGER)
162
+ */
163
+ end: number | string;
122
164
  }
123
165
 
124
166
  export interface GtidSidEntry {
125
167
  sid: string;
168
+ /**
169
+ * GTID tag ('' for the untagged entry). Only present when the server
170
+ * wrote the tagged set encoding (MySQL 8.3+ after any tagged GTID);
171
+ * each (sid, tag) pair is its own entry, repeating the sid.
172
+ */
173
+ tag?: string;
126
174
  intervals: GtidInterval[];
127
175
  }
128
176
 
@@ -197,7 +245,45 @@ export interface TableMapEvent extends BinlogEvent {
197
245
  columnsMetadata: Record<string, unknown>[];
198
246
  /** Reference to the tableMap cache */
199
247
  tableMap: Record<number, TableMapEntry>;
200
- /** Update column info after fetching from INFORMATION_SCHEMA */
248
+ /**
249
+ * Column names carried by the event itself (MySQL 8.0+ with
250
+ * binlog_row_metadata=FULL)
251
+ */
252
+ columnNames?: string[];
253
+ /**
254
+ * Per-column signedness from binlog metadata (MySQL 8.0+, MINIMAL and
255
+ * FULL); true = unsigned. Undefined entries are non-numeric columns.
256
+ */
257
+ signedness?: (boolean | undefined)[];
258
+ /**
259
+ * Primary key column indexes (0-based, all-columns numbering) from
260
+ * binlog_row_metadata=FULL
261
+ */
262
+ primaryKey?: number[];
263
+ /**
264
+ * Per-column visibility from binlog_row_metadata=FULL (MySQL 8.0.23+);
265
+ * false = INVISIBLE column
266
+ */
267
+ columnVisibility?: boolean[];
268
+ /**
269
+ * Per-column geometry subtype from binlog metadata (0 = geometry,
270
+ * 1 = point, ...); undefined entries are non-spatial columns
271
+ */
272
+ geometryTypes?: (number | undefined)[];
273
+ /**
274
+ * True when the event carries binlog_row_metadata=FULL metadata, in
275
+ * which case zongji decodes rows without querying INFORMATION_SCHEMA
276
+ */
277
+ hasSelfDescribingMetadata(): boolean;
278
+ /**
279
+ * True when the table contains classic temporal type codes, which on
280
+ * MariaDB may hide 5.3 "hires" columns that binlog metadata cannot
281
+ * reveal; such tables use the INFORMATION_SCHEMA path even under FULL
282
+ */
283
+ hasAmbiguousTemporalColumns(): boolean;
284
+ /** Build ColumnSchema entries from the event's own metadata (FULL only) */
285
+ buildColumnSchemas(): ColumnSchema[];
286
+ /** Update column info after table metadata is available */
201
287
  updateColumnInfo(): void;
202
288
  }
203
289
 
@@ -262,6 +348,151 @@ export interface PartialUpdateRowsEvent extends BinlogEvent {
262
348
  getEventName(): 'partialupdaterows';
263
349
  }
264
350
 
351
+ // Heartbeat event - sent while the connection idles (when a heartbeat
352
+ // period is configured) and after transactions were skipped server-side
353
+ // during a GTID dump; nextPosition carries the advanced position
354
+ export interface HeartbeatEvent extends BinlogEvent {
355
+ getTypeName(): 'Heartbeat';
356
+ getEventName(): 'heartbeat';
357
+ /** Current binlog filename */
358
+ binlogName: string;
359
+ /**
360
+ * Heartbeat position beyond 4 GiB (MariaDB only: sent in a sub-header
361
+ * when the u32 nextPosition cannot represent it, which is then 0)
362
+ */
363
+ position?: number | string;
364
+ }
365
+
366
+ // MariaDB GTID event (code 162) - replaces the BEGIN Query event of a
367
+ // transaction (standalone=false) or marks a standalone group such as DDL
368
+ export interface MariadbGtidEvent extends BinlogEvent {
369
+ getTypeName(): 'MariadbGtid';
370
+ getEventName(): 'mariadbgtid';
371
+ /**
372
+ * Sequence number within the domain. Arrives as an exact string above
373
+ * Number.MAX_SAFE_INTEGER, so never compare it numerically: use
374
+ * event.gtid with MariadbGtidPosition.covers() (or GtidSet on MySQL)
375
+ * instead.
376
+ */
377
+ seqNo: number | string;
378
+ /** Replication domain id */
379
+ domainId: number;
380
+ /** Originating server id (from the event header) */
381
+ serverId: number;
382
+ /** Raw flags2 byte */
383
+ flags2: number;
384
+ /** True for standalone groups (DDL, ...) with no terminating commit */
385
+ standalone: boolean;
386
+ /** True when the group can be safely rolled back */
387
+ transactional: boolean;
388
+ /** True when the group contains DDL */
389
+ isDdl: boolean;
390
+ /** XA PREPARE group; XID fields present */
391
+ preparedXa: boolean;
392
+ /** XA COMMIT/ROLLBACK group; XID fields present */
393
+ completedXa: boolean;
394
+ /** Group-commit id (same for all members of one group commit) */
395
+ commitId?: number | string;
396
+ /** XA format id, when preparedXa/completedXa */
397
+ xidFormatId?: number;
398
+ /** XA gtrid, when preparedXa/completedXa */
399
+ xidGtrid?: Buffer;
400
+ /** XA bqual, when preparedXa/completedXa */
401
+ xidBqual?: Buffer;
402
+ /** Raw flags_extra byte, when present */
403
+ flagsExtra?: number;
404
+ /** Number of extra engines, when FL_EXTRA_MULTI_ENGINE is set */
405
+ extraEngines?: number;
406
+ /** Start-alter sequence number, for COMMIT/ROLLBACK ALTER groups */
407
+ saSeqNo?: number | string;
408
+ /** Thread id of the originating connection (MariaDB 11.5+) */
409
+ threadId?: number;
410
+ /** Full GTID string (domain-server-sequence) */
411
+ gtid: string;
412
+ }
413
+
414
+ export interface MariadbGtidListEntry {
415
+ domainId: number;
416
+ serverId: number;
417
+ /**
418
+ * Sequence number within the domain. Arrives as an exact string above
419
+ * Number.MAX_SAFE_INTEGER, so never compare it numerically: use the
420
+ * gtid string with MariadbGtidPosition.covers() instead.
421
+ */
422
+ seqNo: number | string;
423
+ /** Full GTID string (domain-server-sequence) */
424
+ gtid: string;
425
+ }
426
+
427
+ // MariaDB GTID list event (code 163) - at the start of every binlog file,
428
+ // the last GTID per (domain, server) seen so far; MariaDB's analogue of
429
+ // MySQL's Previous_gtids
430
+ export interface MariadbGtidListEvent extends BinlogEvent {
431
+ getTypeName(): 'MariadbGtidList';
432
+ getEventName(): 'mariadbgtidlist';
433
+ count: number;
434
+ /** High 4 bits of the count word (FLAG_UNTIL_REACHED, FLAG_IGN_GTIDS) */
435
+ flags: number;
436
+ gtids: MariadbGtidListEntry[];
437
+ }
438
+
439
+ // MariaDB binlog checkpoint event (code 161) - XA-recovery marker naming
440
+ // the oldest binlog file that may still be needed for crash recovery
441
+ export interface BinlogCheckpointEvent extends BinlogEvent {
442
+ getTypeName(): 'BinlogCheckpoint';
443
+ getEventName(): 'binlogcheckpoint';
444
+ binlogName: string;
445
+ }
446
+
447
+ // MariaDB annotate rows event (code 160) - the SQL statement text for the
448
+ // row events that follow
449
+ export interface AnnotateRowsEvent extends BinlogEvent {
450
+ getTypeName(): 'AnnotateRows';
451
+ getEventName(): 'annotaterows';
452
+ /**
453
+ * The event carries the statement's original bytes with no charset
454
+ * metadata; decoded as utf8 (exact for utf8/utf8mb4 clients, the
455
+ * overwhelmingly common case).
456
+ */
457
+ statement: string;
458
+ }
459
+
460
+ // MySQL rows query event (code 29) - the SQL statement text for the row
461
+ // events that follow, sent while the server runs with
462
+ // binlog_rows_query_log_events=ON
463
+ export interface RowsQueryEvent extends BinlogEvent {
464
+ getTypeName(): 'RowsQuery';
465
+ getEventName(): 'rowsquery';
466
+ /**
467
+ * The event carries the statement's original bytes with no charset
468
+ * metadata; decoded as utf8 (exact for utf8/utf8mb4 clients, the
469
+ * overwhelmingly common case).
470
+ */
471
+ statement: string;
472
+ }
473
+
474
+ // MariaDB start encryption event (code 164) - informational; the dump
475
+ // thread decrypts server-side and the stream itself is cleartext
476
+ export interface StartEncryptionEvent extends BinlogEvent {
477
+ getTypeName(): 'StartEncryption';
478
+ getEventName(): 'startencryption';
479
+ scheme: number;
480
+ keyVersion: number;
481
+ nonce: Buffer;
482
+ }
483
+
484
+ // XA prepare event (code 38, MySQL 5.7+ and MariaDB) - terminates an
485
+ // XA-prepared event group
486
+ export interface XaPrepareEvent extends BinlogEvent {
487
+ getTypeName(): 'XaPrepare';
488
+ getEventName(): 'xaprepare';
489
+ /** XA COMMIT ... ONE PHASE (commits immediately) */
490
+ onePhase: boolean;
491
+ xidFormatId: number;
492
+ xidGtrid: Buffer;
493
+ xidBqual: Buffer;
494
+ }
495
+
265
496
  // Unknown event type
266
497
  export interface UnknownEvent extends BinlogEvent {
267
498
  getTypeName(): 'Unknown';
@@ -284,6 +515,14 @@ export type AnyBinlogEvent =
284
515
  | UpdateRowsEvent
285
516
  | TransactionPayloadEvent
286
517
  | PartialUpdateRowsEvent
518
+ | HeartbeatEvent
519
+ | MariadbGtidEvent
520
+ | MariadbGtidListEvent
521
+ | BinlogCheckpointEvent
522
+ | AnnotateRowsEvent
523
+ | RowsQueryEvent
524
+ | StartEncryptionEvent
525
+ | XaPrepareEvent
287
526
  | UnknownEvent;
288
527
 
289
528
  /** Union of all possible getTypeName() return values (e.g. 'Rotate', 'WriteRows') */
@@ -329,14 +568,42 @@ declare class ZongJi extends EventEmitter {
329
568
  stopped: boolean;
330
569
  /** Whether binlog checksum is enabled */
331
570
  useChecksum: boolean;
571
+ /**
572
+ * Server version string as reported by SELECT VERSION(), e.g.
573
+ * '8.4.5' or '11.8.8-MariaDB-ubu2404-log'. Set during start()
574
+ * initialisation, before the 'ready' event.
575
+ */
576
+ serverVersion: string | undefined;
577
+ /** True when the connected server is MariaDB (set during start()) */
578
+ isMariaDb: boolean;
332
579
  /** Current binlog options */
333
580
  options: {
334
581
  serverId?: number;
335
582
  filename?: string;
336
583
  position?: number;
337
584
  startAtEnd?: boolean;
585
+ gtidSet?: string;
338
586
  nonBlock?: boolean;
587
+ requestAnnotateRows?: boolean;
339
588
  };
589
+ /**
590
+ * The transactions this instance knows to be processed: the start()
591
+ * seed plus every transaction whose commit has been observed. A MySQL
592
+ * GTID set ('uuid:1-5,...') or a MariaDB GTID position ('0-1-1234,...')
593
+ * depending on the connected server. Persist it and pass to
594
+ * start({ gtidSet }) to resume, including on a different server.
595
+ * Undefined when no exact seed was available (a mid-file file+position
596
+ * start).
597
+ */
598
+ readonly gtidSet: string | undefined;
599
+ /**
600
+ * The GTID of the transaction whose events are currently being
601
+ * delivered (equal to their event.gtid); undefined between
602
+ * transactions and for anonymous transactions. Only coherent when
603
+ * read synchronously inside a 'binlog' handler: checkpointing code
604
+ * should use event.gtid.
605
+ */
606
+ readonly lastGtid: string | undefined;
340
607
  /** Current filter settings */
341
608
  filters: {
342
609
  includeEvents?: string[];
@@ -394,3 +661,86 @@ declare class ZongJi extends EventEmitter {
394
661
  }
395
662
 
396
663
  export default ZongJi;
664
+
665
+ /**
666
+ * A MySQL GTID set (the model behind zongji.gtidSet): which transactions
667
+ * per source are already executed. String form matches @@gtid_executed,
668
+ * including tagged GTIDs (MySQL 8.3+): 'uuid:1-5:11,uuid2:1-27',
669
+ * 'uuid:1-5:tag_a:1'. Exported so consumers can parse persisted sets and
670
+ * test event.gtid membership without their own parser.
671
+ */
672
+ export class GtidSet {
673
+ /**
674
+ * Parses a GTID set string ('' gives an empty set). Within a uuid
675
+ * block a non-numeric item is a tag naming the source of the interval
676
+ * items that follow it. Throws on malformed input.
677
+ */
678
+ static parse(text: string): GtidSet;
679
+ /** Adds a single transaction ('uuid:gno' or 'uuid:tag:gno') */
680
+ add(gtid: string): void;
681
+ /**
682
+ * Whether a single transaction ('uuid:gno' or 'uuid:tag:gno', e.g. an
683
+ * event.gtid) is contained in the set. null/undefined (an anonymous
684
+ * transaction's event.gtid) is never contained; a malformed string
685
+ * throws.
686
+ */
687
+ contains(gtid: string | null | undefined): boolean;
688
+ isEmpty(): boolean;
689
+ /** Canonical text form (sorted; tagged intervals after untagged) */
690
+ toString(): string;
691
+ }
692
+
693
+ /**
694
+ * A MariaDB GTID position (the model behind zongji.gtidSet on MariaDB):
695
+ * the last transaction processed per replication domain. String form
696
+ * matches @@gtid_binlog_pos / @@gtid_current_pos, e.g. '0-1-1234,1-3-45'.
697
+ * Exported so consumers can parse persisted positions and test coverage
698
+ * via covers() instead of hand-rolling seqNo comparators (which silently
699
+ * round above Number.MAX_SAFE_INTEGER when done with Number; internally
700
+ * this class is exact over the full u64 sequence range).
701
+ */
702
+ export class MariadbGtidPosition {
703
+ /**
704
+ * Parses a GTID position string ('' gives an empty position). Rejects
705
+ * two entries for one domain, as the server would. Throws on malformed
706
+ * input.
707
+ */
708
+ static parse(text: string): MariadbGtidPosition;
709
+ /**
710
+ * Builds the position implied by a MariadbGtidList event's gtids
711
+ * array: the last entry per domain wins
712
+ */
713
+ static fromGtidList(entries: Array<{
714
+ domainId: number;
715
+ serverId: number;
716
+ seqNo: number | string;
717
+ }>): MariadbGtidPosition;
718
+ /**
719
+ * Records a transaction as the last processed in its domain
720
+ * (an overwrite, not a max: "last processed" survives failovers where
721
+ * sequence numbers regress under a new server id)
722
+ */
723
+ add(gtid: {
724
+ domainId: number;
725
+ serverId: number;
726
+ seqNo: number | string;
727
+ }): void;
728
+ /**
729
+ * Whether a transaction ('domain-server-sequence', e.g. an event.gtid)
730
+ * is covered by this position. inclusive (default true) counts the
731
+ * position's own last transaction as covered (snapshot-barrier
732
+ * semantics); redelivery watermarks must pass { inclusive: false },
733
+ * because all events of a transaction share one GTID and an inclusive
734
+ * check would drop every replayed event of the watermark transaction
735
+ * after the first. A server-id mismatch within a domain and an unknown
736
+ * domain both report not covered; null/undefined is never covered; a
737
+ * malformed string throws.
738
+ */
739
+ covers(
740
+ gtid: string | null | undefined,
741
+ options?: { inclusive?: boolean },
742
+ ): boolean;
743
+ isEmpty(): boolean;
744
+ /** Text form, sorted by domain ('D-S-N[,D-S-N...]') */
745
+ toString(): string;
746
+ }