@rindle/api-server 0.7.11 → 0.8.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/README.md +54 -0
- package/dist/index.d.ts +71 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -2
- package/dist/index.js.map +1 -1
- package/dist/streams.d.ts +373 -0
- package/dist/streams.d.ts.map +1 -0
- package/dist/streams.js +1047 -0
- package/dist/streams.js.map +1 -0
- package/package.json +8 -6
- package/src/index.ts +248 -11
- package/src/streams.ts +1341 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import type { StreamFrame, StreamStatus } from "@rindle/client";
|
|
2
|
+
import type { Authorizer, ServerSql, SqlDialect } from "./index.ts";
|
|
3
|
+
export { STREAM_STATUS_STREAMING, assembleDurableText, frameResumePoint, spliceStreamText, } from "@rindle/client";
|
|
4
|
+
export type { StreamFrame, StreamStatus } from "@rindle/client";
|
|
5
|
+
/** What a checkpoint hands the durable plane when the app supplies its own {@link StreamCommit}. */
|
|
6
|
+
export type StreamCommitInput =
|
|
7
|
+
/** The pointer is marked live. The app's own mutator created the row (it owns `chatId`, `role`,
|
|
8
|
+
* the model name…); this only flips it to `streaming`. */
|
|
9
|
+
{
|
|
10
|
+
kind: "open";
|
|
11
|
+
streamId: string;
|
|
12
|
+
meta: unknown;
|
|
13
|
+
hostId?: string;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
}
|
|
16
|
+
/** A prefix advance carrying ONLY its own slice. `text.length === seq - from`, and `from` is the
|
|
17
|
+
* last append the PLANE saw confirmed — so appends are contiguous in the fault-free run, but an
|
|
18
|
+
* append that committed while its ack was lost makes the next one RE-COVER (its `from` lags what
|
|
19
|
+
* the app already applied). See {@link StreamCommit} for the two ways to stay idempotent. */
|
|
20
|
+
| {
|
|
21
|
+
kind: "append";
|
|
22
|
+
streamId: string;
|
|
23
|
+
from: number;
|
|
24
|
+
seq: number;
|
|
25
|
+
text: string;
|
|
26
|
+
}
|
|
27
|
+
/** The seal. `body` is the producer's retained text and `bodyFrom` its absolute start offset:
|
|
28
|
+
* `bodyFrom === 0` — and `body` is the WHOLE response — unless the app opted into trimming via
|
|
29
|
+
* `retainChars` (`commit` mode only). A compacting app requires `bodyFrom === 0` and writes
|
|
30
|
+
* `body` wholesale; a non-compacting app appends the outstanding tail —
|
|
31
|
+
* `body.slice(from - bodyFrom)` — as a final chunk. `seq === bodyFrom + body.length`, always. */
|
|
32
|
+
| {
|
|
33
|
+
kind: "close";
|
|
34
|
+
streamId: string;
|
|
35
|
+
from: number;
|
|
36
|
+
seq: number;
|
|
37
|
+
body: string;
|
|
38
|
+
bodyFrom: number;
|
|
39
|
+
status: StreamStatus;
|
|
40
|
+
error?: string;
|
|
41
|
+
};
|
|
42
|
+
/** The escape hatch: persist the checkpoint however the app likes. Retried on throw (§3.3), so it
|
|
43
|
+
* must be idempotent under a repeated `(from, seq)` — AND under a RE-COVER: an append whose write
|
|
44
|
+
* committed but whose ack was lost leaves the plane believing less than the store holds, so the
|
|
45
|
+
* next append's `(from, seq)` can OVERLAP text already applied. Apply only the unseen suffix
|
|
46
|
+
* (`text.slice(applied - from)` when `applied > from`), or better, return `{seq: applied}` — the
|
|
47
|
+
* authoritative applied length — and the plane resynchronizes instead of re-covering at all.
|
|
48
|
+
* Return `{cancelRequested: true}` to tell the producer the reader asked it to stop (§6). */
|
|
49
|
+
export type StreamCommit = (input: StreamCommitInput) => Promise<void | {
|
|
50
|
+
cancelRequested?: boolean;
|
|
51
|
+
seq?: number;
|
|
52
|
+
}>;
|
|
53
|
+
/** Which columns of the app's own tables the plane reads and writes. Every entry has a default
|
|
54
|
+
* except `cancel`, `error`, and `host`, which are opt-in BY NAMING: the plane never emits SQL
|
|
55
|
+
* against a column the app did not ask it to use. */
|
|
56
|
+
export interface StreamColumns {
|
|
57
|
+
/** The message row's primary key, matched against `streamId`. Default `id`. */
|
|
58
|
+
key?: string;
|
|
59
|
+
/** The compacted response text. Default `body`. */
|
|
60
|
+
body?: string;
|
|
61
|
+
/** {@link STREAM_STATUS_STREAMING} then a {@link StreamStatus}. Default `status`. */
|
|
62
|
+
status?: string;
|
|
63
|
+
/** Total durable length — `length(body) + Σ chunk lengths`. The CAS column (§3.2). Default `seq`. */
|
|
64
|
+
seq?: string;
|
|
65
|
+
/** Opt-in (§6): a truthy value here stops the generation at the next checkpoint. No default —
|
|
66
|
+
* naming it is what turns cancellation on. */
|
|
67
|
+
cancel?: string;
|
|
68
|
+
/** Opt-in: where a failed generation's message is recorded. No default. */
|
|
69
|
+
error?: string;
|
|
70
|
+
/** Opt-in: where the open write records this producer's identity ({@link RindleStreamOptions.hostId}).
|
|
71
|
+
* Naming it upgrades the row-level single-flight guard from check-then-act to a true
|
|
72
|
+
* compare-and-swap — the open write turns conditional and a read-back names the winner (§5.1) —
|
|
73
|
+
* and gives multi-instance subscribe routing a column to read (§4). No default. */
|
|
74
|
+
host?: string;
|
|
75
|
+
/** Chunk primary key; the plane writes the deterministic `"<streamId>:<seq>"`. Default `id`. */
|
|
76
|
+
chunkKey?: string;
|
|
77
|
+
/** Chunk → message reference. Default `streamId`. */
|
|
78
|
+
chunkStream?: string;
|
|
79
|
+
/** The chunk's END offset — the ordering key. Default `seq`. */
|
|
80
|
+
chunkSeq?: string;
|
|
81
|
+
/** The chunk's slice of the response. Default `text`. */
|
|
82
|
+
chunkText?: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The app's tables (§5). The app authors and migrates BOTH — the message row is unambiguously
|
|
86
|
+
* app-owned (it has `chatId`, `role`, token counts) and the chunk row must be reachable from the
|
|
87
|
+
* app's own query as a `related` subquery, which a Rindle system table would make awkward. The plane
|
|
88
|
+
* only needs to be told where things live. Use {@link streamChunkTableDdl} for the chunk table's
|
|
89
|
+
* migration.
|
|
90
|
+
*
|
|
91
|
+
* The message table carries WHATEVER ELSE the app wants; the plane's hard requirements are only:
|
|
92
|
+
*
|
|
93
|
+
* | mapped column | requirement | why |
|
|
94
|
+
* | --- | --- | --- |
|
|
95
|
+
* | `key` | UNIQUE (normally the pk) | every checkpoint targets one row by it |
|
|
96
|
+
* | `seq` | integer, **NOT NULL DEFAULT 0** | the CAS column — nothing matches NULL (§3.2) |
|
|
97
|
+
* | `body` | text, empty at open | compaction overwrites it with the whole response |
|
|
98
|
+
* | `status` | text accepting `streaming`/`complete`/`cancelled`/`error`/`interrupted` | a CHECK
|
|
99
|
+
* constraint that omits one of these turns a seal into an infra failure |
|
|
100
|
+
* | `cancel` | truthy-readable, if mapped | read on the checkpoint round-trip (§6) |
|
|
101
|
+
* | `error` | nullable text, if mapped | compaction writes `NULL` when there is no error |
|
|
102
|
+
* | `host` | text, if mapped | written at open with the producer's token; the read-back decides the open race (§5.1) |
|
|
103
|
+
*
|
|
104
|
+
* `body` is PLANE-OWNED and always a bare string. Rich content (an array of content blocks, tool
|
|
105
|
+
* calls, attachments) belongs in SIBLING columns the app's own mutators write — `flush()` orders the
|
|
106
|
+
* text before them. For genuinely multi-block streaming, point `message` at a per-BLOCK table
|
|
107
|
+
* instead: `streamId` is just an app key, so one stream per block needs nothing from this plane.
|
|
108
|
+
*/
|
|
109
|
+
export interface StreamTables {
|
|
110
|
+
/** The app's message table. Must already contain the row when {@link StreamPlane.open} runs. */
|
|
111
|
+
message: string;
|
|
112
|
+
/** The append-only chunk table. */
|
|
113
|
+
chunks: string;
|
|
114
|
+
columns?: StreamColumns;
|
|
115
|
+
}
|
|
116
|
+
export type StreamCheckpointTarget = {
|
|
117
|
+
tables: StreamTables;
|
|
118
|
+
} | {
|
|
119
|
+
commit: StreamCommit;
|
|
120
|
+
};
|
|
121
|
+
/** When a checkpoint fires — the FIRST of these wins (§3.1). Checkpoints are serialized, so a slow
|
|
122
|
+
* store degrades to fewer, larger checkpoints, never to a queue of them. */
|
|
123
|
+
export interface StreamCheckpointPolicy {
|
|
124
|
+
/** Produced-but-uncommitted characters that force a checkpoint. Default 512. */
|
|
125
|
+
chars?: number;
|
|
126
|
+
/** Milliseconds since the last checkpoint that force one. Default 750. */
|
|
127
|
+
intervalMs?: number;
|
|
128
|
+
/** Retries for a failing commit before the slice is left for the next trigger. Default 3. */
|
|
129
|
+
retries?: number;
|
|
130
|
+
}
|
|
131
|
+
export interface AuthorizeStreamInput<User> {
|
|
132
|
+
user: User;
|
|
133
|
+
streamId: string;
|
|
134
|
+
/** Where the subscriber claims to be. */
|
|
135
|
+
from: number;
|
|
136
|
+
/** The `meta` this stream was opened with — `undefined` when the stream is not hosted here, which
|
|
137
|
+
* is precisely when the app must decide from `streamId` and its own durable state. */
|
|
138
|
+
meta: unknown;
|
|
139
|
+
request?: unknown;
|
|
140
|
+
}
|
|
141
|
+
export interface RindleStreamOptions<User> {
|
|
142
|
+
/** Where checkpoints land: the app's tables (the default path) or a raw `commit` callback. */
|
|
143
|
+
checkpoint: StreamCheckpointTarget;
|
|
144
|
+
/** REQUIRED. Subscribing to a stream is reading someone's chat, so there is no default-allow.
|
|
145
|
+
* Runs BEFORE existence is checked, so a denial cannot be used to probe for stream ids. */
|
|
146
|
+
authorize: Authorizer<AuthorizeStreamInput<User>>;
|
|
147
|
+
policy?: StreamCheckpointPolicy;
|
|
148
|
+
/** This process's identity — it must be UNIQUE per producer process, because the open CAS trusts
|
|
149
|
+
* it to distinguish rivals (§5.1). In `tables` mode, map {@link StreamColumns.host} and the open
|
|
150
|
+
* write persists it on the message row (so the app can route later subscribers to the hosting
|
|
151
|
+
* instance, §4) and uses it as the single-flight token; setting it WITHOUT a mapped `host` column
|
|
152
|
+
* is refused at construction. In `commit` mode it rides the `open` input. When a `host` column is
|
|
153
|
+
* mapped and no hostId is given, a random per-plane token is used — the CAS still holds, routing
|
|
154
|
+
* just has no stable name to read. */
|
|
155
|
+
hostId?: string;
|
|
156
|
+
/** Slack retained BELOW `durableSeq` so a client whose IVM view lags a checkpoint can still join
|
|
157
|
+
* without a `stale` round trip. Text at or above `durableSeq` is never trimmed. Default 64 KiB.
|
|
158
|
+
* `commit`-mode only — REFUSED (a construction-time `TypeError`) in `tables` mode, where
|
|
159
|
+
* compaction needs the whole produced text at close (§3.4), so the buffer is retained in full. */
|
|
160
|
+
retainChars?: number;
|
|
161
|
+
/** How long a sealed stream stays joinable before eviction. Default 30s. */
|
|
162
|
+
lingerMs?: number;
|
|
163
|
+
/** Per-subscriber frame queue cap; overflow drops that subscriber with `stale` (§4). Default 1024. */
|
|
164
|
+
maxQueuedFrames?: number;
|
|
165
|
+
/** A checkpoint that exhausted its retries. The stream keeps streaming — this is a durability
|
|
166
|
+
* stall, not a stream stall — so the error must not vanish. Absent ⇒ `console.error`. */
|
|
167
|
+
onCheckpointError?: (err: unknown, info: {
|
|
168
|
+
streamId: string;
|
|
169
|
+
from: number;
|
|
170
|
+
seq: number;
|
|
171
|
+
}) => void;
|
|
172
|
+
}
|
|
173
|
+
export interface OpenStreamInput<User> {
|
|
174
|
+
user: User;
|
|
175
|
+
/** The app's message-row id: the durable pointer AND the live plane's key. The row must already
|
|
176
|
+
* exist (the app's own mutator wrote it, alongside the user's prompt) with `seq = 0`. */
|
|
177
|
+
streamId: string;
|
|
178
|
+
/** Opaque app payload, passed through to a `commit` callback. Unused in `tables` mode. */
|
|
179
|
+
meta?: unknown;
|
|
180
|
+
request?: unknown;
|
|
181
|
+
}
|
|
182
|
+
/** The producer's handle (§2). One writer per stream, by construction. */
|
|
183
|
+
export interface StreamHandle {
|
|
184
|
+
readonly streamId: string;
|
|
185
|
+
/** Total produced code units. */
|
|
186
|
+
readonly seq: number;
|
|
187
|
+
/** Total code units the store has committed. Never exceeds {@link seq} (contract P). */
|
|
188
|
+
readonly durableSeq: number;
|
|
189
|
+
/** True once a checkpoint round-trip has seen the reader's cancel flag (§6). `pump` stops on it;
|
|
190
|
+
* a hand-rolled generation loop should check it. */
|
|
191
|
+
readonly cancelled: boolean;
|
|
192
|
+
/** Append a delta: fanned to subscribers synchronously, checkpointed on policy. */
|
|
193
|
+
push(text: string): void;
|
|
194
|
+
/** Force a checkpoint and resolve once the store holds every character produced so far. This is
|
|
195
|
+
* the ORDERING primitive: `await flush()` before writing a discrete row (a tool call, a stop
|
|
196
|
+
* reason) so the text precedes it in the store (§1). Rejects if the checkpoint cannot commit. */
|
|
197
|
+
flush(): Promise<number>;
|
|
198
|
+
/** Drain a delta iterable into the stream (the shape every LLM SDK's text stream already has),
|
|
199
|
+
* stopping early — and closing the iterator, which aborts the underlying request — once the
|
|
200
|
+
* reader has cancelled. */
|
|
201
|
+
pump(deltas: AsyncIterable<string>): Promise<void>;
|
|
202
|
+
/** Seal the stream: `cancelled` if the reader asked it to stop, else `complete`. In `tables` mode
|
|
203
|
+
* this is the compaction (§3.4) — it writes the whole body and drops the chunk rows in one
|
|
204
|
+
* transaction, so it also REPAIRS any checkpoint that failed along the way. */
|
|
205
|
+
close(): Promise<void>;
|
|
206
|
+
/** Seal `error` at whatever was produced. Never throws for the reason it is sealing. */
|
|
207
|
+
fail(error: unknown): Promise<void>;
|
|
208
|
+
}
|
|
209
|
+
export interface SubscribeStreamInput<User> {
|
|
210
|
+
user: User;
|
|
211
|
+
streamId: string;
|
|
212
|
+
/** "I already have this many characters" — from the client's IVM view, or a `Last-Event-ID`.
|
|
213
|
+
* A non-negative integer; default 0. */
|
|
214
|
+
from?: number;
|
|
215
|
+
request?: unknown;
|
|
216
|
+
}
|
|
217
|
+
export interface StreamSubscription {
|
|
218
|
+
readonly streamId: string;
|
|
219
|
+
/** Terminates after exactly one of `end` / `stale` / `absent`. */
|
|
220
|
+
readonly frames: AsyncIterable<StreamFrame>;
|
|
221
|
+
/** Detach early (a disconnected client). Idempotent. */
|
|
222
|
+
close(): void;
|
|
223
|
+
}
|
|
224
|
+
/** Every mapped column, defaults applied. `cancel`/`error` stay `undefined` unless named. */
|
|
225
|
+
export interface ResolvedStreamColumns {
|
|
226
|
+
key: string;
|
|
227
|
+
body: string;
|
|
228
|
+
status: string;
|
|
229
|
+
seq: string;
|
|
230
|
+
cancel?: string;
|
|
231
|
+
error?: string;
|
|
232
|
+
host?: string;
|
|
233
|
+
chunkKey: string;
|
|
234
|
+
chunkStream: string;
|
|
235
|
+
chunkSeq: string;
|
|
236
|
+
chunkText: string;
|
|
237
|
+
}
|
|
238
|
+
export declare function resolveStreamColumns(columns: StreamColumns | undefined): ResolvedStreamColumns;
|
|
239
|
+
/** The chunk row's deterministic id: a replayed checkpoint collides with itself and is absorbed by
|
|
240
|
+
* `ON CONFLICT DO NOTHING` — idempotency without an envelope or a dedup ledger (§3.3). */
|
|
241
|
+
export declare function streamChunkId(streamId: string, seq: number): string;
|
|
242
|
+
/**
|
|
243
|
+
* The chunk table's DDL, for the app's migration. The app owns the message table (this only states
|
|
244
|
+
* the three columns the plane needs on it); the chunk table is entirely protocol-shaped, so it is
|
|
245
|
+
* generated rather than hand-written.
|
|
246
|
+
*/
|
|
247
|
+
export declare function streamChunkTableDdl(tables: StreamTables, dialect: SqlDialect): string[];
|
|
248
|
+
/** What the plane needs from the api-server to write a checkpoint: the backend's OUTSIDE-transaction
|
|
249
|
+
* SQL surface (`batch` is one transaction on every backend) and its dialect. Deliberately narrow so
|
|
250
|
+
* `streams.ts` never imports the server (no cycle). */
|
|
251
|
+
export interface StreamSqlSink {
|
|
252
|
+
readonly dialect: SqlDialect;
|
|
253
|
+
readonly sql: ServerSql;
|
|
254
|
+
}
|
|
255
|
+
export declare class StreamPlane<User> {
|
|
256
|
+
readonly chars: number;
|
|
257
|
+
readonly intervalMs: number;
|
|
258
|
+
readonly retries: number;
|
|
259
|
+
readonly retainChars: number;
|
|
260
|
+
readonly lingerMs: number;
|
|
261
|
+
readonly maxQueuedFrames: number;
|
|
262
|
+
private readonly live;
|
|
263
|
+
private readonly opts;
|
|
264
|
+
private readonly sink;
|
|
265
|
+
private readonly mapped;
|
|
266
|
+
private readonly tables;
|
|
267
|
+
/** The open CAS token (§5.1): `hostId`, or a random per-plane stand-in when only the CAS — not
|
|
268
|
+
* routing — needs it. */
|
|
269
|
+
private readonly openToken;
|
|
270
|
+
constructor(opts: RindleStreamOptions<User>, sink?: StreamSqlSink);
|
|
271
|
+
/** Open a stream on an EXISTING message row (§5): the app's own mutator wrote it, alongside the
|
|
272
|
+
* user's prompt, so the pointer is already durable and every client's query already shows the
|
|
273
|
+
* message. This verifies it and flips it to `streaming`. */
|
|
274
|
+
open(input: OpenStreamInput<User>): Promise<StreamHandle>;
|
|
275
|
+
/**
|
|
276
|
+
* The read-only precondition on the app's message row, checked ONCE per `open` (§5.1). Read-only on
|
|
277
|
+
* purpose: it is a decision about the app's data, so re-deciding it per write attempt would let a
|
|
278
|
+
* lost ack turn a success into a refusal.
|
|
279
|
+
*
|
|
280
|
+
* The `streaming` check is the **single-flight guard**, and it lives at the ROW rather than in this
|
|
281
|
+
* process's map because the thing it defends against is distributed: the kick that starts a
|
|
282
|
+
* generation is an at-least-once effect (a retried mutation envelope re-runs its post-commit code,
|
|
283
|
+
* §10.5), so a second kick can land on another instance, where the in-memory map is empty. Two
|
|
284
|
+
* producers on one `streamId` would interleave: both CAS the same length, one stalls, and whichever
|
|
285
|
+
* closes last overwrites the body with ITS buffer. Cheap to refuse; expensive to debug.
|
|
286
|
+
*
|
|
287
|
+
* This probe alone is check-then-act — it and the open write are separate round trips, so two
|
|
288
|
+
* SIMULTANEOUS kicks can both pass it. Mapping a `host` column closes that window: the open write
|
|
289
|
+
* turns conditional and {@link verifyOpenWinner}'s read-back names the winner.
|
|
290
|
+
*/
|
|
291
|
+
private assertOpenable;
|
|
292
|
+
subscribe(input: SubscribeStreamInput<User>): Promise<StreamSubscription>;
|
|
293
|
+
/** Seal every live stream `interrupted`. In mapped-table mode the seal IS the compaction, so a
|
|
294
|
+
* graceful drain loses nothing PRODUCED; the status still says the response was cut short rather
|
|
295
|
+
* than claiming completion (§5). Wire it to SIGTERM. */
|
|
296
|
+
drainStreams(): Promise<void>;
|
|
297
|
+
/** Teardown: drop readers and timers WITHOUT a durable write (that is `drainStreams`). */
|
|
298
|
+
closeSync(): void;
|
|
299
|
+
/** A sealed stream stays joinable for the linger window, so a subscribe that races the last token
|
|
300
|
+
* still gets `end` (and the tail it missed) rather than a bare `absent`. */
|
|
301
|
+
retire(streamId: string): void;
|
|
302
|
+
reportCheckpointError(err: unknown, info: {
|
|
303
|
+
streamId: string;
|
|
304
|
+
from: number;
|
|
305
|
+
seq: number;
|
|
306
|
+
}): void;
|
|
307
|
+
/** Drive ONE checkpoint, retrying on failure. Every statement it emits is idempotent under replay
|
|
308
|
+
* — the chunk insert dedups on its deterministic id, the length CAS refuses to apply twice, the
|
|
309
|
+
* compaction is a whole-row overwrite — so "retry until it sticks" needs no dedup ledger and no
|
|
310
|
+
* `lmid` (§3.3). */
|
|
311
|
+
commit(input: StreamCommitInput): Promise<{
|
|
312
|
+
cancelRequested?: boolean;
|
|
313
|
+
seq?: number;
|
|
314
|
+
} | void>;
|
|
315
|
+
private commitOnce;
|
|
316
|
+
/** The read-back half of the open CAS, run only when a `host` column is mapped. The probe in
|
|
317
|
+
* `assertOpenable` and the open write are separate round trips, so bare check-then-act leaves a
|
|
318
|
+
* window where two simultaneous kicks both pass the probe; the conditional `markStreaming`
|
|
319
|
+
* matches nothing when a rival got there first, and whichever token the row now holds names the
|
|
320
|
+
* winner — a true CAS with no row counts needed. A replayed open (lost ack) reads back its OWN
|
|
321
|
+
* token and proceeds. */
|
|
322
|
+
private verifyOpenWinner;
|
|
323
|
+
/** The post-append read-back (§3.2): the row's authoritative length — which both CONFIRMS an
|
|
324
|
+
* append (the guarded batch reports no row count) and absorbs a committed-but-ack-lost one —
|
|
325
|
+
* plus the reader's cancel flag when mapped (§6), riding the same indexed point read. This read
|
|
326
|
+
* is LOAD-BEARING: a failure here fails the append attempt (retried, then stalled), because
|
|
327
|
+
* claiming durability the store did not confirm is the one dishonesty this plane refuses. */
|
|
328
|
+
private readAppendState;
|
|
329
|
+
/** The store's word on how much is durable — for resynchronizing after a FAILED append, where a
|
|
330
|
+
* lost ack may have left the store ahead of the plane. `undefined` in `commit` mode (no readable
|
|
331
|
+
* authority) or when the read itself fails (the next attempt retries the resync too). */
|
|
332
|
+
probeDurableSeq(streamId: string): Promise<number | undefined>;
|
|
333
|
+
}
|
|
334
|
+
/** The open probe's verdict: the message row is missing or already advanced. Not retried — it is a
|
|
335
|
+
* settled statement about the app's data, and retrying re-asks a question already answered. */
|
|
336
|
+
export declare class StreamOpenRefused extends Error {
|
|
337
|
+
constructor(message: string);
|
|
338
|
+
}
|
|
339
|
+
/** Refused by {@link RindleStreamOptions.authorize}. Distinct from the api-server's own
|
|
340
|
+
* `RindleApiError` so this module stays importable without the server (no cycle); the server
|
|
341
|
+
* translates it to a 403 at the handler seam. */
|
|
342
|
+
export declare class StreamForbidden extends Error {
|
|
343
|
+
readonly streamId: string;
|
|
344
|
+
constructor(streamId: string);
|
|
345
|
+
}
|
|
346
|
+
/** Headers for the SSE response. `x-accel-buffering` is the nginx-family opt-out — without it a
|
|
347
|
+
* buffering proxy holds the tokens and hands the user a paragraph at a time. */
|
|
348
|
+
export declare const STREAM_SSE_HEADERS: Record<string, string>;
|
|
349
|
+
/** Pull a subscribe request out of a fetch-style GET: `?streamId=…&from=…`, with `Last-Event-ID`
|
|
350
|
+
* winning over an explicit `from` (a reconnecting `EventSource` knows better than its own URL —
|
|
351
|
+
* the URL is the ORIGINAL join point, the header is where it actually got to). */
|
|
352
|
+
export declare function streamRequestFromHttp(req: {
|
|
353
|
+
url: string;
|
|
354
|
+
headers: {
|
|
355
|
+
get(name: string): string | null;
|
|
356
|
+
};
|
|
357
|
+
}): {
|
|
358
|
+
streamId: string;
|
|
359
|
+
from: number;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* Encode a subscription as an SSE body. Each positional frame carries `id: <seq>`, so a browser
|
|
363
|
+
* `EventSource` that drops the connection resumes at exactly the right offset with no application
|
|
364
|
+
* code — its own `Last-Event-ID` header is the `from` of the next subscribe ({@link
|
|
365
|
+
* streamRequestFromHttp}).
|
|
366
|
+
*
|
|
367
|
+
* The reader must close the `EventSource` on the `end` frame: `EventSource` reconnects on ANY close,
|
|
368
|
+
* including a clean one.
|
|
369
|
+
*/
|
|
370
|
+
export declare function streamFramesToSse(sub: StreamSubscription, opts?: {
|
|
371
|
+
keepAliveMs?: number;
|
|
372
|
+
}): ReadableStream<Uint8Array>;
|
|
373
|
+
//# sourceMappingURL=streams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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"}
|