@reddb-io/client 1.7.0 → 1.9.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 +34 -0
- package/index.browser.d.ts +453 -0
- package/index.d.ts +118 -0
- package/package.json +41 -4
- package/src/core/auth.js +102 -0
- package/src/core/embedded-rejection.js +90 -0
- package/src/core/errors.js +20 -0
- package/src/core/index.js +43 -0
- package/src/core/insert-ids.js +45 -0
- package/src/core/ndjson.js +59 -0
- package/src/core/reddb.js +314 -0
- package/src/core/serialization.js +94 -0
- package/src/core/url.js +271 -0
- package/src/embedded-rejection.js +9 -87
- package/src/http.js +191 -0
- package/src/index.browser.js +156 -0
- package/src/index.js +29 -404
- package/src/protocol.js +6 -13
- package/src/queue.js +24 -0
- package/src/redwire.js +186 -0
- package/src/streaming-web.js +450 -0
- package/src/streaming.js +362 -0
- package/src/url.js +9 -268
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.
|
|
3
|
+
"version": "1.9.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
|
-
"
|
|
10
|
-
|
|
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"
|