@volter/world-core 2.0.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 (180) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +29 -0
  3. package/app-route.cjs +154 -0
  4. package/app-route.d.cts +7 -0
  5. package/attach.cjs +80 -0
  6. package/dist/app-route.cjs +154 -0
  7. package/dist/app-route.d.cts +7 -0
  8. package/dist/attach.cjs +80 -0
  9. package/dist/generated/pack-facts.json +4306 -0
  10. package/dist/inject.cjs +1097 -0
  11. package/dist/network-policy.cjs +92 -0
  12. package/dist/network-policy.d.cts +10 -0
  13. package/dist/src/actions.d.ts +276 -0
  14. package/dist/src/actions.js +436 -0
  15. package/dist/src/ancestry.d.ts +22 -0
  16. package/dist/src/ancestry.js +238 -0
  17. package/dist/src/args.d.ts +3 -0
  18. package/dist/src/args.js +12 -0
  19. package/dist/src/blob-store.d.ts +55 -0
  20. package/dist/src/blob-store.js +186 -0
  21. package/dist/src/brand-tokens.d.ts +2 -0
  22. package/dist/src/brand-tokens.js +17 -0
  23. package/dist/src/changeset.d.ts +431 -0
  24. package/dist/src/changeset.js +0 -0
  25. package/dist/src/client-bundle.d.ts +1 -0
  26. package/dist/src/client-bundle.js +28 -0
  27. package/dist/src/credential.d.ts +38 -0
  28. package/dist/src/credential.js +114 -0
  29. package/dist/src/derived-core.d.ts +452 -0
  30. package/dist/src/derived-core.js +782 -0
  31. package/dist/src/derived.d.ts +84 -0
  32. package/dist/src/derived.js +122 -0
  33. package/dist/src/emit.d.ts +106 -0
  34. package/dist/src/emit.js +157 -0
  35. package/dist/src/executor.d.ts +120 -0
  36. package/dist/src/executor.js +387 -0
  37. package/dist/src/file-response.d.ts +3 -0
  38. package/dist/src/file-response.js +22 -0
  39. package/dist/src/fork.d.ts +26 -0
  40. package/dist/src/fork.js +68 -0
  41. package/dist/src/git/history.d.ts +36 -0
  42. package/dist/src/git/history.js +298 -0
  43. package/dist/src/git/index.d.ts +6 -0
  44. package/dist/src/git/index.js +6 -0
  45. package/dist/src/git/inflate.d.ts +11 -0
  46. package/dist/src/git/inflate.js +194 -0
  47. package/dist/src/git/objects.d.ts +64 -0
  48. package/dist/src/git/objects.js +161 -0
  49. package/dist/src/git/pack.d.ts +14 -0
  50. package/dist/src/git/pack.js +199 -0
  51. package/dist/src/git/refs.d.ts +19 -0
  52. package/dist/src/git/refs.js +35 -0
  53. package/dist/src/git/smart-http.d.ts +45 -0
  54. package/dist/src/git/smart-http.js +223 -0
  55. package/dist/src/hash.d.ts +38 -0
  56. package/dist/src/hash.js +48 -0
  57. package/dist/src/head.d.ts +140 -0
  58. package/dist/src/head.js +313 -0
  59. package/dist/src/history.d.ts +76 -0
  60. package/dist/src/history.js +322 -0
  61. package/dist/src/index.d.ts +73 -0
  62. package/dist/src/index.js +98 -0
  63. package/dist/src/lifecycle.d.ts +1 -0
  64. package/dist/src/lifecycle.js +8 -0
  65. package/dist/src/log.d.ts +254 -0
  66. package/dist/src/log.js +801 -0
  67. package/dist/src/mirror-shell.d.ts +2 -0
  68. package/dist/src/mirror-shell.js +13 -0
  69. package/dist/src/observe.d.ts +49 -0
  70. package/dist/src/observe.js +148 -0
  71. package/dist/src/pack-assets.d.ts +30 -0
  72. package/dist/src/pack-assets.js +88 -0
  73. package/dist/src/packRegistry.d.ts +374 -0
  74. package/dist/src/packRegistry.js +142 -0
  75. package/dist/src/placeholder-remote.d.ts +22 -0
  76. package/dist/src/placeholder-remote.js +86 -0
  77. package/dist/src/proxy.d.ts +25 -0
  78. package/dist/src/proxy.js +155 -0
  79. package/dist/src/rateBudget.d.ts +367 -0
  80. package/dist/src/rateBudget.js +925 -0
  81. package/dist/src/references.d.ts +18 -0
  82. package/dist/src/references.js +27 -0
  83. package/dist/src/remote-execute.d.ts +22 -0
  84. package/dist/src/remote-execute.js +1 -0
  85. package/dist/src/resource-blob.d.ts +10 -0
  86. package/dist/src/resource-blob.js +56 -0
  87. package/dist/src/scenario.d.ts +197 -0
  88. package/dist/src/scenario.js +425 -0
  89. package/dist/src/schemas.d.ts +78 -0
  90. package/dist/src/schemas.js +50 -0
  91. package/dist/src/serve-http.d.ts +48 -0
  92. package/dist/src/serve-http.js +340 -0
  93. package/dist/src/serve.d.ts +147 -0
  94. package/dist/src/serve.js +507 -0
  95. package/dist/src/shared-blob-index.d.ts +4 -0
  96. package/dist/src/shared-blob-index.js +126 -0
  97. package/dist/src/state-system.d.ts +70 -0
  98. package/dist/src/state-system.js +90 -0
  99. package/dist/src/storage.d.ts +101 -0
  100. package/dist/src/storage.js +337 -0
  101. package/dist/src/twin-fetch.d.ts +64 -0
  102. package/dist/src/twin-fetch.js +91 -0
  103. package/dist/src/types.d.ts +40 -0
  104. package/dist/src/types.js +1 -0
  105. package/dist/src/v1-removed.d.ts +159 -0
  106. package/dist/src/v1-removed.js +124 -0
  107. package/dist/src/volter-home.d.ts +5 -0
  108. package/dist/src/volter-home.js +10 -0
  109. package/dist/src/world-clock.d.ts +4 -0
  110. package/dist/src/world-clock.js +32 -0
  111. package/dist/src/world-env.d.ts +3 -0
  112. package/dist/src/world-env.js +22 -0
  113. package/dist/src/world-store-sql.d.ts +27 -0
  114. package/dist/src/world-store-sql.js +86 -0
  115. package/dist/src/world-store.d.ts +168 -0
  116. package/dist/src/world-store.js +475 -0
  117. package/dist/src/worldConfig.d.ts +9 -0
  118. package/dist/src/worldConfig.js +17 -0
  119. package/dist/stream-bridge.cjs +80 -0
  120. package/dist/vendor-hosts.cjs +200 -0
  121. package/generated/pack-facts.json +4306 -0
  122. package/inject.cjs +1097 -0
  123. package/network-policy.cjs +92 -0
  124. package/network-policy.d.cts +10 -0
  125. package/package.json +103 -0
  126. package/src/actions.ts +564 -0
  127. package/src/ancestry.ts +213 -0
  128. package/src/args.ts +14 -0
  129. package/src/blob-store.ts +185 -0
  130. package/src/brand-tokens.ts +17 -0
  131. package/src/changeset.ts +1032 -0
  132. package/src/client-bundle.ts +29 -0
  133. package/src/credential.ts +140 -0
  134. package/src/derived-core.ts +1004 -0
  135. package/src/derived.ts +176 -0
  136. package/src/emit.ts +242 -0
  137. package/src/executor.ts +431 -0
  138. package/src/file-response.ts +22 -0
  139. package/src/fork.ts +89 -0
  140. package/src/git/history.ts +177 -0
  141. package/src/git/index.ts +6 -0
  142. package/src/git/inflate.ts +125 -0
  143. package/src/git/objects.ts +110 -0
  144. package/src/git/pack.ts +105 -0
  145. package/src/git/refs.ts +25 -0
  146. package/src/git/smart-http.ts +149 -0
  147. package/src/hash.ts +66 -0
  148. package/src/head.ts +318 -0
  149. package/src/history.ts +246 -0
  150. package/src/index.ts +323 -0
  151. package/src/lifecycle.ts +8 -0
  152. package/src/log.ts +793 -0
  153. package/src/mirror-shell.ts +15 -0
  154. package/src/observe.ts +130 -0
  155. package/src/pack-assets.ts +81 -0
  156. package/src/packRegistry.ts +408 -0
  157. package/src/placeholder-remote.ts +81 -0
  158. package/src/proxy.ts +183 -0
  159. package/src/rateBudget.ts +1115 -0
  160. package/src/references.ts +46 -0
  161. package/src/remote-execute.ts +26 -0
  162. package/src/resource-blob.ts +57 -0
  163. package/src/scenario.ts +479 -0
  164. package/src/schemas.ts +56 -0
  165. package/src/serve-http.ts +299 -0
  166. package/src/serve.ts +618 -0
  167. package/src/shared-blob-index.ts +108 -0
  168. package/src/state-system.ts +115 -0
  169. package/src/storage.ts +407 -0
  170. package/src/twin-fetch.ts +147 -0
  171. package/src/types.ts +50 -0
  172. package/src/v1-removed.ts +172 -0
  173. package/src/volter-home.ts +11 -0
  174. package/src/world-clock.ts +33 -0
  175. package/src/world-env.ts +18 -0
  176. package/src/world-store-sql.ts +118 -0
  177. package/src/world-store.ts +572 -0
  178. package/src/worldConfig.ts +27 -0
  179. package/stream-bridge.cjs +80 -0
  180. package/vendor-hosts.cjs +200 -0
@@ -0,0 +1,161 @@
1
+ // GIT OBJECTS OVER THE BLOB SEAM (docs/contributing/architecture.md#the-engine-slot): loose objects — `<type>
2
+ // <size>\0<payload>`, zlib-deflated, addressed by the SHA-1 of the uncompressed form — written
3
+ // and read through the active BlobStore under `<prefix>/objects/<ab>/<rest>`, the exact layout
4
+ // a bare repository keeps. Pure JS: SHA-1 through WebCrypto, loose objects stored uncompressed in zlib framing (storeZlib),
5
+ // a pack deflated through the standard CompressionStream, inflate through ./inflate.ts (it reports consumed bytes, which a packfile
6
+ // reader needs). Bun and workerd alike; no `git` binary anywhere.
7
+ import { inflateZlib } from "./inflate.js";
8
+ const ENC = new TextEncoder();
9
+ const DEC = new TextDecoder();
10
+ export async function sha1Hex(bytes) {
11
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-1', bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)));
12
+ return [...digest].map((b) => b.toString(16).padStart(2, '0')).join('');
13
+ }
14
+ export function hexToBytes(hex) { const out = new Uint8Array(hex.length / 2); for (let i = 0; i < out.length; i++)
15
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); return out; }
16
+ export function bytesToHex(b) { return [...b].map((byte) => byte.toString(16).padStart(2, '0')).join(''); }
17
+ export function concat(...parts) { const n = parts.reduce((s, p) => s + p.length, 0); const out = new Uint8Array(n); let o = 0; for (const p of parts) {
18
+ out.set(p, o);
19
+ o += p.length;
20
+ } return out; }
21
+ /** The loose-object form: header + payload (what the sha is computed over). */
22
+ export function encodeLoose(type, payload) { return concat(ENC.encode(`${type} ${payload.length}\0`), payload); }
23
+ export function decodeLoose(bytes) {
24
+ const nul = bytes.indexOf(0);
25
+ if (nul < 0)
26
+ throw new Error('git object: no header');
27
+ const [type, size] = DEC.decode(bytes.subarray(0, nul)).split(' ');
28
+ const payload = bytes.subarray(nul + 1);
29
+ if (Number(size) !== payload.length)
30
+ throw new Error(`git object: header says ${size} bytes, payload is ${payload.length}`);
31
+ return { type: type, payload };
32
+ }
33
+ export async function hashObject(type, payload) { return sha1Hex(encodeLoose(type, payload)); }
34
+ export async function deflateZlib(bytes) {
35
+ const cs = new CompressionStream('deflate');
36
+ const w = cs.writable.getWriter();
37
+ void w.write(new Uint8Array(bytes));
38
+ void w.close();
39
+ return new Uint8Array(await new Response(cs.readable).arrayBuffer());
40
+ }
41
+ /** zlib with stored (uncompressed) deflate blocks: what git writes at `core.compression 0`, and what every
42
+ * inflater reads. A loose object is written on every commit a World makes; compressing each one through the
43
+ * asynchronous CompressionStream made a write's cost the stream's round trips, not the object's size. */
44
+ export function storeZlib(bytes) {
45
+ const blocks = Math.max(1, Math.ceil(bytes.length / 0xffff));
46
+ const out = new Uint8Array(2 + bytes.length + blocks * 5 + 4);
47
+ out[0] = 0x78;
48
+ out[1] = 0x01;
49
+ let o = 2;
50
+ for (let b = 0; b < blocks; b++) {
51
+ const start = b * 0xffff;
52
+ const len = Math.min(0xffff, bytes.length - start);
53
+ out[o++] = b === blocks - 1 ? 1 : 0;
54
+ out[o++] = len & 0xff;
55
+ out[o++] = len >>> 8;
56
+ out[o++] = ~len & 0xff;
57
+ out[o++] = (~len >>> 8) & 0xff;
58
+ out.set(bytes.subarray(start, start + len), o);
59
+ o += len;
60
+ }
61
+ let a = 1;
62
+ let d = 0;
63
+ for (let i = 0; i < bytes.length; i++) {
64
+ a = (a + bytes[i]) % 65521;
65
+ d = (d + a) % 65521;
66
+ }
67
+ const adler = ((d << 16) | a) >>> 0;
68
+ out[o++] = adler >>> 24;
69
+ out[o++] = (adler >>> 16) & 0xff;
70
+ out[o++] = (adler >>> 8) & 0xff;
71
+ out[o++] = adler & 0xff;
72
+ return out;
73
+ }
74
+ /** The object store: one bare repo's objects under a key prefix in the blob seam. */
75
+ export class GitObjectStore {
76
+ blobs;
77
+ prefix;
78
+ constructor(blobs, prefix) {
79
+ this.blobs = blobs;
80
+ this.prefix = prefix;
81
+ }
82
+ key(sha) { return `${this.prefix}/objects/${sha.slice(0, 2)}/${sha.slice(2)}`; }
83
+ async has(sha) { return this.blobs.exists(this.key(sha)); }
84
+ async write(type, payload) {
85
+ const loose = encodeLoose(type, payload);
86
+ const sha = await sha1Hex(loose);
87
+ if (!(await this.blobs.exists(this.key(sha))))
88
+ await this.blobs.put(this.key(sha), storeZlib(loose));
89
+ return sha;
90
+ }
91
+ async read(sha) {
92
+ const z = await this.blobs.get(this.key(sha));
93
+ if (z === null)
94
+ return null;
95
+ return decodeLoose(inflateZlib(z).out);
96
+ }
97
+ }
98
+ /** git sorts tree entries by name, with a directory's name compared as if it ended in '/'. */
99
+ export function sortTreeEntries(entries) {
100
+ const k = (e) => (e.mode === '40000' || e.mode === '040000' ? `${e.name}/` : e.name);
101
+ return [...entries].sort((a, b) => (k(a) < k(b) ? -1 : k(a) > k(b) ? 1 : 0));
102
+ }
103
+ export function encodeTree(entries) {
104
+ return concat(...sortTreeEntries(entries).map((e) => concat(ENC.encode(`${e.mode.replace(/^0+(?=\d)/, '')} ${e.name}\0`), hexToBytes(e.sha))));
105
+ }
106
+ export function decodeTree(payload) {
107
+ const out = [];
108
+ let i = 0;
109
+ while (i < payload.length) {
110
+ const sp = payload.indexOf(0x20, i);
111
+ const nul = payload.indexOf(0, sp);
112
+ out.push({ mode: DEC.decode(payload.subarray(i, sp)), name: DEC.decode(payload.subarray(sp + 1, nul)), sha: bytesToHex(payload.subarray(nul + 1, nul + 21)) });
113
+ i = nul + 21;
114
+ }
115
+ return out;
116
+ }
117
+ const ident = (i) => `${i.name} <${i.email}> ${i.time} ${i.tz}`;
118
+ export function encodeCommit(c) {
119
+ return ENC.encode(`tree ${c.tree}\n${c.parents.map((p) => `parent ${p}\n`).join('')}author ${ident(c.author)}\ncommitter ${ident(c.committer)}\n\n${c.message}`);
120
+ }
121
+ function parseIdent(s) { const m = /^(.*) <([^>]*)> (\d+) ([+-]\d{4})$/.exec(s); if (!m)
122
+ throw new Error(`git ident: ${s}`); return { name: m[1], email: m[2], time: Number(m[3]), tz: m[4] }; }
123
+ export function decodeCommit(payload) {
124
+ const text = DEC.decode(payload);
125
+ const sep = text.indexOf('\n\n');
126
+ const head = text.slice(0, sep);
127
+ const message = text.slice(sep + 2);
128
+ const c = { tree: '', parents: [], author: undefined, committer: undefined, message };
129
+ for (const line of head.split('\n')) {
130
+ const sp = line.indexOf(' ');
131
+ const k = line.slice(0, sp), v = line.slice(sp + 1);
132
+ if (k === 'tree')
133
+ c.tree = v;
134
+ else if (k === 'parent')
135
+ c.parents.push(v);
136
+ else if (k === 'author')
137
+ c.author = parseIdent(v);
138
+ else if (k === 'committer')
139
+ c.committer = parseIdent(v);
140
+ }
141
+ return c;
142
+ }
143
+ export function encodeTag(t) { return ENC.encode(`object ${t.object}\ntype ${t.type}\ntag ${t.tag}\n${t.tagger ? `tagger ${ident(t.tagger)}\n` : ''}\n${t.message}`); }
144
+ export function decodeTag(payload) {
145
+ const text = DEC.decode(payload);
146
+ const sep = text.indexOf('\n\n');
147
+ const t = { object: '', type: 'commit', tag: '', message: text.slice(sep + 2) };
148
+ for (const line of text.slice(0, sep).split('\n')) {
149
+ const sp = line.indexOf(' ');
150
+ const k = line.slice(0, sp), v = line.slice(sp + 1);
151
+ if (k === 'object')
152
+ t.object = v;
153
+ else if (k === 'type')
154
+ t.type = v;
155
+ else if (k === 'tag')
156
+ t.tag = v;
157
+ else if (k === 'tagger')
158
+ t.tagger = parseIdent(v);
159
+ }
160
+ return t;
161
+ }
@@ -0,0 +1,14 @@
1
+ import { GitObjectStore, type GitObjectType } from './objects.js';
2
+ /** Apply a git delta (copy / insert ops) to a base payload. */
3
+ export declare function applyDelta(base: Uint8Array, delta: Uint8Array): Uint8Array;
4
+ export type PackedObject = {
5
+ type: GitObjectType;
6
+ sha: string;
7
+ };
8
+ /** Read a pack, resolve every object, write them to `store`; returns each object's type and sha in pack order. */
9
+ export declare function readPack(pack: Uint8Array, store: GitObjectStore): Promise<PackedObject[]>;
10
+ /** Write a pack of undeltified objects, trailer included. */
11
+ export declare function writePack(objects: Array<{
12
+ type: GitObjectType;
13
+ payload: Uint8Array;
14
+ }>): Promise<Uint8Array>;
@@ -0,0 +1,199 @@
1
+ // PACKFILES (gitformat-pack): what a client sends on push and what a server sends on fetch.
2
+ // READ: parse a v2 pack — undeltified objects (zlib at an offset, consumed bytes from the kernel
3
+ // inflater), OFS_DELTA and REF_DELTA resolved against earlier objects in the pack or the object
4
+ // store, the trailing SHA-1 verified — and write every resolved object to the store. WRITE: a pack
5
+ // of undeltified objects with the trailer, which every git accepts (deltas are an optimization a
6
+ // twin does not owe a client).
7
+ import { inflateZlib } from "./inflate.js";
8
+ import { bytesToHex, concat, deflateZlib, sha1Hex } from "./objects.js";
9
+ const TYPE_BY_CODE = { 1: 'commit', 2: 'tree', 3: 'blob', 4: 'tag' };
10
+ const CODE_BY_TYPE = { commit: 1, tree: 2, blob: 3, tag: 4 };
11
+ const OFS_DELTA = 6, REF_DELTA = 7;
12
+ function readVarSize(input, at) {
13
+ let c = input[at];
14
+ const type = (c >> 4) & 7;
15
+ let value = c & 15;
16
+ let shift = 4;
17
+ let i = at + 1;
18
+ while (c & 0x80) {
19
+ c = input[i++];
20
+ value += (c & 0x7f) * 2 ** shift;
21
+ shift += 7;
22
+ }
23
+ return { value, type, next: i };
24
+ }
25
+ function readDeltaSize(d, at) {
26
+ let value = 0, shift = 0, i = at, c;
27
+ do {
28
+ c = d[i++];
29
+ value += (c & 0x7f) * 2 ** shift;
30
+ shift += 7;
31
+ } while (c & 0x80);
32
+ return { value, next: i };
33
+ }
34
+ /** Apply a git delta (copy / insert ops) to a base payload. */
35
+ export function applyDelta(base, delta) {
36
+ let p = 0;
37
+ const src = readDeltaSize(delta, p);
38
+ p = src.next;
39
+ if (src.value !== base.length)
40
+ throw new Error(`delta: base size ${src.value} != ${base.length}`);
41
+ const dst = readDeltaSize(delta, p);
42
+ p = dst.next;
43
+ const out = new Uint8Array(dst.value);
44
+ let o = 0;
45
+ while (p < delta.length) {
46
+ const op = delta[p++];
47
+ if (op & 0x80) {
48
+ let off = 0, len = 0;
49
+ if (op & 0x01)
50
+ off |= delta[p++];
51
+ if (op & 0x02)
52
+ off |= delta[p++] << 8;
53
+ if (op & 0x04)
54
+ off |= delta[p++] << 16;
55
+ if (op & 0x08)
56
+ off |= delta[p++] * 2 ** 24;
57
+ if (op & 0x10)
58
+ len |= delta[p++];
59
+ if (op & 0x20)
60
+ len |= delta[p++] << 8;
61
+ if (op & 0x40)
62
+ len |= delta[p++] << 16;
63
+ if (len === 0)
64
+ len = 0x10000;
65
+ out.set(base.subarray(off, off + len), o);
66
+ o += len;
67
+ }
68
+ else if (op > 0) {
69
+ out.set(delta.subarray(p, p + op), o);
70
+ o += op;
71
+ p += op;
72
+ }
73
+ else
74
+ throw new Error('delta: reserved op 0');
75
+ }
76
+ if (o !== dst.value)
77
+ throw new Error(`delta: produced ${o} bytes, expected ${dst.value}`);
78
+ return out;
79
+ }
80
+ // Delta bases are re-read from the store they were just written to; only this many bytes of
81
+ // resolved payloads stay in memory (git's own core.deltaBaseCacheLimit is 96 MiB). Holding every
82
+ // payload until the pack ends made a full-history push cost the repository's whole inflated size.
83
+ const DELTA_BASE_CACHE_BYTES = 96 * 1024 * 1024;
84
+ /** Read a pack, resolve every object, write them to `store`; returns each object's type and sha in pack order. */
85
+ export async function readPack(pack, store) {
86
+ const dec = new TextDecoder();
87
+ if (dec.decode(pack.subarray(0, 4)) !== 'PACK')
88
+ throw new Error('pack: bad signature');
89
+ const version = (pack[4] << 24) | (pack[5] << 16) | (pack[6] << 8) | pack[7];
90
+ if (version !== 2 && version !== 3)
91
+ throw new Error(`pack: version ${version}`);
92
+ const count = ((pack[8] << 24) | (pack[9] << 16) | (pack[10] << 8) | pack[11]) >>> 0;
93
+ const trailer = bytesToHex(pack.subarray(pack.length - 20));
94
+ const expected = await sha1Hex(pack.subarray(0, pack.length - 20));
95
+ if (trailer !== expected)
96
+ throw new Error('pack: checksum mismatch');
97
+ const cache = new Map();
98
+ let cached = 0;
99
+ const remember = (sha, obj) => {
100
+ if (obj.payload.length > DELTA_BASE_CACHE_BYTES || cache.has(sha))
101
+ return;
102
+ cache.set(sha, obj);
103
+ cached += obj.payload.length;
104
+ for (const [old, { payload }] of cache) {
105
+ if (cached <= DELTA_BASE_CACHE_BYTES)
106
+ break;
107
+ cache.delete(old);
108
+ cached -= payload.length;
109
+ }
110
+ };
111
+ const payloadOf = async (sha) => {
112
+ const hit = cache.get(sha);
113
+ if (!hit)
114
+ return store.read(sha);
115
+ cache.delete(sha);
116
+ cache.set(sha, hit);
117
+ return hit;
118
+ };
119
+ const byOffset = new Map();
120
+ const out = [];
121
+ let at = 12;
122
+ for (let n = 0; n < count; n++) {
123
+ const start = at;
124
+ const h = readVarSize(pack, at);
125
+ at = h.next;
126
+ let type;
127
+ let payload;
128
+ if (h.type === OFS_DELTA || h.type === REF_DELTA) {
129
+ let baseSha;
130
+ if (h.type === OFS_DELTA) {
131
+ let c = pack[at++];
132
+ let off = c & 0x7f;
133
+ while (c & 0x80) {
134
+ c = pack[at++];
135
+ off = ((off + 1) * 128) + (c & 0x7f);
136
+ }
137
+ const entry = byOffset.get(start - off);
138
+ if (!entry)
139
+ throw new Error(`pack: ofs-delta base at ${start - off} not found`);
140
+ baseSha = entry.sha;
141
+ }
142
+ else {
143
+ baseSha = bytesToHex(pack.subarray(at, at + 20));
144
+ at += 20;
145
+ }
146
+ const base = await payloadOf(baseSha);
147
+ if (!base)
148
+ throw new Error(`pack: ${h.type === OFS_DELTA ? 'ofs' : 'ref'}-delta base ${baseSha} not found`);
149
+ const z = inflateZlib(pack, at);
150
+ at += z.consumed;
151
+ type = base.type;
152
+ payload = applyDelta(base.payload, z.out);
153
+ }
154
+ else {
155
+ const known = TYPE_BY_CODE[h.type];
156
+ if (!known)
157
+ throw new Error(`pack: object type ${h.type}`);
158
+ const z = inflateZlib(pack, at);
159
+ at += z.consumed;
160
+ if (z.out.length !== h.value)
161
+ throw new Error(`pack: object size ${z.out.length} != ${h.value}`);
162
+ type = known;
163
+ payload = z.out;
164
+ }
165
+ const obj = { type, sha: await store.write(type, payload) };
166
+ remember(obj.sha, { type, payload });
167
+ byOffset.set(start, obj);
168
+ out.push(obj);
169
+ }
170
+ return out;
171
+ }
172
+ function encodeVarSize(type, size) {
173
+ const bytes = [];
174
+ let c = (type << 4) | (size & 15);
175
+ size = Math.floor(size / 16);
176
+ while (size > 0) {
177
+ bytes.push(c | 0x80);
178
+ c = size & 0x7f;
179
+ size = Math.floor(size / 128);
180
+ }
181
+ bytes.push(c);
182
+ return new Uint8Array(bytes);
183
+ }
184
+ /** Write a pack of undeltified objects, trailer included. */
185
+ export async function writePack(objects) {
186
+ const header = new Uint8Array(12);
187
+ header.set(new TextEncoder().encode('PACK'), 0);
188
+ header[7] = 2;
189
+ header[8] = (objects.length >>> 24) & 255;
190
+ header[9] = (objects.length >>> 16) & 255;
191
+ header[10] = (objects.length >>> 8) & 255;
192
+ header[11] = objects.length & 255;
193
+ const parts = [header];
194
+ for (const o of objects)
195
+ parts.push(encodeVarSize(CODE_BY_TYPE[o.type], o.payload.length), await deflateZlib(o.payload));
196
+ const body = concat(...parts);
197
+ const trailer = await sha1Hex(body);
198
+ return concat(body, new Uint8Array(trailer.match(/../g).map((h) => parseInt(h, 16))));
199
+ }
@@ -0,0 +1,19 @@
1
+ import type { WorldStore } from '../world-store.js';
2
+ export declare class GitRefs {
3
+ private readonly store;
4
+ private readonly prefix;
5
+ constructor(store: WorldStore, prefix: string);
6
+ private path;
7
+ get(name: string): string | null;
8
+ set(name: string, sha: string): void;
9
+ delete(name: string): void;
10
+ head(): string;
11
+ setHead(target: string): void;
12
+ /** Run `fn` holding the repository's ref lock (the world store's exclusive lock). */
13
+ withLock<T>(fn: () => T): T;
14
+ /** Every `refs/...` name with its sha, sorted by name. */
15
+ list(): Array<{
16
+ name: string;
17
+ sha: string;
18
+ }>;
19
+ }
@@ -0,0 +1,35 @@
1
+ export class GitRefs {
2
+ store;
3
+ prefix;
4
+ constructor(store, prefix) {
5
+ this.store = store;
6
+ this.prefix = prefix;
7
+ }
8
+ path(name) { return `${this.prefix}/${name}`; }
9
+ get(name) { const v = this.store.read(this.path(name)); return v === null ? null : v.trim(); }
10
+ set(name, sha) { this.store.write(this.path(name), `${sha}\n`); }
11
+ delete(name) { if (this.store.exists(this.path(name)))
12
+ this.store.remove(this.path(name)); }
13
+ head() { const v = this.store.read(this.path('HEAD')); return v === null ? 'refs/heads/main' : v.trim().replace(/^ref: /, ''); }
14
+ setHead(target) { this.store.write(this.path('HEAD'), `ref: ${target}\n`); }
15
+ /** Run `fn` holding the repository's ref lock (the world store's exclusive lock). */
16
+ withLock(fn) { return this.store.withLock(this.path('refs.lock'), fn); }
17
+ /** Every `refs/...` name with its sha, sorted by name. */
18
+ list() {
19
+ const out = [];
20
+ // stat before read: on the fs store a directory read throws (EISDIR); the memory store answers null
21
+ const walk = (dir, rel) => { for (const child of this.store.list(dir)) {
22
+ const p = `${dir}/${child}`;
23
+ const r = `${rel}/${child}`;
24
+ if (this.store.stat(p)?.isDirectory) {
25
+ walk(p, r);
26
+ continue;
27
+ }
28
+ const sha = this.store.read(p);
29
+ if (sha !== null)
30
+ out.push({ name: r, sha: sha.trim() });
31
+ } };
32
+ walk(this.path('refs'), 'refs');
33
+ return out.sort((a, b) => (a.name < b.name ? -1 : 1));
34
+ }
35
+ }
@@ -0,0 +1,45 @@
1
+ import { GitObjectStore } from './objects.js';
2
+ import type { GitRefs } from './refs.js';
3
+ export declare const ZERO_SHA: string;
4
+ export declare const FLUSH: Uint8Array<ArrayBuffer>;
5
+ export declare function pktLine(s: string | Uint8Array): Uint8Array;
6
+ /** Pkt-lines up to and including the first flush; `rest` is what follows (a pack, on push). */
7
+ export declare function readPktLines(bytes: Uint8Array): {
8
+ lines: string[];
9
+ rest: Uint8Array;
10
+ };
11
+ /** Every pkt-line in the body across all flushes (stateless clients batch haves between flushes); `rest` follows the first flush. */
12
+ export declare function readAllPktLines(bytes: Uint8Array): {
13
+ lines: string[];
14
+ rest: Uint8Array;
15
+ };
16
+ export type Service = 'git-upload-pack' | 'git-receive-pack';
17
+ /** `GET info/refs?service=<service>`: the ref advertisement, HEAD first as a symref. */
18
+ export declare function advertisement(service: Service, refs: GitRefs): Uint8Array;
19
+ /** `POST git-upload-pack`: wants and haves in, NAK and (once `done`) the pack out. */
20
+ export declare function uploadPack(body: Uint8Array, store: GitObjectStore): Promise<Uint8Array>;
21
+ export type RefCommand = {
22
+ old: string;
23
+ new: string;
24
+ name: string; /** false when `new` does not descend from `old` (a force push); true for creates, deletes and fast-forwards. */
25
+ fastForward: boolean;
26
+ };
27
+ /** Bounded ancestry walk: is `ancestor` reachable from `sha` through parents? */
28
+ export declare function isAncestor(store: GitObjectStore, ancestor: string, sha: string, limit?: number): Promise<boolean>;
29
+ export type PushPolicy = (cmd: RefCommand) => string | null | Promise<string | null>;
30
+ /** `POST git-receive-pack`: commands and a pack in, refs updated under `policy`, report-status out. */
31
+ export declare function receivePack(body: Uint8Array, store: GitObjectStore, refs: GitRefs, policy?: PushPolicy): Promise<{
32
+ response: Uint8Array;
33
+ applied: RefCommand[];
34
+ }>;
35
+ export type GitRepo = {
36
+ store: GitObjectStore;
37
+ refs: GitRefs;
38
+ policy?: PushPolicy;
39
+ onPush?: (applied: RefCommand[]) => void | Promise<void>;
40
+ };
41
+ /** Route one request under a repo: returns null when the path is not a smart-HTTP endpoint. */
42
+ export declare function serveSmartHttp(req: Request, repo: GitRepo, tailPath: string): Promise<Response | null>;
43
+ /** The POST half of `serveSmartHttp` for a caller that already holds the body: a push pack is
44
+ * the size of the repository, so re-wrapping it in a Request only to read it back is a full copy. */
45
+ export declare function serveSmartHttpPost(body: Uint8Array, contentEncoding: string, repo: GitRepo, tailPath: 'git-upload-pack' | 'git-receive-pack'): Promise<Response>;