@reddb-io/client 1.6.0 → 1.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 CHANGED
@@ -88,6 +88,40 @@ when the callback or a `tx.query()` / `tx.insert()` call throws. Nested
88
88
  transactions on the same connection are rejected with `NESTED_TX_NOT_SUPPORTED`;
89
89
  open another `connect()` handle for independent concurrent transactions.
90
90
 
91
+ ## Streaming
92
+
93
+ `db.query()` stays a one-shot Promise — ideal for small reads. For large
94
+ result sets or continuous ingest, use the explicit streaming surface so you
95
+ never accidentally buffer a huge result or OOM. The surface is identical
96
+ whether the connection is RedWire (`red://`) or HTTP (`http://`): RedWire is
97
+ used when available, HTTP NDJSON otherwise.
98
+
99
+ ```js
100
+ // Read: a Node Readable that is also an AsyncIterable<Row>.
101
+ const users = db.collection('users')
102
+ for await (const row of users.stream('SELECT id, name FROM users')) {
103
+ console.log(row.id, row.name)
104
+ }
105
+
106
+ // Write: a Node Writable; the server's terminal envelope resolves completion().
107
+ import { splitNdjson } from '@reddb-io/client'
108
+ import { createReadStream } from 'node:fs'
109
+ import { pipeline } from 'node:stream/promises'
110
+
111
+ const sink = users.inputStream() // columns inferred from the first row's keys
112
+ await pipeline(createReadStream('rows.ndjson'), splitNdjson(), sink)
113
+ const { row_count } = await sink.completion()
114
+ ```
115
+
116
+ Backpressure flows naturally: the Readable via `read()` / `pause()` /
117
+ `resume()`, the Writable via `write()`'s return value and the `'drain'` event.
118
+ Both expose `.cancel(reason?)` — a `StreamCancel` over RedWire, an
119
+ `AbortController.abort()` over HTTP — which terminates the underlying transport
120
+ stream and rejects any pending iteration (or `.completion()`) with a
121
+ `STREAM_CANCELLED` error. A mid-stream server error surfaces as an `'error'`
122
+ event and as a rejected iteration. `db.stream(sql)` / `db.inputStream(target)`
123
+ are also available directly on the connection without a `collection()` handle.
124
+
91
125
  ## Rich Helpers
92
126
 
93
127
  The client follows the SDK Helper Spec for the shared JS/TS surface:
@@ -0,0 +1,453 @@
1
+ /**
2
+ * @reddb-io/client — TypeScript definitions for the **browser** entrypoint
3
+ * (`src/index.browser.js`). Hand-written, kept in sync with the Node stub
4
+ * `index.d.ts`.
5
+ *
6
+ * The browser surface is identical to Node's, with two deliberate differences:
7
+ *
8
+ * - It declares **no `node:stream` dependency**: the streaming row wrappers
9
+ * `RowReadable` / `RowWritable` are described by their public surface
10
+ * (`AsyncIterable<Row>`, `cancel()`, `completion()`) rather than by
11
+ * extending Node's `Readable` / `Writable`, so browser type-checking never
12
+ * pulls in `@types/node`.
13
+ * - It omits `splitNdjson()` (a `node:stream` `Transform`), which has no
14
+ * browser counterpart.
15
+ *
16
+ * `connect()` only reaches `http(s)://` from a browser; `grpc(s)://`,
17
+ * `red(s)://`, and `pg` throw `RedDBError` with code
18
+ * `'BROWSER_TRANSPORT_UNSUPPORTED'`, and embedded URIs throw
19
+ * `EmbeddedNotSupported`.
20
+ */
21
+
22
+ export type AuthOptions =
23
+ | { token: string }
24
+ | { apiKey: string }
25
+ | { username: string; password: string; loginUrl?: string }
26
+
27
+ export interface ConnectOptions {
28
+ /** Authentication credentials for the HTTP transport. */
29
+ auth?: AuthOptions
30
+ }
31
+
32
+ export interface QueryResult {
33
+ statement: string
34
+ affected: number
35
+ columns: string[]
36
+ rows: Array<Record<string, unknown>>
37
+ }
38
+
39
+ export type QueryParam =
40
+ | null
41
+ | boolean
42
+ | number
43
+ | string
44
+ | Uint8Array
45
+ | Date
46
+ | Float32Array
47
+ | Float64Array
48
+ | number[]
49
+ | Record<string, unknown>
50
+
51
+ export interface InsertResult { affected: number; rid: string | number; id: string | number }
52
+ export interface BulkInsertResult {
53
+ affected: number
54
+ rids: Array<string | number>
55
+ ids: Array<string | number>
56
+ }
57
+ export interface GetResult { entity: Record<string, unknown> | null }
58
+ export interface DeleteResult { affected: number }
59
+ export interface CollectionMeta {
60
+ name: string
61
+ model: string
62
+ capabilities: string[]
63
+ [key: string]: unknown
64
+ }
65
+ export interface HealthResult { ok: boolean; version: string }
66
+ export interface VersionResult { version: string; protocol: string }
67
+
68
+ export type Role = 'read' | 'write' | 'admin'
69
+
70
+ export interface LoginResult {
71
+ token: string
72
+ username: string
73
+ role: Role
74
+ expires_at: number
75
+ }
76
+
77
+ export interface WhoamiResult { username: string; role: Role }
78
+ export interface CreateApiKeyResult { key: string; role: Role; created_at: number }
79
+ export interface ChangePasswordResult { ok: true }
80
+ export interface RevokeApiKeyResult { ok: true }
81
+
82
+ export class RedDBError extends Error {
83
+ readonly name: 'RedDBError'
84
+ readonly code: string
85
+ readonly data: unknown
86
+ constructor(code: string, message: string, data?: unknown)
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Cache API
91
+ // ---------------------------------------------------------------------------
92
+
93
+ export interface CachePutOptions {
94
+ ttl_ms?: number
95
+ tags?: string[]
96
+ policy?: {
97
+ idle_evict_ms?: number
98
+ stale_while_revalidate_ms?: number
99
+ jitter_ms?: number
100
+ }
101
+ }
102
+
103
+ export type CacheExistsStatus = 'present' | 'absent' | 'maybe'
104
+
105
+ export class CacheClient {
106
+ get(namespace: string, key: string): Promise<Uint8Array | null>
107
+ put(
108
+ namespace: string,
109
+ key: string,
110
+ value: Uint8Array | string,
111
+ opts?: CachePutOptions,
112
+ ): Promise<void>
113
+ exists(namespace: string, key: string): Promise<CacheExistsStatus>
114
+ invalidate(namespace: string, key: string): Promise<void>
115
+ invalidatePrefix(namespace: string, prefix: string): Promise<number>
116
+ invalidateTags(namespace: string, tags: string[]): Promise<number>
117
+ flushNamespace(namespace: string): Promise<void>
118
+ }
119
+
120
+ export interface KvWatchEvent {
121
+ key: string
122
+ op: 'insert' | 'update' | 'delete'
123
+ before: unknown
124
+ after: unknown
125
+ lsn: number
126
+ committed_at: number
127
+ dropped_event_count: number
128
+ }
129
+
130
+ export class KvClient {
131
+ put(
132
+ key: string,
133
+ value: unknown,
134
+ options?: { collection?: string; expireMs?: number; tags?: string[] },
135
+ ): Promise<QueryResult>
136
+ get(key: string, options?: { collection?: string }): Promise<unknown | null>
137
+ getMany(keys: string[], options?: { collection?: string }): Promise<Array<unknown | null>>
138
+ exists(key: string, options?: { collection?: string }): Promise<{ exists: boolean }>
139
+ delete(key: string, options?: { collection?: string }): Promise<DeleteResult>
140
+ list(options?: { collection?: string; prefix?: string; limit?: number }): Promise<{
141
+ items: Array<{ key: string; value: unknown }>
142
+ }>
143
+ invalidateTags(tags: string[], options?: { collection?: string }): Promise<number>
144
+ watch(
145
+ key: string,
146
+ options?: { collection?: string; sinceLsn?: number; limit?: number },
147
+ ): AsyncIterable<KvWatchEvent>
148
+ watchPrefix(
149
+ prefix: string,
150
+ options?: { collection?: string; sinceLsn?: number; limit?: number },
151
+ ): AsyncIterable<KvWatchEvent>
152
+ }
153
+
154
+ export interface DocumentInsertResult<T extends Record<string, unknown> = Record<string, unknown>> {
155
+ affected: number
156
+ rid: string | number
157
+ item: T & { rid: string | number }
158
+ }
159
+
160
+ export class DocumentClient {
161
+ insert<T extends Record<string, unknown> = Record<string, unknown>>(
162
+ collection: string,
163
+ document: Record<string, unknown>,
164
+ ): Promise<DocumentInsertResult<T>>
165
+ get<T extends Record<string, unknown> = Record<string, unknown>>(
166
+ collection: string,
167
+ rid: string | number,
168
+ ): Promise<T & { rid: string | number }>
169
+ list<T extends Record<string, unknown> = Record<string, unknown>>(
170
+ collection: string,
171
+ options?: { filter?: string; orderBy?: string; order_by?: string; limit?: number },
172
+ ): Promise<{ items: Array<T & { rid: string | number }> }>
173
+ patch<T extends Record<string, unknown> = Record<string, unknown>>(
174
+ collection: string,
175
+ rid: string | number,
176
+ patch: Record<string, unknown>,
177
+ ): Promise<T & { rid: string | number }>
178
+ delete(collection: string, rid: string | number): Promise<DeleteResult>
179
+ }
180
+
181
+ export class QueueClient {
182
+ push(
183
+ queue: string,
184
+ value: unknown,
185
+ options?: { priority?: number },
186
+ ): Promise<QueryResult>
187
+ pop(queue: string, count?: number): Promise<unknown[]>
188
+ peek(queue: string, count?: number): Promise<unknown[]>
189
+ len(queue: string): Promise<number>
190
+ purge(queue: string): Promise<QueryResult>
191
+ readWait(
192
+ queue: string,
193
+ consumer: string,
194
+ options: { waitMs: number; group?: string; count?: number },
195
+ ): Promise<unknown[]>
196
+ }
197
+
198
+ /**
199
+ * Caller-typed SELECT builder. RedDB does not infer `T`; provide it
200
+ * explicitly with `db.from<T>('collection')`.
201
+ */
202
+ export class TypedQueryBuilder<T extends Record<string, unknown> = Record<string, unknown>> {
203
+ select(): TypedQueryBuilder<T>
204
+ select(column: '*'): TypedQueryBuilder<T>
205
+ select<K extends keyof T & string>(...columns: K[]): TypedQueryBuilder<Pick<T, K>>
206
+ select<K extends keyof T & string>(columns: K[]): TypedQueryBuilder<Pick<T, K>>
207
+ where(condition: string, params: QueryParam[]): TypedQueryBuilder<T>
208
+ where(condition: string, ...params: QueryParam[]): TypedQueryBuilder<T>
209
+ run(): Promise<T[]>
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // Streaming API (PRD #759 / S11) — Web-streams variant
214
+ // ---------------------------------------------------------------------------
215
+
216
+ export type Row = Record<string, unknown>
217
+
218
+ export interface StreamDescriptor {
219
+ result_kind?: string
220
+ columns?: Array<{ name: string; type?: string; nullable?: boolean }>
221
+ schema_fingerprint?: string
222
+ [key: string]: unknown
223
+ }
224
+
225
+ export interface StreamCursor {
226
+ token: string
227
+ snapshot_lsn?: number
228
+ ttl_ms?: number
229
+ expires_at_ms?: number
230
+ resumable?: boolean
231
+ }
232
+
233
+ export interface StreamEndEnvelope {
234
+ row_count?: number
235
+ committed_rid?: number
236
+ chunk_count?: number
237
+ lease_handle?: string
238
+ snapshot_lsn?: number
239
+ [key: string]: unknown
240
+ }
241
+
242
+ export interface StreamOptions {
243
+ /** Abort the stream when this signal fires. */
244
+ signal?: AbortSignal
245
+ /** Resume a prior stream from an opaque cursor token (HTTP only). */
246
+ cursor?: string
247
+ }
248
+
249
+ export interface InputStreamOptions {
250
+ signal?: AbortSignal
251
+ /** Ingest column set; inferred from the first row's keys when omitted. */
252
+ columns?: string[]
253
+ }
254
+
255
+ /**
256
+ * A streaming read backed by a Web `ReadableStream`. Conforms to
257
+ * `AsyncIterable<Row>` — `for await (const row of stream)` yields rows as they
258
+ * arrive. `'descriptor'` / `'cursor'` events fire when the transport surfaces
259
+ * them. Unlike the Node entry, this does **not** extend `node:stream`'s
260
+ * `Readable`.
261
+ */
262
+ export class RowReadable implements AsyncIterable<Row> {
263
+ /** Schema descriptor (HTTP NDJSON) once seen, else null. */
264
+ readonly descriptor: StreamDescriptor | null
265
+ /** Resumable cursor control frame once seen, else null. */
266
+ readonly cursor: StreamCursor | null
267
+ /** Terminal `end` envelope once the stream completes, else null. */
268
+ readonly endInfo: StreamEndEnvelope | null
269
+ /** Subscribe to `'descriptor'` / `'cursor'` / `'error'` / `'end'` events. */
270
+ on(event: string, listener: (...args: unknown[]) => void): this
271
+ once(event: string, listener: (...args: unknown[]) => void): this
272
+ off(event: string, listener: (...args: unknown[]) => void): this
273
+ /** Terminate the stream; rejects pending iterations with a cancellation error. */
274
+ cancel(reason?: string): Promise<void>
275
+ [Symbol.asyncIterator](): AsyncIterableIterator<Row>
276
+ }
277
+
278
+ /**
279
+ * A streaming write backed by a Web `WritableStream`. `write(row)` pushes a
280
+ * row (backpressure flows through the returned promise); `end()` signals
281
+ * end-of-stream and the server's terminal envelope resolves `.completion()`.
282
+ * Unlike the Node entry, this does **not** extend `node:stream`'s `Writable`.
283
+ */
284
+ export class RowWritable {
285
+ /** Terminal `end` envelope once the write completes, else null. */
286
+ readonly endInfo: StreamEndEnvelope | null
287
+ /** Push a row; the returned promise resolves when backpressure clears. */
288
+ write(row: Row): Promise<void>
289
+ /** Signal end-of-stream. */
290
+ end(): Promise<void>
291
+ /** Resolves with the server's terminal envelope; rejects on error/cancel. */
292
+ completion(): Promise<StreamEndEnvelope>
293
+ /** Abandon the ingest; rejects `.completion()` with a cancellation error. */
294
+ cancel(reason?: string): Promise<void>
295
+ }
296
+
297
+ /**
298
+ * A streaming-capable collection/table handle. `query()` is a one-shot
299
+ * Promise; `stream()` / `inputStream()` are the streaming surfaces.
300
+ */
301
+ export class Collection {
302
+ readonly name: string
303
+ query(sql: string): Promise<QueryResult>
304
+ query(sql: string, params: QueryParam[]): Promise<QueryResult>
305
+ query(sql: string, ...params: QueryParam[]): Promise<QueryResult>
306
+ stream(sql: string, opts?: StreamOptions): RowReadable
307
+ inputStream(opts?: InputStreamOptions): RowWritable
308
+ }
309
+
310
+ export class ConfigClient {
311
+ put(
312
+ key: string,
313
+ value: unknown,
314
+ options?: {
315
+ collection?: string
316
+ tags?: string[]
317
+ secretRef?: { collection: string; key: string }
318
+ },
319
+ ): Promise<QueryResult>
320
+ get(key: string, options?: { collection?: string }): Promise<QueryResult>
321
+ resolve(key: string, options?: { collection?: string }): Promise<QueryResult>
322
+ }
323
+
324
+ export class VaultClient {
325
+ put(
326
+ key: string,
327
+ value: unknown,
328
+ options?: { collection?: string; tags?: string[] },
329
+ ): Promise<QueryResult>
330
+ get(key: string, options?: { collection?: string }): Promise<QueryResult>
331
+ unseal(key: string, options?: { collection?: string }): Promise<QueryResult>
332
+ }
333
+
334
+ /**
335
+ * Specialised error thrown when an embedded URI is passed to the
336
+ * thin client. Always has `code === 'EmbeddedNotSupported'`. Use
337
+ * `@reddb-io/sdk` instead for in-memory or file-backed engines.
338
+ */
339
+ export class EmbeddedNotSupported extends RedDBError {
340
+ readonly name: 'EmbeddedNotSupported'
341
+ readonly code: 'EmbeddedNotSupported'
342
+ readonly uri: string
343
+ constructor(uri: string)
344
+ }
345
+
346
+ export const EMBEDDED_REJECTION_MESSAGE: string
347
+
348
+ /** Returns true when `uri` selects the embedded engine. */
349
+ export function isEmbeddedUri(uri: string): boolean
350
+
351
+ export interface RedDBTransaction {
352
+ query(sql: string): Promise<QueryResult>
353
+ query(sql: string, params: QueryParam[]): Promise<QueryResult>
354
+ query(sql: string, ...params: QueryParam[]): Promise<QueryResult>
355
+ execute(sql: string): Promise<QueryResult>
356
+ execute(sql: string, params: QueryParam[]): Promise<QueryResult>
357
+ execute(sql: string, ...params: QueryParam[]): Promise<QueryResult>
358
+ insert(collection: string, payload: Record<string, unknown>): Promise<InsertResult>
359
+ bulkInsert(
360
+ collection: string,
361
+ payloads: Array<Record<string, unknown>>,
362
+ ): Promise<BulkInsertResult>
363
+ transaction<T>(
364
+ callback: (tx: RedDBTransaction) => T | Promise<T>,
365
+ ): Promise<T>
366
+ }
367
+
368
+ export class RedDB {
369
+ readonly cache: CacheClient
370
+ readonly queue: QueueClient
371
+ readonly documents: DocumentClient
372
+ readonly kv: KvClient & ((collection?: string) => KvClient)
373
+ readonly config: (collection?: string) => ConfigClient
374
+ readonly vault: (collection?: string) => VaultClient
375
+
376
+ query(sql: string): Promise<QueryResult>
377
+ query(sql: string, params: QueryParam[]): Promise<QueryResult>
378
+ query(sql: string, ...params: QueryParam[]): Promise<QueryResult>
379
+ execute(sql: string): Promise<QueryResult>
380
+ execute(sql: string, params: QueryParam[]): Promise<QueryResult>
381
+ execute(sql: string, ...params: QueryParam[]): Promise<QueryResult>
382
+ insert(collection: string, payload: Record<string, unknown>): Promise<InsertResult>
383
+ bulkInsert(
384
+ collection: string,
385
+ payloads: Array<Record<string, unknown>>,
386
+ ): Promise<BulkInsertResult>
387
+ transaction<T>(
388
+ callback: (tx: RedDBTransaction) => T | Promise<T>,
389
+ ): Promise<T>
390
+ exists(collection: string): Promise<boolean>
391
+ list(): Promise<CollectionMeta[]>
392
+ /**
393
+ * Caller-typed collection handle. Supply `T`; the SDK does not
394
+ * generate or validate row types at runtime.
395
+ */
396
+ from<T extends Record<string, unknown> = Record<string, unknown>>(
397
+ collection: string,
398
+ ): TypedQueryBuilder<T>
399
+ /** Streaming-capable handle for a collection/table (PRD #759 S11). */
400
+ collection(name: string): Collection
401
+ /** Stream a read-only SELECT as an `AsyncIterable<Row>` over HTTP NDJSON. */
402
+ stream(sql: string, opts?: StreamOptions): RowReadable
403
+ /**
404
+ * Open a streaming write into `target`; `.completion()` resolves with
405
+ * the server's terminal envelope.
406
+ */
407
+ inputStream(target: string, opts?: InputStreamOptions): RowWritable
408
+ get(collection: string, id: string | number): Promise<GetResult>
409
+ delete(collection: string, id: string | number): Promise<DeleteResult>
410
+ health(): Promise<HealthResult>
411
+ version(): Promise<VersionResult>
412
+
413
+ login(username: string, password: string): Promise<LoginResult>
414
+ whoami(): Promise<WhoamiResult>
415
+ changePassword(currentPassword: string, newPassword: string): Promise<ChangePasswordResult>
416
+ createApiKey(opts?: { username?: string; role?: Role }): Promise<CreateApiKeyResult>
417
+ revokeApiKey(key: string): Promise<RevokeApiKeyResult>
418
+
419
+ close(): Promise<void>
420
+ }
421
+
422
+ /**
423
+ * Connect to a remote RedDB instance from a browser.
424
+ *
425
+ * Only `http://host:port` / `https://host:port` are reachable from a browser
426
+ * sandbox. `grpc(s)://`, `red(s)://`, and `pg` throw `RedDBError` with code
427
+ * `'BROWSER_TRANSPORT_UNSUPPORTED'`. Embedded URIs (`memory://`, `memory:`,
428
+ * `file:///path`, `red:///`, `red://:memory[:]`) throw `EmbeddedNotSupported`.
429
+ */
430
+ export function connect(uri: string, options?: ConnectOptions): Promise<RedDB>
431
+
432
+ /** Exchange username + password for a bearer token via /auth/login. */
433
+ export function login(
434
+ loginUrl: string,
435
+ credentials: { username: string; password: string },
436
+ ): Promise<LoginResult>
437
+
438
+ export interface ParsedUri {
439
+ kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'grpc' | 'grpcs' | 'pg'
440
+ host?: string
441
+ port?: number
442
+ path?: string
443
+ username?: string
444
+ password?: string
445
+ token?: string
446
+ apiKey?: string
447
+ loginUrl?: string
448
+ params?: URLSearchParams
449
+ originalUri: string
450
+ }
451
+
452
+ export function parseUri(uri: string): ParsedUri
453
+ export function deriveLoginUrl(parsed: ParsedUri): string
package/index.d.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  * RedDB driver. Hand-written, kept in sync with src/index.js.
4
4
  */
5
5
 
6
+ import type { Readable, Writable, Transform } from 'node:stream'
7
+
6
8
  export type AuthOptions =
7
9
  | { token: string }
8
10
  | { apiKey: string }
@@ -183,6 +185,17 @@ export class QueueClient {
183
185
  peek(queue: string, count?: number): Promise<unknown[]>
184
186
  len(queue: string): Promise<number>
185
187
  purge(queue: string): Promise<QueryResult>
188
+ /**
189
+ * Live `QUEUE READ … WAIT <ms>` helper (PRD #718 / #725). Blocks
190
+ * until a message is available, the wait budget elapses, or the
191
+ * server cancels. Timeout returns an empty array — same shape as
192
+ * an empty pop, never throws. `waitMs` is required.
193
+ */
194
+ readWait(
195
+ queue: string,
196
+ consumer: string,
197
+ options: { waitMs: number; group?: string; count?: number },
198
+ ): Promise<unknown[]>
186
199
  }
187
200
 
188
201
  /**
@@ -199,6 +212,98 @@ export class TypedQueryBuilder<T extends Record<string, unknown> = Record<string
199
212
  run(): Promise<T[]>
200
213
  }
201
214
 
215
+ // ---------------------------------------------------------------------------
216
+ // Streaming API (PRD #759 / S11)
217
+ // ---------------------------------------------------------------------------
218
+
219
+ export type Row = Record<string, unknown>
220
+
221
+ export interface StreamDescriptor {
222
+ result_kind?: string
223
+ columns?: Array<{ name: string; type?: string; nullable?: boolean }>
224
+ schema_fingerprint?: string
225
+ [key: string]: unknown
226
+ }
227
+
228
+ export interface StreamCursor {
229
+ token: string
230
+ snapshot_lsn?: number
231
+ ttl_ms?: number
232
+ expires_at_ms?: number
233
+ resumable?: boolean
234
+ }
235
+
236
+ export interface StreamEndEnvelope {
237
+ row_count?: number
238
+ committed_rid?: number
239
+ chunk_count?: number
240
+ lease_handle?: string
241
+ snapshot_lsn?: number
242
+ [key: string]: unknown
243
+ }
244
+
245
+ export interface StreamOptions {
246
+ /** Abort the stream when this signal fires. */
247
+ signal?: AbortSignal
248
+ /** Resume a prior stream from an opaque cursor token (HTTP only). */
249
+ cursor?: string
250
+ }
251
+
252
+ export interface InputStreamOptions {
253
+ signal?: AbortSignal
254
+ /** Ingest column set; inferred from the first row's keys when omitted. */
255
+ columns?: string[]
256
+ }
257
+
258
+ /**
259
+ * A streaming read. A Node `Readable` in object mode that also conforms
260
+ * to `AsyncIterable<Row>` — `for await (const row of stream)` yields rows
261
+ * as they arrive. `'descriptor'` / `'cursor'` events fire when the
262
+ * transport surfaces them.
263
+ */
264
+ export class RowReadable extends Readable implements AsyncIterable<Row> {
265
+ /** Schema descriptor (HTTP NDJSON) once seen, else null. */
266
+ readonly descriptor: StreamDescriptor | null
267
+ /** Resumable cursor control frame once seen, else null. */
268
+ readonly cursor: StreamCursor | null
269
+ /** Terminal `end` envelope once the stream completes, else null. */
270
+ readonly endInfo: StreamEndEnvelope | null
271
+ /** Terminate the stream; rejects pending iterations with a cancellation error. */
272
+ cancel(reason?: string): Promise<void>
273
+ [Symbol.asyncIterator](): AsyncIterableIterator<Row>
274
+ }
275
+
276
+ /**
277
+ * A streaming write. A Node `Writable` in object mode; `end()` signals
278
+ * end-of-stream and the server's terminal envelope resolves
279
+ * `.completion()`.
280
+ */
281
+ export class RowWritable extends Writable {
282
+ /** Resolves with the server's terminal envelope; rejects on error/cancel. */
283
+ completion(): Promise<StreamEndEnvelope>
284
+ /** Abandon the ingest; rejects `.completion()` with a cancellation error. */
285
+ cancel(reason?: string): Promise<void>
286
+ }
287
+
288
+ /**
289
+ * A `Transform` that splits an NDJSON byte stream into row objects, ready
290
+ * to pipe into `table.inputStream()`.
291
+ */
292
+ export function splitNdjson(): Transform
293
+
294
+ /**
295
+ * A streaming-capable collection/table handle. `query()` is a one-shot
296
+ * Promise; `stream()` / `inputStream()` are the streaming surfaces.
297
+ */
298
+ export class Collection {
299
+ readonly name: string
300
+ query(sql: string): Promise<QueryResult>
301
+ query(sql: string, params: QueryParam[]): Promise<QueryResult>
302
+ query(sql: string, ...params: QueryParam[]): Promise<QueryResult>
303
+ stream(sql: string, opts?: StreamOptions): RowReadable
304
+ inputStream(opts?: InputStreamOptions): RowWritable
305
+ }
306
+
202
307
  export class ConfigClient {
203
308
  put(
204
309
  key: string,
@@ -288,6 +393,19 @@ export class RedDB {
288
393
  from<T extends Record<string, unknown> = Record<string, unknown>>(
289
394
  collection: string,
290
395
  ): TypedQueryBuilder<T>
396
+ /** Streaming-capable handle for a collection/table (PRD #759 S11). */
397
+ collection(name: string): Collection
398
+ /**
399
+ * Stream a read-only SELECT as a `Readable` / `AsyncIterable<Row>`.
400
+ * Uses RedWire when the connection is RedWire, HTTP NDJSON when it is
401
+ * HTTP — identical surface either way.
402
+ */
403
+ stream(sql: string, opts?: StreamOptions): RowReadable
404
+ /**
405
+ * Open a streaming write into `target`; `.completion()` resolves with
406
+ * the server's terminal envelope.
407
+ */
408
+ inputStream(target: string, opts?: InputStreamOptions): RowWritable
291
409
  get(collection: string, id: string | number): Promise<GetResult>
292
410
  delete(collection: string, id: string | number): Promise<DeleteResult>
293
411
  health(): Promise<HealthResult>
package/package.json CHANGED
@@ -1,19 +1,56 @@
1
1
  {
2
2
  "name": "@reddb-io/client",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Thin remote-only RedDB driver. Downloads the `red_client` binary on install. Speaks RedWire/gRPC/HTTP. Embedded URIs (memory://, file://, red:///path) are rejected — use @reddb-io/sdk for those.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
+ "browser": "./src/index.browser.js",
7
8
  "exports": {
8
9
  ".": {
9
- "import": "./src/index.js",
10
- "types": "./index.d.ts"
11
- }
10
+ "browser": {
11
+ "types": "./index.browser.d.ts",
12
+ "import": "./src/index.browser.js"
13
+ },
14
+ "node": {
15
+ "types": "./index.d.ts",
16
+ "import": "./src/index.js"
17
+ },
18
+ "bun": {
19
+ "types": "./index.d.ts",
20
+ "import": "./src/index.js"
21
+ },
22
+ "deno": {
23
+ "types": "./index.d.ts",
24
+ "import": "./src/index.js"
25
+ },
26
+ "default": {
27
+ "types": "./index.browser.d.ts",
28
+ "import": "./src/index.browser.js"
29
+ }
30
+ },
31
+ "./node": {
32
+ "types": "./index.d.ts",
33
+ "import": "./src/index.js"
34
+ },
35
+ "./browser": {
36
+ "types": "./index.browser.d.ts",
37
+ "import": "./src/index.browser.js"
38
+ },
39
+ "./bun": {
40
+ "types": "./index.d.ts",
41
+ "import": "./src/index.js"
42
+ },
43
+ "./deno": {
44
+ "types": "./index.d.ts",
45
+ "import": "./src/index.js"
46
+ },
47
+ "./package.json": "./package.json"
12
48
  },
13
49
  "types": "./index.d.ts",
14
50
  "files": [
15
51
  "src/",
16
52
  "index.d.ts",
53
+ "index.browser.d.ts",
17
54
  "postinstall.js",
18
55
  "README.md",
19
56
  "LICENSE"