@ai-matrx/agents 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -3
- package/README.md +30 -6
- package/dist/index.cjs +289 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +287 -50
- package/dist/index.js.map +1 -1
- package/dist/projection/request.cjs +1 -1
- package/dist/projection/request.cjs.map +1 -1
- package/dist/projection/request.js +1 -1
- package/dist/projection/request.js.map +1 -1
- package/dist/projection/workflow.cjs +145 -18
- package/dist/projection/workflow.cjs.map +1 -1
- package/dist/projection/workflow.d.cts +18 -1
- package/dist/projection/workflow.d.ts +18 -1
- package/dist/projection/workflow.js +145 -19
- package/dist/projection/workflow.js.map +1 -1
- package/dist/stream/ndjson.cjs +143 -30
- package/dist/stream/ndjson.cjs.map +1 -1
- package/dist/stream/ndjson.d.cts +42 -7
- package/dist/stream/ndjson.d.ts +42 -7
- package/dist/stream/ndjson.js +142 -31
- package/dist/stream/ndjson.js.map +1 -1
- package/package.json +1 -1
package/dist/stream/ndjson.cjs
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// stream/ndjson.ts
|
|
4
|
+
var DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;
|
|
4
5
|
function isRecord(value) {
|
|
5
6
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
7
|
}
|
|
7
8
|
function normalizeMatrxStreamEnvelope(value) {
|
|
8
9
|
if (!isRecord(value)) return null;
|
|
9
10
|
if (typeof value.event === "string") {
|
|
10
|
-
|
|
11
|
+
const streamSeq = typeof value.stream_seq === "number" && Number.isFinite(value.stream_seq) ? value.stream_seq : void 0;
|
|
12
|
+
return {
|
|
13
|
+
event: value.event,
|
|
14
|
+
data: value.data,
|
|
15
|
+
...streamSeq === void 0 ? {} : { stream_seq: streamSeq }
|
|
16
|
+
};
|
|
11
17
|
}
|
|
12
18
|
if (value.e === "c" && typeof value.t === "string") {
|
|
13
19
|
return { event: "chunk", data: { text: value.t } };
|
|
@@ -17,74 +23,179 @@ function normalizeMatrxStreamEnvelope(value) {
|
|
|
17
23
|
}
|
|
18
24
|
return null;
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
const queue = [];
|
|
22
|
-
let wakeConsumer = null;
|
|
23
|
-
let readerFinished = false;
|
|
24
|
-
const enqueue = (item) => {
|
|
25
|
-
queue.push(item);
|
|
26
|
-
const wake = wakeConsumer;
|
|
27
|
-
wakeConsumer = null;
|
|
28
|
-
wake?.();
|
|
29
|
-
};
|
|
30
|
-
const reader = body.getReader();
|
|
26
|
+
function createMatrxNdjsonFramer(options = {}) {
|
|
31
27
|
const decoder = new TextDecoder();
|
|
32
|
-
|
|
28
|
+
let buffer = "";
|
|
29
|
+
let lineNumber = 0;
|
|
30
|
+
let finished = false;
|
|
31
|
+
const assertOpen = () => {
|
|
32
|
+
if (finished) throw new Error("Matrx NDJSON framer is already finished");
|
|
33
|
+
};
|
|
34
|
+
const parseLine = (line, atCompletion) => {
|
|
35
|
+
const currentLineNumber = ++lineNumber;
|
|
33
36
|
const trimmed = line.trim();
|
|
34
|
-
if (!trimmed) return;
|
|
37
|
+
if (!trimmed) return null;
|
|
35
38
|
let parsed;
|
|
36
39
|
try {
|
|
37
40
|
parsed = JSON.parse(trimmed);
|
|
38
41
|
} catch (error) {
|
|
39
|
-
options.onMalformedLine?.({
|
|
40
|
-
|
|
42
|
+
options.onMalformedLine?.({
|
|
43
|
+
line: trimmed,
|
|
44
|
+
error,
|
|
45
|
+
lineNumber: currentLineNumber,
|
|
46
|
+
atCompletion
|
|
47
|
+
});
|
|
48
|
+
return null;
|
|
41
49
|
}
|
|
42
50
|
const envelope = normalizeMatrxStreamEnvelope(parsed);
|
|
43
|
-
if (envelope) {
|
|
44
|
-
enqueue({ kind: "event", value: envelope });
|
|
45
|
-
} else {
|
|
51
|
+
if (!envelope) {
|
|
46
52
|
options.onUnknownEnvelope?.(parsed);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
options.onValidEnvelope?.({
|
|
56
|
+
raw: parsed,
|
|
57
|
+
envelope,
|
|
58
|
+
line: trimmed,
|
|
59
|
+
lineNumber: currentLineNumber,
|
|
60
|
+
atCompletion
|
|
61
|
+
});
|
|
62
|
+
return envelope;
|
|
63
|
+
};
|
|
64
|
+
const pushDecodedText = (fragment) => {
|
|
65
|
+
buffer += fragment;
|
|
66
|
+
const lines = buffer.split("\n");
|
|
67
|
+
buffer = lines.pop() ?? "";
|
|
68
|
+
const envelopes = [];
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const envelope = parseLine(line, false);
|
|
71
|
+
if (envelope) envelopes.push(envelope);
|
|
47
72
|
}
|
|
73
|
+
return envelopes;
|
|
48
74
|
};
|
|
75
|
+
return {
|
|
76
|
+
pushText(fragment) {
|
|
77
|
+
assertOpen();
|
|
78
|
+
return pushDecodedText(decoder.decode() + fragment);
|
|
79
|
+
},
|
|
80
|
+
pushBytes(fragment) {
|
|
81
|
+
assertOpen();
|
|
82
|
+
return pushDecodedText(decoder.decode(fragment, { stream: true }));
|
|
83
|
+
},
|
|
84
|
+
finish() {
|
|
85
|
+
assertOpen();
|
|
86
|
+
finished = true;
|
|
87
|
+
const envelopes = pushDecodedText(decoder.decode());
|
|
88
|
+
if (buffer.length > 0) {
|
|
89
|
+
const envelope = parseLine(buffer, true);
|
|
90
|
+
buffer = "";
|
|
91
|
+
if (envelope) envelopes.push(envelope);
|
|
92
|
+
}
|
|
93
|
+
return envelopes;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function readAheadLimit(value) {
|
|
98
|
+
const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;
|
|
99
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
100
|
+
throw new RangeError("maxReadAhead must be a positive safe integer");
|
|
101
|
+
}
|
|
102
|
+
return limit;
|
|
103
|
+
}
|
|
104
|
+
async function* readMatrxNdjsonStream(body, options = {}) {
|
|
105
|
+
const maxReadAhead = readAheadLimit(options.maxReadAhead);
|
|
106
|
+
const queue = [];
|
|
107
|
+
let queuedEventCount = 0;
|
|
108
|
+
let wakeConsumer = null;
|
|
109
|
+
let wakeProducer = null;
|
|
110
|
+
let readerFinished = false;
|
|
111
|
+
let consumerClosed = false;
|
|
112
|
+
const wakeWaitingConsumer = () => {
|
|
113
|
+
const wake = wakeConsumer;
|
|
114
|
+
wakeConsumer = null;
|
|
115
|
+
wake?.();
|
|
116
|
+
};
|
|
117
|
+
const wakeWaitingProducer = () => {
|
|
118
|
+
const wake = wakeProducer;
|
|
119
|
+
wakeProducer = null;
|
|
120
|
+
wake?.();
|
|
121
|
+
};
|
|
122
|
+
const enqueueTerminal = (item) => {
|
|
123
|
+
if (consumerClosed) return;
|
|
124
|
+
queue.push(item);
|
|
125
|
+
wakeWaitingConsumer();
|
|
126
|
+
};
|
|
127
|
+
const enqueueEvent = async (value) => {
|
|
128
|
+
while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
|
|
129
|
+
await new Promise((resolve) => {
|
|
130
|
+
wakeProducer = resolve;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (consumerClosed || options.signal?.aborted) return false;
|
|
134
|
+
queue.push({ kind: "event", value });
|
|
135
|
+
queuedEventCount += 1;
|
|
136
|
+
wakeWaitingConsumer();
|
|
137
|
+
return true;
|
|
138
|
+
};
|
|
139
|
+
const waitForReadCapacity = async () => {
|
|
140
|
+
while (queuedEventCount >= maxReadAhead && !consumerClosed && !options.signal?.aborted) {
|
|
141
|
+
await new Promise((resolve) => {
|
|
142
|
+
wakeProducer = resolve;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return !consumerClosed && !options.signal?.aborted;
|
|
146
|
+
};
|
|
147
|
+
const reader = body.getReader();
|
|
148
|
+
const framer = createMatrxNdjsonFramer(options);
|
|
49
149
|
const onAbort = () => {
|
|
150
|
+
wakeWaitingProducer();
|
|
151
|
+
wakeWaitingConsumer();
|
|
50
152
|
void reader.cancel(options.signal?.reason).catch(() => void 0);
|
|
51
153
|
};
|
|
52
154
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
155
|
+
if (options.signal?.aborted) onAbort();
|
|
53
156
|
const readerPromise = (async () => {
|
|
54
|
-
let buffer = "";
|
|
55
157
|
try {
|
|
56
|
-
while (!options.signal?.aborted) {
|
|
158
|
+
while (!options.signal?.aborted && !consumerClosed) {
|
|
159
|
+
if (!await waitForReadCapacity()) return;
|
|
57
160
|
const { value, done } = await reader.read();
|
|
58
161
|
if (done) break;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
162
|
+
for (const envelope of framer.pushBytes(value)) {
|
|
163
|
+
if (!await enqueueEvent(envelope)) return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!options.signal?.aborted && !consumerClosed) {
|
|
167
|
+
for (const envelope of framer.finish()) {
|
|
168
|
+
if (!await enqueueEvent(envelope)) return;
|
|
169
|
+
}
|
|
63
170
|
}
|
|
64
|
-
buffer += decoder.decode();
|
|
65
|
-
if (!options.signal?.aborted && buffer.trim()) parseLine(buffer);
|
|
66
171
|
} catch (error) {
|
|
67
|
-
const aborted = options.signal?.aborted || error instanceof Error && error.name === "AbortError";
|
|
68
|
-
if (!aborted)
|
|
172
|
+
const aborted = options.signal?.aborted || consumerClosed || error instanceof Error && error.name === "AbortError";
|
|
173
|
+
if (!aborted) enqueueTerminal({ kind: "error", error });
|
|
69
174
|
} finally {
|
|
70
175
|
readerFinished = true;
|
|
71
176
|
reader.releaseLock();
|
|
72
|
-
|
|
177
|
+
enqueueTerminal({ kind: "done" });
|
|
73
178
|
}
|
|
74
179
|
})();
|
|
75
180
|
try {
|
|
76
181
|
while (true) {
|
|
77
182
|
if (queue.length === 0) {
|
|
183
|
+
if (options.signal?.aborted || readerFinished) return;
|
|
78
184
|
await new Promise((resolve) => {
|
|
79
185
|
wakeConsumer = resolve;
|
|
80
186
|
});
|
|
81
187
|
}
|
|
82
188
|
const item = queue.shift();
|
|
189
|
+
if (item?.kind === "event") queuedEventCount -= 1;
|
|
190
|
+
wakeWaitingProducer();
|
|
83
191
|
if (!item || item.kind === "done") return;
|
|
84
192
|
if (item.kind === "error") throw item.error;
|
|
85
193
|
yield item.value;
|
|
86
194
|
}
|
|
87
195
|
} finally {
|
|
196
|
+
consumerClosed = true;
|
|
197
|
+
wakeWaitingProducer();
|
|
198
|
+
wakeWaitingConsumer();
|
|
88
199
|
options.signal?.removeEventListener("abort", onAbort);
|
|
89
200
|
if (!readerFinished) {
|
|
90
201
|
await reader.cancel().catch(() => void 0);
|
|
@@ -93,6 +204,8 @@ async function* readMatrxNdjsonStream(body, options = {}) {
|
|
|
93
204
|
}
|
|
94
205
|
}
|
|
95
206
|
|
|
207
|
+
exports.DEFAULT_MATRX_NDJSON_READ_AHEAD = DEFAULT_MATRX_NDJSON_READ_AHEAD;
|
|
208
|
+
exports.createMatrxNdjsonFramer = createMatrxNdjsonFramer;
|
|
96
209
|
exports.normalizeMatrxStreamEnvelope = normalizeMatrxStreamEnvelope;
|
|
97
210
|
exports.readMatrxNdjsonStream = readMatrxNdjsonStream;
|
|
98
211
|
//# sourceMappingURL=ndjson.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../stream/ndjson.ts"],"names":[],"mappings":"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","file":"ndjson.cjs","sourcesContent":["/**\n * Canonical AI Matrx NDJSON wire kernel.\n *\n * This module is deliberately independent of React, Redux, Next.js, Supabase,\n * and generated application types. Every Matrx client uses it to turn the\n * backend's byte stream into the same normalized `{ event, data }` envelopes.\n * Host runtimes remain responsible for HTTP/auth errors and for deciding what\n * each event means in their state model.\n */\n\nexport interface MatrxStreamEnvelope<TData = unknown> {\n event: string;\n data: TData;\n}\n\nexport interface MatrxNdjsonIssue {\n line: string;\n error: unknown;\n}\n\nexport interface ReadMatrxNdjsonOptions {\n signal?: AbortSignal;\n /** Malformed JSON is non-fatal, but it must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx event envelope is also non-fatal. */\n onUnknownEnvelope?: (value: unknown) => void;\n}\n\ntype QueueItem =\n | { kind: \"event\"; value: MatrxStreamEnvelope }\n | { kind: \"error\"; error: unknown }\n | { kind: \"done\" };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize both supported Matrx wire shapes:\n *\n * - full: `{ \"event\": \"chunk\", \"data\": { \"text\": \"...\" } }`\n * - compact chunk: `{ \"e\": \"c\", \"t\": \"...\" }`\n * - compact reasoning: `{ \"e\": \"r\", \"t\": \"...\" }`\n */\nexport function normalizeMatrxStreamEnvelope(\n value: unknown,\n): MatrxStreamEnvelope | null {\n if (!isRecord(value)) return null;\n\n if (typeof value.event === \"string\") {\n return { event: value.event, data: value.data };\n }\n if (value.e === \"c\" && typeof value.t === \"string\") {\n return { event: \"chunk\", data: { text: value.t } };\n }\n if (value.e === \"r\" && typeof value.t === \"string\") {\n return { event: \"reasoning_chunk\", data: { text: value.t } };\n }\n return null;\n}\n\n/**\n * Read and normalize a Matrx NDJSON response body without applying consumer\n * backpressure to the network reader. The background read-ahead is important:\n * large tool payloads must keep draining even while React or another host is\n * processing the previous event.\n */\nexport async function* readMatrxNdjsonStream(\n body: ReadableStream<Uint8Array>,\n options: ReadMatrxNdjsonOptions = {},\n): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n const queue: QueueItem[] = [];\n let wakeConsumer: (() => void) | null = null;\n let readerFinished = false;\n\n const enqueue = (item: QueueItem): void => {\n queue.push(item);\n const wake = wakeConsumer;\n wakeConsumer = null;\n wake?.();\n };\n\n const reader = body.getReader();\n const decoder = new TextDecoder();\n\n const parseLine = (line: string): void => {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch (error) {\n options.onMalformedLine?.({ line: trimmed, error });\n return;\n }\n\n const envelope = normalizeMatrxStreamEnvelope(parsed);\n if (envelope) {\n enqueue({ kind: \"event\", value: envelope });\n } else {\n options.onUnknownEnvelope?.(parsed);\n }\n };\n\n const onAbort = (): void => {\n void reader.cancel(options.signal?.reason).catch(() => undefined);\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n const readerPromise = (async (): Promise<void> => {\n let buffer = \"\";\n try {\n while (!options.signal?.aborted) {\n const { value, done } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n for (const line of lines) parseLine(line);\n }\n\n buffer += decoder.decode();\n if (!options.signal?.aborted && buffer.trim()) parseLine(buffer);\n } catch (error) {\n const aborted =\n options.signal?.aborted ||\n (error instanceof Error && error.name === \"AbortError\");\n if (!aborted) enqueue({ kind: \"error\", error });\n } finally {\n readerFinished = true;\n reader.releaseLock();\n enqueue({ kind: \"done\" });\n }\n })();\n\n try {\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n }\n\n const item = queue.shift();\n if (!item || item.kind === \"done\") return;\n if (item.kind === \"error\") throw item.error;\n yield item.value;\n }\n } finally {\n options.signal?.removeEventListener(\"abort\", onAbort);\n if (!readerFinished) {\n await reader.cancel().catch(() => undefined);\n }\n await readerPromise;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../stream/ndjson.ts"],"names":[],"mappings":"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","file":"ndjson.cjs","sourcesContent":["/**\n * Canonical AI Matrx NDJSON wire kernel.\n *\n * This module is deliberately independent of React, Redux, Next.js, Supabase,\n * and generated application types. Every Matrx client uses it to turn the\n * backend's byte stream into the same normalized `{ event, data }` envelopes.\n * Host runtimes remain responsible for HTTP/auth errors and for deciding what\n * each event means in their state model.\n */\n\nexport interface MatrxStreamEnvelope<TData = unknown> {\n event: string;\n data: TData;\n /** Monotonic transport sequence from full envelopes, when supplied. */\n stream_seq?: number;\n}\n\nexport interface MatrxNdjsonIssue {\n line: string;\n error: unknown;\n /** One-based physical NDJSON line number. */\n lineNumber: number;\n /** True when an unterminated trailing fragment was parsed by `finish()`. */\n atCompletion: boolean;\n}\n\nexport interface MatrxStreamEnvelopeObservation {\n /** Exact parsed JSON value before compact/full normalization. */\n raw: unknown;\n envelope: MatrxStreamEnvelope;\n line: string;\n lineNumber: number;\n atCompletion: boolean;\n}\n\nexport interface MatrxNdjsonFramerOptions {\n /** Malformed JSON is non-fatal, but it must never disappear silently. */\n onMalformedLine?: (issue: MatrxNdjsonIssue) => void;\n /** Valid JSON with no recognized Matrx event envelope is also non-fatal. */\n onUnknownEnvelope?: (value: unknown) => void;\n /** Observe every valid envelope without changing or consuming it. */\n onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;\n}\n\nexport interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {\n signal?: AbortSignal;\n /** Maximum normalized events read ahead of the iterator consumer. */\n maxReadAhead?: number;\n}\n\nexport interface MatrxNdjsonFramer {\n /** Push a text fragment. Fragments may split JSON tokens or line endings. */\n pushText(fragment: string): MatrxStreamEnvelope[];\n /** Push a byte fragment. UTF-8 code points may span calls. */\n pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];\n /** Parse the final unterminated line and flush any pending UTF-8 bytes. */\n finish(): MatrxStreamEnvelope[];\n}\n\ntype QueueItem =\n | { kind: \"event\"; value: MatrxStreamEnvelope }\n | { kind: \"error\"; error: unknown }\n | { kind: \"done\" };\n\nexport const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Normalize both supported Matrx wire shapes:\n *\n * - full: `{ \"event\": \"chunk\", \"data\": { \"text\": \"...\" } }`\n * - compact chunk: `{ \"e\": \"c\", \"t\": \"...\" }`\n * - compact reasoning: `{ \"e\": \"r\", \"t\": \"...\" }`\n */\nexport function normalizeMatrxStreamEnvelope(\n value: unknown,\n): MatrxStreamEnvelope | null {\n if (!isRecord(value)) return null;\n\n if (typeof value.event === \"string\") {\n const streamSeq =\n typeof value.stream_seq === \"number\" && Number.isFinite(value.stream_seq)\n ? value.stream_seq\n : undefined;\n return {\n event: value.event,\n data: value.data,\n ...(streamSeq === undefined ? {} : { stream_seq: streamSeq }),\n };\n }\n if (value.e === \"c\" && typeof value.t === \"string\") {\n return { event: \"chunk\", data: { text: value.t } };\n }\n if (value.e === \"r\" && typeof value.t === \"string\") {\n return { event: \"reasoning_chunk\", data: { text: value.t } };\n }\n return null;\n}\n\n/**\n * Create the transport-independent NDJSON framer used by the stream reader.\n * Browser extensions, desktop bridges, WebSockets, and tests can feed it\n * fragmented strings or bytes without constructing a `ReadableStream`.\n */\nexport function createMatrxNdjsonFramer(\n options: MatrxNdjsonFramerOptions = {},\n): MatrxNdjsonFramer {\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lineNumber = 0;\n let finished = false;\n\n const assertOpen = (): void => {\n if (finished) throw new Error(\"Matrx NDJSON framer is already finished\");\n };\n\n const parseLine = (\n line: string,\n atCompletion: boolean,\n ): MatrxStreamEnvelope | null => {\n const currentLineNumber = ++lineNumber;\n const trimmed = line.trim();\n if (!trimmed) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed) as unknown;\n } catch (error) {\n options.onMalformedLine?.({\n line: trimmed,\n error,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return null;\n }\n\n const envelope = normalizeMatrxStreamEnvelope(parsed);\n if (!envelope) {\n options.onUnknownEnvelope?.(parsed);\n return null;\n }\n options.onValidEnvelope?.({\n raw: parsed,\n envelope,\n line: trimmed,\n lineNumber: currentLineNumber,\n atCompletion,\n });\n return envelope;\n };\n\n const pushDecodedText = (fragment: string): MatrxStreamEnvelope[] => {\n buffer += fragment;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n const envelopes: MatrxStreamEnvelope[] = [];\n for (const line of lines) {\n const envelope = parseLine(line, false);\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n };\n\n return {\n pushText(fragment) {\n assertOpen();\n // Flush any pending byte fragment before switching to explicit text.\n return pushDecodedText(decoder.decode() + fragment);\n },\n pushBytes(fragment) {\n assertOpen();\n return pushDecodedText(decoder.decode(fragment, { stream: true }));\n },\n finish() {\n assertOpen();\n finished = true;\n const envelopes = pushDecodedText(decoder.decode());\n if (buffer.length > 0) {\n const envelope = parseLine(buffer, true);\n buffer = \"\";\n if (envelope) envelopes.push(envelope);\n }\n return envelopes;\n },\n };\n}\n\nfunction readAheadLimit(value: number | undefined): number {\n const limit = value ?? DEFAULT_MATRX_NDJSON_READ_AHEAD;\n if (!Number.isSafeInteger(limit) || limit < 1) {\n throw new RangeError(\"maxReadAhead must be a positive safe integer\");\n }\n return limit;\n}\n\n/**\n * Read and normalize a Matrx NDJSON response body with bounded background\n * read-ahead. Consumer work does not stall the network until `maxReadAhead`\n * complete events are waiting; the bound prevents an abandoned or blocked\n * consumer from growing memory without limit.\n */\nexport async function* readMatrxNdjsonStream(\n body: ReadableStream<Uint8Array>,\n options: ReadMatrxNdjsonOptions = {},\n): AsyncGenerator<MatrxStreamEnvelope, void, undefined> {\n const maxReadAhead = readAheadLimit(options.maxReadAhead);\n const queue: QueueItem[] = [];\n let queuedEventCount = 0;\n let wakeConsumer: (() => void) | null = null;\n let wakeProducer: (() => void) | null = null;\n let readerFinished = false;\n let consumerClosed = false;\n\n const wakeWaitingConsumer = (): void => {\n const wake = wakeConsumer;\n wakeConsumer = null;\n wake?.();\n };\n const wakeWaitingProducer = (): void => {\n const wake = wakeProducer;\n wakeProducer = null;\n wake?.();\n };\n const enqueueTerminal = (item: QueueItem): void => {\n if (consumerClosed) return;\n queue.push(item);\n wakeWaitingConsumer();\n };\n const enqueueEvent = async (value: MatrxStreamEnvelope): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n if (consumerClosed || options.signal?.aborted) return false;\n queue.push({ kind: \"event\", value });\n queuedEventCount += 1;\n wakeWaitingConsumer();\n return true;\n };\n const waitForReadCapacity = async (): Promise<boolean> => {\n while (\n queuedEventCount >= maxReadAhead &&\n !consumerClosed &&\n !options.signal?.aborted\n ) {\n await new Promise<void>((resolve) => {\n wakeProducer = resolve;\n });\n }\n return !consumerClosed && !options.signal?.aborted;\n };\n\n const reader = body.getReader();\n const framer = createMatrxNdjsonFramer(options);\n\n const onAbort = (): void => {\n wakeWaitingProducer();\n wakeWaitingConsumer();\n void reader.cancel(options.signal?.reason).catch(() => undefined);\n };\n options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n if (options.signal?.aborted) onAbort();\n\n const readerPromise = (async (): Promise<void> => {\n try {\n while (!options.signal?.aborted && !consumerClosed) {\n if (!(await waitForReadCapacity())) return;\n const { value, done } = await reader.read();\n if (done) break;\n for (const envelope of framer.pushBytes(value)) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n\n if (!options.signal?.aborted && !consumerClosed) {\n for (const envelope of framer.finish()) {\n if (!(await enqueueEvent(envelope))) return;\n }\n }\n } catch (error) {\n const aborted =\n options.signal?.aborted ||\n consumerClosed ||\n (error instanceof Error && error.name === \"AbortError\");\n if (!aborted) enqueueTerminal({ kind: \"error\", error });\n } finally {\n readerFinished = true;\n reader.releaseLock();\n enqueueTerminal({ kind: \"done\" });\n }\n })();\n\n try {\n while (true) {\n if (queue.length === 0) {\n if (options.signal?.aborted || readerFinished) return;\n await new Promise<void>((resolve) => {\n wakeConsumer = resolve;\n });\n }\n\n const item = queue.shift();\n if (item?.kind === \"event\") queuedEventCount -= 1;\n wakeWaitingProducer();\n if (!item || item.kind === \"done\") return;\n if (item.kind === \"error\") throw item.error;\n yield item.value;\n }\n } finally {\n consumerClosed = true;\n wakeWaitingProducer();\n wakeWaitingConsumer();\n options.signal?.removeEventListener(\"abort\", onAbort);\n if (!readerFinished) {\n await reader.cancel().catch(() => undefined);\n }\n await readerPromise;\n }\n}\n"]}
|
package/dist/stream/ndjson.d.cts
CHANGED
|
@@ -10,18 +10,47 @@
|
|
|
10
10
|
interface MatrxStreamEnvelope<TData = unknown> {
|
|
11
11
|
event: string;
|
|
12
12
|
data: TData;
|
|
13
|
+
/** Monotonic transport sequence from full envelopes, when supplied. */
|
|
14
|
+
stream_seq?: number;
|
|
13
15
|
}
|
|
14
16
|
interface MatrxNdjsonIssue {
|
|
15
17
|
line: string;
|
|
16
18
|
error: unknown;
|
|
19
|
+
/** One-based physical NDJSON line number. */
|
|
20
|
+
lineNumber: number;
|
|
21
|
+
/** True when an unterminated trailing fragment was parsed by `finish()`. */
|
|
22
|
+
atCompletion: boolean;
|
|
17
23
|
}
|
|
18
|
-
interface
|
|
19
|
-
|
|
24
|
+
interface MatrxStreamEnvelopeObservation {
|
|
25
|
+
/** Exact parsed JSON value before compact/full normalization. */
|
|
26
|
+
raw: unknown;
|
|
27
|
+
envelope: MatrxStreamEnvelope;
|
|
28
|
+
line: string;
|
|
29
|
+
lineNumber: number;
|
|
30
|
+
atCompletion: boolean;
|
|
31
|
+
}
|
|
32
|
+
interface MatrxNdjsonFramerOptions {
|
|
20
33
|
/** Malformed JSON is non-fatal, but it must never disappear silently. */
|
|
21
34
|
onMalformedLine?: (issue: MatrxNdjsonIssue) => void;
|
|
22
35
|
/** Valid JSON with no recognized Matrx event envelope is also non-fatal. */
|
|
23
36
|
onUnknownEnvelope?: (value: unknown) => void;
|
|
37
|
+
/** Observe every valid envelope without changing or consuming it. */
|
|
38
|
+
onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;
|
|
39
|
+
}
|
|
40
|
+
interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
/** Maximum normalized events read ahead of the iterator consumer. */
|
|
43
|
+
maxReadAhead?: number;
|
|
44
|
+
}
|
|
45
|
+
interface MatrxNdjsonFramer {
|
|
46
|
+
/** Push a text fragment. Fragments may split JSON tokens or line endings. */
|
|
47
|
+
pushText(fragment: string): MatrxStreamEnvelope[];
|
|
48
|
+
/** Push a byte fragment. UTF-8 code points may span calls. */
|
|
49
|
+
pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];
|
|
50
|
+
/** Parse the final unterminated line and flush any pending UTF-8 bytes. */
|
|
51
|
+
finish(): MatrxStreamEnvelope[];
|
|
24
52
|
}
|
|
53
|
+
declare const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;
|
|
25
54
|
/**
|
|
26
55
|
* Normalize both supported Matrx wire shapes:
|
|
27
56
|
*
|
|
@@ -31,11 +60,17 @@ interface ReadMatrxNdjsonOptions {
|
|
|
31
60
|
*/
|
|
32
61
|
declare function normalizeMatrxStreamEnvelope(value: unknown): MatrxStreamEnvelope | null;
|
|
33
62
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
63
|
+
* Create the transport-independent NDJSON framer used by the stream reader.
|
|
64
|
+
* Browser extensions, desktop bridges, WebSockets, and tests can feed it
|
|
65
|
+
* fragmented strings or bytes without constructing a `ReadableStream`.
|
|
66
|
+
*/
|
|
67
|
+
declare function createMatrxNdjsonFramer(options?: MatrxNdjsonFramerOptions): MatrxNdjsonFramer;
|
|
68
|
+
/**
|
|
69
|
+
* Read and normalize a Matrx NDJSON response body with bounded background
|
|
70
|
+
* read-ahead. Consumer work does not stall the network until `maxReadAhead`
|
|
71
|
+
* complete events are waiting; the bound prevents an abandoned or blocked
|
|
72
|
+
* consumer from growing memory without limit.
|
|
38
73
|
*/
|
|
39
74
|
declare function readMatrxNdjsonStream(body: ReadableStream<Uint8Array>, options?: ReadMatrxNdjsonOptions): AsyncGenerator<MatrxStreamEnvelope, void, undefined>;
|
|
40
75
|
|
|
41
|
-
export { type MatrxNdjsonIssue, type MatrxStreamEnvelope, type ReadMatrxNdjsonOptions, normalizeMatrxStreamEnvelope, readMatrxNdjsonStream };
|
|
76
|
+
export { DEFAULT_MATRX_NDJSON_READ_AHEAD, type MatrxNdjsonFramer, type MatrxNdjsonFramerOptions, type MatrxNdjsonIssue, type MatrxStreamEnvelope, type MatrxStreamEnvelopeObservation, type ReadMatrxNdjsonOptions, createMatrxNdjsonFramer, normalizeMatrxStreamEnvelope, readMatrxNdjsonStream };
|
package/dist/stream/ndjson.d.ts
CHANGED
|
@@ -10,18 +10,47 @@
|
|
|
10
10
|
interface MatrxStreamEnvelope<TData = unknown> {
|
|
11
11
|
event: string;
|
|
12
12
|
data: TData;
|
|
13
|
+
/** Monotonic transport sequence from full envelopes, when supplied. */
|
|
14
|
+
stream_seq?: number;
|
|
13
15
|
}
|
|
14
16
|
interface MatrxNdjsonIssue {
|
|
15
17
|
line: string;
|
|
16
18
|
error: unknown;
|
|
19
|
+
/** One-based physical NDJSON line number. */
|
|
20
|
+
lineNumber: number;
|
|
21
|
+
/** True when an unterminated trailing fragment was parsed by `finish()`. */
|
|
22
|
+
atCompletion: boolean;
|
|
17
23
|
}
|
|
18
|
-
interface
|
|
19
|
-
|
|
24
|
+
interface MatrxStreamEnvelopeObservation {
|
|
25
|
+
/** Exact parsed JSON value before compact/full normalization. */
|
|
26
|
+
raw: unknown;
|
|
27
|
+
envelope: MatrxStreamEnvelope;
|
|
28
|
+
line: string;
|
|
29
|
+
lineNumber: number;
|
|
30
|
+
atCompletion: boolean;
|
|
31
|
+
}
|
|
32
|
+
interface MatrxNdjsonFramerOptions {
|
|
20
33
|
/** Malformed JSON is non-fatal, but it must never disappear silently. */
|
|
21
34
|
onMalformedLine?: (issue: MatrxNdjsonIssue) => void;
|
|
22
35
|
/** Valid JSON with no recognized Matrx event envelope is also non-fatal. */
|
|
23
36
|
onUnknownEnvelope?: (value: unknown) => void;
|
|
37
|
+
/** Observe every valid envelope without changing or consuming it. */
|
|
38
|
+
onValidEnvelope?: (observation: MatrxStreamEnvelopeObservation) => void;
|
|
39
|
+
}
|
|
40
|
+
interface ReadMatrxNdjsonOptions extends MatrxNdjsonFramerOptions {
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
/** Maximum normalized events read ahead of the iterator consumer. */
|
|
43
|
+
maxReadAhead?: number;
|
|
44
|
+
}
|
|
45
|
+
interface MatrxNdjsonFramer {
|
|
46
|
+
/** Push a text fragment. Fragments may split JSON tokens or line endings. */
|
|
47
|
+
pushText(fragment: string): MatrxStreamEnvelope[];
|
|
48
|
+
/** Push a byte fragment. UTF-8 code points may span calls. */
|
|
49
|
+
pushBytes(fragment: Uint8Array): MatrxStreamEnvelope[];
|
|
50
|
+
/** Parse the final unterminated line and flush any pending UTF-8 bytes. */
|
|
51
|
+
finish(): MatrxStreamEnvelope[];
|
|
24
52
|
}
|
|
53
|
+
declare const DEFAULT_MATRX_NDJSON_READ_AHEAD = 64;
|
|
25
54
|
/**
|
|
26
55
|
* Normalize both supported Matrx wire shapes:
|
|
27
56
|
*
|
|
@@ -31,11 +60,17 @@ interface ReadMatrxNdjsonOptions {
|
|
|
31
60
|
*/
|
|
32
61
|
declare function normalizeMatrxStreamEnvelope(value: unknown): MatrxStreamEnvelope | null;
|
|
33
62
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
63
|
+
* Create the transport-independent NDJSON framer used by the stream reader.
|
|
64
|
+
* Browser extensions, desktop bridges, WebSockets, and tests can feed it
|
|
65
|
+
* fragmented strings or bytes without constructing a `ReadableStream`.
|
|
66
|
+
*/
|
|
67
|
+
declare function createMatrxNdjsonFramer(options?: MatrxNdjsonFramerOptions): MatrxNdjsonFramer;
|
|
68
|
+
/**
|
|
69
|
+
* Read and normalize a Matrx NDJSON response body with bounded background
|
|
70
|
+
* read-ahead. Consumer work does not stall the network until `maxReadAhead`
|
|
71
|
+
* complete events are waiting; the bound prevents an abandoned or blocked
|
|
72
|
+
* consumer from growing memory without limit.
|
|
38
73
|
*/
|
|
39
74
|
declare function readMatrxNdjsonStream(body: ReadableStream<Uint8Array>, options?: ReadMatrxNdjsonOptions): AsyncGenerator<MatrxStreamEnvelope, void, undefined>;
|
|
40
75
|
|
|
41
|
-
export { type MatrxNdjsonIssue, type MatrxStreamEnvelope, type ReadMatrxNdjsonOptions, normalizeMatrxStreamEnvelope, readMatrxNdjsonStream };
|
|
76
|
+
export { DEFAULT_MATRX_NDJSON_READ_AHEAD, type MatrxNdjsonFramer, type MatrxNdjsonFramerOptions, type MatrxNdjsonIssue, type MatrxStreamEnvelope, type MatrxStreamEnvelopeObservation, type ReadMatrxNdjsonOptions, createMatrxNdjsonFramer, normalizeMatrxStreamEnvelope, readMatrxNdjsonStream };
|