@rigxyz/tapd 0.4.0 → 0.5.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.
@@ -159,7 +159,14 @@ var RelayClient = class {
159
159
  );
160
160
  return expectJson(route, res);
161
161
  }
162
- /** PUT bytes to a presigned URL. Throws on non-2xx. */
162
+ /**
163
+ * PUT bytes to a presigned URL. Throws on non-2xx — except 409, which
164
+ * means the content-addressed object is already stored (same hash = same
165
+ * bytes). That's idempotent success, not a conflict: two files with
166
+ * identical content (e.g. several empty files), or an initial upload that
167
+ * overlaps the daemon's first scan, race to PUT the same key. Treating
168
+ * 409 as fatal aborts the whole push and wedges sync at "N pending".
169
+ */
163
170
  async putObjectBytes(uploadUrl, bytes) {
164
171
  const route = "PUT <uploadUrl>";
165
172
  const res = await this.fetch(uploadUrl, {
@@ -167,6 +174,7 @@ var RelayClient = class {
167
174
  body: new Uint8Array(bytes),
168
175
  headers: { "content-length": String(bytes.byteLength) }
169
176
  });
177
+ if (res.status === 409) return;
170
178
  if (!res.ok) {
171
179
  throw new RelayError(route, res.status, await readJson(res));
172
180
  }
@@ -285,9 +293,8 @@ function joinUrl(base, suffix) {
285
293
  }
286
294
 
287
295
  // src/state-db.ts
288
- import { mkdirSync as mkdirSync2 } from "fs";
296
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync } from "fs";
289
297
  import { dirname as dirname2 } from "path";
290
- import Database from "better-sqlite3";
291
298
  var CURSOR_PREFIX = "chg_";
292
299
  function cursorGreaterThan(a, b) {
293
300
  const an = a.startsWith(CURSOR_PREFIX) ? safeBigInt(a.slice(CURSOR_PREFIX.length)) : null;
@@ -303,93 +310,109 @@ function safeBigInt(s) {
303
310
  return null;
304
311
  }
305
312
  }
306
- var SCHEMA = `
307
- CREATE TABLE IF NOT EXISTS paths (
308
- path TEXT PRIMARY KEY,
309
- last_applied_change_id TEXT,
310
- last_seen_hash TEXT,
311
- local_dirty INTEGER NOT NULL DEFAULT 0,
312
- last_scan_at TEXT
313
- );
314
-
315
- CREATE TABLE IF NOT EXISTS meta (
316
- key TEXT PRIMARY KEY,
317
- value TEXT NOT NULL
318
- );
319
- `;
313
+ var pendingFlushers = /* @__PURE__ */ new Set();
314
+ var exitHookInstalled = false;
315
+ function installExitHook() {
316
+ if (exitHookInstalled) return;
317
+ exitHookInstalled = true;
318
+ process.once("exit", () => {
319
+ for (const flush of pendingFlushers) {
320
+ try {
321
+ flush();
322
+ } catch {
323
+ }
324
+ }
325
+ });
326
+ }
327
+ function loadStore(dbPath) {
328
+ try {
329
+ const parsed = JSON.parse(readFileSync2(dbPath, "utf8"));
330
+ if (parsed && parsed.version === 1 && parsed.meta && parsed.paths) {
331
+ return { version: 1, meta: parsed.meta, paths: parsed.paths };
332
+ }
333
+ } catch {
334
+ }
335
+ return { version: 1, meta: {}, paths: {} };
336
+ }
320
337
  function openStateDb(dbPath) {
321
338
  mkdirSync2(dirname2(dbPath), { recursive: true });
322
- const db = new Database(dbPath);
323
- db.pragma("journal_mode = WAL");
324
- db.exec(SCHEMA);
325
- const stmtUpsert = db.prepare(`
326
- INSERT INTO paths (path, last_applied_change_id, last_seen_hash, local_dirty, last_scan_at)
327
- VALUES (@path, @last_applied_change_id, @last_seen_hash, @local_dirty, @last_scan_at)
328
- ON CONFLICT(path) DO UPDATE SET
329
- last_applied_change_id = excluded.last_applied_change_id,
330
- last_seen_hash = excluded.last_seen_hash,
331
- local_dirty = excluded.local_dirty,
332
- last_scan_at = excluded.last_scan_at
333
- `);
334
- const stmtGet = db.prepare(`SELECT * FROM paths WHERE path = ?`);
335
- const stmtDelete = db.prepare(`DELETE FROM paths WHERE path = ?`);
336
- const stmtListPaths = db.prepare(`SELECT path FROM paths`);
337
- const stmtSetMeta = db.prepare(`
338
- INSERT INTO meta (key, value) VALUES (?, ?)
339
- ON CONFLICT(key) DO UPDATE SET value = excluded.value
340
- `);
341
- const stmtGetMeta = db.prepare(`SELECT value FROM meta WHERE key = ?`);
339
+ const store = loadStore(dbPath);
340
+ let dirty = false;
341
+ let timer = null;
342
+ const flush = () => {
343
+ if (timer) {
344
+ clearTimeout(timer);
345
+ timer = null;
346
+ }
347
+ if (!dirty) return;
348
+ const tmp = `${dbPath}.tmp`;
349
+ writeFileSync2(tmp, JSON.stringify(store), "utf8");
350
+ renameSync(tmp, dbPath);
351
+ dirty = false;
352
+ };
353
+ const schedule = () => {
354
+ dirty = true;
355
+ if (timer) return;
356
+ timer = setTimeout(flush, 100);
357
+ timer.unref?.();
358
+ };
359
+ pendingFlushers.add(flush);
360
+ installExitHook();
342
361
  return {
343
362
  getCursor() {
344
- const row = stmtGetMeta.get("cursor");
345
- return row?.value ?? "chg_0";
363
+ return store.meta.cursor ?? "chg_0";
346
364
  },
347
365
  setCursor(cursor) {
348
- const current = stmtGetMeta.get("cursor")?.value;
366
+ const current = store.meta.cursor;
349
367
  if (current && !cursorGreaterThan(cursor, current)) return;
350
- stmtSetMeta.run("cursor", cursor);
368
+ store.meta.cursor = cursor;
369
+ schedule();
351
370
  },
352
371
  upsertPath(state) {
353
- stmtUpsert.run({
354
- path: state.path,
355
- last_applied_change_id: state.lastAppliedChangeId,
356
- last_seen_hash: state.lastSeenHash,
357
- local_dirty: state.localDirty ? 1 : 0,
358
- last_scan_at: state.lastScanAt
359
- });
372
+ store.paths[state.path] = {
373
+ lastAppliedChangeId: state.lastAppliedChangeId,
374
+ lastSeenHash: state.lastSeenHash,
375
+ localDirty: state.localDirty,
376
+ lastScanAt: state.lastScanAt
377
+ };
378
+ schedule();
360
379
  },
361
380
  getPath(path) {
362
- const row = stmtGet.get(path);
363
- if (!row) return null;
381
+ const r = store.paths[path];
382
+ if (!r) return null;
364
383
  return {
365
- path: row.path,
366
- lastAppliedChangeId: row.last_applied_change_id,
367
- lastSeenHash: row.last_seen_hash,
368
- localDirty: row.local_dirty !== 0,
369
- lastScanAt: row.last_scan_at
384
+ path,
385
+ lastAppliedChangeId: r.lastAppliedChangeId,
386
+ lastSeenHash: r.lastSeenHash,
387
+ localDirty: r.localDirty,
388
+ lastScanAt: r.lastScanAt
370
389
  };
371
390
  },
372
391
  deletePath(path) {
373
- stmtDelete.run(path);
392
+ if (path in store.paths) {
393
+ delete store.paths[path];
394
+ schedule();
395
+ }
374
396
  },
375
397
  listPaths() {
376
- return stmtListPaths.all().map((r) => r.path);
398
+ return Object.keys(store.paths);
377
399
  },
378
400
  setMeta(key, value) {
379
- stmtSetMeta.run(key, value);
401
+ store.meta[key] = value;
402
+ schedule();
380
403
  },
381
404
  getMeta(key) {
382
- const row = stmtGetMeta.get(key);
383
- return row?.value ?? null;
405
+ return store.meta[key] ?? null;
384
406
  },
385
407
  close() {
386
- db.close();
408
+ flush();
409
+ pendingFlushers.delete(flush);
387
410
  }
388
411
  };
389
412
  }
390
413
 
391
414
  // src/tapignore.ts
392
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
415
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
393
416
  import { join as join2 } from "path";
394
417
  import ignore from "ignore";
395
418
  var BUILTIN_IGNORE_LINES = [
@@ -425,7 +448,7 @@ function loadIgnore(rootDir) {
425
448
  ig.add(BUILTIN_IGNORE_LINES.join("\n"));
426
449
  const projectIgnore = join2(rootDir, ".tapignore");
427
450
  if (existsSync2(projectIgnore)) {
428
- ig.add(readFileSync2(projectIgnore, "utf8"));
451
+ ig.add(readFileSync3(projectIgnore, "utf8"));
429
452
  }
430
453
  return ig;
431
454
  }
@@ -560,11 +583,35 @@ async function hashFile(absPath) {
560
583
 
561
584
  // src/init.ts
562
585
  import { createHash as createHash2 } from "crypto";
563
- import { readFileSync as readFileSync3 } from "fs";
586
+ import { readFileSync as readFileSync4 } from "fs";
564
587
  import { join as join4 } from "path";
588
+
589
+ // src/pool.ts
590
+ async function mapPool(items, limit, fn) {
591
+ let next = 0;
592
+ let failed = false;
593
+ const worker = async () => {
594
+ while (!failed) {
595
+ const idx = next++;
596
+ if (idx >= items.length) break;
597
+ const item = items[idx];
598
+ try {
599
+ await fn(item);
600
+ } catch (err) {
601
+ failed = true;
602
+ throw err;
603
+ }
604
+ }
605
+ };
606
+ const n = Math.max(1, Math.min(limit, items.length));
607
+ await Promise.all(Array.from({ length: n }, worker));
608
+ }
609
+
610
+ // src/init.ts
565
611
  function sha256OfBuffer(bytes) {
566
612
  return `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
567
613
  }
614
+ var UPLOAD_CONCURRENCY = 8;
568
615
  var AlreadyInitializedError = class extends Error {
569
616
  constructor(path) {
570
617
  super(`already initialized \u2014 ${path} exists`);
@@ -609,27 +656,27 @@ async function init(opts) {
609
656
  const mutatedDuringScan = /* @__PURE__ */ new Set();
610
657
  let uploaded = 0;
611
658
  let reused = 0;
612
- for (const [hash, f] of uniqueHashes) {
613
- const prep = await client.prepareUpload({ hash, size: f.size });
659
+ await mapPool([...uniqueHashes.values()], UPLOAD_CONCURRENCY, async (f) => {
660
+ const prep = await client.prepareUpload({ hash: f.hash, size: f.size });
614
661
  if (prep.exists) {
615
662
  reused += 1;
616
- continue;
663
+ return;
617
664
  }
618
- const bytes = readFileSync3(join4(opts.rootDir, f.path));
665
+ const bytes = readFileSync4(join4(opts.rootDir, f.path));
619
666
  const actual = sha256OfBuffer(bytes);
620
- if (actual !== hash) {
667
+ if (actual !== f.hash) {
621
668
  mutatedDuringScan.add(f.path);
622
669
  scan.warnings.push({
623
670
  path: f.path,
624
671
  reason: "invalid_path"
625
672
  // closest existing warning kind; semantics are file-mutated-mid-scan
626
673
  });
627
- continue;
674
+ return;
628
675
  }
629
676
  await client.putObjectBytes(prep.uploadUrl, bytes);
630
- await client.completeUpload({ hash, size: f.size });
677
+ await client.completeUpload({ hash: f.hash, size: f.size });
631
678
  uploaded += 1;
632
- }
679
+ });
633
680
  const events = [
634
681
  ...hashed.filter((f) => !mutatedDuringScan.has(f.path)).map((f) => ({
635
682
  op: "write",
@@ -786,10 +833,10 @@ import {
786
833
  existsSync as existsSync3,
787
834
  mkdirSync as mkdirSync3,
788
835
  readdirSync as readdirSync2,
789
- readFileSync as readFileSync4,
836
+ readFileSync as readFileSync5,
790
837
  realpathSync,
791
838
  statSync as statSync2,
792
- writeFileSync as writeFileSync2
839
+ writeFileSync as writeFileSync3
793
840
  } from "fs";
794
841
  import { dirname as dirname3, isAbsolute, join as join6, relative } from "path";
795
842
  var NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;
@@ -875,14 +922,14 @@ var MOUNT_FILE_SUFFIX = ".local.json";
875
922
  function writeImportMount(rootDir, name, mount) {
876
923
  const path = importMountFile(rootDir, name);
877
924
  mkdirSync3(dirname3(path), { recursive: true });
878
- writeFileSync2(path, JSON.stringify(mount, null, 2) + "\n", { mode: 384 });
925
+ writeFileSync3(path, JSON.stringify(mount, null, 2) + "\n", { mode: 384 });
879
926
  }
880
927
  function readImportMount(rootDir, name) {
881
928
  const file = importMountFile(rootDir, name);
882
929
  if (!existsSync3(file)) return { ok: false, reason: "missing_token_file" };
883
930
  let parsed;
884
931
  try {
885
- parsed = JSON.parse(readFileSync4(file, "utf8"));
932
+ parsed = JSON.parse(readFileSync5(file, "utf8"));
886
933
  } catch {
887
934
  return { ok: false, reason: "malformed_json" };
888
935
  }
@@ -966,7 +1013,7 @@ function pathsOverlap(a, b) {
966
1013
  }
967
1014
 
968
1015
  // src/import-invite.ts
969
- import { existsSync as existsSync4, readFileSync as readFileSync5, rmSync } from "fs";
1016
+ import { existsSync as existsSync4, readFileSync as readFileSync6, rmSync } from "fs";
970
1017
  import { dirname as dirname4, join as joinPath2 } from "path";
971
1018
  var ImportValidationFailed = class extends Error {
972
1019
  constructor(reason) {
@@ -1063,7 +1110,7 @@ function readRawMount(rootDir, name) {
1063
1110
  const file = importMountFile(rootDir, name);
1064
1111
  if (!existsSync4(file)) return null;
1065
1112
  try {
1066
- return JSON.parse(readFileSync5(file, "utf8"));
1113
+ return JSON.parse(readFileSync6(file, "utf8"));
1067
1114
  } catch {
1068
1115
  return null;
1069
1116
  }
@@ -1115,11 +1162,11 @@ import {
1115
1162
  lstatSync,
1116
1163
  mkdirSync as mkdirSync4,
1117
1164
  readdirSync as readdirSync3,
1118
- readFileSync as readFileSync6,
1119
- renameSync,
1165
+ readFileSync as readFileSync7,
1166
+ renameSync as renameSync2,
1120
1167
  rmdirSync,
1121
1168
  rmSync as rmSync2,
1122
- writeFileSync as writeFileSync3
1169
+ writeFileSync as writeFileSync4
1123
1170
  } from "fs";
1124
1171
  import { basename, dirname as dirname5, extname, join as join7, sep as sep2 } from "path";
1125
1172
  var STATE_KEY_CONFLICT_COUNT = "conflict_count";
@@ -1141,7 +1188,7 @@ function sha256Hex(bytes) {
1141
1188
  return createHash3("sha256").update(bytes).digest("hex");
1142
1189
  }
1143
1190
  function sha256OfFile(absPath) {
1144
- return `sha256:${sha256Hex(readFileSync6(absPath))}`;
1191
+ return `sha256:${sha256Hex(readFileSync7(absPath))}`;
1145
1192
  }
1146
1193
  function conflictSidecarPath(path, ev) {
1147
1194
  const dir = dirname5(path);
@@ -1274,8 +1321,8 @@ async function applyEvent(opts) {
1274
1321
  const sidecarAbs = join7(rootDir, sidecarPath);
1275
1322
  mkdirSync4(dirname5(sidecarAbs), { recursive: true });
1276
1323
  const tmp2 = `${sidecarAbs}.tap-tmp.${randomBytes(6).toString("hex")}`;
1277
- writeFileSync3(tmp2, bytes, { mode: ev.executable ? 493 : 420 });
1278
- renameSync(tmp2, sidecarAbs);
1324
+ writeFileSync4(tmp2, bytes, { mode: ev.executable ? 493 : 420 });
1325
+ renameSync2(tmp2, sidecarAbs);
1279
1326
  stateDb.upsertPath({
1280
1327
  path: ev.path,
1281
1328
  lastAppliedChangeId: ev.id,
@@ -1288,8 +1335,8 @@ async function applyEvent(opts) {
1288
1335
  }
1289
1336
  mkdirSync4(dirname5(abs), { recursive: true });
1290
1337
  const tmp = `${abs}.tap-tmp.${randomBytes(6).toString("hex")}`;
1291
- writeFileSync3(tmp, bytes, { mode: ev.executable ? 493 : 420 });
1292
- renameSync(tmp, abs);
1338
+ writeFileSync4(tmp, bytes, { mode: ev.executable ? 493 : 420 });
1339
+ renameSync2(tmp, abs);
1293
1340
  stateDb.upsertPath({
1294
1341
  path: ev.path,
1295
1342
  lastAppliedChangeId: ev.id,
@@ -1601,8 +1648,9 @@ async function startSubscription(opts) {
1601
1648
 
1602
1649
  // src/uploader.ts
1603
1650
  import { createHash as createHash4 } from "crypto";
1604
- import { readFileSync as readFileSync7 } from "fs";
1651
+ import { readFileSync as readFileSync8 } from "fs";
1605
1652
  import { join as join8 } from "path";
1653
+ var UPLOAD_CONCURRENCY2 = 8;
1606
1654
  function sha256OfBuffer2(bytes) {
1607
1655
  return `sha256:${createHash4("sha256").update(bytes).digest("hex")}`;
1608
1656
  }
@@ -1674,23 +1722,23 @@ async function scanAndPush(opts) {
1674
1722
  const skipWarnings = [];
1675
1723
  let uploaded = 0;
1676
1724
  let reused = 0;
1677
- for (const [hash, f] of uniqueHashes) {
1678
- const prep = await client.prepareUpload({ hash, size: f.size });
1725
+ await mapPool([...uniqueHashes.values()], UPLOAD_CONCURRENCY2, async (f) => {
1726
+ const prep = await client.prepareUpload({ hash: f.hash, size: f.size });
1679
1727
  if (prep.exists) {
1680
1728
  reused += 1;
1681
- continue;
1729
+ return;
1682
1730
  }
1683
- const bytes = readFileSync7(join8(rootDir, f.path));
1731
+ const bytes = readFileSync8(join8(rootDir, f.path));
1684
1732
  const actual = sha256OfBuffer2(bytes);
1685
- if (actual !== hash) {
1733
+ if (actual !== f.hash) {
1686
1734
  mutatedPaths.add(f.path);
1687
1735
  skipWarnings.push({ path: f.path, reason: `mutated_during_scan:${actual}` });
1688
- continue;
1736
+ return;
1689
1737
  }
1690
1738
  await client.putObjectBytes(prep.uploadUrl, bytes);
1691
- await client.completeUpload({ hash, size: f.size });
1739
+ await client.completeUpload({ hash: f.hash, size: f.size });
1692
1740
  uploaded += 1;
1693
- }
1741
+ });
1694
1742
  const events = [];
1695
1743
  for (const w of diff.writes) {
1696
1744
  if (mutatedPaths.has(w.path)) continue;
@@ -1989,4 +2037,4 @@ export {
1989
2037
  scanAndPush,
1990
2038
  start
1991
2039
  };
1992
- //# sourceMappingURL=chunk-YPZ2UNI5.js.map
2040
+ //# sourceMappingURL=chunk-XMYPUAYD.js.map