@toon-protocol/rig 2.1.0 → 2.3.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.
@@ -1,9 +1,13 @@
1
1
  import {
2
+ ARWEAVE_FETCH_TIMEOUT_MS,
3
+ ARWEAVE_GATEWAYS,
2
4
  buildRepoAnnouncement,
3
- buildRepoRefs
4
- } from "./chunk-HPSOQP7Q.js";
5
+ buildRepoRefs,
6
+ isValidArweaveTxId
7
+ } from "./chunk-3HRFDH7H.js";
5
8
  import {
6
- MAX_OBJECT_SIZE
9
+ MAX_OBJECT_SIZE,
10
+ hashGitObject
7
11
  } from "./chunk-X2CZPPDM.js";
8
12
 
9
13
  // src/repo-reader.ts
@@ -409,6 +413,443 @@ var GitRepoReader = class {
409
413
  }
410
414
  };
411
415
 
416
+ // src/object-fetch.ts
417
+ var DEFAULT_CONCURRENCY = 8;
418
+ var ObjectIntegrityError = class extends Error {
419
+ constructor(objects) {
420
+ super(
421
+ `${objects.length} downloaded object(s) failed SHA-1 verification \u2014 the gateway content does not match the announced git SHA(s): ` + objects.map((o) => `${o.sha} (tx ${o.txId})`).join(", ") + ". Refusing to write corrupt/tampered objects."
422
+ );
423
+ this.objects = objects;
424
+ this.name = "ObjectIntegrityError";
425
+ }
426
+ objects;
427
+ };
428
+ var OBJECT_TYPES2 = [
429
+ "blob",
430
+ "tree",
431
+ "commit",
432
+ "tag"
433
+ ];
434
+ function verifyObjectBody(expectedSha, bytes) {
435
+ const body = Buffer.from(bytes);
436
+ for (const type of OBJECT_TYPES2) {
437
+ if (hashGitObject(type, body).sha === expectedSha) {
438
+ return { sha: expectedSha, type, body };
439
+ }
440
+ }
441
+ return null;
442
+ }
443
+ async function fetchTxBytes(txId, options = {}) {
444
+ if (!isValidArweaveTxId(txId)) return null;
445
+ const gateways = options.gateways ?? ARWEAVE_GATEWAYS;
446
+ const fetchFn = options.fetchFn ?? fetch;
447
+ const timeoutMs = options.timeoutMs ?? ARWEAVE_FETCH_TIMEOUT_MS;
448
+ for (const gateway of gateways) {
449
+ try {
450
+ const response = await fetchFn(`${gateway}/${txId}`, {
451
+ signal: AbortSignal.timeout(timeoutMs)
452
+ });
453
+ if (!response.ok) continue;
454
+ return new Uint8Array(await response.arrayBuffer());
455
+ } catch {
456
+ }
457
+ }
458
+ return null;
459
+ }
460
+ async function downloadGitObjects(entries, options = {}) {
461
+ const queue = [...entries];
462
+ const total = queue.length;
463
+ const concurrency = Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY);
464
+ const objects = /* @__PURE__ */ new Map();
465
+ const unavailable = [];
466
+ const corrupt = [];
467
+ let done = 0;
468
+ const worker = async () => {
469
+ for (; ; ) {
470
+ const next = queue.shift();
471
+ if (!next) return;
472
+ const [sha, txId] = next;
473
+ const bytes = await fetchTxBytes(txId, options);
474
+ if (bytes === null) {
475
+ unavailable.push({ sha, txId });
476
+ } else {
477
+ const verified = verifyObjectBody(sha, bytes);
478
+ if (verified === null) {
479
+ corrupt.push({ sha, txId });
480
+ } else {
481
+ objects.set(sha, verified);
482
+ }
483
+ }
484
+ done += 1;
485
+ options.onObject?.(done, total);
486
+ }
487
+ };
488
+ await Promise.all(
489
+ Array.from({ length: Math.min(concurrency, total) }, () => worker())
490
+ );
491
+ if (corrupt.length > 0) throw new ObjectIntegrityError(corrupt);
492
+ return { objects, unavailable };
493
+ }
494
+ var FULL_SHA_RE2 = /^[0-9a-f]{40}$/;
495
+ var GITLINK_MODE = "160000";
496
+ function bytesToHex(bytes) {
497
+ let hex = "";
498
+ for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
499
+ return hex;
500
+ }
501
+ function referencedShas(object) {
502
+ switch (object.type) {
503
+ case "blob":
504
+ return [];
505
+ case "tree": {
506
+ const refs = [];
507
+ const data = object.body;
508
+ let offset = 0;
509
+ while (offset < data.length) {
510
+ const spaceIdx = data.indexOf(32, offset);
511
+ if (spaceIdx === -1) break;
512
+ const mode = data.subarray(offset, spaceIdx).toString("utf-8");
513
+ const nulIdx = data.indexOf(0, spaceIdx + 1);
514
+ if (nulIdx === -1 || nulIdx + 21 > data.length) break;
515
+ const sha = bytesToHex(data.subarray(nulIdx + 1, nulIdx + 21));
516
+ if (mode !== GITLINK_MODE) refs.push(sha);
517
+ offset = nulIdx + 21;
518
+ }
519
+ return refs;
520
+ }
521
+ case "commit": {
522
+ const refs = [];
523
+ const text = object.body.toString("utf-8");
524
+ const headerEnd = text.indexOf("\n\n");
525
+ const header = headerEnd === -1 ? text : text.slice(0, headerEnd);
526
+ for (const line of header.split("\n")) {
527
+ if (line.startsWith("tree ")) refs.push(line.slice(5).trim());
528
+ else if (line.startsWith("parent ")) refs.push(line.slice(7).trim());
529
+ }
530
+ return refs.filter((sha) => FULL_SHA_RE2.test(sha));
531
+ }
532
+ case "tag": {
533
+ const text = object.body.toString("utf-8");
534
+ const match = /^object ([0-9a-f]{40})$/m.exec(text);
535
+ return match?.[1] ? [match[1]] : [];
536
+ }
537
+ }
538
+ }
539
+ function walkClosure(tips, objects, presentLocally = /* @__PURE__ */ new Set()) {
540
+ const reachable = /* @__PURE__ */ new Set();
541
+ const missing = /* @__PURE__ */ new Set();
542
+ const stack = [...new Set(tips)];
543
+ while (stack.length > 0) {
544
+ const sha = stack.pop();
545
+ if (reachable.has(sha) || missing.has(sha)) continue;
546
+ if (presentLocally.has(sha)) continue;
547
+ const object = objects.get(sha);
548
+ if (!object) {
549
+ missing.add(sha);
550
+ continue;
551
+ }
552
+ reachable.add(sha);
553
+ for (const ref of referencedShas(object)) {
554
+ if (!reachable.has(ref) && !missing.has(ref)) stack.push(ref);
555
+ }
556
+ }
557
+ return { reachable, missing: [...missing] };
558
+ }
559
+
560
+ // src/read-pipeline.ts
561
+ var MAX_PASSES = 64;
562
+ async function collectRepoObjects(options) {
563
+ const { tips, shaToTxId, resolveMissing } = options;
564
+ const present = options.presentLocally ?? /* @__PURE__ */ new Set();
565
+ const txIds = new Map(shaToTxId);
566
+ const resolverAsked = /* @__PURE__ */ new Set();
567
+ const undownloadable = /* @__PURE__ */ new Map();
568
+ const objects = /* @__PURE__ */ new Map();
569
+ const initial = [];
570
+ for (const [sha, txId] of txIds) {
571
+ if (!present.has(sha)) initial.push([sha, txId]);
572
+ }
573
+ const bulk = await downloadGitObjects(initial, options);
574
+ for (const [sha, object] of bulk.objects) objects.set(sha, object);
575
+ for (const { sha, txId } of bulk.unavailable) undownloadable.set(sha, txId);
576
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
577
+ const closure = walkClosure(tips, objects, present);
578
+ const open = closure.missing.filter((sha) => !undownloadable.has(sha));
579
+ if (open.length === 0) break;
580
+ const toResolve = open.filter(
581
+ (sha) => !txIds.has(sha) && !resolverAsked.has(sha)
582
+ );
583
+ for (const sha of toResolve) resolverAsked.add(sha);
584
+ if (toResolve.length > 0) {
585
+ const resolved = await resolveMissing(toResolve);
586
+ for (const [sha, txId] of resolved) txIds.set(sha, txId);
587
+ }
588
+ const batch = [];
589
+ for (const sha of open) {
590
+ const txId = txIds.get(sha);
591
+ if (txId !== void 0 && !objects.has(sha)) batch.push([sha, txId]);
592
+ }
593
+ if (batch.length === 0) break;
594
+ const result = await downloadGitObjects(batch, options);
595
+ for (const [sha, object] of result.objects) objects.set(sha, object);
596
+ for (const { sha, txId } of result.unavailable)
597
+ undownloadable.set(sha, txId);
598
+ if (result.objects.size === 0) break;
599
+ }
600
+ const finalClosure = walkClosure(tips, objects, present);
601
+ const missing = finalClosure.missing.map((sha) => ({
602
+ sha,
603
+ txId: txIds.get(sha) ?? null
604
+ }));
605
+ const reachable = finalClosure.reachable;
606
+ const skippedUnavailable = [...undownloadable].filter(
607
+ ([sha]) => !reachable.has(sha) && !finalClosure.missing.includes(sha)
608
+ ).map(([sha, txId]) => ({ sha, txId }));
609
+ return { objects, missing, skippedUnavailable };
610
+ }
611
+ function missingObjectsMessage(missing, context) {
612
+ const listed = missing.slice(0, 20).map(
613
+ (m) => ` ${m.sha}${m.txId ? ` (tx ${m.txId})` : " (no Arweave tx found)"}`
614
+ ).join("\n");
615
+ const more = missing.length > 20 ? `
616
+ \u2026 and ${missing.length - 20} more` : "";
617
+ return `${context}: ${missing.length} required object(s) could not be downloaded:
618
+ ${listed}${more}
619
+ Recently pushed objects can take 10-20 minutes to become fetchable from Arweave gateways \u2014 if this repo was just pushed, retry in a few minutes. Nothing was written.`;
620
+ }
621
+
622
+ // src/materialize.ts
623
+ import { execFile as execFile2, spawn as spawn2 } from "child_process";
624
+ import { promisify as promisify2 } from "util";
625
+ var execFileAsync2 = promisify2(execFile2);
626
+ var ObjectWriteMismatchError = class extends Error {
627
+ constructor(expectedSha, writtenSha) {
628
+ super(
629
+ `git hash-object wrote ${writtenSha} where ${expectedSha} was expected \u2014 object content does not round-trip; aborting`
630
+ );
631
+ this.expectedSha = expectedSha;
632
+ this.writtenSha = writtenSha;
633
+ this.name = "ObjectWriteMismatchError";
634
+ }
635
+ expectedSha;
636
+ writtenSha;
637
+ };
638
+ var FULL_SHA_RE3 = /^[0-9a-f]{40}$/;
639
+ function isSafeRefname(refname) {
640
+ if (!refname.startsWith("refs/") || refname.length > 1024) return false;
641
+ if (/[\u0000-\u0020~^:?*[\\\u007f]/.test(refname)) return false;
642
+ if (refname.includes("..") || refname.includes("@{")) return false;
643
+ if (refname.endsWith("/") || refname.endsWith(".")) return false;
644
+ for (const part of refname.split("/")) {
645
+ if (part === "" || part.startsWith(".") || part.startsWith("-"))
646
+ return false;
647
+ if (part.endsWith(".lock")) return false;
648
+ }
649
+ return true;
650
+ }
651
+ function assertSafeRefname(refname) {
652
+ if (!isSafeRefname(refname)) {
653
+ throw new Error(
654
+ `unsafe ref name from remote state: ${JSON.stringify(refname)} \u2014 refusing`
655
+ );
656
+ }
657
+ }
658
+ function assertFullSha2(sha) {
659
+ if (!FULL_SHA_RE3.test(sha)) {
660
+ throw new Error(`not a full 40-hex SHA-1: ${JSON.stringify(sha)}`);
661
+ }
662
+ }
663
+ async function runGit(repoPath, args) {
664
+ try {
665
+ const { stdout } = await execFileAsync2("git", args, {
666
+ cwd: repoPath,
667
+ encoding: "utf-8",
668
+ maxBuffer: 64 * 1024 * 1024
669
+ });
670
+ return stdout;
671
+ } catch (err) {
672
+ const e = err;
673
+ throw new Error(
674
+ `git ${args[0]} failed${typeof e.code === "number" ? ` (exit ${e.code})` : ""}: ${(e.stderr ?? e.message ?? "").trim()}`
675
+ );
676
+ }
677
+ }
678
+ async function writeGitObject(repoPath, object) {
679
+ assertFullSha2(object.sha);
680
+ const written = await new Promise((resolve, reject) => {
681
+ const child = spawn2(
682
+ "git",
683
+ ["hash-object", "-w", "--stdin", "-t", object.type],
684
+ { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] }
685
+ );
686
+ let stdout = "";
687
+ let stderr = "";
688
+ child.stdout.on("data", (chunk) => {
689
+ stdout += chunk.toString("utf-8");
690
+ });
691
+ child.stderr.on("data", (chunk) => {
692
+ stderr += chunk.toString("utf-8");
693
+ });
694
+ child.on("error", (err) => {
695
+ reject(new Error(`failed to spawn git hash-object: ${err.message}`));
696
+ });
697
+ child.on("close", (code) => {
698
+ if (code !== 0) {
699
+ return reject(
700
+ new Error(`git hash-object failed (exit ${code}): ${stderr.trim()}`)
701
+ );
702
+ }
703
+ resolve(stdout.trim());
704
+ });
705
+ child.stdin.on("error", () => {
706
+ });
707
+ child.stdin.write(object.body);
708
+ child.stdin.end();
709
+ });
710
+ if (written !== object.sha) {
711
+ throw new ObjectWriteMismatchError(object.sha, written);
712
+ }
713
+ }
714
+ async function writeGitObjects(repoPath, objects) {
715
+ let count = 0;
716
+ for (const object of objects) {
717
+ await writeGitObject(repoPath, object);
718
+ count += 1;
719
+ }
720
+ return count;
721
+ }
722
+ async function updateRef(repoPath, refname, sha) {
723
+ assertSafeRefname(refname);
724
+ assertFullSha2(sha);
725
+ await runGit(repoPath, ["update-ref", refname, sha]);
726
+ }
727
+ async function setHeadSymref(repoPath, refname) {
728
+ assertSafeRefname(refname);
729
+ await runGit(repoPath, ["symbolic-ref", "HEAD", refname]);
730
+ }
731
+
732
+ // src/npub.ts
733
+ var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
734
+ function bech32Polymod(values) {
735
+ const GEN = [996825010, 642813549, 513874426, 1027748829, 705979059];
736
+ let chk = 1;
737
+ for (const v of values) {
738
+ const b = chk >> 25;
739
+ chk = (chk & 33554431) << 5 ^ v;
740
+ for (let i = 0; i < 5; i++) {
741
+ chk ^= b >> i & 1 ? GEN[i] : 0;
742
+ }
743
+ }
744
+ return chk;
745
+ }
746
+ function bech32HrpExpand(hrp) {
747
+ const ret = [];
748
+ for (let i = 0; i < hrp.length; i++) {
749
+ ret.push(hrp.charCodeAt(i) >> 5);
750
+ }
751
+ ret.push(0);
752
+ for (let i = 0; i < hrp.length; i++) {
753
+ ret.push(hrp.charCodeAt(i) & 31);
754
+ }
755
+ return ret;
756
+ }
757
+ function bech32CreateChecksum(hrp, data) {
758
+ const values = bech32HrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
759
+ const polymod = bech32Polymod(values) ^ 1;
760
+ const ret = [];
761
+ for (let i = 0; i < 6; i++) {
762
+ ret.push(polymod >> 5 * (5 - i) & 31);
763
+ }
764
+ return ret;
765
+ }
766
+ function convertBits(data, fromBits, toBits, pad) {
767
+ let acc = 0;
768
+ let bits = 0;
769
+ const ret = [];
770
+ const maxv = (1 << toBits) - 1;
771
+ for (const value of data) {
772
+ acc = acc << fromBits | value;
773
+ bits += fromBits;
774
+ while (bits >= toBits) {
775
+ bits -= toBits;
776
+ ret.push(acc >> bits & maxv);
777
+ }
778
+ }
779
+ if (pad && bits > 0) {
780
+ ret.push(acc << toBits - bits & maxv);
781
+ }
782
+ return ret;
783
+ }
784
+ function hexToBytes(hex) {
785
+ const bytes = [];
786
+ for (let i = 0; i < hex.length; i += 2) {
787
+ bytes.push(parseInt(hex.slice(i, i + 2), 16));
788
+ }
789
+ return bytes;
790
+ }
791
+ function hexToNpub(hexPubkey) {
792
+ const hrp = "npub";
793
+ const bytes = hexToBytes(hexPubkey);
794
+ const words = convertBits(bytes, 8, 5, true);
795
+ const checksum = bech32CreateChecksum(hrp, words);
796
+ const combined = words.concat(checksum);
797
+ return hrp + "1" + combined.map((d) => BECH32_CHARSET[d]).join("");
798
+ }
799
+ function npubToHex(npub) {
800
+ const lower = npub.toLowerCase();
801
+ if (lower !== npub && npub.toUpperCase() !== npub) {
802
+ throw new Error("npub: mixed case");
803
+ }
804
+ if (!lower.startsWith("npub1")) {
805
+ throw new Error("npub: invalid prefix");
806
+ }
807
+ if (lower.length !== 63) {
808
+ throw new Error("npub: invalid length");
809
+ }
810
+ const data = [];
811
+ for (let i = 5; i < lower.length; i++) {
812
+ const idx = BECH32_CHARSET.indexOf(lower[i]);
813
+ if (idx === -1) throw new Error("npub: invalid character");
814
+ data.push(idx);
815
+ }
816
+ const hrpExpanded = bech32HrpExpand("npub");
817
+ if (bech32Polymod(hrpExpanded.concat(data)) !== 1) {
818
+ throw new Error("npub: invalid checksum");
819
+ }
820
+ const words = data.slice(0, -6);
821
+ const bytes = convertBits(words, 5, 8, false);
822
+ if (bytes.length !== 32) {
823
+ throw new Error("npub: invalid data length");
824
+ }
825
+ const totalBits = words.length * 5;
826
+ const trailingBits = totalBits - bytes.length * 8;
827
+ if (trailingBits > 0) {
828
+ const lastWord = words[words.length - 1];
829
+ const mask = (1 << trailingBits) - 1;
830
+ if ((lastWord & mask) !== 0) {
831
+ throw new Error("npub: non-zero padding bits");
832
+ }
833
+ }
834
+ return bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
835
+ }
836
+ var HEX64_RE = /^[0-9a-f]{64}$/;
837
+ function ownerToHex(owner) {
838
+ if (HEX64_RE.test(owner)) return owner;
839
+ if (owner.startsWith("npub1")) {
840
+ try {
841
+ return npubToHex(owner);
842
+ } catch (err) {
843
+ throw new Error(
844
+ `invalid owner ${JSON.stringify(owner)}: ${err instanceof Error ? err.message : String(err)}`
845
+ );
846
+ }
847
+ }
848
+ throw new Error(
849
+ `invalid owner ${JSON.stringify(owner)}: expected a 64-char lowercase hex pubkey or an npub1\u2026 string`
850
+ );
851
+ }
852
+
412
853
  // src/routes.ts
413
854
  function serializeFeeEstimate(plan) {
414
855
  return {
@@ -695,6 +1136,25 @@ async function executePush(options) {
695
1136
  export {
696
1137
  GitError,
697
1138
  GitRepoReader,
1139
+ DEFAULT_CONCURRENCY,
1140
+ ObjectIntegrityError,
1141
+ verifyObjectBody,
1142
+ fetchTxBytes,
1143
+ downloadGitObjects,
1144
+ referencedShas,
1145
+ walkClosure,
1146
+ collectRepoObjects,
1147
+ missingObjectsMessage,
1148
+ ObjectWriteMismatchError,
1149
+ isSafeRefname,
1150
+ runGit,
1151
+ writeGitObject,
1152
+ writeGitObjects,
1153
+ updateRef,
1154
+ setHeadSymref,
1155
+ hexToNpub,
1156
+ npubToHex,
1157
+ ownerToHex,
698
1158
  serializeFeeEstimate,
699
1159
  serializePushPlan,
700
1160
  serializeEventReceipt,
@@ -704,4 +1164,4 @@ export {
704
1164
  planPush,
705
1165
  executePush
706
1166
  };
707
- //# sourceMappingURL=chunk-LFGDLD6J.js.map
1167
+ //# sourceMappingURL=chunk-W2SDL2PE.js.map