@streamotter/gateway 0.1.0-rc.1

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -0
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +17 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/internals.d.ts +6 -0
  8. package/dist/internals.d.ts.map +1 -0
  9. package/dist/internals.js +6 -0
  10. package/dist/internals.js.map +1 -0
  11. package/dist/management/index.d.ts +26 -0
  12. package/dist/management/index.d.ts.map +1 -0
  13. package/dist/management/index.js +354 -0
  14. package/dist/management/index.js.map +1 -0
  15. package/dist/runtime/budget.d.ts +24 -0
  16. package/dist/runtime/budget.d.ts.map +1 -0
  17. package/dist/runtime/budget.js +56 -0
  18. package/dist/runtime/budget.js.map +1 -0
  19. package/dist/runtime/core.d.ts +67 -0
  20. package/dist/runtime/core.d.ts.map +1 -0
  21. package/dist/runtime/core.js +34 -0
  22. package/dist/runtime/core.js.map +1 -0
  23. package/dist/runtime/gateway.d.ts +117 -0
  24. package/dist/runtime/gateway.d.ts.map +1 -0
  25. package/dist/runtime/gateway.js +881 -0
  26. package/dist/runtime/gateway.js.map +1 -0
  27. package/dist/runtime/identity.d.ts +28 -0
  28. package/dist/runtime/identity.d.ts.map +1 -0
  29. package/dist/runtime/identity.js +92 -0
  30. package/dist/runtime/identity.js.map +1 -0
  31. package/dist/runtime/session.d.ts +49 -0
  32. package/dist/runtime/session.d.ts.map +1 -0
  33. package/dist/runtime/session.js +299 -0
  34. package/dist/runtime/session.js.map +1 -0
  35. package/dist/runtime/subscription.d.ts +65 -0
  36. package/dist/runtime/subscription.d.ts.map +1 -0
  37. package/dist/runtime/subscription.js +482 -0
  38. package/dist/runtime/subscription.js.map +1 -0
  39. package/dist/runtime/traces.d.ts +26 -0
  40. package/dist/runtime/traces.d.ts.map +1 -0
  41. package/dist/runtime/traces.js +98 -0
  42. package/dist/runtime/traces.js.map +1 -0
  43. package/dist/runtime/util.d.ts +50 -0
  44. package/dist/runtime/util.d.ts.map +1 -0
  45. package/dist/runtime/util.js +148 -0
  46. package/dist/runtime/util.js.map +1 -0
  47. package/dist/sources/fixture.d.ts +27 -0
  48. package/dist/sources/fixture.d.ts.map +1 -0
  49. package/dist/sources/fixture.js +89 -0
  50. package/dist/sources/fixture.js.map +1 -0
  51. package/dist/sources/kafka.d.ts +63 -0
  52. package/dist/sources/kafka.d.ts.map +1 -0
  53. package/dist/sources/kafka.js +418 -0
  54. package/dist/sources/kafka.js.map +1 -0
  55. package/dist/sources/kafkajs-patch.d.ts +11 -0
  56. package/dist/sources/kafkajs-patch.d.ts.map +1 -0
  57. package/dist/sources/kafkajs-patch.js +34 -0
  58. package/dist/sources/kafkajs-patch.js.map +1 -0
  59. package/dist/sources/types.d.ts +46 -0
  60. package/dist/sources/types.d.ts.map +1 -0
  61. package/dist/sources/types.js +2 -0
  62. package/dist/sources/types.js.map +1 -0
  63. package/dist/transport/socketio.d.ts +44 -0
  64. package/dist/transport/socketio.d.ts.map +1 -0
  65. package/dist/transport/socketio.js +80 -0
  66. package/dist/transport/socketio.js.map +1 -0
  67. package/dist/transport/types.d.ts +10 -0
  68. package/dist/transport/types.d.ts.map +1 -0
  69. package/dist/transport/types.js +2 -0
  70. package/dist/transport/types.js.map +1 -0
  71. package/package.json +61 -0
  72. package/src/index.ts +20 -0
  73. package/src/internals.ts +5 -0
  74. package/src/management/index.ts +371 -0
  75. package/src/runtime/budget.ts +60 -0
  76. package/src/runtime/core.ts +101 -0
  77. package/src/runtime/gateway.ts +892 -0
  78. package/src/runtime/identity.ts +99 -0
  79. package/src/runtime/session.ts +329 -0
  80. package/src/runtime/subscription.ts +531 -0
  81. package/src/runtime/traces.ts +102 -0
  82. package/src/runtime/util.ts +157 -0
  83. package/src/sources/fixture.ts +95 -0
  84. package/src/sources/kafka.ts +440 -0
  85. package/src/sources/kafkajs-patch.ts +41 -0
  86. package/src/sources/types.ts +42 -0
  87. package/src/transport/socketio.ts +125 -0
  88. package/src/transport/types.ts +10 -0
@@ -0,0 +1,98 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { StreamOtterError, utf8ByteLength } from "@streamotter/contracts";
3
+ import { nowIso } from "./util.js";
4
+ /**
5
+ * Bounded in-memory trace metadata. Never stores payloads or credentials. Cursors
6
+ * are ephemeral positions scoped to this gateway run, not data-recovery cursors.
7
+ */
8
+ export class TraceBuffer {
9
+ #maxEntries;
10
+ #maxBytes;
11
+ #runId = randomBytes(6).toString("base64url");
12
+ #entries = [];
13
+ #head = 0;
14
+ #bytes = 0;
15
+ #nextSeq = 1;
16
+ constructor(maxEntries, maxBytes) {
17
+ this.#maxEntries = maxEntries;
18
+ this.#maxBytes = maxBytes;
19
+ }
20
+ record(input) {
21
+ const seq = this.#nextSeq++;
22
+ const trace = { id: `${this.#runId}-${seq}`, at: nowIso(), ...input };
23
+ const bytes = utf8ByteLength(JSON.stringify(trace));
24
+ this.#entries.push({ seq, trace, bytes });
25
+ this.#bytes += bytes;
26
+ while (this.size > this.#maxEntries || (this.#bytes > this.#maxBytes && this.size > 0)) {
27
+ const dropped = this.#entries[this.#head];
28
+ if (dropped === undefined)
29
+ break;
30
+ this.#bytes -= dropped.bytes;
31
+ this.#head++;
32
+ }
33
+ if (this.#head > 1024 && this.#head * 2 > this.#entries.length) {
34
+ this.#entries = this.#entries.slice(this.#head);
35
+ this.#head = 0;
36
+ }
37
+ }
38
+ get size() {
39
+ return this.#entries.length - this.#head;
40
+ }
41
+ #cursorFor(seq) {
42
+ return Buffer.from(`${this.#runId}:${seq}`).toString("base64url");
43
+ }
44
+ #parseCursor(cursor) {
45
+ let decoded;
46
+ try {
47
+ decoded = Buffer.from(cursor, "base64url").toString("utf8");
48
+ }
49
+ catch {
50
+ throw new StreamOtterError("INVALID_REQUEST", { message: "The trace cursor is malformed." });
51
+ }
52
+ const match = /^([A-Za-z0-9_-]+):(\d{1,15})$/.exec(decoded);
53
+ if (match === null)
54
+ throw new StreamOtterError("INVALID_REQUEST", { message: "The trace cursor is malformed." });
55
+ if (match[1] !== this.#runId)
56
+ throw new StreamOtterError("TRACE_CURSOR_EXPIRED");
57
+ return Number(match[2]);
58
+ }
59
+ /**
60
+ * Without a cursor, returns the newest `limit` matching traces (oldest first).
61
+ * With a cursor, returns up to `limit` matching traces recorded after it.
62
+ * nextCursor continues after the last examined trace, so callers can poll.
63
+ */
64
+ page(query) {
65
+ const matches = (entry) => (query.sourceId === undefined || entry.trace.sourceId === query.sourceId)
66
+ && (query.channel === undefined || entry.trace.channel === query.channel)
67
+ && (query.outcome === undefined || entry.trace.outcome === query.outcome);
68
+ const oldestSeq = this.#entries[this.#head]?.seq ?? this.#nextSeq;
69
+ if (query.cursor === undefined) {
70
+ const items = [];
71
+ for (let i = this.#entries.length - 1; i >= this.#head && items.length < query.limit; i--) {
72
+ const entry = this.#entries[i];
73
+ if (entry !== undefined && matches(entry))
74
+ items.push(entry.trace);
75
+ }
76
+ items.reverse();
77
+ return { items, nextCursor: this.#cursorFor(this.#nextSeq - 1) };
78
+ }
79
+ const after = this.#parseCursor(query.cursor);
80
+ if (after + 1 < oldestSeq)
81
+ throw new StreamOtterError("TRACE_CURSOR_EXPIRED");
82
+ const items = [];
83
+ let last = after;
84
+ for (let i = this.#head + Math.max(0, after + 1 - oldestSeq); i < this.#entries.length; i++) {
85
+ const entry = this.#entries[i];
86
+ if (entry === undefined)
87
+ break;
88
+ last = entry.seq;
89
+ if (matches(entry)) {
90
+ items.push(entry.trace);
91
+ if (items.length >= query.limit)
92
+ break;
93
+ }
94
+ }
95
+ return { items, nextCursor: this.#cursorFor(last) };
96
+ }
97
+ }
98
+ //# sourceMappingURL=traces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traces.js","sourceRoot":"","sources":["../../src/runtime/traces.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { type Awaitable, type GatewayLogger, type Json } from "@streamotter/contracts";
2
+ export type HandlerOutcome<T> = {
3
+ kind: "ok";
4
+ value: T;
5
+ } | {
6
+ kind: "timeout";
7
+ } | {
8
+ kind: "aborted";
9
+ } | {
10
+ kind: "error";
11
+ error: unknown;
12
+ };
13
+ /**
14
+ * Invokes an application handler with a deadline and an AbortSignal linked to
15
+ * `parent`. Late results after timeout or abort are ignored.
16
+ */
17
+ export declare function invokeHandler<T>(handler: (context: {
18
+ signal: AbortSignal;
19
+ requestId: string;
20
+ }) => Awaitable<T>, options: {
21
+ timeoutMs: number;
22
+ requestId: string;
23
+ parent?: AbortSignal | undefined;
24
+ }): Promise<HandlerOutcome<T>>;
25
+ export declare function sha256Hex(value: unknown): string;
26
+ export declare function newId(): string;
27
+ export declare function nowIso(): string;
28
+ /** Describes an application error for operator logs without its stack. */
29
+ export declare function describeError(error: unknown): Json;
30
+ /** Structured console logger; the default operator diagnostics sink. */
31
+ export declare function consoleLogger(minimum?: "info" | "warn" | "error"): GatewayLogger;
32
+ export declare const silentLogger: GatewayLogger;
33
+ /** setTimeout that tolerates delays beyond the 32-bit timer limit. */
34
+ export declare function setLongTimeout(callback: () => void, delayMs: number): {
35
+ clear(): void;
36
+ };
37
+ /** Counting semaphore whose waits honor a deadline and an abort signal. */
38
+ export declare class Semaphore {
39
+ #private;
40
+ constructor(permits: number);
41
+ acquire(signal: AbortSignal, deadline: number): Promise<boolean>;
42
+ release(): void;
43
+ }
44
+ /** Token bucket used for per-connection control-request rate limits. */
45
+ export declare class TokenBucket {
46
+ #private;
47
+ constructor(ratePerSecond: number, capacity: number);
48
+ take(): boolean;
49
+ }
50
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/runtime/util.ts"],"names":[],"mappings":"AACA,OAAO,EAAmC,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAExH,MAAM,MAAM,cAAc,CAAC,CAAC,IACxB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtC;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,OAAO,EAAE,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAC9E,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,GAClF,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAyB5B;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGlD;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,OAAO,GAAE,MAAM,GAAG,MAAM,GAAG,OAAgB,GAAG,aAAa,CAYxF;AAED,eAAO,MAAM,YAAY,EAAE,aAAoD,CAAC;AAEhF,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,IAAI,IAAI,CAAA;CAAE,CAYvF;AAED,2EAA2E;AAC3E,qBAAa,SAAS;;gBAIR,OAAO,EAAE,MAAM;IAI3B,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BhE,OAAO,IAAI,IAAI;CAKhB;AAED,wEAAwE;AACxE,qBAAa,WAAW;;gBAMV,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAMnD,IAAI,IAAI,OAAO;CAQhB"}
@@ -0,0 +1,148 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { canonicalJson, StreamOtterError } from "@streamotter/contracts";
3
+ /**
4
+ * Invokes an application handler with a deadline and an AbortSignal linked to
5
+ * `parent`. Late results after timeout or abort are ignored.
6
+ */
7
+ export async function invokeHandler(handler, options) {
8
+ const controller = new AbortController();
9
+ const { parent } = options;
10
+ if (parent?.aborted)
11
+ return { kind: "aborted" };
12
+ let settle;
13
+ const race = new Promise(resolve => { settle = resolve; });
14
+ const timer = setTimeout(() => {
15
+ controller.abort(new StreamOtterError("TIMEOUT", { requestId: options.requestId }));
16
+ settle?.({ kind: "timeout" });
17
+ }, options.timeoutMs);
18
+ const onParentAbort = () => {
19
+ controller.abort(new StreamOtterError("CANCELLED", { requestId: options.requestId }));
20
+ settle?.({ kind: "aborted" });
21
+ };
22
+ parent?.addEventListener("abort", onParentAbort, { once: true });
23
+ try {
24
+ Promise.resolve()
25
+ .then(() => handler({ signal: controller.signal, requestId: options.requestId }))
26
+ .then(value => settle?.({ kind: "ok", value }), (error) => settle?.({ kind: "error", error }));
27
+ return await race;
28
+ }
29
+ finally {
30
+ clearTimeout(timer);
31
+ parent?.removeEventListener("abort", onParentAbort);
32
+ settle = undefined;
33
+ }
34
+ }
35
+ export function sha256Hex(value) {
36
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
37
+ }
38
+ export function newId() {
39
+ return randomUUID();
40
+ }
41
+ export function nowIso() {
42
+ return new Date().toISOString();
43
+ }
44
+ /** Describes an application error for operator logs without its stack. */
45
+ export function describeError(error) {
46
+ if (error instanceof Error)
47
+ return { name: error.name, message: error.message.slice(0, 200) };
48
+ return { name: typeof error };
49
+ }
50
+ /** Structured console logger; the default operator diagnostics sink. */
51
+ export function consoleLogger(minimum = "info") {
52
+ const order = { info: 0, warn: 1, error: 2 };
53
+ const write = (level, message, fields) => {
54
+ if (order[level] < order[minimum])
55
+ return;
56
+ const line = JSON.stringify({ at: nowIso(), level, message, ...(fields ?? {}) });
57
+ if (level === "info")
58
+ console.log(line);
59
+ else
60
+ console.error(line);
61
+ };
62
+ return {
63
+ info: (message, fields) => write("info", message, fields),
64
+ warn: (message, fields) => write("warn", message, fields),
65
+ error: (message, fields) => write("error", message, fields)
66
+ };
67
+ }
68
+ export const silentLogger = { info() { }, warn() { }, error() { } };
69
+ /** setTimeout that tolerates delays beyond the 32-bit timer limit. */
70
+ export function setLongTimeout(callback, delayMs) {
71
+ const MAX = 2_147_000_000;
72
+ let handle;
73
+ const schedule = (remaining) => {
74
+ handle = setTimeout(() => {
75
+ if (remaining > MAX)
76
+ schedule(remaining - MAX);
77
+ else
78
+ callback();
79
+ }, Math.max(0, Math.min(remaining, MAX)));
80
+ handle.unref?.();
81
+ };
82
+ schedule(delayMs);
83
+ return { clear: () => clearTimeout(handle) };
84
+ }
85
+ /** Counting semaphore whose waits honor a deadline and an abort signal. */
86
+ export class Semaphore {
87
+ #available;
88
+ #waiters = [];
89
+ constructor(permits) {
90
+ this.#available = permits;
91
+ }
92
+ acquire(signal, deadline) {
93
+ if (signal.aborted || Date.now() >= deadline)
94
+ return Promise.resolve(false);
95
+ if (this.#available > 0) {
96
+ this.#available--;
97
+ return Promise.resolve(true);
98
+ }
99
+ return new Promise(resolve => {
100
+ const waiter = {
101
+ resolve: (acquired) => {
102
+ clearTimeout(timer);
103
+ signal.removeEventListener("abort", onAbort);
104
+ resolve(acquired);
105
+ }
106
+ };
107
+ const drop = () => {
108
+ const index = this.#waiters.indexOf(waiter);
109
+ if (index >= 0)
110
+ this.#waiters.splice(index, 1);
111
+ waiter.resolve(false);
112
+ };
113
+ const onAbort = () => drop();
114
+ const timer = setTimeout(drop, Math.max(0, deadline - Date.now()));
115
+ signal.addEventListener("abort", onAbort, { once: true });
116
+ this.#waiters.push(waiter);
117
+ });
118
+ }
119
+ release() {
120
+ const next = this.#waiters.shift();
121
+ if (next !== undefined)
122
+ next.resolve(true);
123
+ else
124
+ this.#available++;
125
+ }
126
+ }
127
+ /** Token bucket used for per-connection control-request rate limits. */
128
+ export class TokenBucket {
129
+ #ratePerSecond;
130
+ #capacity;
131
+ #tokens;
132
+ #updated = Date.now();
133
+ constructor(ratePerSecond, capacity) {
134
+ this.#ratePerSecond = ratePerSecond;
135
+ this.#capacity = capacity;
136
+ this.#tokens = capacity;
137
+ }
138
+ take() {
139
+ const now = Date.now();
140
+ this.#tokens = Math.min(this.#capacity, this.#tokens + ((now - this.#updated) / 1000) * this.#ratePerSecond);
141
+ this.#updated = now;
142
+ if (this.#tokens < 1)
143
+ return false;
144
+ this.#tokens -= 1;
145
+ return true;
146
+ }
147
+ }
148
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/runtime/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAiD,MAAM,wBAAwB,CAAC;AAQxH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAA8E,EAC9E,OAAmF;IAEnF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3B,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAChD,IAAI,MAA0D,CAAC;IAC/D,MAAM,IAAI,GAAG,IAAI,OAAO,CAAoB,OAAO,CAAC,EAAE,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,UAAU,CAAC,KAAK,CAAC,IAAI,gBAAgB,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QACpF,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAChC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACtB,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,UAAU,CAAC,KAAK,CAAC,IAAI,gBAAgB,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QACtF,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC;IACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,OAAO,CAAC,OAAO,EAAE;aACd,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;aAChF,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC1G,OAAO,MAAM,IAAI,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QACpD,MAAM,GAAG,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,KAAK;IACnB,OAAO,UAAU,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,MAAM;IACpB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC9F,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,CAAC;AAChC,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,UAAqC,MAAM;IACvE,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAW,CAAC;IACtD,MAAM,KAAK,GAAG,CAAC,KAAyB,EAAE,OAAe,EAAE,MAAuC,EAAE,EAAE;QACpG,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;YAAE,OAAO;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACjF,IAAI,KAAK,KAAK,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;;YAAM,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC,CAAC;IACF,OAAO;QACL,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;QACzD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;QACzD,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAkB,EAAE,IAAI,KAAI,CAAC,EAAE,IAAI,KAAI,CAAC,EAAE,KAAK,KAAI,CAAC,EAAE,CAAC;AAEhF,sEAAsE;AACtE,MAAM,UAAU,cAAc,CAAC,QAAoB,EAAE,OAAe;IAClE,MAAM,GAAG,GAAG,aAAa,CAAC;IAC1B,IAAI,MAAsB,CAAC;IAC3B,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,EAAE;QACrC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;YACvB,IAAI,SAAS,GAAG,GAAG;gBAAE,QAAQ,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC;;gBAC1C,QAAQ,EAAE,CAAC;QAClB,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;IACnB,CAAC,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,2EAA2E;AAC3E,MAAM,OAAO,SAAS;IACpB,UAAU,CAAS;IACV,QAAQ,GAA+C,EAAE,CAAC;IAEnE,YAAY,OAAe;QACzB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC;IAC5B,CAAC;IAED,OAAO,CAAC,MAAmB,EAAE,QAAgB;QAC3C,IAAI,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5E,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC3B,MAAM,MAAM,GAAG;gBACb,OAAO,EAAE,CAAC,QAAiB,EAAE,EAAE;oBAC7B,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAC7C,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACpB,CAAC;aACF,CAAC;YACF,MAAM,IAAI,GAAG,GAAG,EAAE;gBAChB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;gBAC5C,IAAI,KAAK,IAAI,CAAC;oBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC/C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC,CAAC;YACF,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACnE,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnC,IAAI,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;;YACtC,IAAI,CAAC,UAAU,EAAE,CAAC;IACzB,CAAC;CACF;AAED,wEAAwE;AACxE,MAAM,OAAO,WAAW;IACb,cAAc,CAAS;IACvB,SAAS,CAAS;IAC3B,OAAO,CAAS;IAChB,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEtB,YAAY,aAAqB,EAAE,QAAgB;QACjD,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC;IAC1B,CAAC;IAED,IAAI;QACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7G,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACnC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ import { type DiagnosticStep, type Json } from "@streamotter/contracts";
2
+ import type { SourceAdapter, SourceSink } from "./types.ts";
3
+ export interface FixtureRecord {
4
+ key: string | null;
5
+ value: Json;
6
+ }
7
+ /**
8
+ * Deterministic development source. Records advance in array order only when
9
+ * requested. A paused fixture keeps its position; resume retries that record.
10
+ */
11
+ export declare class FixtureSourceAdapter implements SourceAdapter {
12
+ #private;
13
+ readonly kind: "fixture";
14
+ constructor(records: readonly FixtureRecord[], sink: SourceSink, onCommit: () => void);
15
+ get position(): {
16
+ index: number;
17
+ total: number;
18
+ paused: boolean;
19
+ };
20
+ start(): Promise<void>;
21
+ stop(): Promise<void>;
22
+ /** Processes up to `count` records in order; resolves with the number committed. */
23
+ advance(count: number): Promise<number>;
24
+ resume(): Promise<void>;
25
+ check(): Promise<DiagnosticStep[]>;
26
+ }
27
+ //# sourceMappingURL=fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../src/sources/fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,aAAa;IAAG,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE;AAElE;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,aAAa;;IACxD,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAU;gBASvB,OAAO,EAAE,SAAS,aAAa,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI;IAMrF,IAAI,QAAQ,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAEhE;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,oFAAoF;IACpF,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBjC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAWvB,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;CA2BzC"}
@@ -0,0 +1,89 @@
1
+ import { StreamOtterError } from "@streamotter/contracts";
2
+ /**
3
+ * Deterministic development source. Records advance in array order only when
4
+ * requested. A paused fixture keeps its position; resume retries that record.
5
+ */
6
+ export class FixtureSourceAdapter {
7
+ kind = "fixture";
8
+ #records;
9
+ #sink;
10
+ #onCommit;
11
+ #index = 0;
12
+ #paused = false;
13
+ #stopped = false;
14
+ #chain = Promise.resolve();
15
+ constructor(records, sink, onCommit) {
16
+ this.#records = records;
17
+ this.#sink = sink;
18
+ this.#onCommit = onCommit;
19
+ }
20
+ get position() {
21
+ return { index: this.#index, total: this.#records.length, paused: this.#paused };
22
+ }
23
+ async start() {
24
+ this.#sink.setStatus("healthy");
25
+ }
26
+ async stop() {
27
+ this.#stopped = true;
28
+ await this.#chain.catch(() => undefined);
29
+ }
30
+ /** Processes up to `count` records in order; resolves with the number committed. */
31
+ advance(count) {
32
+ const run = this.#chain.then(async () => {
33
+ if (this.#stopped)
34
+ throw new StreamOtterError("SOURCE_UNAVAILABLE", { message: "The gateway is stopping." });
35
+ if (this.#paused) {
36
+ throw new StreamOtterError("SOURCE_UNAVAILABLE", { message: "The fixture source is paused; resume it after correcting the cause." });
37
+ }
38
+ let advanced = 0;
39
+ while (advanced < count && this.#index < this.#records.length && !this.#stopped) {
40
+ if (!(await this.#step()))
41
+ break;
42
+ advanced++;
43
+ }
44
+ return advanced;
45
+ });
46
+ this.#chain = run.catch(() => undefined);
47
+ return run;
48
+ }
49
+ async resume() {
50
+ const run = this.#chain.then(async () => {
51
+ if (!this.#paused || this.#stopped)
52
+ return;
53
+ this.#paused = false;
54
+ this.#sink.setStatus("healthy");
55
+ await this.#step();
56
+ });
57
+ this.#chain = run.catch(() => undefined);
58
+ await run;
59
+ }
60
+ async check() {
61
+ return [
62
+ { stage: "resolve", outcome: "ok", message: `Fixture with ${this.#records.length} records is registered.` },
63
+ { stage: "connect", outcome: "skipped", message: "Fixture sources have no network connection." },
64
+ { stage: "tls", outcome: "skipped", message: "Fixture sources have no network connection." },
65
+ { stage: "authenticate", outcome: "skipped", message: "Fixture sources have no credentials." },
66
+ { stage: "metadata", outcome: "ok", message: `Position ${this.#index} of ${this.#records.length}${this.#paused ? " (paused)" : ""}.` }
67
+ ];
68
+ }
69
+ /** Processes the record at the current index. Returns false when it paused or was abandoned. */
70
+ async #step() {
71
+ const record = this.#records[this.#index];
72
+ if (record === undefined)
73
+ return false;
74
+ const outcome = await this.#sink.process({
75
+ key: record.key,
76
+ value: record.value,
77
+ position: { kind: "fixture", index: String(this.#index) }
78
+ });
79
+ if (outcome.kind === "commit") {
80
+ this.#index++;
81
+ this.#onCommit();
82
+ return true;
83
+ }
84
+ if (outcome.kind === "pause")
85
+ this.#paused = true;
86
+ return false;
87
+ }
88
+ }
89
+ //# sourceMappingURL=fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/sources/fixture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { type ISocketFactory } from "kafkajs";
2
+ import { type DiagnosticStep, type KafkaConnection, type Source, type SourceRecord } from "@streamotter/contracts";
3
+ import type { SourceAdapter, SourceSink } from "./types.ts";
4
+ export interface ResolvedKafkaConnection {
5
+ readonly brokers: readonly string[];
6
+ readonly tls: boolean;
7
+ readonly ca: string | null;
8
+ readonly sasl: {
9
+ mechanism: "plain" | "scram-sha-256" | "scram-sha-512";
10
+ username: string;
11
+ password: string;
12
+ } | null;
13
+ }
14
+ /** Resolves environment secret references and CA files. Never logs resolved values. */
15
+ export declare function resolveKafkaConnection(profile: KafkaConnection, configDir: string): Promise<ResolvedKafkaConnection>;
16
+ /**
17
+ * Socket factory equivalent to KafkaJS's default, but tracking every socket so
18
+ * stop() can guarantee release. KafkaJS can leave a broker connection open after
19
+ * reconnecting through a broker restart.
20
+ */
21
+ export declare class TrackedSockets {
22
+ #private;
23
+ readonly factory: ISocketFactory;
24
+ get size(): number;
25
+ /**
26
+ * Destroys remaining sockets with an error so KafkaJS runs its own error path:
27
+ * it rejects queued requests, disconnects, and stops the connection's timers.
28
+ */
29
+ destroyAll(): void;
30
+ }
31
+ /**
32
+ * KafkaJS adapter with explicit progress management: auto-commit and automatic
33
+ * batch resolution are disabled, records are processed in partition order with
34
+ * heartbeats, and the next offset is committed only after the gateway finishes
35
+ * processing a record. A poison record pauses the whole source and seeks back so
36
+ * resume retries the same record; nothing after it is committed.
37
+ */
38
+ export declare class KafkaSourceAdapter implements SourceAdapter {
39
+ #private;
40
+ readonly kind: "kafka";
41
+ constructor(options: {
42
+ projectId: string;
43
+ sourceId: string;
44
+ source: Extract<Source, {
45
+ kind: "kafka";
46
+ }>;
47
+ connection: ResolvedKafkaConnection;
48
+ sink: SourceSink;
49
+ onCommit: () => void;
50
+ beforeCommit?: (sourceId: string, position: SourceRecord["position"]) => Promise<void>;
51
+ });
52
+ start(): Promise<void>;
53
+ stop(deadline: number): Promise<void>;
54
+ resume(): Promise<void>;
55
+ check(deadline: number): Promise<DiagnosticStep[]>;
56
+ }
57
+ export declare function createKafkaSourceAdapter(options: ConstructorParameters<typeof KafkaSourceAdapter>[0]): KafkaSourceAdapter;
58
+ /**
59
+ * Staged diagnostics for an existing profile: resolve → connect → tls →
60
+ * authenticate → metadata. Messages name brokers and stages, never credentials.
61
+ */
62
+ export declare function runKafkaDiagnostics(connection: ResolvedKafkaConnection, topics: readonly string[], deadline: number): Promise<DiagnosticStep[]>;
63
+ //# sourceMappingURL=kafka.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kafka.d.ts","sourceRoot":"","sources":["../../src/sources/kafka.ts"],"names":[],"mappings":"AAKA,OAAgB,EAAiB,KAAK,cAAc,EAAsC,MAAM,SAAS,CAAC;AAC1G,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAErI,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK5D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,GAAG,eAAe,GAAG,eAAe,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACtH;AAED,uFAAuF;AACvF,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA4B1H;AAED;;;;GAIG;AACH,qBAAa,cAAc;;IAGzB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAQ9B;IAEF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI;CAInB;AAsCD;;;;;;GAMG;AACH,qBAAa,kBAAmB,YAAW,aAAa;;IACtD,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAU;gBAkBrB,OAAO,EAAE;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAC3C,UAAU,EAAE,uBAAuB,CAAC;QACpC,IAAI,EAAE,UAAU,CAAC;QACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;QACrB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACxF;IAUK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwDtB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBrC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAOvB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAiEzD;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAEzH;AAkBD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,uBAAuB,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAsGrJ"}