@intx/hub-api 0.1.2

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 (55) hide show
  1. package/README.md +29 -0
  2. package/package.json +28 -0
  3. package/src/app.test.ts +225 -0
  4. package/src/app.ts +382 -0
  5. package/src/auth.ts +21 -0
  6. package/src/context.ts +38 -0
  7. package/src/format.ts +9 -0
  8. package/src/git-http/advertise-refs.test.ts +459 -0
  9. package/src/git-http/advertise-refs.ts +226 -0
  10. package/src/git-http/pkt-line.test.ts +220 -0
  11. package/src/git-http/pkt-line.ts +235 -0
  12. package/src/git-http/receive-pack.test.ts +397 -0
  13. package/src/git-http/receive-pack.ts +261 -0
  14. package/src/git-http/side-band-64k.test.ts +181 -0
  15. package/src/git-http/side-band-64k.ts +134 -0
  16. package/src/git-http/upload-pack.test.ts +545 -0
  17. package/src/git-http/upload-pack.ts +396 -0
  18. package/src/index.ts +23 -0
  19. package/src/middleware/git-token-auth.test.ts +587 -0
  20. package/src/middleware/git-token-auth.ts +315 -0
  21. package/src/middleware/grant.ts +106 -0
  22. package/src/middleware/session.ts +13 -0
  23. package/src/middleware/tenant.test.ts +192 -0
  24. package/src/middleware/tenant.ts +101 -0
  25. package/src/openapi.ts +66 -0
  26. package/src/pagination.ts +117 -0
  27. package/src/routes/agent-data.ts +179 -0
  28. package/src/routes/agent-state-git.ts +562 -0
  29. package/src/routes/agents.test.ts +337 -0
  30. package/src/routes/agents.ts +704 -0
  31. package/src/routes/approvals.ts +130 -0
  32. package/src/routes/assets.test.ts +567 -0
  33. package/src/routes/assets.ts +592 -0
  34. package/src/routes/credentials.ts +435 -0
  35. package/src/routes/git-tokens.test.ts +709 -0
  36. package/src/routes/git-tokens.ts +771 -0
  37. package/src/routes/grants.ts +509 -0
  38. package/src/routes/instances.test.ts +1103 -0
  39. package/src/routes/instances.ts +1797 -0
  40. package/src/routes/me.ts +405 -0
  41. package/src/routes/oauth-clients.ts +349 -0
  42. package/src/routes/observability.ts +146 -0
  43. package/src/routes/offerings.ts +382 -0
  44. package/src/routes/principals.ts +515 -0
  45. package/src/routes/providers.ts +351 -0
  46. package/src/routes/roles.ts +452 -0
  47. package/src/routes/sidecars.ts +221 -0
  48. package/src/routes/tenant-federation.ts +225 -0
  49. package/src/routes/tenants.ts +369 -0
  50. package/src/routes/wallets.ts +370 -0
  51. package/src/session.ts +44 -0
  52. package/src/timeline-reconstruction.test.ts +786 -0
  53. package/src/timeline-reconstruction.ts +383 -0
  54. package/tsconfig.json +4 -0
  55. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,181 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ chunkPackToSideBand,
4
+ SIDE_BAND_CHANNEL_MAX_PAYLOAD,
5
+ } from "./side-band-64k";
6
+ import type { SideBandChannel } from "./side-band-64k";
7
+
8
+ async function collectStream(
9
+ stream: ReadableStream<Uint8Array>,
10
+ ): Promise<Uint8Array> {
11
+ const reader = stream.getReader();
12
+ const parts: Uint8Array[] = [];
13
+ for (;;) {
14
+ const { value, done } = await reader.read();
15
+ if (done) break;
16
+ if (value) parts.push(value);
17
+ }
18
+ const total = parts.reduce((n, p) => n + p.length, 0);
19
+ const out = new Uint8Array(total);
20
+ let off = 0;
21
+ for (const p of parts) {
22
+ out.set(p, off);
23
+ off += p.length;
24
+ }
25
+ return out;
26
+ }
27
+
28
+ type Frame = { channel: SideBandChannel; payload: Uint8Array };
29
+
30
+ function parseFrames(buf: Uint8Array): Frame[] {
31
+ const dec = new TextDecoder();
32
+ const frames: Frame[] = [];
33
+ let off = 0;
34
+ while (off < buf.length) {
35
+ if (off + 4 > buf.length) {
36
+ throw new Error(`truncated pkt-line length at ${off}`);
37
+ }
38
+ const lenHex = dec.decode(buf.slice(off, off + 4));
39
+ const len = parseInt(lenHex, 16);
40
+ if (Number.isNaN(len)) {
41
+ throw new Error(`malformed pkt-line length: ${lenHex}`);
42
+ }
43
+ if (len === 0) {
44
+ off += 4;
45
+ // flush — side-band stream shouldn't normally end with a bare
46
+ // flush mid-pack, but allow it for completeness.
47
+ continue;
48
+ }
49
+ if (len < 5) {
50
+ throw new Error(`side-band frame too short: ${len}`);
51
+ }
52
+ if (off + len > buf.length) {
53
+ throw new Error(`truncated pkt-line body at ${off}`);
54
+ }
55
+ const channelByte = buf[off + 4];
56
+ if (channelByte !== 1 && channelByte !== 2 && channelByte !== 3) {
57
+ throw new Error(`unknown side-band channel: ${channelByte}`);
58
+ }
59
+ const channel: SideBandChannel = channelByte;
60
+ const payload = buf.slice(off + 5, off + len);
61
+ frames.push({ channel, payload });
62
+ off += len;
63
+ }
64
+ return frames;
65
+ }
66
+
67
+ function packStream(...chunks: Uint8Array[]): ReadableStream<Uint8Array> {
68
+ let i = 0;
69
+ return new ReadableStream({
70
+ pull(controller) {
71
+ if (i < chunks.length) {
72
+ controller.enqueue(chunks[i++]);
73
+ } else {
74
+ controller.close();
75
+ }
76
+ },
77
+ });
78
+ }
79
+
80
+ describe("chunkPackToSideBand channel-1 chunking", () => {
81
+ test("small pack passes through as a single channel-1 frame", async () => {
82
+ const pack = new Uint8Array([1, 2, 3, 4, 5]);
83
+ const stream = chunkPackToSideBand(packStream(pack));
84
+ const buf = await collectStream(stream);
85
+ const frames = parseFrames(buf);
86
+ expect(frames.length).toBe(1);
87
+ const first = frames[0];
88
+ if (!first) throw new Error("expected one frame");
89
+ expect(first.channel).toBe(1);
90
+ expect(Array.from(first.payload)).toEqual([1, 2, 3, 4, 5]);
91
+ });
92
+
93
+ test("every channel-1 frame is at most SIDE_BAND_CHANNEL_MAX_PAYLOAD bytes", async () => {
94
+ const size = SIDE_BAND_CHANNEL_MAX_PAYLOAD * 3 + 17;
95
+ const pack = new Uint8Array(size);
96
+ for (let i = 0; i < size; i++) pack[i] = i & 0xff;
97
+ const stream = chunkPackToSideBand(packStream(pack));
98
+ const buf = await collectStream(stream);
99
+ const frames = parseFrames(buf);
100
+ expect(frames.length).toBeGreaterThan(1);
101
+ for (const f of frames) {
102
+ expect(f.channel).toBe(1);
103
+ expect(f.payload.length).toBeLessThanOrEqual(
104
+ SIDE_BAND_CHANNEL_MAX_PAYLOAD,
105
+ );
106
+ }
107
+ });
108
+
109
+ test("multi-chunk channel-1 reassembly is byte-exact", async () => {
110
+ const size = SIDE_BAND_CHANNEL_MAX_PAYLOAD * 2 + 511;
111
+ const pack = new Uint8Array(size);
112
+ for (let i = 0; i < size; i++) pack[i] = (i * 7) & 0xff;
113
+ // Feed in randomly sized input chunks; the chunker output must
114
+ // still reassemble byte-equal to the source.
115
+ const inputChunks: Uint8Array[] = [];
116
+ let off = 0;
117
+ while (off < size) {
118
+ const take = Math.min(size - off, 1000);
119
+ inputChunks.push(pack.slice(off, off + take));
120
+ off += take;
121
+ }
122
+ const stream = chunkPackToSideBand(packStream(...inputChunks));
123
+ const buf = await collectStream(stream);
124
+ const frames = parseFrames(buf);
125
+ const reassembled = new Uint8Array(size);
126
+ let r = 0;
127
+ for (const f of frames) {
128
+ expect(f.channel).toBe(1);
129
+ reassembled.set(f.payload, r);
130
+ r += f.payload.length;
131
+ }
132
+ expect(r).toBe(size);
133
+ expect(Array.from(reassembled)).toEqual(Array.from(pack));
134
+ });
135
+ });
136
+
137
+ describe("chunkPackToSideBand channel-2 progress", () => {
138
+ test("progress messages emit on channel 2 interleaved with channel-1 data", async () => {
139
+ const pack = new Uint8Array([10, 20, 30, 40]);
140
+ const stream = chunkPackToSideBand(packStream(pack), {
141
+ progress: ["counting objects", "compressing"],
142
+ });
143
+ const buf = await collectStream(stream);
144
+ const frames = parseFrames(buf);
145
+ const progress = frames
146
+ .filter((f) => f.channel === 2)
147
+ .map((f) => new TextDecoder().decode(f.payload));
148
+ expect(progress).toEqual(["counting objects", "compressing"]);
149
+ const data = frames.filter((f) => f.channel === 1);
150
+ expect(data.length).toBe(1);
151
+ const first = data[0];
152
+ if (!first) throw new Error("expected channel-1 frame");
153
+ expect(Array.from(first.payload)).toEqual([10, 20, 30, 40]);
154
+ });
155
+ });
156
+
157
+ describe("chunkPackToSideBand channel-3 fatal", () => {
158
+ test("fatal error emitted as channel-3 frame and stream ends", async () => {
159
+ const pack = new Uint8Array([1, 2, 3]);
160
+ const stream = chunkPackToSideBand(packStream(pack), {
161
+ fatal: "pack stream aborted",
162
+ });
163
+ const buf = await collectStream(stream);
164
+ const frames = parseFrames(buf);
165
+ const fatal = frames.filter((f) => f.channel === 3);
166
+ expect(fatal.length).toBe(1);
167
+ const fatalFrame = fatal[0];
168
+ if (!fatalFrame) throw new Error("expected channel-3 frame");
169
+ expect(new TextDecoder().decode(fatalFrame.payload)).toBe(
170
+ "pack stream aborted",
171
+ );
172
+ // Fatal terminates: no channel-1 frames must appear after the
173
+ // channel-3 frame in the output.
174
+ const fatalIdx = frames.findIndex((f) => f.channel === 3);
175
+ for (let i = fatalIdx + 1; i < frames.length; i++) {
176
+ const f = frames[i];
177
+ if (!f) continue;
178
+ expect(f.channel).not.toBe(1);
179
+ }
180
+ });
181
+ });
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Side-band-64k framing for git smart-HTTP. Each outbound frame is a
3
+ * pkt-line whose first body byte is a channel marker:
4
+ *
5
+ * - channel 1: pack data
6
+ * - channel 2: progress messages (shown by stock git as `remote: ...`)
7
+ * - channel 3: fatal error; the remote terminates the transfer
8
+ *
9
+ * The maximum pkt-line payload is 0xFFF0 (65520) bytes including the
10
+ * channel marker, so each channel-1 frame can carry at most
11
+ * `SIDE_BAND_CHANNEL_MAX_PAYLOAD` bytes of pack data.
12
+ */
13
+
14
+ const PKT_LINE_HEADER_BYTES = 4;
15
+ const PKT_LINE_MAX_FRAME = 0xfff0;
16
+
17
+ export const SIDE_BAND_CHANNEL_MAX_PAYLOAD =
18
+ PKT_LINE_MAX_FRAME - PKT_LINE_HEADER_BYTES - 1; // 65515
19
+
20
+ export type SideBandChannel = 1 | 2 | 3;
21
+
22
+ export type ChunkPackToSideBandOpts = {
23
+ /** Progress messages emitted on channel 2 before pack data. */
24
+ progress?: string[];
25
+ /**
26
+ * If set, the source stream is abandoned and a single channel-3
27
+ * fatal frame is emitted after any channel-2 progress messages.
28
+ */
29
+ fatal?: string;
30
+ };
31
+
32
+ function hexDigit(v: number): string {
33
+ if (v < 10) return String.fromCharCode(0x30 + v);
34
+ return String.fromCharCode(0x61 + (v - 10));
35
+ }
36
+
37
+ function hex4(n: number): string {
38
+ return (
39
+ hexDigit((n >> 12) & 0xf) +
40
+ hexDigit((n >> 8) & 0xf) +
41
+ hexDigit((n >> 4) & 0xf) +
42
+ hexDigit(n & 0xf)
43
+ );
44
+ }
45
+
46
+ function frame(channel: SideBandChannel, body: Uint8Array): Uint8Array {
47
+ const length = body.length + 1 + PKT_LINE_HEADER_BYTES;
48
+ if (length > PKT_LINE_MAX_FRAME) {
49
+ throw new Error(
50
+ `side-band frame too large: ${length} > ${PKT_LINE_MAX_FRAME}`,
51
+ );
52
+ }
53
+ const out = new Uint8Array(length);
54
+ const header = new TextEncoder().encode(hex4(length));
55
+ out.set(header, 0);
56
+ out[PKT_LINE_HEADER_BYTES] = channel;
57
+ out.set(body, PKT_LINE_HEADER_BYTES + 1);
58
+ return out;
59
+ }
60
+
61
+ function textFrame(channel: SideBandChannel, msg: string): Uint8Array {
62
+ return frame(channel, new TextEncoder().encode(msg));
63
+ }
64
+
65
+ function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
66
+ const out = new Uint8Array(a.length + b.length);
67
+ out.set(a, 0);
68
+ out.set(b, a.length);
69
+ return out;
70
+ }
71
+
72
+ function sliceBytes(a: Uint8Array, start: number, end?: number): Uint8Array {
73
+ const e = end ?? a.length;
74
+ const out = new Uint8Array(e - start);
75
+ out.set(a.subarray(start, e), 0);
76
+ return out;
77
+ }
78
+
79
+ /**
80
+ * Wrap a packfile byte stream in side-band-64k frames. Channel-1
81
+ * frames are emitted in order, each at most
82
+ * `SIDE_BAND_CHANNEL_MAX_PAYLOAD` bytes. Optional progress strings
83
+ * are emitted on channel 2 before the first channel-1 frame. If
84
+ * `fatal` is set, the source stream is not read; a single channel-3
85
+ * frame carrying the error message is emitted after any progress
86
+ * messages and the output ends.
87
+ */
88
+ export function chunkPackToSideBand(
89
+ pack: ReadableStream<Uint8Array>,
90
+ opts: ChunkPackToSideBandOpts = {},
91
+ ): ReadableStream<Uint8Array> {
92
+ const progress = opts.progress ?? [];
93
+ const fatal = opts.fatal;
94
+
95
+ return new ReadableStream<Uint8Array>({
96
+ async start(controller) {
97
+ try {
98
+ for (const msg of progress) {
99
+ controller.enqueue(textFrame(2, msg));
100
+ }
101
+ if (fatal !== undefined) {
102
+ controller.enqueue(textFrame(3, fatal));
103
+ await pack.cancel().catch(() => undefined);
104
+ controller.close();
105
+ return;
106
+ }
107
+ const reader = pack.getReader();
108
+ let pending: Uint8Array = new Uint8Array(0);
109
+ for (;;) {
110
+ const r = await reader.read();
111
+ if (r.done) break;
112
+ const value = r.value;
113
+ if (!value || value.length === 0) continue;
114
+ pending =
115
+ pending.length === 0
116
+ ? new Uint8Array(value)
117
+ : concatBytes(pending, value);
118
+ while (pending.length >= SIDE_BAND_CHANNEL_MAX_PAYLOAD) {
119
+ controller.enqueue(
120
+ frame(1, sliceBytes(pending, 0, SIDE_BAND_CHANNEL_MAX_PAYLOAD)),
121
+ );
122
+ pending = sliceBytes(pending, SIDE_BAND_CHANNEL_MAX_PAYLOAD);
123
+ }
124
+ }
125
+ if (pending.length > 0) {
126
+ controller.enqueue(frame(1, pending));
127
+ }
128
+ controller.close();
129
+ } catch (cause) {
130
+ controller.error(cause);
131
+ }
132
+ },
133
+ });
134
+ }