@platformatic/sqlite-js-stream 0.0.0-stage → 0.1.1
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/CHANGELOG.md +40 -0
- package/LICENSE +202 -0
- package/NOTICE +22 -0
- package/README.md +482 -2
- package/dist/backends/file/index.d.ts +4 -0
- package/dist/backends/file/index.d.ts.map +1 -0
- package/dist/backends/file/index.js +3 -0
- package/dist/backends/file/leaser.d.ts +20 -0
- package/dist/backends/file/leaser.d.ts.map +1 -0
- package/dist/backends/file/leaser.js +123 -0
- package/dist/backends/file/replica-client.d.ts +38 -0
- package/dist/backends/file/replica-client.d.ts.map +1 -0
- package/dist/backends/file/replica-client.js +209 -0
- package/dist/db.d.ts +80 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +484 -0
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +66 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/leaser.d.ts +55 -0
- package/dist/leaser.d.ts.map +1 -0
- package/dist/leaser.js +60 -0
- package/dist/ltx/byte-reader.d.ts +18 -0
- package/dist/ltx/byte-reader.d.ts.map +1 -0
- package/dist/ltx/byte-reader.js +38 -0
- package/dist/ltx/bytes.d.ts +20 -0
- package/dist/ltx/bytes.d.ts.map +1 -0
- package/dist/ltx/bytes.js +69 -0
- package/dist/ltx/checksum.d.ts +20 -0
- package/dist/ltx/checksum.d.ts.map +1 -0
- package/dist/ltx/checksum.js +42 -0
- package/dist/ltx/compaction-level.d.ts +58 -0
- package/dist/ltx/compaction-level.d.ts.map +1 -0
- package/dist/ltx/compaction-level.js +115 -0
- package/dist/ltx/compactor.d.ts +33 -0
- package/dist/ltx/compactor.d.ts.map +1 -0
- package/dist/ltx/compactor.js +137 -0
- package/dist/ltx/constants.d.ts +49 -0
- package/dist/ltx/constants.d.ts.map +1 -0
- package/dist/ltx/constants.js +58 -0
- package/dist/ltx/crc64.d.ts +29 -0
- package/dist/ltx/crc64.d.ts.map +1 -0
- package/dist/ltx/crc64.js +57 -0
- package/dist/ltx/decoder.d.ts +55 -0
- package/dist/ltx/decoder.d.ts.map +1 -0
- package/dist/ltx/decoder.js +236 -0
- package/dist/ltx/encoder.d.ts +49 -0
- package/dist/ltx/encoder.d.ts.map +1 -0
- package/dist/ltx/encoder.js +159 -0
- package/dist/ltx/file-info.d.ts +24 -0
- package/dist/ltx/file-info.d.ts.map +1 -0
- package/dist/ltx/file-info.js +74 -0
- package/dist/ltx/filename.d.ts +13 -0
- package/dist/ltx/filename.d.ts.map +1 -0
- package/dist/ltx/filename.js +23 -0
- package/dist/ltx/header.d.ts +17 -0
- package/dist/ltx/header.d.ts.map +1 -0
- package/dist/ltx/header.js +115 -0
- package/dist/ltx/index.d.ts +24 -0
- package/dist/ltx/index.d.ts.map +1 -0
- package/dist/ltx/index.js +25 -0
- package/dist/ltx/lz4.d.ts +41 -0
- package/dist/ltx/lz4.d.ts.map +1 -0
- package/dist/ltx/lz4.js +264 -0
- package/dist/ltx/page-index.d.ts +26 -0
- package/dist/ltx/page-index.d.ts.map +1 -0
- package/dist/ltx/page-index.js +118 -0
- package/dist/ltx/page.d.ts +27 -0
- package/dist/ltx/page.d.ts.map +1 -0
- package/dist/ltx/page.js +66 -0
- package/dist/ltx/sink.d.ts +17 -0
- package/dist/ltx/sink.d.ts.map +1 -0
- package/dist/ltx/sink.js +21 -0
- package/dist/ltx/stream.d.ts +64 -0
- package/dist/ltx/stream.d.ts.map +1 -0
- package/dist/ltx/stream.js +248 -0
- package/dist/ltx/streaming-compactor.d.ts +34 -0
- package/dist/ltx/streaming-compactor.d.ts.map +1 -0
- package/dist/ltx/streaming-compactor.js +140 -0
- package/dist/ltx/trailer.d.ts +12 -0
- package/dist/ltx/trailer.d.ts.map +1 -0
- package/dist/ltx/trailer.js +47 -0
- package/dist/ltx/types.d.ts +79 -0
- package/dist/ltx/types.d.ts.map +1 -0
- package/dist/ltx/types.js +70 -0
- package/dist/replica-client.d.ts +92 -0
- package/dist/replica-client.d.ts.map +1 -0
- package/dist/replica-client.js +122 -0
- package/dist/replica-url.d.ts +20 -0
- package/dist/replica-url.d.ts.map +1 -0
- package/dist/replica-url.js +49 -0
- package/dist/replica.d.ts +379 -0
- package/dist/replica.d.ts.map +1 -0
- package/dist/replica.js +1253 -0
- package/dist/store.d.ts +34 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +60 -0
- package/dist/util/fs.d.ts +28 -0
- package/dist/util/fs.d.ts.map +1 -0
- package/dist/util/fs.js +73 -0
- package/dist/util/logger.d.ts +10 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +7 -0
- package/dist/util/stream.d.ts +11 -0
- package/dist/util/stream.d.ts.map +1 -0
- package/dist/util/stream.js +25 -0
- package/dist/wal/checksum.d.ts +17 -0
- package/dist/wal/checksum.d.ts.map +1 -0
- package/dist/wal/checksum.js +30 -0
- package/dist/wal/constants.d.ts +8 -0
- package/dist/wal/constants.d.ts.map +1 -0
- package/dist/wal/constants.js +7 -0
- package/dist/wal/index.d.ts +5 -0
- package/dist/wal/index.d.ts.map +1 -0
- package/dist/wal/index.js +4 -0
- package/dist/wal/reader.d.ts +61 -0
- package/dist/wal/reader.d.ts.map +1 -0
- package/dist/wal/reader.js +207 -0
- package/package.json +68 -3
- package/src/backends/file/index.ts +3 -0
- package/src/backends/file/leaser.ts +145 -0
- package/src/backends/file/replica-client.ts +246 -0
- package/src/db.ts +553 -0
- package/src/errors.ts +74 -0
- package/src/index.ts +26 -0
- package/src/leaser.ts +86 -0
- package/src/ltx/byte-reader.ts +46 -0
- package/src/ltx/bytes.ts +87 -0
- package/src/ltx/checksum.ts +46 -0
- package/src/ltx/compaction-level.ts +135 -0
- package/src/ltx/compactor.ts +167 -0
- package/src/ltx/constants.ts +71 -0
- package/src/ltx/crc64.ts +65 -0
- package/src/ltx/decoder.ts +282 -0
- package/src/ltx/encoder.ts +200 -0
- package/src/ltx/file-info.ts +81 -0
- package/src/ltx/filename.ts +27 -0
- package/src/ltx/header.ts +126 -0
- package/src/ltx/index.ts +27 -0
- package/src/ltx/lz4.ts +279 -0
- package/src/ltx/page-index.ts +140 -0
- package/src/ltx/page.ts +80 -0
- package/src/ltx/sink.ts +33 -0
- package/src/ltx/stream.ts +286 -0
- package/src/ltx/streaming-compactor.ts +169 -0
- package/src/ltx/trailer.ts +58 -0
- package/src/ltx/types.ts +152 -0
- package/src/lz4js.d.ts +27 -0
- package/src/replica-client.ts +222 -0
- package/src/replica-url.ts +72 -0
- package/src/replica.ts +1480 -0
- package/src/store.ts +71 -0
- package/src/util/fs.ts +82 -0
- package/src/util/logger.ts +15 -0
- package/src/util/stream.ts +28 -0
- package/src/wal/checksum.ts +39 -0
- package/src/wal/constants.ts +10 -0
- package/src/wal/index.ts +5 -0
- package/src/wal/reader.ts +264 -0
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store manages one or more Replicas (a litestream instance).
|
|
3
|
+
*
|
|
4
|
+
* MVP: a thin orchestration layer that opens replicas for database paths with
|
|
5
|
+
* their replica clients, syncs/snapshots them, and closes. Monitor loops,
|
|
6
|
+
* scheduling, and retention are future work.
|
|
7
|
+
*/
|
|
8
|
+
import type { Replica, RetentionOptions, ReplicaRunOptions } from './replica.js';
|
|
9
|
+
import type { ReplicaClient } from './replica-client.js';
|
|
10
|
+
/** Store coordinates multiple replicated databases. */
|
|
11
|
+
export declare class Store {
|
|
12
|
+
private items;
|
|
13
|
+
/** OpenDB opens a replica for a database path and wires it to a client. */
|
|
14
|
+
openDB(path: string, client: ReplicaClient): Promise<Replica>;
|
|
15
|
+
/** Replicas returns all open replicas. */
|
|
16
|
+
replicas(): Replica[];
|
|
17
|
+
/** SyncAll syncs every database (local writes then replica upload). */
|
|
18
|
+
syncAll(): Promise<void>;
|
|
19
|
+
/** SnapshotAll writes & uploads level-9 snapshots for every database. */
|
|
20
|
+
snapshotAll(): Promise<void>;
|
|
21
|
+
/** CompactAll compacts pending LTX files for every database. */
|
|
22
|
+
compactAll(): Promise<void>;
|
|
23
|
+
/** EnforceRetentionAll prunes old snapshots/files for every database. */
|
|
24
|
+
enforceRetentionAll(options?: RetentionOptions): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Run starts each replica's continuous sync/compaction/retention loop and
|
|
27
|
+
* resolves once `options.signal` is aborted. Pass per-replica intervals and a
|
|
28
|
+
* retention policy via {@link ReplicaRunOptions}.
|
|
29
|
+
*/
|
|
30
|
+
run(options?: ReplicaRunOptions): Promise<void>;
|
|
31
|
+
/** Close releases all replicas' internal connections. */
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,uDAAuD;AACvD,qBAAa,KAAK;IAChB,OAAO,CAAC,KAAK,CAAiB;IAE9B,2EAA2E;IACrE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAMnE,0CAA0C;IAC1C,QAAQ,IAAI,OAAO,EAAE;IAIrB,uEAAuE;IACjE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,yEAAyE;IACnE,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,gEAAgE;IAC1D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC,yEAAyE;IACnE,mBAAmB,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE;;;;OAIG;IACG,GAAG,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,yDAAyD;IACzD,KAAK,IAAI,IAAI;CAId"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store manages one or more Replicas (a litestream instance).
|
|
3
|
+
*
|
|
4
|
+
* MVP: a thin orchestration layer that opens replicas for database paths with
|
|
5
|
+
* their replica clients, syncs/snapshots them, and closes. Monitor loops,
|
|
6
|
+
* scheduling, and retention are future work.
|
|
7
|
+
*/
|
|
8
|
+
import { Replica as ReplicaClass } from "./replica.js";
|
|
9
|
+
/** Store coordinates multiple replicated databases. */
|
|
10
|
+
export class Store {
|
|
11
|
+
items = [];
|
|
12
|
+
/** OpenDB opens a replica for a database path and wires it to a client. */
|
|
13
|
+
async openDB(path, client) {
|
|
14
|
+
const replica = new ReplicaClass(path, client);
|
|
15
|
+
this.items.push(replica);
|
|
16
|
+
return replica;
|
|
17
|
+
}
|
|
18
|
+
/** Replicas returns all open replicas. */
|
|
19
|
+
replicas() {
|
|
20
|
+
return [...this.items];
|
|
21
|
+
}
|
|
22
|
+
/** SyncAll syncs every database (local writes then replica upload). */
|
|
23
|
+
async syncAll() {
|
|
24
|
+
for (const replica of this.items) {
|
|
25
|
+
await replica.sync();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** SnapshotAll writes & uploads level-9 snapshots for every database. */
|
|
29
|
+
async snapshotAll() {
|
|
30
|
+
for (const replica of this.items) {
|
|
31
|
+
await replica.snapshot();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** CompactAll compacts pending LTX files for every database. */
|
|
35
|
+
async compactAll() {
|
|
36
|
+
for (const replica of this.items) {
|
|
37
|
+
await replica.compact();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** EnforceRetentionAll prunes old snapshots/files for every database. */
|
|
41
|
+
async enforceRetentionAll(options = {}) {
|
|
42
|
+
for (const replica of this.items) {
|
|
43
|
+
await replica.enforceRetention(options);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Run starts each replica's continuous sync/compaction/retention loop and
|
|
48
|
+
* resolves once `options.signal` is aborted. Pass per-replica intervals and a
|
|
49
|
+
* retention policy via {@link ReplicaRunOptions}.
|
|
50
|
+
*/
|
|
51
|
+
async run(options = {}) {
|
|
52
|
+
await Promise.all(this.items.map((replica) => replica.run(options)));
|
|
53
|
+
}
|
|
54
|
+
/** Close releases all replicas' internal connections. */
|
|
55
|
+
close() {
|
|
56
|
+
for (const replica of this.items)
|
|
57
|
+
replica.close();
|
|
58
|
+
this.items = [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** File-system helpers for atomic writes and directory fsync. */
|
|
2
|
+
import { type FileHandle } from 'node:fs/promises';
|
|
3
|
+
import type { ByteSink } from '../ltx/sink.js';
|
|
4
|
+
/** Ensures a directory (and parents) exists; no-op if already present. */
|
|
5
|
+
export declare function mkdirAll(path: string): Promise<void>;
|
|
6
|
+
/** Fsyncs a directory so a preceding rename within it is durable. */
|
|
7
|
+
export declare function fsyncDir(path: string): Promise<void>;
|
|
8
|
+
/** Returns the parent directory of a path (portable). */
|
|
9
|
+
export declare function parentDir(path: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* A file-backed {@link ByteSink}. It batches bytes into fixed-size blocks and
|
|
12
|
+
* writes them synchronously, so the LTX encoder can stream a large file to disk
|
|
13
|
+
* without buffering the whole file (or the whole database) in memory.
|
|
14
|
+
*/
|
|
15
|
+
export declare class FileSink implements ByteSink {
|
|
16
|
+
private fh;
|
|
17
|
+
private buf;
|
|
18
|
+
private pos;
|
|
19
|
+
private filePos;
|
|
20
|
+
constructor(fh: FileHandle, chunkSize?: number);
|
|
21
|
+
write(bytes: Uint8Array): void;
|
|
22
|
+
/** Returns the number of bytes written so far. */
|
|
23
|
+
get length(): number;
|
|
24
|
+
private flushSync;
|
|
25
|
+
/** Flushes any remaining buffered bytes and fsyncs the file. */
|
|
26
|
+
flush(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/util/fs.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAGjE,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,0EAA0E;AAC1E,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1D;AAED,qEAAqE;AACrE,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO1D;AAED,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,qBAAa,QAAS,YAAW,QAAQ;IACvC,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,OAAO,CAAK;gBAER,EAAE,EAAE,UAAU,EAAE,SAAS,SAAa;IAKlD,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAW9B,kDAAkD;IAClD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,OAAO,CAAC,SAAS;IAOjB,gEAAgE;IAC1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
package/dist/util/fs.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** File-system helpers for atomic writes and directory fsync. */
|
|
2
|
+
import { writeSync } from 'node:fs';
|
|
3
|
+
import { mkdir, open } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
/** Ensures a directory (and parents) exists; no-op if already present. */
|
|
6
|
+
export async function mkdirAll(path) {
|
|
7
|
+
await mkdir(path, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
/** Fsyncs a directory so a preceding rename within it is durable. */
|
|
10
|
+
export async function fsyncDir(path) {
|
|
11
|
+
const handle = await open(path, 'r');
|
|
12
|
+
try {
|
|
13
|
+
await handle.sync();
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
await handle.close();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Returns the parent directory of a path (portable). */
|
|
20
|
+
export function parentDir(path) {
|
|
21
|
+
return dirname(path);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A file-backed {@link ByteSink}. It batches bytes into fixed-size blocks and
|
|
25
|
+
* writes them synchronously, so the LTX encoder can stream a large file to disk
|
|
26
|
+
* without buffering the whole file (or the whole database) in memory.
|
|
27
|
+
*/
|
|
28
|
+
export class FileSink {
|
|
29
|
+
fh;
|
|
30
|
+
buf;
|
|
31
|
+
pos = 0;
|
|
32
|
+
filePos = 0;
|
|
33
|
+
constructor(fh, chunkSize = 256 * 1024) {
|
|
34
|
+
this.fh = fh;
|
|
35
|
+
this.buf = new Uint8Array(chunkSize);
|
|
36
|
+
}
|
|
37
|
+
write(bytes) {
|
|
38
|
+
let o = 0;
|
|
39
|
+
while (o < bytes.length) {
|
|
40
|
+
if (this.pos === this.buf.length)
|
|
41
|
+
this.flushSync();
|
|
42
|
+
const take = Math.min(this.buf.length - this.pos, bytes.length - o);
|
|
43
|
+
this.buf.set(bytes.subarray(o, o + take), this.pos);
|
|
44
|
+
this.pos += take;
|
|
45
|
+
o += take;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Returns the number of bytes written so far. */
|
|
49
|
+
get length() {
|
|
50
|
+
return this.filePos + this.pos;
|
|
51
|
+
}
|
|
52
|
+
flushSync() {
|
|
53
|
+
if (this.pos === 0)
|
|
54
|
+
return;
|
|
55
|
+
writeAllSync(this.fh, this.buf, this.pos, this.filePos);
|
|
56
|
+
this.filePos += this.pos;
|
|
57
|
+
this.pos = 0;
|
|
58
|
+
}
|
|
59
|
+
/** Flushes any remaining buffered bytes and fsyncs the file. */
|
|
60
|
+
async flush() {
|
|
61
|
+
this.flushSync();
|
|
62
|
+
await this.fh.sync();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Writes all of `buf[0..len)` to `handle`'s fd starting at `position`. */
|
|
66
|
+
function writeAllSync(handle, buf, len, position) {
|
|
67
|
+
const fd = handle.fd;
|
|
68
|
+
let offset = 0;
|
|
69
|
+
while (offset < len) {
|
|
70
|
+
const written = writeSync(fd, buf, offset, len - offset, position + offset);
|
|
71
|
+
offset += written;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Minimal structured logger interface (stand-in for Go's slog.Logger). */
|
|
2
|
+
export interface Logger {
|
|
3
|
+
debug(msg: string, ...args: unknown[]): void;
|
|
4
|
+
info(msg: string, ...args: unknown[]): void;
|
|
5
|
+
warn(msg: string, ...args: unknown[]): void;
|
|
6
|
+
error(msg: string, ...args: unknown[]): void;
|
|
7
|
+
}
|
|
8
|
+
/** A no-op logger used when none is provided. */
|
|
9
|
+
export declare const noopLogger: Logger;
|
|
10
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC9C;AAED,iDAAiD;AACjD,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Stream helpers for consuming/capturing LTX byte streams. */
|
|
2
|
+
/** Concatenates an array of byte chunks into a single Uint8Array. */
|
|
3
|
+
export declare function concatBytes(chunks: readonly Uint8Array[]): Uint8Array;
|
|
4
|
+
/**
|
|
5
|
+
* Reads an entire async iterable of byte chunks into a single Uint8Array.
|
|
6
|
+
* String chunks are encoded as UTF-8.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readAll(stream: AsyncIterable<Uint8Array | string> | {
|
|
9
|
+
[Symbol.asyncIterator](): AsyncIterator<Uint8Array | string>;
|
|
10
|
+
}): Promise<Uint8Array>;
|
|
11
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/util/stream.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,CAUrE;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,MAAM,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG;IAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,CAAA;CAAE,GAC5G,OAAO,CAAC,UAAU,CAAC,CAMrB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Stream helpers for consuming/capturing LTX byte streams. */
|
|
2
|
+
/** Concatenates an array of byte chunks into a single Uint8Array. */
|
|
3
|
+
export function concatBytes(chunks) {
|
|
4
|
+
let total = 0;
|
|
5
|
+
for (const c of chunks)
|
|
6
|
+
total += c.length;
|
|
7
|
+
const out = new Uint8Array(total);
|
|
8
|
+
let o = 0;
|
|
9
|
+
for (const c of chunks) {
|
|
10
|
+
out.set(c, o);
|
|
11
|
+
o += c.length;
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Reads an entire async iterable of byte chunks into a single Uint8Array.
|
|
17
|
+
* String chunks are encoded as UTF-8.
|
|
18
|
+
*/
|
|
19
|
+
export async function readAll(stream) {
|
|
20
|
+
const chunks = [];
|
|
21
|
+
for await (const chunk of stream) {
|
|
22
|
+
chunks.push(typeof chunk === 'string' ? new TextEncoder().encode(chunk) : Uint8Array.from(chunk));
|
|
23
|
+
}
|
|
24
|
+
return concatBytes(chunks);
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite WAL checksum.
|
|
3
|
+
*
|
|
4
|
+
* Matches litestream's `WALChecksum` and SQLite's wal-checksum algorithm.
|
|
5
|
+
* The byte order is chosen from the WAL magic: 0x377f0682 => little-endian,
|
|
6
|
+
* 0x377f0683 => big-endian. The checksum runs over 8-byte units:
|
|
7
|
+
* s0 += u32[a] + s1 ; s1 += u32[a+4] + s0
|
|
8
|
+
*/
|
|
9
|
+
export type ByteOrder = 'le' | 'be';
|
|
10
|
+
/** Reads a u32 from `buf` at `off` using the given byte order. */
|
|
11
|
+
export declare function readWalU32(bo: ByteOrder, buf: Uint8Array, off: number): number;
|
|
12
|
+
/**
|
|
13
|
+
* walChecksum computes a running SQLite WAL checksum over a byte slice whose
|
|
14
|
+
* length is a multiple of 8. Returns the updated (s0, s1) pair.
|
|
15
|
+
*/
|
|
16
|
+
export declare function walChecksum(bo: ByteOrder, s0: number, s1: number, b: Uint8Array): [number, number];
|
|
17
|
+
//# sourceMappingURL=checksum.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checksum.d.ts","sourceRoot":"","sources":["../../src/wal/checksum.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpC,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,SAAS,EACb,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,CAAC,EAAE,UAAU,GACZ,CAAC,MAAM,EAAE,MAAM,CAAC,CASlB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite WAL checksum.
|
|
3
|
+
*
|
|
4
|
+
* Matches litestream's `WALChecksum` and SQLite's wal-checksum algorithm.
|
|
5
|
+
* The byte order is chosen from the WAL magic: 0x377f0682 => little-endian,
|
|
6
|
+
* 0x377f0683 => big-endian. The checksum runs over 8-byte units:
|
|
7
|
+
* s0 += u32[a] + s1 ; s1 += u32[a+4] + s0
|
|
8
|
+
*/
|
|
9
|
+
import { readU32BE, readU32LE } from "../ltx/bytes.js";
|
|
10
|
+
import { WALFormatError } from "../errors.js";
|
|
11
|
+
/** Reads a u32 from `buf` at `off` using the given byte order. */
|
|
12
|
+
export function readWalU32(bo, buf, off) {
|
|
13
|
+
if (bo === 'le')
|
|
14
|
+
return readU32LE(buf, off);
|
|
15
|
+
return readU32BE(buf, off);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* walChecksum computes a running SQLite WAL checksum over a byte slice whose
|
|
19
|
+
* length is a multiple of 8. Returns the updated (s0, s1) pair.
|
|
20
|
+
*/
|
|
21
|
+
export function walChecksum(bo, s0, s1, b) {
|
|
22
|
+
if (b.length % 8 !== 0) {
|
|
23
|
+
throw new WALFormatError('misaligned checksum byte slice');
|
|
24
|
+
}
|
|
25
|
+
for (let i = 0; i < b.length; i += 8) {
|
|
26
|
+
s0 = (s0 + readWalU32(bo, b, i) + s1) >>> 0;
|
|
27
|
+
s1 = (s1 + readWalU32(bo, b, i + 4) + s0) >>> 0;
|
|
28
|
+
}
|
|
29
|
+
return [s0, s1];
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** SQLite WAL (Write-Ahead Log) file constants. */
|
|
2
|
+
export declare const WALHeaderSize = 32;
|
|
3
|
+
export declare const WALFrameHeaderSize = 24;
|
|
4
|
+
/** WAL magic values (always read big-endian; determines checksum byte order). */
|
|
5
|
+
export declare const WALMagicLE = 931071618;
|
|
6
|
+
export declare const WALMagicBE = 931071619;
|
|
7
|
+
export declare const WALVersion = 3007000;
|
|
8
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/wal/constants.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAEnD,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,iFAAiF;AACjF,eAAO,MAAM,UAAU,YAAa,CAAC;AACrC,eAAO,MAAM,UAAU,YAAa,CAAC;AAErC,eAAO,MAAM,UAAU,UAAU,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** SQLite WAL (Write-Ahead Log) file constants. */
|
|
2
|
+
export const WALHeaderSize = 32;
|
|
3
|
+
export const WALFrameHeaderSize = 24;
|
|
4
|
+
/** WAL magic values (always read big-endian; determines checksum byte order). */
|
|
5
|
+
export const WALMagicLE = 0x377f0682; // checksums are computed little-endian
|
|
6
|
+
export const WALMagicBE = 0x377f0683; // checksums are computed big-endian
|
|
7
|
+
export const WALVersion = 3007000;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wal/index.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAEhE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WALReader parses a SQLite WAL file and verifies salt & checksum integrity
|
|
3
|
+
* while reading. It does not enforce transaction boundaries; the caller is
|
|
4
|
+
* responsible for handling uncommitted frames.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors litestream's `WALReader` (wal_reader.go).
|
|
7
|
+
*/
|
|
8
|
+
/** Raised when the WAL ends, or when a salt/checksum mismatch is detected. */
|
|
9
|
+
export declare class WALEOFError extends Error {
|
|
10
|
+
}
|
|
11
|
+
/** Raised when starting mid-WAL and the previous frame does not match. */
|
|
12
|
+
export declare class PrevFrameMismatchError extends Error {
|
|
13
|
+
readonly cause: Error;
|
|
14
|
+
constructor(cause: Error);
|
|
15
|
+
}
|
|
16
|
+
export interface WALFrame {
|
|
17
|
+
pgno: number;
|
|
18
|
+
commit: number;
|
|
19
|
+
}
|
|
20
|
+
export interface WALPageMap {
|
|
21
|
+
/** Latest committed offset (file offset) for each page. */
|
|
22
|
+
pageIndex: Map<number, number>;
|
|
23
|
+
/** Offset just past the last read frame. */
|
|
24
|
+
maxOffset: number;
|
|
25
|
+
/** Final database size in pages (from the last commit frame). */
|
|
26
|
+
commit: number;
|
|
27
|
+
/** Whether the scan was bounded by maxBytes. */
|
|
28
|
+
limited: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** WALReader reads and validates frames from an in-memory WAL byte buffer. */
|
|
31
|
+
export declare class WALReader {
|
|
32
|
+
private data;
|
|
33
|
+
private bo;
|
|
34
|
+
private pageSizeVal;
|
|
35
|
+
private seqVal;
|
|
36
|
+
private salt1Value;
|
|
37
|
+
private salt2Value;
|
|
38
|
+
private chksum1;
|
|
39
|
+
private chksum2;
|
|
40
|
+
private frameN;
|
|
41
|
+
constructor(data: Uint8Array);
|
|
42
|
+
/** Creates a reader positioned at `offset`, loading salts from the caller. */
|
|
43
|
+
static withOffset(data: Uint8Array, offset: number, salt1: number, salt2: number): WALReader;
|
|
44
|
+
/** PageSize returns the page size from the header. */
|
|
45
|
+
pageSize(): number;
|
|
46
|
+
/** Seq returns the checkpoint sequence from the header. */
|
|
47
|
+
seq(): number;
|
|
48
|
+
/** Salt1 returns the header salt-1 value. */
|
|
49
|
+
salt1(): number;
|
|
50
|
+
/** Salt2 returns the header salt-2 value. */
|
|
51
|
+
salt2(): number;
|
|
52
|
+
/** Offset returns the file offset of the last read frame (0 if none). */
|
|
53
|
+
offset(): number;
|
|
54
|
+
/** ReadFrame reads the next frame, verifying salt & checksum. */
|
|
55
|
+
readFrame(data: Uint8Array): WALFrame;
|
|
56
|
+
/** PageMap scans committed frames and returns the latest page offsets. */
|
|
57
|
+
pageMap(maxBytes?: number): WALPageMap;
|
|
58
|
+
private readHeader;
|
|
59
|
+
private readFrameInternal;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../../src/wal/reader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,8EAA8E;AAC9E,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAEzC,0EAA0E;AAC1E,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;gBACV,KAAK,EAAE,KAAK;CAIzB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,8EAA8E;AAC9E,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,MAAM,CAAK;gBAEP,IAAI,EAAE,UAAU;IAK5B,8EAA8E;IAC9E,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS;IAiC5F,sDAAsD;IACtD,QAAQ,IAAI,MAAM;IAIlB,2DAA2D;IAC3D,GAAG,IAAI,MAAM;IAIb,6CAA6C;IAC7C,KAAK,IAAI,MAAM;IAIf,6CAA6C;IAC7C,KAAK,IAAI,MAAM;IAIf,yEAAyE;IACzE,MAAM,IAAI,MAAM;IAKhB,iEAAiE;IACjE,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,QAAQ;IAIrC,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,SAAI,GAAG,UAAU;IAsDjC,OAAO,CAAC,UAAU;IA+BlB,OAAO,CAAC,iBAAiB;CAqD1B"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WALReader parses a SQLite WAL file and verifies salt & checksum integrity
|
|
3
|
+
* while reading. It does not enforce transaction boundaries; the caller is
|
|
4
|
+
* responsible for handling uncommitted frames.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors litestream's `WALReader` (wal_reader.go).
|
|
7
|
+
*/
|
|
8
|
+
import { WALFrameHeaderSize, WALHeaderSize, WALMagicBE, WALMagicLE, WALVersion } from "./constants.js";
|
|
9
|
+
import { walChecksum } from "./checksum.js";
|
|
10
|
+
import { readU32BE } from "../ltx/bytes.js";
|
|
11
|
+
import { WALFormatError } from "../errors.js";
|
|
12
|
+
/** Raised when the WAL ends, or when a salt/checksum mismatch is detected. */
|
|
13
|
+
export class WALEOFError extends Error {
|
|
14
|
+
}
|
|
15
|
+
/** Raised when starting mid-WAL and the previous frame does not match. */
|
|
16
|
+
export class PrevFrameMismatchError extends Error {
|
|
17
|
+
cause;
|
|
18
|
+
constructor(cause) {
|
|
19
|
+
super(`prev frame mismatch: ${cause.message}`);
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** WALReader reads and validates frames from an in-memory WAL byte buffer. */
|
|
24
|
+
export class WALReader {
|
|
25
|
+
data;
|
|
26
|
+
bo;
|
|
27
|
+
pageSizeVal = 0;
|
|
28
|
+
seqVal = 0;
|
|
29
|
+
salt1Value = 0;
|
|
30
|
+
salt2Value = 0;
|
|
31
|
+
chksum1 = 0;
|
|
32
|
+
chksum2 = 0;
|
|
33
|
+
frameN = 0;
|
|
34
|
+
constructor(data) {
|
|
35
|
+
this.data = data;
|
|
36
|
+
this.readHeader();
|
|
37
|
+
}
|
|
38
|
+
/** Creates a reader positioned at `offset`, loading salts from the caller. */
|
|
39
|
+
static withOffset(data, offset, salt1, salt2) {
|
|
40
|
+
if (offset <= WALHeaderSize) {
|
|
41
|
+
throw new WALFormatError(`offset (${offset}) must be greater than the wal header size (${WALHeaderSize})`);
|
|
42
|
+
}
|
|
43
|
+
const r = new WALReader(data);
|
|
44
|
+
// Load the salt from the caller; the beginning of the file may be gone.
|
|
45
|
+
r.salt1Value = salt1;
|
|
46
|
+
r.salt2Value = salt2;
|
|
47
|
+
// Ensure the offset is positioned on a frame start.
|
|
48
|
+
const frameSize = r.pageSizeVal + WALFrameHeaderSize;
|
|
49
|
+
if ((offset - WALHeaderSize) % frameSize !== 0) {
|
|
50
|
+
throw new WALFormatError(`unaligned wal offset ${offset} for page size ${r.pageSizeVal}`);
|
|
51
|
+
}
|
|
52
|
+
r.frameN = Math.floor((offset - WALHeaderSize) / frameSize);
|
|
53
|
+
// Read the previous page to load the checksum state.
|
|
54
|
+
r.frameN--;
|
|
55
|
+
try {
|
|
56
|
+
r.readFrameInternal(new Uint8Array(r.pageSizeVal), false);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err instanceof WALEOFError) {
|
|
60
|
+
throw new PrevFrameMismatchError(err);
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
return r;
|
|
65
|
+
}
|
|
66
|
+
/** PageSize returns the page size from the header. */
|
|
67
|
+
pageSize() {
|
|
68
|
+
return this.pageSizeVal;
|
|
69
|
+
}
|
|
70
|
+
/** Seq returns the checkpoint sequence from the header. */
|
|
71
|
+
seq() {
|
|
72
|
+
return this.seqVal;
|
|
73
|
+
}
|
|
74
|
+
/** Salt1 returns the header salt-1 value. */
|
|
75
|
+
salt1() {
|
|
76
|
+
return this.salt1Value;
|
|
77
|
+
}
|
|
78
|
+
/** Salt2 returns the header salt-2 value. */
|
|
79
|
+
salt2() {
|
|
80
|
+
return this.salt2Value;
|
|
81
|
+
}
|
|
82
|
+
/** Offset returns the file offset of the last read frame (0 if none). */
|
|
83
|
+
offset() {
|
|
84
|
+
if (this.frameN === 0)
|
|
85
|
+
return 0;
|
|
86
|
+
return WALHeaderSize + (this.frameN - 1) * (WALFrameHeaderSize + this.pageSizeVal);
|
|
87
|
+
}
|
|
88
|
+
/** ReadFrame reads the next frame, verifying salt & checksum. */
|
|
89
|
+
readFrame(data) {
|
|
90
|
+
return this.readFrameInternal(data, true);
|
|
91
|
+
}
|
|
92
|
+
/** PageMap scans committed frames and returns the latest page offsets. */
|
|
93
|
+
pageMap(maxBytes = 0) {
|
|
94
|
+
const m = new Map();
|
|
95
|
+
const txMap = new Map();
|
|
96
|
+
const data = new Uint8Array(this.pageSizeVal);
|
|
97
|
+
const frameSize = WALFrameHeaderSize + this.pageSizeVal;
|
|
98
|
+
const startOffset = WALHeaderSize + this.frameN * frameSize;
|
|
99
|
+
let commit = 0;
|
|
100
|
+
let limited = false;
|
|
101
|
+
for (;;) {
|
|
102
|
+
let frame;
|
|
103
|
+
try {
|
|
104
|
+
frame = this.readFrame(data);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (err instanceof WALEOFError)
|
|
108
|
+
break;
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
// Buffer the latest offset for each page in the current transaction.
|
|
112
|
+
const offset = this.offset();
|
|
113
|
+
txMap.set(frame.pgno, offset);
|
|
114
|
+
// On a commit record, flush pending pages into the full map.
|
|
115
|
+
if (frame.commit !== 0) {
|
|
116
|
+
for (const [pgno, off] of txMap)
|
|
117
|
+
m.set(pgno, off);
|
|
118
|
+
txMap.clear();
|
|
119
|
+
commit = frame.commit;
|
|
120
|
+
if (maxBytes > 0 && this.offset() + frameSize - startOffset >= maxBytes) {
|
|
121
|
+
limited = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Drop pages beyond the final commit size (DB may have shrunk via VACUUM).
|
|
127
|
+
for (const pgno of [...m.keys()]) {
|
|
128
|
+
if (pgno > commit)
|
|
129
|
+
m.delete(pgno);
|
|
130
|
+
}
|
|
131
|
+
if (m.size === 0) {
|
|
132
|
+
return { pageIndex: m, maxOffset: 0, commit: 0, limited };
|
|
133
|
+
}
|
|
134
|
+
// Compute the highest frame offset and extend past its data.
|
|
135
|
+
let end = 0;
|
|
136
|
+
for (const offset of m.values()) {
|
|
137
|
+
if (end === 0 || offset > end)
|
|
138
|
+
end = offset;
|
|
139
|
+
}
|
|
140
|
+
end += frameSize;
|
|
141
|
+
return { pageIndex: m, maxOffset: end, commit, limited };
|
|
142
|
+
}
|
|
143
|
+
readHeader() {
|
|
144
|
+
if (this.data.length < WALHeaderSize) {
|
|
145
|
+
throw new WALEOFError();
|
|
146
|
+
}
|
|
147
|
+
const hdr = this.data.subarray(0, WALHeaderSize);
|
|
148
|
+
const magic = readU32BE(hdr, 0);
|
|
149
|
+
if (magic === WALMagicLE)
|
|
150
|
+
this.bo = 'le';
|
|
151
|
+
else if (magic === WALMagicBE)
|
|
152
|
+
this.bo = 'be';
|
|
153
|
+
else
|
|
154
|
+
throw new WALFormatError(`invalid wal header magic: ${magic.toString(16)}`);
|
|
155
|
+
const chksum1 = readU32BE(hdr, 24);
|
|
156
|
+
const chksum2 = readU32BE(hdr, 28);
|
|
157
|
+
const [v0, v1] = walChecksum(this.bo, 0, 0, hdr.subarray(0, 24));
|
|
158
|
+
if (v0 !== chksum1 || v1 !== chksum2) {
|
|
159
|
+
throw new WALEOFError();
|
|
160
|
+
}
|
|
161
|
+
const version = readU32BE(hdr, 4);
|
|
162
|
+
if (version !== WALVersion) {
|
|
163
|
+
throw new WALFormatError(`unsupported wal version: ${version}`);
|
|
164
|
+
}
|
|
165
|
+
this.pageSizeVal = readU32BE(hdr, 8);
|
|
166
|
+
this.seqVal = readU32BE(hdr, 12);
|
|
167
|
+
this.salt1Value = readU32BE(hdr, 16);
|
|
168
|
+
this.salt2Value = readU32BE(hdr, 20);
|
|
169
|
+
this.chksum1 = chksum1;
|
|
170
|
+
this.chksum2 = chksum2;
|
|
171
|
+
}
|
|
172
|
+
readFrameInternal(data, verifyChecksum) {
|
|
173
|
+
if (data.length !== this.pageSizeVal) {
|
|
174
|
+
throw new WALFormatError(`buffer size (${data.length}) must match page size (${this.pageSizeVal})`);
|
|
175
|
+
}
|
|
176
|
+
const frameSize = this.pageSizeVal + WALFrameHeaderSize;
|
|
177
|
+
const offset = WALHeaderSize + this.frameN * frameSize;
|
|
178
|
+
if (offset + frameSize > this.data.length) {
|
|
179
|
+
throw new WALEOFError();
|
|
180
|
+
}
|
|
181
|
+
const hdr = this.data.subarray(offset, offset + WALFrameHeaderSize);
|
|
182
|
+
const page = this.data.subarray(offset + WALFrameHeaderSize, offset + frameSize);
|
|
183
|
+
// Verify the frame salt matches the header salt.
|
|
184
|
+
const salt1 = readU32BE(hdr, 8);
|
|
185
|
+
const salt2 = readU32BE(hdr, 12);
|
|
186
|
+
if (this.salt1Value !== salt1 || this.salt2Value !== salt2) {
|
|
187
|
+
throw new WALEOFError();
|
|
188
|
+
}
|
|
189
|
+
const fchksum1 = readU32BE(hdr, 16);
|
|
190
|
+
const fchksum2 = readU32BE(hdr, 20);
|
|
191
|
+
if (verifyChecksum) {
|
|
192
|
+
[this.chksum1, this.chksum2] = walChecksum(this.bo, this.chksum1, this.chksum2, hdr.subarray(0, 8));
|
|
193
|
+
[this.chksum1, this.chksum2] = walChecksum(this.bo, this.chksum1, this.chksum2, page);
|
|
194
|
+
if (this.chksum1 !== fchksum1 || this.chksum2 !== fchksum2) {
|
|
195
|
+
throw new WALEOFError();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this.chksum1 = fchksum1;
|
|
200
|
+
this.chksum2 = fchksum2;
|
|
201
|
+
}
|
|
202
|
+
const pgno = readU32BE(hdr, 0);
|
|
203
|
+
const commit = readU32BE(hdr, 4);
|
|
204
|
+
this.frameN++;
|
|
205
|
+
return { pgno, commit };
|
|
206
|
+
}
|
|
207
|
+
}
|