@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.
- package/LICENSE +202 -0
- package/README.md +29 -0
- package/app-route.cjs +154 -0
- package/app-route.d.cts +7 -0
- package/attach.cjs +80 -0
- package/dist/app-route.cjs +154 -0
- package/dist/app-route.d.cts +7 -0
- package/dist/attach.cjs +80 -0
- package/dist/generated/pack-facts.json +4306 -0
- package/dist/inject.cjs +1097 -0
- package/dist/network-policy.cjs +92 -0
- package/dist/network-policy.d.cts +10 -0
- package/dist/src/actions.d.ts +276 -0
- package/dist/src/actions.js +436 -0
- package/dist/src/ancestry.d.ts +22 -0
- package/dist/src/ancestry.js +238 -0
- package/dist/src/args.d.ts +3 -0
- package/dist/src/args.js +12 -0
- package/dist/src/blob-store.d.ts +55 -0
- package/dist/src/blob-store.js +186 -0
- package/dist/src/brand-tokens.d.ts +2 -0
- package/dist/src/brand-tokens.js +17 -0
- package/dist/src/changeset.d.ts +431 -0
- package/dist/src/changeset.js +0 -0
- package/dist/src/client-bundle.d.ts +1 -0
- package/dist/src/client-bundle.js +28 -0
- package/dist/src/credential.d.ts +38 -0
- package/dist/src/credential.js +114 -0
- package/dist/src/derived-core.d.ts +452 -0
- package/dist/src/derived-core.js +782 -0
- package/dist/src/derived.d.ts +84 -0
- package/dist/src/derived.js +122 -0
- package/dist/src/emit.d.ts +106 -0
- package/dist/src/emit.js +157 -0
- package/dist/src/executor.d.ts +120 -0
- package/dist/src/executor.js +387 -0
- package/dist/src/file-response.d.ts +3 -0
- package/dist/src/file-response.js +22 -0
- package/dist/src/fork.d.ts +26 -0
- package/dist/src/fork.js +68 -0
- package/dist/src/git/history.d.ts +36 -0
- package/dist/src/git/history.js +298 -0
- package/dist/src/git/index.d.ts +6 -0
- package/dist/src/git/index.js +6 -0
- package/dist/src/git/inflate.d.ts +11 -0
- package/dist/src/git/inflate.js +194 -0
- package/dist/src/git/objects.d.ts +64 -0
- package/dist/src/git/objects.js +161 -0
- package/dist/src/git/pack.d.ts +14 -0
- package/dist/src/git/pack.js +199 -0
- package/dist/src/git/refs.d.ts +19 -0
- package/dist/src/git/refs.js +35 -0
- package/dist/src/git/smart-http.d.ts +45 -0
- package/dist/src/git/smart-http.js +223 -0
- package/dist/src/hash.d.ts +38 -0
- package/dist/src/hash.js +48 -0
- package/dist/src/head.d.ts +140 -0
- package/dist/src/head.js +313 -0
- package/dist/src/history.d.ts +76 -0
- package/dist/src/history.js +322 -0
- package/dist/src/index.d.ts +73 -0
- package/dist/src/index.js +98 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.js +8 -0
- package/dist/src/log.d.ts +254 -0
- package/dist/src/log.js +801 -0
- package/dist/src/mirror-shell.d.ts +2 -0
- package/dist/src/mirror-shell.js +13 -0
- package/dist/src/observe.d.ts +49 -0
- package/dist/src/observe.js +148 -0
- package/dist/src/pack-assets.d.ts +30 -0
- package/dist/src/pack-assets.js +88 -0
- package/dist/src/packRegistry.d.ts +374 -0
- package/dist/src/packRegistry.js +142 -0
- package/dist/src/placeholder-remote.d.ts +22 -0
- package/dist/src/placeholder-remote.js +86 -0
- package/dist/src/proxy.d.ts +25 -0
- package/dist/src/proxy.js +155 -0
- package/dist/src/rateBudget.d.ts +367 -0
- package/dist/src/rateBudget.js +925 -0
- package/dist/src/references.d.ts +18 -0
- package/dist/src/references.js +27 -0
- package/dist/src/remote-execute.d.ts +22 -0
- package/dist/src/remote-execute.js +1 -0
- package/dist/src/resource-blob.d.ts +10 -0
- package/dist/src/resource-blob.js +56 -0
- package/dist/src/scenario.d.ts +197 -0
- package/dist/src/scenario.js +425 -0
- package/dist/src/schemas.d.ts +78 -0
- package/dist/src/schemas.js +50 -0
- package/dist/src/serve-http.d.ts +48 -0
- package/dist/src/serve-http.js +340 -0
- package/dist/src/serve.d.ts +147 -0
- package/dist/src/serve.js +507 -0
- package/dist/src/shared-blob-index.d.ts +4 -0
- package/dist/src/shared-blob-index.js +126 -0
- package/dist/src/state-system.d.ts +70 -0
- package/dist/src/state-system.js +90 -0
- package/dist/src/storage.d.ts +101 -0
- package/dist/src/storage.js +337 -0
- package/dist/src/twin-fetch.d.ts +64 -0
- package/dist/src/twin-fetch.js +91 -0
- package/dist/src/types.d.ts +40 -0
- package/dist/src/types.js +1 -0
- package/dist/src/v1-removed.d.ts +159 -0
- package/dist/src/v1-removed.js +124 -0
- package/dist/src/volter-home.d.ts +5 -0
- package/dist/src/volter-home.js +10 -0
- package/dist/src/world-clock.d.ts +4 -0
- package/dist/src/world-clock.js +32 -0
- package/dist/src/world-env.d.ts +3 -0
- package/dist/src/world-env.js +22 -0
- package/dist/src/world-store-sql.d.ts +27 -0
- package/dist/src/world-store-sql.js +86 -0
- package/dist/src/world-store.d.ts +168 -0
- package/dist/src/world-store.js +475 -0
- package/dist/src/worldConfig.d.ts +9 -0
- package/dist/src/worldConfig.js +17 -0
- package/dist/stream-bridge.cjs +80 -0
- package/dist/vendor-hosts.cjs +200 -0
- package/generated/pack-facts.json +4306 -0
- package/inject.cjs +1097 -0
- package/network-policy.cjs +92 -0
- package/network-policy.d.cts +10 -0
- package/package.json +103 -0
- package/src/actions.ts +564 -0
- package/src/ancestry.ts +213 -0
- package/src/args.ts +14 -0
- package/src/blob-store.ts +185 -0
- package/src/brand-tokens.ts +17 -0
- package/src/changeset.ts +1032 -0
- package/src/client-bundle.ts +29 -0
- package/src/credential.ts +140 -0
- package/src/derived-core.ts +1004 -0
- package/src/derived.ts +176 -0
- package/src/emit.ts +242 -0
- package/src/executor.ts +431 -0
- package/src/file-response.ts +22 -0
- package/src/fork.ts +89 -0
- package/src/git/history.ts +177 -0
- package/src/git/index.ts +6 -0
- package/src/git/inflate.ts +125 -0
- package/src/git/objects.ts +110 -0
- package/src/git/pack.ts +105 -0
- package/src/git/refs.ts +25 -0
- package/src/git/smart-http.ts +149 -0
- package/src/hash.ts +66 -0
- package/src/head.ts +318 -0
- package/src/history.ts +246 -0
- package/src/index.ts +323 -0
- package/src/lifecycle.ts +8 -0
- package/src/log.ts +793 -0
- package/src/mirror-shell.ts +15 -0
- package/src/observe.ts +130 -0
- package/src/pack-assets.ts +81 -0
- package/src/packRegistry.ts +408 -0
- package/src/placeholder-remote.ts +81 -0
- package/src/proxy.ts +183 -0
- package/src/rateBudget.ts +1115 -0
- package/src/references.ts +46 -0
- package/src/remote-execute.ts +26 -0
- package/src/resource-blob.ts +57 -0
- package/src/scenario.ts +479 -0
- package/src/schemas.ts +56 -0
- package/src/serve-http.ts +299 -0
- package/src/serve.ts +618 -0
- package/src/shared-blob-index.ts +108 -0
- package/src/state-system.ts +115 -0
- package/src/storage.ts +407 -0
- package/src/twin-fetch.ts +147 -0
- package/src/types.ts +50 -0
- package/src/v1-removed.ts +172 -0
- package/src/volter-home.ts +11 -0
- package/src/world-clock.ts +33 -0
- package/src/world-env.ts +18 -0
- package/src/world-store-sql.ts +118 -0
- package/src/world-store.ts +572 -0
- package/src/worldConfig.ts +27 -0
- package/stream-bridge.cjs +80 -0
- package/vendor-hosts.cjs +200 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// HISTORY AND DIFF: what a hosting twin's REST plane asks of the object store beyond raw reads —
|
|
2
|
+
// the merge base of two commits, the changed-file list between two commits with GitHub's
|
|
3
|
+
// three-dot semantics (merge-base(base, head)..head), per-file addition/deletion counts and a
|
|
4
|
+
// unified patch from a Myers line diff. Pure functions of the store; the `git` binary is the
|
|
5
|
+
// oracle these were proven against, never a dependency.
|
|
6
|
+
import { decodeCommit, decodeTree } from "./objects.js";
|
|
7
|
+
async function parentsOf(store, sha) { const o = await store.read(sha); return o?.type === 'commit' ? decodeCommit(o.payload).parents : []; }
|
|
8
|
+
/** Ancestors of `sha` (inclusive), bounded. */
|
|
9
|
+
async function ancestors(store, sha, limit) {
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
const stack = [sha];
|
|
12
|
+
while (stack.length && seen.size < limit) {
|
|
13
|
+
const s = stack.pop();
|
|
14
|
+
if (seen.has(s))
|
|
15
|
+
continue;
|
|
16
|
+
seen.add(s);
|
|
17
|
+
stack.push(...(await parentsOf(store, s)));
|
|
18
|
+
}
|
|
19
|
+
return seen;
|
|
20
|
+
}
|
|
21
|
+
/** A best common ancestor of `a` and `b` (one that is not an ancestor of another common ancestor), or null. */
|
|
22
|
+
export async function mergeBase(store, a, b, limit = 20000) {
|
|
23
|
+
if (!(await store.has(a)) || !(await store.has(b)))
|
|
24
|
+
return null;
|
|
25
|
+
const ofA = await ancestors(store, a, limit);
|
|
26
|
+
// BFS from b in parent order: the first commits reached that lie in A's ancestry are candidates; a
|
|
27
|
+
// candidate reached through another candidate is dominated and dropped.
|
|
28
|
+
const candidates = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const queue = [b];
|
|
31
|
+
while (queue.length && seen.size < limit) {
|
|
32
|
+
const s = queue.shift();
|
|
33
|
+
if (seen.has(s))
|
|
34
|
+
continue;
|
|
35
|
+
seen.add(s);
|
|
36
|
+
if (ofA.has(s)) {
|
|
37
|
+
candidates.push(s);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
queue.push(...(await parentsOf(store, s)));
|
|
41
|
+
}
|
|
42
|
+
if (candidates.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
if (candidates.length === 1)
|
|
45
|
+
return candidates[0];
|
|
46
|
+
const dominated = new Set();
|
|
47
|
+
for (const c of candidates) {
|
|
48
|
+
const anc = await ancestors(store, c, limit);
|
|
49
|
+
for (const d of candidates)
|
|
50
|
+
if (d !== c && anc.has(d))
|
|
51
|
+
dominated.add(d);
|
|
52
|
+
}
|
|
53
|
+
return candidates.find((c) => !dominated.has(c)) ?? candidates[0];
|
|
54
|
+
}
|
|
55
|
+
async function treeOf(store, sha) {
|
|
56
|
+
const o = await store.read(sha);
|
|
57
|
+
if (!o)
|
|
58
|
+
return [];
|
|
59
|
+
if (o.type === 'commit')
|
|
60
|
+
return treeOf(store, decodeCommit(o.payload).tree);
|
|
61
|
+
return o.type === 'tree' ? decodeTree(o.payload) : [];
|
|
62
|
+
}
|
|
63
|
+
const isDir = (m) => m === '40000' || m === '040000';
|
|
64
|
+
/** Recursive tree compare between two commits or trees (paths in the post-image order, no rename pairing — like `diff-tree -r` without -M). */
|
|
65
|
+
export async function diffTrees(store, from, to, prefix = '') {
|
|
66
|
+
const a = from ? await treeOf(store, from) : [];
|
|
67
|
+
const b = await treeOf(store, to);
|
|
68
|
+
const byName = new Map(a.map((e) => [e.name, e]));
|
|
69
|
+
const out = [];
|
|
70
|
+
const seenNames = new Set();
|
|
71
|
+
const all = [...a.map((e) => e.name), ...b.map((e) => e.name)].filter((n) => (seenNames.has(n) ? false : (seenNames.add(n), true))).sort();
|
|
72
|
+
const bByName = new Map(b.map((e) => [e.name, e]));
|
|
73
|
+
for (const name of all) {
|
|
74
|
+
const ax = byName.get(name), by = bByName.get(name);
|
|
75
|
+
const path = prefix + name;
|
|
76
|
+
if (ax && by && ax.sha === by.sha && ax.mode === by.mode)
|
|
77
|
+
continue;
|
|
78
|
+
const xd = ax ? isDir(ax.mode) : false, yd = by ? isDir(by.mode) : false;
|
|
79
|
+
if (xd || yd) {
|
|
80
|
+
if (xd && yd)
|
|
81
|
+
out.push(...(await diffTrees(store, ax.sha, by.sha, `${path}/`)));
|
|
82
|
+
else {
|
|
83
|
+
if (xd)
|
|
84
|
+
out.push(...(await diffTrees(store, ax.sha, '4b825dc642cb6eb9a060e54bf8d69288fbee4904', `${path}/`)).map((c) => ({ ...c })));
|
|
85
|
+
else if (ax)
|
|
86
|
+
out.push({ path, status: 'removed', oldSha: ax.sha, oldMode: ax.mode });
|
|
87
|
+
if (yd)
|
|
88
|
+
out.push(...(await diffTrees(store, null, by.sha, `${path}/`)));
|
|
89
|
+
else if (by)
|
|
90
|
+
out.push({ path, status: 'added', newSha: by.sha, newMode: by.mode });
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (ax && by)
|
|
95
|
+
out.push({ path, status: 'modified', oldSha: ax.sha, newSha: by.sha, oldMode: ax.mode, newMode: by.mode });
|
|
96
|
+
else if (ax)
|
|
97
|
+
out.push({ path, status: 'removed', oldSha: ax.sha, oldMode: ax.mode });
|
|
98
|
+
else if (by)
|
|
99
|
+
out.push({ path, status: 'added', newSha: by.sha, newMode: by.mode });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
function splitLines(text) { if (text === '')
|
|
104
|
+
return []; const lines = text.split('\n'); if (lines[lines.length - 1] === '')
|
|
105
|
+
lines.pop(); return lines; }
|
|
106
|
+
/** A shortest edit script between two texts (Myers, O((N+M)D)). */
|
|
107
|
+
export function lineDiff(oldText, newText) {
|
|
108
|
+
const a = splitLines(oldText), b = splitLines(newText);
|
|
109
|
+
const n = a.length, m = b.length;
|
|
110
|
+
const max = n + m;
|
|
111
|
+
const v = new Map([[1, 0]]);
|
|
112
|
+
const trace = [];
|
|
113
|
+
outer: for (let d = 0; d <= max; d++) {
|
|
114
|
+
trace.push(new Map(v));
|
|
115
|
+
for (let k = -d; k <= d; k += 2) {
|
|
116
|
+
let ax = k === -d || (k !== d && (v.get(k - 1) ?? 0) < (v.get(k + 1) ?? 0)) ? (v.get(k + 1) ?? 0) : (v.get(k - 1) ?? 0) + 1;
|
|
117
|
+
let by = ax - k;
|
|
118
|
+
while (ax < n && by < m && a[ax] === b[by]) {
|
|
119
|
+
ax++;
|
|
120
|
+
by++;
|
|
121
|
+
}
|
|
122
|
+
v.set(k, ax);
|
|
123
|
+
if (ax >= n && by >= m)
|
|
124
|
+
break outer;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const edits = [];
|
|
128
|
+
let ax = n, by = m;
|
|
129
|
+
for (let d = trace.length - 1; d >= 0; d--) {
|
|
130
|
+
const vv = trace[d];
|
|
131
|
+
const k = ax - by;
|
|
132
|
+
const prevK = k === -d || (k !== d && (vv.get(k - 1) ?? 0) < (vv.get(k + 1) ?? 0)) ? k + 1 : k - 1;
|
|
133
|
+
const prevX = vv.get(prevK) ?? 0;
|
|
134
|
+
const prevY = prevX - prevK;
|
|
135
|
+
while (ax > prevX && by > prevY) {
|
|
136
|
+
edits.push({ kind: ' ', line: a[ax - 1] });
|
|
137
|
+
ax--;
|
|
138
|
+
by--;
|
|
139
|
+
}
|
|
140
|
+
if (d > 0) {
|
|
141
|
+
if (ax === prevX) {
|
|
142
|
+
edits.push({ kind: '+', line: b[by - 1] });
|
|
143
|
+
by--;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
edits.push({ kind: '-', line: a[ax - 1] });
|
|
147
|
+
ax--;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return edits.reverse();
|
|
152
|
+
}
|
|
153
|
+
/** The unified-diff hunks (the body GitHub serves as a file's `patch`), 3 lines of context. */
|
|
154
|
+
export function unifiedPatch(oldText, newText, context = 3) {
|
|
155
|
+
const edits = lineDiff(oldText, newText);
|
|
156
|
+
if (!edits.some((e) => e.kind !== ' '))
|
|
157
|
+
return '';
|
|
158
|
+
const oldNoNl = oldText !== '' && !oldText.endsWith('\n'), newNoNl = newText !== '' && !newText.endsWith('\n');
|
|
159
|
+
const oldLines = splitLines(oldText).length, newLines = splitLines(newText).length;
|
|
160
|
+
// group edits into hunks: a run of changes with up to `context` unchanged lines around it
|
|
161
|
+
const hunks = [];
|
|
162
|
+
let i = 0;
|
|
163
|
+
while (i < edits.length) {
|
|
164
|
+
if (edits[i].kind === ' ') {
|
|
165
|
+
i++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const start = Math.max(0, i - context);
|
|
169
|
+
let end = i;
|
|
170
|
+
while (end < edits.length) {
|
|
171
|
+
if (edits[end].kind !== ' ') {
|
|
172
|
+
end++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
let run = end;
|
|
176
|
+
while (run < edits.length && edits[run].kind === ' ')
|
|
177
|
+
run++;
|
|
178
|
+
if (run - end > context * 2 || run >= edits.length) {
|
|
179
|
+
end = Math.min(edits.length, end + context);
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
end = run;
|
|
183
|
+
}
|
|
184
|
+
hunks.push({ start, end });
|
|
185
|
+
i = end;
|
|
186
|
+
}
|
|
187
|
+
let oldPos = 0, newPos = 0, out = '';
|
|
188
|
+
let cursor = 0;
|
|
189
|
+
for (const h of hunks) {
|
|
190
|
+
for (; cursor < h.start; cursor++) {
|
|
191
|
+
const e = edits[cursor];
|
|
192
|
+
if (e.kind !== '+')
|
|
193
|
+
oldPos++;
|
|
194
|
+
if (e.kind !== '-')
|
|
195
|
+
newPos++;
|
|
196
|
+
}
|
|
197
|
+
let oc = 0, nc = 0, body = '';
|
|
198
|
+
for (let j = h.start; j < h.end; j++) {
|
|
199
|
+
const e = edits[j];
|
|
200
|
+
body += `${e.kind}${e.line}\n`;
|
|
201
|
+
const lastOld = e.kind !== '+' && oldPos + oc === oldLines - 1, lastNew = e.kind !== '-' && newPos + nc === newLines - 1;
|
|
202
|
+
if (e.kind !== '+')
|
|
203
|
+
oc++;
|
|
204
|
+
if (e.kind !== '-')
|
|
205
|
+
nc++;
|
|
206
|
+
if ((e.kind === '-' && lastOld && oldNoNl) || (e.kind === '+' && lastNew && newNoNl) || (e.kind === ' ' && lastOld && oldNoNl && lastNew && newNoNl))
|
|
207
|
+
body += '\\n';
|
|
208
|
+
}
|
|
209
|
+
const range = (p, c) => (c === 1 ? `${p + 1}` : `${c === 0 ? p : p + 1},${c}`);
|
|
210
|
+
out += `@@ -${range(oldPos, oc)} +${range(newPos, nc)} @@\n${body}`;
|
|
211
|
+
oldPos += oc;
|
|
212
|
+
newPos += nc;
|
|
213
|
+
cursor = h.end;
|
|
214
|
+
}
|
|
215
|
+
return out.replace(/\n$/, '');
|
|
216
|
+
}
|
|
217
|
+
const DEC = new TextDecoder();
|
|
218
|
+
function looksBinary(bytes) { const n = Math.min(bytes.length, 8000); for (let i = 0; i < n; i++)
|
|
219
|
+
if (bytes[i] === 0)
|
|
220
|
+
return true; return false; }
|
|
221
|
+
/** GitHub's file list between `base` and `head` (three-dot: from merge-base(base, head), or base when histories are unrelated), with counts and patches. */
|
|
222
|
+
export async function diffFiles(store, baseSha, headSha, opts = {}) {
|
|
223
|
+
if (!(await store.has(baseSha)) || !(await store.has(headSha)))
|
|
224
|
+
return null;
|
|
225
|
+
const from = (await mergeBase(store, baseSha, headSha)) ?? baseSha;
|
|
226
|
+
const out = [];
|
|
227
|
+
for (const c of await diffTrees(store, from, headSha)) {
|
|
228
|
+
const oldB = c.oldSha ? (await store.read(c.oldSha))?.payload ?? new Uint8Array() : new Uint8Array();
|
|
229
|
+
const newB = c.newSha ? (await store.read(c.newSha))?.payload ?? new Uint8Array() : new Uint8Array();
|
|
230
|
+
const file = { filename: c.path, status: c.status, additions: 0, deletions: 0, ...(c.newSha ? { sha: c.newSha } : {}) };
|
|
231
|
+
if (!looksBinary(oldB) && !looksBinary(newB)) {
|
|
232
|
+
const oldT = DEC.decode(oldB), newT = DEC.decode(newB);
|
|
233
|
+
const edits = lineDiff(oldT, newT);
|
|
234
|
+
file.additions = edits.filter((e) => e.kind === '+').length;
|
|
235
|
+
file.deletions = edits.filter((e) => e.kind === '-').length;
|
|
236
|
+
if (opts.patch) {
|
|
237
|
+
const p = unifiedPatch(oldT, newT);
|
|
238
|
+
if (p)
|
|
239
|
+
file.patch = p;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
out.push(file);
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
/** Three-way line merge. Return null for overlapping edits; preserve exact line endings.
|
|
247
|
+
* Binary payloads belong to the caller's conflict path, never a text decoder's replacement. */
|
|
248
|
+
export function mergeText(base, ours, theirs) {
|
|
249
|
+
if (ours === theirs || theirs === base)
|
|
250
|
+
return ours;
|
|
251
|
+
if (ours === base)
|
|
252
|
+
return theirs;
|
|
253
|
+
// Encode whole lines, including their terminators, before diffing. Thus changing the final
|
|
254
|
+
// newline is a real edit and CRLF bytes survive without a separate post-merge guess.
|
|
255
|
+
const lines = (text) => (text.match(/[^\n]*\n|[^\n]+$/g) ?? []).map(s => JSON.stringify(s));
|
|
256
|
+
const original = lines(base);
|
|
257
|
+
const changes = (text) => {
|
|
258
|
+
const out = [];
|
|
259
|
+
let cursor = 0;
|
|
260
|
+
let hunk;
|
|
261
|
+
for (const edit of lineDiff(original.join('\n'), lines(text).join('\n'))) {
|
|
262
|
+
if (edit.kind === ' ') {
|
|
263
|
+
hunk = undefined;
|
|
264
|
+
cursor++;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (!hunk) {
|
|
268
|
+
hunk = { start: cursor, end: cursor, lines: [] };
|
|
269
|
+
out.push(hunk);
|
|
270
|
+
}
|
|
271
|
+
if (edit.kind === '-') {
|
|
272
|
+
cursor++;
|
|
273
|
+
hunk.end++;
|
|
274
|
+
}
|
|
275
|
+
else
|
|
276
|
+
hunk.lines.push(edit.line);
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
};
|
|
280
|
+
const left = changes(ours), right = changes(theirs);
|
|
281
|
+
const identical = (a, b) => a.start === b.start && a.end === b.end && JSON.stringify(a.lines) === JSON.stringify(b.lines);
|
|
282
|
+
const edits = [...left];
|
|
283
|
+
for (const b of right) {
|
|
284
|
+
if (left.some(a => identical(a, b)))
|
|
285
|
+
continue;
|
|
286
|
+
for (const a of left) {
|
|
287
|
+
// Two insertions at one point or touching changed intervals have ambiguous order.
|
|
288
|
+
if (a.start <= b.end && b.start <= a.end)
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
edits.push(b);
|
|
292
|
+
}
|
|
293
|
+
edits.sort((a, b) => b.start - a.start);
|
|
294
|
+
const result = [...original];
|
|
295
|
+
for (const h of edits)
|
|
296
|
+
result.splice(h.start, h.end - h.start, ...h.lines);
|
|
297
|
+
return result.map(line => JSON.parse(line)).join('');
|
|
298
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Raw DEFLATE (RFC 1951) starting at `offset`: the bytes out and the input consumed. */
|
|
2
|
+
export declare function inflateRaw(input: Uint8Array, offset?: number): {
|
|
3
|
+
out: Uint8Array;
|
|
4
|
+
consumed: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function adler32(bytes: Uint8Array): number;
|
|
7
|
+
/** zlib (RFC 1950) starting at `offset`: header checked, Adler-32 verified, consumed includes both. */
|
|
8
|
+
export declare function inflateZlib(input: Uint8Array, offset?: number): {
|
|
9
|
+
out: Uint8Array;
|
|
10
|
+
consumed: number;
|
|
11
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// A pure-JS zlib INFLATE that reports the bytes it consumed — the one thing the standard
|
|
2
|
+
// DecompressionStream cannot say, and the one thing a packfile reader must know (each object's
|
|
3
|
+
// zlib stream is followed by the next object's header with no length in between). Stored,
|
|
4
|
+
// fixed-Huffman and dynamic-Huffman blocks (RFC 1951) inside a zlib wrapper (RFC 1950: a 2-byte
|
|
5
|
+
// header, an Adler-32 trailer). No dependency; runs on Bun and workerd alike.
|
|
6
|
+
const LENGTH_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258];
|
|
7
|
+
const LENGTH_EXTRA = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0];
|
|
8
|
+
const DIST_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577];
|
|
9
|
+
const DIST_EXTRA = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13];
|
|
10
|
+
const CL_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
|
|
11
|
+
function buildHuffman(lengths, n) {
|
|
12
|
+
const counts = new Uint16Array(16);
|
|
13
|
+
const symbols = new Uint16Array(n);
|
|
14
|
+
for (let i = 0; i < n; i++)
|
|
15
|
+
counts[lengths[i]]++;
|
|
16
|
+
counts[0] = 0;
|
|
17
|
+
const offs = new Uint16Array(16);
|
|
18
|
+
for (let i = 1; i < 16; i++)
|
|
19
|
+
offs[i] = offs[i - 1] + counts[i - 1];
|
|
20
|
+
for (let i = 0; i < n; i++)
|
|
21
|
+
if (lengths[i])
|
|
22
|
+
symbols[offs[lengths[i]]++] = i;
|
|
23
|
+
return { counts, symbols };
|
|
24
|
+
}
|
|
25
|
+
class BitReader {
|
|
26
|
+
input;
|
|
27
|
+
pos;
|
|
28
|
+
bitBuf = 0;
|
|
29
|
+
bitCount = 0;
|
|
30
|
+
constructor(input, offset) {
|
|
31
|
+
this.input = input;
|
|
32
|
+
this.pos = offset;
|
|
33
|
+
}
|
|
34
|
+
bit() {
|
|
35
|
+
if (this.bitCount === 0) {
|
|
36
|
+
if (this.pos >= this.input.length)
|
|
37
|
+
throw new Error('inflate: unexpected end of input');
|
|
38
|
+
this.bitBuf = this.input[this.pos++];
|
|
39
|
+
this.bitCount = 8;
|
|
40
|
+
}
|
|
41
|
+
const b = this.bitBuf & 1;
|
|
42
|
+
this.bitBuf >>>= 1;
|
|
43
|
+
this.bitCount--;
|
|
44
|
+
return b;
|
|
45
|
+
}
|
|
46
|
+
bits(n) { let v = 0; for (let i = 0; i < n; i++)
|
|
47
|
+
v |= this.bit() << i; return v; }
|
|
48
|
+
symbol(h) {
|
|
49
|
+
let code = 0, first = 0, index = 0;
|
|
50
|
+
for (let len = 1; len < 16; len++) {
|
|
51
|
+
code |= this.bit();
|
|
52
|
+
const count = h.counts[len];
|
|
53
|
+
if (code - count < first)
|
|
54
|
+
return h.symbols[index + (code - first)];
|
|
55
|
+
index += count;
|
|
56
|
+
first += count;
|
|
57
|
+
first <<= 1;
|
|
58
|
+
code <<= 1;
|
|
59
|
+
}
|
|
60
|
+
throw new Error('inflate: bad huffman code');
|
|
61
|
+
}
|
|
62
|
+
alignByte() { this.bitBuf = 0; this.bitCount = 0; }
|
|
63
|
+
}
|
|
64
|
+
class Out {
|
|
65
|
+
buf = new Uint8Array(1 << 16);
|
|
66
|
+
len = 0;
|
|
67
|
+
push(b) { if (this.len === this.buf.length) {
|
|
68
|
+
const n = new Uint8Array(this.buf.length * 2);
|
|
69
|
+
n.set(this.buf);
|
|
70
|
+
this.buf = n;
|
|
71
|
+
} this.buf[this.len++] = b; }
|
|
72
|
+
copy(dist, length) { if (dist > this.len)
|
|
73
|
+
throw new Error('inflate: distance too far back'); for (let i = 0; i < length; i++)
|
|
74
|
+
this.push(this.buf[this.len - dist]); }
|
|
75
|
+
// A right-sized copy: a view would pin the whole (>= 64 KiB, doubling) scratch buffer behind
|
|
76
|
+
// every inflated object, which is what a 105k-object push cost the github twin (~7 GB).
|
|
77
|
+
bytes() { return this.buf.slice(0, this.len); }
|
|
78
|
+
}
|
|
79
|
+
let FIXED;
|
|
80
|
+
function fixedTables() {
|
|
81
|
+
if (FIXED)
|
|
82
|
+
return FIXED;
|
|
83
|
+
const l = new Uint8Array(288);
|
|
84
|
+
for (let i = 0; i < 144; i++)
|
|
85
|
+
l[i] = 8;
|
|
86
|
+
for (let i = 144; i < 256; i++)
|
|
87
|
+
l[i] = 9;
|
|
88
|
+
for (let i = 256; i < 280; i++)
|
|
89
|
+
l[i] = 7;
|
|
90
|
+
for (let i = 280; i < 288; i++)
|
|
91
|
+
l[i] = 8;
|
|
92
|
+
const d = new Uint8Array(30).fill(5);
|
|
93
|
+
FIXED = { lit: buildHuffman(l, 288), dist: buildHuffman(d, 30) };
|
|
94
|
+
return FIXED;
|
|
95
|
+
}
|
|
96
|
+
function inflateBlockData(r, out, lit, dist) {
|
|
97
|
+
for (;;) {
|
|
98
|
+
const sym = r.symbol(lit);
|
|
99
|
+
if (sym < 256)
|
|
100
|
+
out.push(sym);
|
|
101
|
+
else if (sym === 256)
|
|
102
|
+
return;
|
|
103
|
+
else {
|
|
104
|
+
const li = sym - 257;
|
|
105
|
+
if (li >= 29)
|
|
106
|
+
throw new Error('inflate: bad length symbol');
|
|
107
|
+
const length = LENGTH_BASE[li] + r.bits(LENGTH_EXTRA[li]);
|
|
108
|
+
const di = r.symbol(dist);
|
|
109
|
+
if (di >= 30)
|
|
110
|
+
throw new Error('inflate: bad distance symbol');
|
|
111
|
+
out.copy(DIST_BASE[di] + r.bits(DIST_EXTRA[di]), length);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function inflateDynamic(r, out) {
|
|
116
|
+
const hlit = r.bits(5) + 257, hdist = r.bits(5) + 1, hclen = r.bits(4) + 4;
|
|
117
|
+
const cl = new Uint8Array(19);
|
|
118
|
+
for (let i = 0; i < hclen; i++)
|
|
119
|
+
cl[CL_ORDER[i]] = r.bits(3);
|
|
120
|
+
const clh = buildHuffman(cl, 19);
|
|
121
|
+
const lengths = new Uint8Array(hlit + hdist);
|
|
122
|
+
for (let i = 0; i < hlit + hdist;) {
|
|
123
|
+
const sym = r.symbol(clh);
|
|
124
|
+
if (sym < 16)
|
|
125
|
+
lengths[i++] = sym;
|
|
126
|
+
else if (sym === 16) {
|
|
127
|
+
if (i === 0)
|
|
128
|
+
throw new Error('inflate: repeat with no previous length');
|
|
129
|
+
const prev = lengths[i - 1];
|
|
130
|
+
for (let n = 3 + r.bits(2); n > 0; n--)
|
|
131
|
+
lengths[i++] = prev;
|
|
132
|
+
}
|
|
133
|
+
else if (sym === 17) {
|
|
134
|
+
for (let n = 3 + r.bits(3); n > 0; n--)
|
|
135
|
+
lengths[i++] = 0;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
for (let n = 11 + r.bits(7); n > 0; n--)
|
|
139
|
+
lengths[i++] = 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
inflateBlockData(r, out, buildHuffman(lengths.subarray(0, hlit), hlit), buildHuffman(lengths.subarray(hlit), hdist));
|
|
143
|
+
}
|
|
144
|
+
/** Raw DEFLATE (RFC 1951) starting at `offset`: the bytes out and the input consumed. */
|
|
145
|
+
export function inflateRaw(input, offset = 0) {
|
|
146
|
+
const r = new BitReader(input, offset);
|
|
147
|
+
const out = new Out();
|
|
148
|
+
let final = 0;
|
|
149
|
+
do {
|
|
150
|
+
final = r.bit();
|
|
151
|
+
const type = r.bits(2);
|
|
152
|
+
if (type === 0) {
|
|
153
|
+
r.alignByte();
|
|
154
|
+
const len = input[r.pos] | (input[r.pos + 1] << 8);
|
|
155
|
+
const nlen = input[r.pos + 2] | (input[r.pos + 3] << 8);
|
|
156
|
+
if ((len ^ 0xffff) !== nlen)
|
|
157
|
+
throw new Error('inflate: stored block length mismatch');
|
|
158
|
+
r.pos += 4;
|
|
159
|
+
for (let i = 0; i < len; i++)
|
|
160
|
+
out.push(input[r.pos++]);
|
|
161
|
+
}
|
|
162
|
+
else if (type === 1) {
|
|
163
|
+
const t = fixedTables();
|
|
164
|
+
inflateBlockData(r, out, t.lit, t.dist);
|
|
165
|
+
}
|
|
166
|
+
else if (type === 2)
|
|
167
|
+
inflateDynamic(r, out);
|
|
168
|
+
else
|
|
169
|
+
throw new Error('inflate: reserved block type');
|
|
170
|
+
} while (!final);
|
|
171
|
+
return { out: out.bytes(), consumed: r.pos - offset };
|
|
172
|
+
}
|
|
173
|
+
export function adler32(bytes) {
|
|
174
|
+
let a = 1, b = 0;
|
|
175
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
176
|
+
a = (a + bytes[i]) % 65521;
|
|
177
|
+
b = (b + a) % 65521;
|
|
178
|
+
}
|
|
179
|
+
return ((b << 16) | a) >>> 0;
|
|
180
|
+
}
|
|
181
|
+
/** zlib (RFC 1950) starting at `offset`: header checked, Adler-32 verified, consumed includes both. */
|
|
182
|
+
export function inflateZlib(input, offset = 0) {
|
|
183
|
+
const cmf = input[offset], flg = input[offset + 1];
|
|
184
|
+
if ((cmf & 0x0f) !== 8 || ((cmf << 8) | flg) % 31 !== 0)
|
|
185
|
+
throw new Error('inflate: not a zlib stream');
|
|
186
|
+
if (flg & 0x20)
|
|
187
|
+
throw new Error('inflate: preset dictionary is not supported');
|
|
188
|
+
const { out, consumed } = inflateRaw(input, offset + 2);
|
|
189
|
+
const t = offset + 2 + consumed;
|
|
190
|
+
const expected = ((input[t] << 24) | (input[t + 1] << 16) | (input[t + 2] << 8) | input[t + 3]) >>> 0;
|
|
191
|
+
if (expected !== adler32(out))
|
|
192
|
+
throw new Error('inflate: adler32 mismatch');
|
|
193
|
+
return { out, consumed: consumed + 6 };
|
|
194
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { BlobStore } from '../blob-store.js';
|
|
2
|
+
export type GitObjectType = 'commit' | 'tree' | 'blob' | 'tag';
|
|
3
|
+
export declare function sha1Hex(bytes: Uint8Array): Promise<string>;
|
|
4
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
|
5
|
+
export declare function bytesToHex(b: Uint8Array): string;
|
|
6
|
+
export declare function concat(...parts: Uint8Array[]): Uint8Array;
|
|
7
|
+
/** The loose-object form: header + payload (what the sha is computed over). */
|
|
8
|
+
export declare function encodeLoose(type: GitObjectType, payload: Uint8Array): Uint8Array;
|
|
9
|
+
export declare function decodeLoose(bytes: Uint8Array): {
|
|
10
|
+
type: GitObjectType;
|
|
11
|
+
payload: Uint8Array;
|
|
12
|
+
};
|
|
13
|
+
export declare function hashObject(type: GitObjectType, payload: Uint8Array): Promise<string>;
|
|
14
|
+
export declare function deflateZlib(bytes: Uint8Array): Promise<Uint8Array>;
|
|
15
|
+
/** zlib with stored (uncompressed) deflate blocks: what git writes at `core.compression 0`, and what every
|
|
16
|
+
* inflater reads. A loose object is written on every commit a World makes; compressing each one through the
|
|
17
|
+
* asynchronous CompressionStream made a write's cost the stream's round trips, not the object's size. */
|
|
18
|
+
export declare function storeZlib(bytes: Uint8Array): Uint8Array;
|
|
19
|
+
/** The object store: one bare repo's objects under a key prefix in the blob seam. */
|
|
20
|
+
export declare class GitObjectStore {
|
|
21
|
+
private readonly blobs;
|
|
22
|
+
private readonly prefix;
|
|
23
|
+
constructor(blobs: BlobStore, prefix: string);
|
|
24
|
+
key(sha: string): string;
|
|
25
|
+
has(sha: string): Promise<boolean>;
|
|
26
|
+
write(type: GitObjectType, payload: Uint8Array): Promise<string>;
|
|
27
|
+
read(sha: string): Promise<{
|
|
28
|
+
type: GitObjectType;
|
|
29
|
+
payload: Uint8Array;
|
|
30
|
+
} | null>;
|
|
31
|
+
}
|
|
32
|
+
export type TreeEntry = {
|
|
33
|
+
mode: string;
|
|
34
|
+
name: string;
|
|
35
|
+
sha: string;
|
|
36
|
+
};
|
|
37
|
+
/** git sorts tree entries by name, with a directory's name compared as if it ended in '/'. */
|
|
38
|
+
export declare function sortTreeEntries(entries: TreeEntry[]): TreeEntry[];
|
|
39
|
+
export declare function encodeTree(entries: TreeEntry[]): Uint8Array;
|
|
40
|
+
export declare function decodeTree(payload: Uint8Array): TreeEntry[];
|
|
41
|
+
export type Ident = {
|
|
42
|
+
name: string;
|
|
43
|
+
email: string;
|
|
44
|
+
time: number;
|
|
45
|
+
tz: string;
|
|
46
|
+
};
|
|
47
|
+
export type Commit = {
|
|
48
|
+
tree: string;
|
|
49
|
+
parents: string[];
|
|
50
|
+
author: Ident;
|
|
51
|
+
committer: Ident;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
export declare function encodeCommit(c: Commit): Uint8Array;
|
|
55
|
+
export declare function decodeCommit(payload: Uint8Array): Commit;
|
|
56
|
+
export type Tag = {
|
|
57
|
+
object: string;
|
|
58
|
+
type: GitObjectType;
|
|
59
|
+
tag: string;
|
|
60
|
+
tagger?: Ident;
|
|
61
|
+
message: string;
|
|
62
|
+
};
|
|
63
|
+
export declare function encodeTag(t: Tag): Uint8Array;
|
|
64
|
+
export declare function decodeTag(payload: Uint8Array): Tag;
|