sproutboat 0.28.0 → 0.29.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/SURFACE.md +3 -3
  3. package/THIRD_PARTY_NOTICES.md +9 -0
  4. package/package.json +6 -6
  5. package/src/build.ts +12 -2
  6. package/src/compile.ts +23 -1
  7. package/src/main.ts +13 -2
  8. package/src/native-restore-sources.ts +48 -0
  9. package/src/native-restore.ts +155 -0
  10. package/src/surface.ts +2 -2
  11. package/vendor/ltx-restore/LICENSE +202 -0
  12. package/vendor/ltx-restore/NOTICE +32 -0
  13. package/vendor/ltx-restore/crc64-native.ts.txt +7 -0
  14. package/vendor/ltx-restore/errors.ts.txt +74 -0
  15. package/vendor/ltx-restore/file-io.ts.txt +54 -0
  16. package/vendor/ltx-restore/ltx/byte-reader.ts.txt +46 -0
  17. package/vendor/ltx-restore/ltx/bytes.ts.txt +87 -0
  18. package/vendor/ltx-restore/ltx/checksum.ts.txt +46 -0
  19. package/vendor/ltx-restore/ltx/constants.ts.txt +71 -0
  20. package/vendor/ltx-restore/ltx/crc64.ts.txt +73 -0
  21. package/vendor/ltx-restore/ltx/decoder.ts.txt +285 -0
  22. package/vendor/ltx-restore/ltx/header.ts.txt +126 -0
  23. package/vendor/ltx-restore/ltx/lz4.ts.txt +260 -0
  24. package/vendor/ltx-restore/ltx/page-index.ts.txt +140 -0
  25. package/vendor/ltx-restore/ltx/page.ts.txt +80 -0
  26. package/vendor/ltx-restore/ltx/sink.ts.txt +33 -0
  27. package/vendor/ltx-restore/ltx/trailer.ts.txt +58 -0
  28. package/vendor/ltx-restore/ltx/types.ts.txt +152 -0
  29. package/vendor/ltx-restore/lz4js/README.md +39 -0
  30. package/vendor/ltx-restore/lz4js/lz4.js.txt +560 -0
  31. package/vendor/ltx-restore/lz4js/package.json +36 -0
  32. package/vendor/ltx-restore/lz4js/util.js.txt +54 -0
  33. package/vendor/ltx-restore/lz4js/xxh32.js.txt +102 -0
  34. package/vendor/ltx-restore/program.ts.txt +3 -0
  35. package/vendor/ltx-restore/restore-native.ts.txt +120 -0
  36. package/vendor/ltx-restore/restore.ts.txt +96 -0
@@ -0,0 +1,102 @@
1
+ // xxh32.js - implementation of xxhash32 in plain JavaScript
2
+ var util = require('./util.js');
3
+
4
+ // xxhash32 primes
5
+ var prime1 = 0x9e3779b1;
6
+ var prime2 = 0x85ebca77;
7
+ var prime3 = 0xc2b2ae3d;
8
+ var prime4 = 0x27d4eb2f;
9
+ var prime5 = 0x165667b1;
10
+
11
+ // Utility functions/primitives
12
+ // --
13
+
14
+ function rotl32 (x, r) {
15
+ x = x | 0;
16
+ r = r | 0;
17
+
18
+ return x >>> (32 - r | 0) | x << r | 0;
19
+ }
20
+
21
+ function rotmul32 (h, r, m) {
22
+ h = h | 0;
23
+ r = r | 0;
24
+ m = m | 0;
25
+
26
+ return util.imul(h >>> (32 - r | 0) | h << r, m) | 0;
27
+ }
28
+
29
+ function shiftxor32 (h, s) {
30
+ h = h | 0;
31
+ s = s | 0;
32
+
33
+ return h >>> s ^ h | 0;
34
+ }
35
+
36
+ // Implementation
37
+ // --
38
+
39
+ function xxhapply (h, src, m0, s, m1) {
40
+ return rotmul32(util.imul(src, m0) + h, s, m1);
41
+ }
42
+
43
+ function xxh1 (h, src, index) {
44
+ return rotmul32((h + util.imul(src[index], prime5)), 11, prime1);
45
+ }
46
+
47
+ function xxh4 (h, src, index) {
48
+ return xxhapply(h, util.readU32(src, index), prime3, 17, prime4);
49
+ }
50
+
51
+ function xxh16 (h, src, index) {
52
+ return [
53
+ xxhapply(h[0], util.readU32(src, index + 0), prime2, 13, prime1),
54
+ xxhapply(h[1], util.readU32(src, index + 4), prime2, 13, prime1),
55
+ xxhapply(h[2], util.readU32(src, index + 8), prime2, 13, prime1),
56
+ xxhapply(h[3], util.readU32(src, index + 12), prime2, 13, prime1)
57
+ ];
58
+ }
59
+
60
+ function xxh32 (seed, src, index, len) {
61
+ var h, l;
62
+ l = len;
63
+ if (len >= 16) {
64
+ h = [
65
+ seed + prime1 + prime2,
66
+ seed + prime2,
67
+ seed,
68
+ seed - prime1
69
+ ];
70
+
71
+ while (len >= 16) {
72
+ h = xxh16(h, src, index);
73
+
74
+ index += 16;
75
+ len -= 16;
76
+ }
77
+
78
+ h = rotl32(h[0], 1) + rotl32(h[1], 7) + rotl32(h[2], 12) + rotl32(h[3], 18) + l;
79
+ } else {
80
+ h = (seed + prime5 + len) >>> 0;
81
+ }
82
+
83
+ while (len >= 4) {
84
+ h = xxh4(h, src, index);
85
+
86
+ index += 4;
87
+ len -= 4;
88
+ }
89
+
90
+ while (len > 0) {
91
+ h = xxh1(h, src, index);
92
+
93
+ index++;
94
+ len--;
95
+ }
96
+
97
+ h = shiftxor32(util.imul(shiftxor32(util.imul(shiftxor32(h, 15), prime2), 13), prime3), 16);
98
+
99
+ return h >>> 0;
100
+ }
101
+
102
+ exports.hash = xxh32;
@@ -0,0 +1,3 @@
1
+ import { restoreStartup } from './restore.ts';
2
+ restoreStartup();
3
+ export default { fetch() { throw new Error('restore startup must exit'); } };
@@ -0,0 +1,120 @@
1
+ // Trusted offline controller. Called only by the native restore startup unit.
2
+ export function restoreControl(op: string, first: string, second: string): string {
3
+ let result = '';
4
+ Porffor.c`
5
+ const char *ov, *av, *bv; size_t on, an, bn; char *oo = 0, *ao = 0, *bo = 0;
6
+ porf_native_fetch_read_value(op, &ov, &on, &oo);
7
+ porf_native_fetch_read_value(first, &av, &an, &ao);
8
+ porf_native_fetch_read_value(second, &bv, &bn, &bo);
9
+ char a[4096], b[4096], out[8192] = "ERROR: restore operation failed";
10
+ static int owner = -1;
11
+ static char pending[4096] = "";
12
+ if (an >= sizeof(a) || bn >= sizeof(b) || memchr(av, 0, an) || memchr(bv, 0, bn)) {
13
+ snprintf(out, sizeof(out), "ERROR: invalid restore path");
14
+ } else {
15
+ memcpy(a, av, an); a[an] = 0; memcpy(b, bv, bn); b[bn] = 0;
16
+ if (on && ov[0] == 's') {
17
+ char directory[4096], lockpath[4096];
18
+ if (!realpath(a, directory)) snprintf(out, sizeof(out), "ERROR: source generation does not exist");
19
+ else {
20
+ snprintf(lockpath, sizeof(lockpath), "%s", directory);
21
+ char *slash = strrchr(lockpath, '/');
22
+ if (slash) *slash = 0;
23
+ size_t n = strlen(lockpath);
24
+ if (n + 6 >= sizeof(lockpath)) snprintf(out, sizeof(out), "ERROR: source path too long");
25
+ else {
26
+ memcpy(lockpath + n, ".lock", 6);
27
+ owner = open(lockpath, O_RDWR | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
28
+ int lockerr = owner < 0 && errno != ENOENT;
29
+ struct stat lockst;
30
+ if (owner >= 0 && (fstat(owner, &lockst) || !S_ISREG(lockst.st_mode) || flock(owner, LOCK_EX | LOCK_NB))) lockerr = 1;
31
+ if (lockerr) snprintf(out, sizeof(out), "ERROR: source capture is active or cannot be locked");
32
+ else {
33
+ DIR *dir = opendir(directory);
34
+ uint64_t count = 0, max = 0; int bad = !dir;
35
+ struct dirent *entry;
36
+ while (dir && (entry = readdir(dir))) {
37
+ size_t len = strlen(entry->d_name);
38
+ if (len < 4 || strcmp(entry->d_name + len - 4, ".ltx")) continue;
39
+ unsigned long long lo = 0, hi = 0; char canonical[64];
40
+ if (len != 37 || sscanf(entry->d_name, "%16llx-%16llx.ltx", &lo, &hi) != 2 || !lo || lo != hi) { bad = 1; break; }
41
+ snprintf(canonical, sizeof(canonical), "%016llx-%016llx.ltx", lo, hi);
42
+ if (strcmp(canonical, entry->d_name)) { bad = 1; break; }
43
+ count++; if (hi > max) max = hi;
44
+ }
45
+ if (dir) closedir(dir);
46
+ if (bad || !count || count != max) snprintf(out, sizeof(out), "ERROR: expected one complete contiguous capture generation");
47
+ else snprintf(out, sizeof(out), "%llu", (unsigned long long)count);
48
+ }
49
+ }
50
+ }
51
+ } else if (on && ov[0] == 't') {
52
+ struct stat st; int bad = 0; char path[4096];
53
+ const char *suffixes[] = {"", "-wal", "-shm", "-journal"};
54
+ for (int i = 0; i < 4; i++) {
55
+ if (snprintf(path, sizeof(path), "%s%s", a, suffixes[i]) >= sizeof(path)) { bad = 1; break; }
56
+ if (!lstat(path, &st) || errno != ENOENT) { bad = 1; break; }
57
+ }
58
+ if (bad) snprintf(out, sizeof(out), "ERROR: output or SQLite sidecar already exists");
59
+ else if (snprintf(path, sizeof(path), "%s.restore-XXXXXX", a) >= sizeof(path)) snprintf(out, sizeof(out), "ERROR: output path too long");
60
+ else {
61
+ int fd = mkstemp(path);
62
+ if (fd >= 0) { close(fd); snprintf(pending, sizeof(pending), "%s", path); snprintf(out, sizeof(out), "%s", path); }
63
+ else snprintf(out, sizeof(out), "ERROR: cannot create temporary output");
64
+ }
65
+ } else if (on && ov[0] == 'i') {
66
+ // Immutable is safe here: the privately owned temporary database is closed for writing.
67
+ char uri[12320]; size_t p = 5; memcpy(uri, "file:", 5);
68
+ for (size_t i = 0; i < an; i++) {
69
+ unsigned char c = (unsigned char)a[i];
70
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '/' || c == '_' || c == '-' || c == '.') uri[p++] = c;
71
+ else { snprintf(uri + p, 4, "%%%02X", c); p += 3; }
72
+ }
73
+ memcpy(uri + p, "?immutable=1", 13);
74
+ sqlite3 *db = 0; sqlite3_stmt *stmt = 0; int ok = 0;
75
+ if (!sqlite3_open_v2(uri, &db, 1 | 0x40 | 0x8000, 0) &&
76
+ !sqlite3_exec(db, "PRAGMA cache_size=-1024; PRAGMA mmap_size=0; PRAGMA temp_store=FILE", 0, 0, 0) &&
77
+ !sqlite3_prepare_v2(db, "PRAGMA integrity_check(1)", -1, &stmt, 0) && sqlite3_step(stmt) == 100) {
78
+ const unsigned char *text = sqlite3_column_text(stmt, 0);
79
+ ok = text && !strcmp((const char*)text, "ok") && sqlite3_step(stmt) == 101;
80
+ }
81
+ if (stmt) sqlite3_finalize(stmt);
82
+ if (db) sqlite3_close(db);
83
+ snprintf(out, sizeof(out), "%s", ok ? "ok" : "ERROR: restored database failed integrity_check");
84
+ } else if (on && ov[0] == 'p') {
85
+ struct stat st; int bad = 0; char path[4096];
86
+ const char *suffixes[] = {"", "-wal", "-shm", "-journal"};
87
+ for (int i = 0; i < 4; i++) {
88
+ if (snprintf(path, sizeof(path), "%s%s", b, suffixes[i]) >= sizeof(path) || !lstat(path, &st) || errno != ENOENT) { bad = 1; break; }
89
+ }
90
+ snprintf(path, sizeof(path), "%s", b);
91
+ char *slash = strrchr(path, '/');
92
+ if (slash) { if (slash == path) slash[1] = 0; else *slash = 0; }
93
+ else snprintf(path, sizeof(path), ".");
94
+ int dir = open(path, O_RDONLY | O_DIRECTORY);
95
+ if (bad || dir < 0 || link(a, b)) snprintf(out, sizeof(out), "ERROR: output publication refused");
96
+ else if (fsync(dir) || unlink(a) || fsync(dir)) snprintf(out, sizeof(out), "ERROR: publication durability failed; output may exist");
97
+ else { pending[0] = 0; snprintf(out, sizeof(out), "ok"); }
98
+ if (dir >= 0) close(dir);
99
+ } else if (on && ov[0] == 'u') {
100
+ int rc = unlink(a); snprintf(out, sizeof(out), "%s", !rc || errno == ENOENT ? "ok" : "ERROR: temporary cleanup failed");
101
+ } else if (on && ov[0] == 'r') {
102
+ if (owner >= 0) { close(owner); owner = -1; }
103
+ snprintf(out, sizeof(out), "ok");
104
+ } else if (on && ov[0] == 'q') {
105
+ // This startup command owns cleanup at exit, including cross-function JS throws.
106
+ int code = atoi(a);
107
+ if (pending[0] && unlink(pending) && errno != ENOENT) {
108
+ fprintf(stderr, "temporary cleanup failed: %s\n", pending); code = 1;
109
+ }
110
+ if (owner >= 0) close(owner);
111
+ if (sb_cli_restore_stdout >= 0) { dup2(sb_cli_restore_stdout, 1); close(sb_cli_restore_stdout); }
112
+ fprintf(code ? stderr : stdout, "%s\n", b); exit(code);
113
+ }
114
+ }
115
+ result = porf_box((f64)porf_native_fetch_alloc_bytestring(out, strlen(out)), 195);
116
+ if (oo) free(oo); if (ao) free(ao); if (bo) free(bo);
117
+ `;
118
+ if (result.startsWith('ERROR:')) throw new Error(result);
119
+ return result;
120
+ }
@@ -0,0 +1,96 @@
1
+ import { Decoder } from './ltx/decoder.ts';
2
+ import { ChecksumFlag, lockPgno } from './ltx/constants.ts';
3
+ import { checksumPageWithHasher } from './ltx/checksum.ts';
4
+ import { Crc64, CRC_PROFILE } from './ltx/crc64.ts';
5
+ import { FileReader, FileSink } from './file-io.ts';
6
+ import { restoreControl } from './restore-native.ts';
7
+ declare function __sbEnv(name: string): string;
8
+
9
+ // Only the immutable, single-TXID files emitted by the native capture writer are accepted.
10
+ // A copied generation or a stopped writer is required. Never restore over an existing DB.
11
+ export function restoreGeneration(source: string, output: string, target: string): string {
12
+ let temporary = '';
13
+ let sink: FileSink | null = null;
14
+ try {
15
+ const available = BigInt(restoreControl('scan', source, ''));
16
+ const last = target ? BigInt(target) : available;
17
+ if (last < 1n || last > available) throw new Error('requested TXID is outside this generation');
18
+ temporary = restoreControl('temp', output, '');
19
+ sink = new FileSink(temporary);
20
+ let checksum = ChecksumFlag, pageSize = 0, pages = 0;
21
+ const hash = new Crc64();
22
+ for (let txid = 1n; txid <= last; txid++) {
23
+ const hex = txid.toString(16).padStart(16, '0');
24
+ const input = new FileReader(source + '/' + hex + '-' + hex + '.ltx');
25
+ try {
26
+ const decoder = new Decoder(new Uint8Array(0));
27
+ decoder.setReader(input); decoder.setRetainPageIndex(false); decoder.decodeHeader();
28
+ const h = decoder.Header();
29
+ if (h.flags !== 0 || h.minTXID !== txid || h.maxTXID !== txid ||
30
+ h.preApplyChecksum !== (txid === 1n ? 0n : checksum) ||
31
+ (pageSize !== 0 && h.pageSize !== pageSize)) throw new Error('LTX chain continuity failed');
32
+ pageSize = h.pageSize;
33
+ const lock = lockPgno(pageSize), data = new Uint8Array(pageSize), old = new Uint8Array(pageSize);
34
+ for (let pgno = h.commit + 1; pgno <= pages; pgno++) {
35
+ if (pgno === lock) continue;
36
+ sink.readAt(old, (pgno - 1) * pageSize);
37
+ checksum = ChecksumFlag | (checksum ^ checksumPageWithHasher(hash, pgno, old));
38
+ }
39
+ let previous = 0, growth = pages + 1;
40
+ for (;;) {
41
+ const ph = decoder.decodePage(data);
42
+ if (!ph) break;
43
+ const pgno = ph.pgno;
44
+ if (pgno <= previous || pgno === lock || ph.flags !== 1) throw new Error('invalid LTX page order or flags');
45
+ previous = pgno;
46
+ if (pgno <= pages) {
47
+ sink.readAt(old, (pgno - 1) * pageSize);
48
+ checksum = ChecksumFlag | (checksum ^ checksumPageWithHasher(hash, pgno, old));
49
+ } else {
50
+ if (growth === lock) growth++;
51
+ if (pgno !== growth) throw new Error('missing newly allocated page');
52
+ growth++;
53
+ }
54
+ checksum = ChecksumFlag | (checksum ^ checksumPageWithHasher(hash, pgno, data));
55
+ sink.writeAt(data, (pgno - 1) * pageSize);
56
+ }
57
+ if (growth === lock) growth++;
58
+ if (h.commit > pages && growth !== h.commit + 1) throw new Error('incomplete database growth');
59
+ decoder.close();
60
+ if (checksum !== decoder.Trailer().postApplyChecksum) throw new Error('database checksum mismatch');
61
+ pages = h.commit; sink.truncate(pages * pageSize);
62
+ } finally { input.close(); }
63
+ }
64
+ // Verify physical output once, independent of incremental checksum accounting.
65
+ let physical = ChecksumFlag;
66
+ const page = new Uint8Array(pageSize);
67
+ for (let pgno = 1; pgno <= pages; pgno++) {
68
+ if (pgno === lockPgno(pageSize)) continue;
69
+ sink.readAt(page, (pgno - 1) * pageSize);
70
+ physical = ChecksumFlag | (physical ^ checksumPageWithHasher(hash, pgno, page));
71
+ }
72
+ if (physical !== checksum) throw new Error('physical database checksum mismatch');
73
+ sink.sync(); sink.close(); sink = null;
74
+ restoreControl('integrity', temporary, '');
75
+ restoreControl('publish', temporary, output);
76
+ temporary = '';
77
+ const result = { txid: last.toString(), checksum: checksum.toString(16), bytes: pages * pageSize };
78
+ if (CRC_PROFILE[0]) {
79
+ result.crcMs = CRC_PROFILE[1];
80
+ result.crcBytes = CRC_PROFILE[2];
81
+ }
82
+ return JSON.stringify(result);
83
+ } finally {
84
+ if (sink) sink.close();
85
+ try { if (temporary) restoreControl('unlink', temporary, ''); }
86
+ finally { restoreControl('release', '', ''); }
87
+ }
88
+ }
89
+
90
+ export function restoreStartup(): void {
91
+ try {
92
+ CRC_PROFILE[3] = 1;
93
+ const result = restoreGeneration(__sbEnv('SB_LTX_RESTORE_GENERATION'), __sbEnv('SB_LTX_RESTORE_OUTPUT'), __sbEnv('SB_LTX_RESTORE_TXID'));
94
+ restoreControl('quit', '0', result);
95
+ } catch (error) { restoreControl('quit', '1', String(error)); }
96
+ }