@reddb-io/sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @reddb-io/sdk
2
+
3
+ Official RedDB SDK for JavaScript and TypeScript. Speaks JSON-RPC 2.0 over
4
+ stdio to a local `red` binary, which is downloaded automatically on install.
5
+ Works in **Node 18+**, **Bun** and **Deno** (via `npm:` specifier) — same
6
+ package, no per-runtime fork.
7
+
8
+ Use this package for application code. If you just want to launch the CLI from
9
+ npm, use:
10
+
11
+ ```bash
12
+ npx @reddb-io/cli@latest version
13
+ npx @reddb-io/cli@latest server --http-bind 127.0.0.1:8080 --path ./data.rdb
14
+ ```
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pnpm add @reddb-io/sdk
20
+ # or
21
+ npm install @reddb-io/sdk
22
+ # or
23
+ bun add @reddb-io/sdk
24
+ ```
25
+
26
+ In Deno:
27
+
28
+ ```ts
29
+ import { connect } from 'npm:@reddb-io/sdk'
30
+ ```
31
+
32
+ The `postinstall` script downloads the matching `red` binary from GitHub
33
+ Releases into `node_modules/@reddb-io/sdk/bin/`. If your environment blocks
34
+ postinstall scripts or has no network, set `REDDB_BINARY_PATH=/path/to/red`
35
+ and the driver will use that instead.
36
+
37
+ ## Quickstart
38
+
39
+ ```js
40
+ import { connect } from '@reddb-io/sdk'
41
+
42
+ const db = await connect('memory://') // ephemeral
43
+ // or: await connect('file:///var/lib/reddb/data.rdb') // persisted
44
+
45
+ await db.insert('users', { name: 'Alice', age: 30 })
46
+ await db.bulkInsert('users', [{ name: 'Bob' }, { name: 'Carol' }])
47
+
48
+ const result = await db.query('SELECT * FROM users')
49
+ console.log(result.rows)
50
+
51
+ await db.close()
52
+ ```
53
+
54
+ TypeScript is the same API:
55
+
56
+ ```ts
57
+ import { connect, RedDBError } from '@reddb-io/sdk'
58
+
59
+ const db = await connect('file:///var/lib/reddb/data.rdb')
60
+
61
+ try {
62
+ const result = await db.query('SELECT * FROM users LIMIT 10')
63
+ console.log(result.rows)
64
+ } catch (err) {
65
+ if (err instanceof RedDBError) {
66
+ console.error(err.code, err.message)
67
+ }
68
+ }
69
+
70
+ await db.close()
71
+ ```
72
+
73
+ ## Connection URIs
74
+
75
+ | URI | Mode |
76
+ |----------------------------|--------------------------------------|
77
+ | `memory://` | Ephemeral, in-memory database |
78
+ | `file:///absolute/path` | Embedded engine, persisted to disk |
79
+ | `grpc://host:port` | Remote server (planned, not yet) |
80
+
81
+ `grpc://` is not supported by the JS driver yet — the binary needs the
82
+ `--connect` flag wired up first. See `PLAN_DRIVERS.md` in the repo root.
83
+
84
+ ## API
85
+
86
+ ### `connect(uri, options?) → Promise<RedDB>`
87
+
88
+ Spawns `red rpc --stdio` with arguments derived from the URI, attaches a
89
+ JSON-RPC client to its stdin/stdout, then returns a connection handle.
90
+
91
+ Options:
92
+ - `binary` — override the `red` binary path. Defaults to `bin/red` next to the
93
+ package, or `REDDB_BINARY_PATH` env var if set.
94
+
95
+ Examples:
96
+
97
+ ```js
98
+ import { connect } from '@reddb-io/sdk'
99
+
100
+ const db = await connect('memory://')
101
+ const persisted = await connect('file:///tmp/app.rdb')
102
+ const custom = await connect('memory://', { binary: '/usr/local/bin/red' })
103
+ ```
104
+
105
+ ### `db.query(sql) → Promise<{ statement, affected, columns, rows }>`
106
+
107
+ ### `db.insert(collection, payload) → Promise<{ affected, id? }>`
108
+
109
+ ### `db.bulkInsert(collection, payloads) → Promise<{ affected }>`
110
+
111
+ ### `db.get(collection, id) → Promise<{ entity }>`
112
+
113
+ ### `db.delete(collection, id) → Promise<{ affected }>`
114
+
115
+ ### `db.health() → Promise<{ ok, version }>`
116
+
117
+ ### `db.version() → Promise<{ version, protocol }>`
118
+
119
+ ### `db.close() → Promise<void>`
120
+
121
+ Sends `close` to the binary, waits for it to exit. Calls after `close()` reject
122
+ with `RedDBError('CLIENT_CLOSED', ...)`.
123
+
124
+ ## Errors
125
+
126
+ All RPC failures throw `RedDBError`:
127
+
128
+ ```js
129
+ import { RedDBError } from '@reddb-io/sdk'
130
+
131
+ try {
132
+ await db.query('NOT VALID SQL')
133
+ } catch (err) {
134
+ if (err instanceof RedDBError) {
135
+ console.error(err.code) // 'QUERY_ERROR'
136
+ console.error(err.message) // server-provided detail
137
+ console.error(err.data) // optional structured data
138
+ }
139
+ }
140
+ ```
141
+
142
+ Stable error codes:
143
+
144
+ | code | when |
145
+ |-------------------|------------------------------------------------------------|
146
+ | `PARSE_ERROR` | Server got malformed JSON (driver bug, please report) |
147
+ | `INVALID_REQUEST` | Missing field or unknown method |
148
+ | `INVALID_PARAMS` | params didn't match the method schema |
149
+ | `QUERY_ERROR` | SQL parse, type or constraint error |
150
+ | `NOT_FOUND` | Entity / collection does not exist |
151
+ | `INTERNAL_ERROR` | Server caught a panic |
152
+ | `CLIENT_CLOSED` | Driver-side: call after `close()` or unexpected EOF |
153
+ | `UNSUPPORTED_SCHEME` | URI scheme not yet supported by the driver |
154
+
155
+ ## Limits
156
+
157
+ - **stdio IPC overhead.** Each call is a JSON serialize, write, parse, read
158
+ round-trip. For most apps (web servers, scripts, ETL) this is invisible.
159
+ For very high-throughput single-process ingestion, use the embedded Rust
160
+ crate `reddb` directly.
161
+ - **No edge runtimes.** Cloudflare Workers, Vercel Edge and the browser have
162
+ no subprocess support. If you need RedDB there, wait for the planned HTTP
163
+ transport (see `PLAN_DRIVERS.md`).
164
+ - **One process per connection.** No connection pooling yet. If you need
165
+ concurrent independent transactions, open multiple `connect()` handles.
166
+
167
+ ## Testing locally
168
+
169
+ ```bash
170
+ cargo build --bin red # at repo root
171
+ cd drivers/js
172
+ node test/smoke.test.mjs
173
+ ```
174
+
175
+ Same test runs in Bun and Deno:
176
+
177
+ ```bash
178
+ bun test/smoke.test.mjs
179
+ deno run -A test/smoke.test.mjs
180
+ ```
181
+
182
+ ## Production deploy
183
+
184
+ When you're ready to point this driver at a production RedDB cluster:
185
+
186
+ - **Run RedDB with the encrypted vault** so auth state and
187
+ `red.secret.*` values are protected at rest. See
188
+ [`docs/security/vault.md`](../../docs/security/vault.md).
189
+ - **Use Docker secrets or your cloud secret manager** to inject the
190
+ certificate — never bake it into an image. See
191
+ [`docs/getting-started/docker.md`](../../docs/getting-started/docker.md).
192
+ - **Track every secret** the driver consumes (bearer tokens, mTLS
193
+ cert + key, OAuth JWTs) in
194
+ [`docs/operations/secrets.md`](../../docs/operations/secrets.md).
195
+ - **Use `reds://` (TLS)** or `red://...?tls=true` for any traffic
196
+ crossing the network — never plain `red://` outside localhost.
197
+ - **TLS posture, mTLS, OAuth/JWT and reverse-proxy patterns** are
198
+ covered in [`docs/security/transport-tls.md`](../../docs/security/transport-tls.md).
199
+ - See [Policies](../../docs/security/policies.md) for IAM-style authorization.
package/index.d.ts ADDED
@@ -0,0 +1,362 @@
1
+ /**
2
+ * RedDB JavaScript driver — TypeScript definitions.
3
+ *
4
+ * Hand-written, kept in sync with src/index.js.
5
+ */
6
+
7
+ /**
8
+ * Authentication credentials. Only meaningful for `grpc://` URIs;
9
+ * embedded modes (memory://, file://) inherit the caller's
10
+ * filesystem privileges and reject auth options at the boundary.
11
+ *
12
+ * The shape is `{ token }` (or its `{ apiKey }` alias). For
13
+ * username/password login, mint a token first via the standalone
14
+ * `login(httpUrl, { username, password })` helper, then pass it
15
+ * here — the gRPC surface does not currently bridge `auth.login`.
16
+ */
17
+ export type AuthOptions =
18
+ | { token: string }
19
+ | { apiKey: string }
20
+ | { username: string; password: string; loginUrl?: string }
21
+
22
+ /**
23
+ * TLS / mTLS configuration for `redwire(s)://` connections.
24
+ * `ca` / `cert` / `key` accept a filesystem path or a PEM string
25
+ * starting with `-----BEGIN`.
26
+ */
27
+ export interface TlsOptions {
28
+ ca?: string | Uint8Array
29
+ cert?: string | Uint8Array
30
+ key?: string | Uint8Array
31
+ servername?: string
32
+ rejectUnauthorized?: boolean
33
+ }
34
+
35
+ export interface ConnectOptions {
36
+ /** Override the path to the `red` binary (defaults to bundled). */
37
+ binary?: string
38
+ /** Authentication credentials (grpc:// only). */
39
+ auth?: AuthOptions
40
+ /**
41
+ * TLS for `redwire(s)://` connections. URL params (`tls=true`,
42
+ * `cert=`, `key=`, `ca=`) feed the same field; this option
43
+ * always wins.
44
+ */
45
+ tls?: TlsOptions
46
+ }
47
+
48
+ export interface QueryResult {
49
+ statement: string
50
+ affected: number
51
+ columns: string[]
52
+ rows: Array<Record<string, unknown>>
53
+ }
54
+
55
+ export interface InsertResult {
56
+ affected: number
57
+ /** Present when the underlying engine surfaces the inserted entity id. */
58
+ id?: string | number
59
+ }
60
+
61
+ export interface BulkInsertResult {
62
+ affected: number
63
+ }
64
+
65
+ export interface GetResult {
66
+ entity: Record<string, unknown> | null
67
+ }
68
+
69
+ export interface DeleteResult {
70
+ affected: number
71
+ }
72
+
73
+ export interface HealthResult {
74
+ ok: boolean
75
+ version: string
76
+ }
77
+
78
+ export interface VersionResult {
79
+ version: string
80
+ protocol: string
81
+ }
82
+
83
+ export type Role = 'read' | 'write' | 'admin'
84
+
85
+ export interface LoginResult {
86
+ token: string
87
+ username: string
88
+ role: Role
89
+ expires_at: number
90
+ }
91
+
92
+ export interface WhoamiResult {
93
+ username: string
94
+ role: Role
95
+ }
96
+
97
+ export interface CreateApiKeyResult {
98
+ key: string
99
+ role: Role
100
+ created_at: number
101
+ }
102
+
103
+ export interface ChangePasswordResult {
104
+ ok: true
105
+ }
106
+
107
+ export interface RevokeApiKeyResult {
108
+ ok: true
109
+ }
110
+
111
+ export class RedDBError extends Error {
112
+ readonly name: 'RedDBError'
113
+ readonly code: string
114
+ readonly data: unknown
115
+ constructor(code: string, message: string, data?: unknown)
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Cache API
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /** Options for cache.put(). Maps to Rust BlobCachePolicy fields. */
123
+ export interface CachePutOptions {
124
+ /** Entry TTL in milliseconds. Omit to use the namespace default. */
125
+ ttl_ms?: number
126
+ /** Tags for group invalidation via cache.invalidateTags(). */
127
+ tags?: string[]
128
+ /** Extended TTL policy (idle eviction, stale-while-revalidate, jitter). */
129
+ policy?: {
130
+ idle_evict_ms?: number
131
+ stale_while_revalidate_ms?: number
132
+ jitter_ms?: number
133
+ }
134
+ }
135
+
136
+ export interface CacheGetResult {
137
+ /** Raw bytes of the cached value. null when not found. */
138
+ value: Uint8Array | null
139
+ }
140
+
141
+ export type CacheExistsStatus = 'present' | 'absent' | 'maybe'
142
+
143
+ export interface CacheInvalidateResult {
144
+ removed: number
145
+ }
146
+
147
+ export class CacheClient {
148
+ /** Fetch a cached value. Returns Uint8Array on hit, null on miss. */
149
+ get(namespace: string, key: string): Promise<Uint8Array | null>
150
+ /** Store a value in the cache. */
151
+ put(
152
+ namespace: string,
153
+ key: string,
154
+ value: Uint8Array | Buffer | string,
155
+ opts?: CachePutOptions,
156
+ ): Promise<void>
157
+ /** Check whether a key exists. */
158
+ exists(namespace: string, key: string): Promise<CacheExistsStatus>
159
+ /** Remove a single entry. */
160
+ invalidate(namespace: string, key: string): Promise<void>
161
+ /** Remove all entries whose key starts with prefix. Returns count removed. */
162
+ invalidatePrefix(namespace: string, prefix: string): Promise<number>
163
+ /** Remove all entries tagged with any of the given tags. Returns count removed. */
164
+ invalidateTags(namespace: string, tags: string[]): Promise<number>
165
+ /** Remove all entries in a namespace (routes to POST /admin/blob_cache/flush_namespace). */
166
+ flushNamespace(namespace: string): Promise<void>
167
+ }
168
+
169
+ export interface KvWatchEvent {
170
+ key: string
171
+ op: 'insert' | 'update' | 'delete'
172
+ before: unknown
173
+ after: unknown
174
+ lsn: number
175
+ committed_at: number
176
+ dropped_event_count: number
177
+ }
178
+
179
+ export class KvClient {
180
+ put(
181
+ key: string,
182
+ value: unknown,
183
+ options?: { collection?: string; expireMs?: number; tags?: string[] },
184
+ ): Promise<QueryResult>
185
+ invalidateTags(tags: string[], options?: { collection?: string }): Promise<number>
186
+ watch(
187
+ key: string,
188
+ options?: { collection?: string; sinceLsn?: number; limit?: number },
189
+ ): AsyncIterable<KvWatchEvent>
190
+ watchPrefix(
191
+ prefix: string,
192
+ options?: { collection?: string; sinceLsn?: number; limit?: number },
193
+ ): AsyncIterable<KvWatchEvent>
194
+ }
195
+
196
+ export class ConfigClient {
197
+ put(
198
+ key: string,
199
+ value: unknown,
200
+ options?: {
201
+ collection?: string
202
+ tags?: string[]
203
+ secretRef?: { collection: string; key: string }
204
+ },
205
+ ): Promise<QueryResult>
206
+ get(key: string, options?: { collection?: string }): Promise<QueryResult>
207
+ resolve(key: string, options?: { collection?: string }): Promise<QueryResult>
208
+ }
209
+
210
+ export class VaultClient {
211
+ put(
212
+ key: string,
213
+ value: unknown,
214
+ options?: { collection?: string; tags?: string[] },
215
+ ): Promise<QueryResult>
216
+ get(key: string, options?: { collection?: string }): Promise<QueryResult>
217
+ unseal(key: string, options?: { collection?: string }): Promise<QueryResult>
218
+ }
219
+
220
+ export class RedDB {
221
+ readonly cache: CacheClient
222
+ readonly kv: KvClient & ((collection?: string) => KvClient)
223
+ readonly config: (collection?: string) => ConfigClient
224
+ readonly vault: (collection?: string) => VaultClient
225
+
226
+ query(sql: string): Promise<QueryResult>
227
+ insert(collection: string, payload: Record<string, unknown>): Promise<InsertResult>
228
+ bulkInsert(
229
+ collection: string,
230
+ payloads: Array<Record<string, unknown>>,
231
+ ): Promise<BulkInsertResult>
232
+ get(collection: string, id: string | number): Promise<GetResult>
233
+ delete(collection: string, id: string | number): Promise<DeleteResult>
234
+ health(): Promise<HealthResult>
235
+ version(): Promise<VersionResult>
236
+
237
+ // Auth surface — only meaningful when connected via grpc://.
238
+ // Embedded modes will receive 'unknown method' from the bridge.
239
+ login(username: string, password: string): Promise<LoginResult>
240
+ whoami(): Promise<WhoamiResult>
241
+ changePassword(currentPassword: string, newPassword: string): Promise<ChangePasswordResult>
242
+ createApiKey(opts?: { username?: string; role?: Role }): Promise<CreateApiKeyResult>
243
+ revokeApiKey(key: string): Promise<RevokeApiKeyResult>
244
+
245
+ close(): Promise<void>
246
+ }
247
+
248
+ /**
249
+ * Connect to a RedDB instance.
250
+ *
251
+ * Accepted URI schemes:
252
+ * - `memory://` — ephemeral in-memory database
253
+ * - `file:///absolute/path` — embedded, persisted to disk
254
+ * - `grpc://host:port` — remote server
255
+ */
256
+ export function connect(uri: string, options?: ConnectOptions): Promise<RedDB>
257
+
258
+ /**
259
+ * Exchange username + password for a bearer token by hitting the
260
+ * server's `POST /auth/login` HTTP endpoint. The returned `token`
261
+ * can be passed to `connect(uri, { auth: { token } })`.
262
+ *
263
+ * @example
264
+ * import { connect, login } from '@reddb-io/sdk'
265
+ * const { token } = await login(
266
+ * 'https://reddb.example.com/auth/login',
267
+ * { username: 'admin', password: 'secret' },
268
+ * )
269
+ * const db = await connect('grpc://reddb.example.com:5051', { auth: { token } })
270
+ */
271
+ export function login(
272
+ loginUrl: string,
273
+ credentials: { username: string; password: string },
274
+ ): Promise<LoginResult>
275
+
276
+ /**
277
+ * Translate a connection URI + (optional) auth into argv for
278
+ * `red rpc --stdio`. Exported for tests / debug. New code should
279
+ * use `parseUri` directly and let `connect` handle dispatch.
280
+ */
281
+ export function uriToArgs(
282
+ uri: string,
283
+ auth?: { kind: 'token'; token: string } | null,
284
+ ): string[]
285
+
286
+ /**
287
+ * Parsed `red://` (or legacy) URI. Returned by `parseUri`.
288
+ */
289
+ export interface ParsedUri {
290
+ kind: 'embedded' | 'http' | 'https' | 'grpc' | 'grpcs' | 'pg'
291
+ host?: string
292
+ port?: number
293
+ path?: string
294
+ username?: string
295
+ password?: string
296
+ token?: string
297
+ apiKey?: string
298
+ loginUrl?: string
299
+ params?: URLSearchParams
300
+ originalUri: string
301
+ }
302
+
303
+ /** Parse any URI (red://, memory://, file://, grpc://, http(s)://) into a normalised shape. */
304
+ export function parseUri(uri: string): ParsedUri
305
+
306
+ /** Derive the HTTP `/auth/login` URL from a parsed URI. */
307
+ export function deriveLoginUrl(parsed: ParsedUri): string
308
+
309
+ // ---------------------------------------------------------------
310
+ // RedWire native TCP transport (drivers/js/src/redwire.js)
311
+ // ---------------------------------------------------------------
312
+
313
+ /** RedWire frame kinds. Numeric values are the wire-stable spec. */
314
+ export const MessageKind: Readonly<{
315
+ Query: 0x01
316
+ Result: 0x02
317
+ Error: 0x03
318
+ BulkInsert: 0x04
319
+ BulkOk: 0x05
320
+ Hello: 0x10
321
+ HelloAck: 0x11
322
+ AuthRequest: 0x12
323
+ AuthResponse: 0x13
324
+ AuthOk: 0x14
325
+ AuthFail: 0x15
326
+ Bye: 0x16
327
+ Ping: 0x17
328
+ Pong: 0x18
329
+ }>
330
+
331
+ export type RedWireAuth =
332
+ | { kind: 'anonymous' }
333
+ | { kind: 'bearer'; token: string }
334
+
335
+ export interface RedWireConnectOptions {
336
+ host: string
337
+ port: number
338
+ auth?: RedWireAuth
339
+ clientName?: string
340
+ /** When set, wraps the socket in TLS (mTLS via cert + key). */
341
+ tls?: TlsOptions
342
+ }
343
+
344
+ /**
345
+ * Open a RedWire connection. Speaks the binary protocol directly via
346
+ * a TCP socket — no spawn, no fetch. Returned client matches the
347
+ * `RpcClient` / `HttpRpcClient` surface so it slots into the
348
+ * existing `RedDB` class.
349
+ */
350
+ export function connectRedwire(opts: RedWireConnectOptions): Promise<RedWireClient>
351
+
352
+ export class RedWireClient {
353
+ /**
354
+ * Generic RPC entry. Routes:
355
+ * - 'query' → Query frame (SQL string)
356
+ * - 'insert' → BulkInsert frame, single-row shape
357
+ * - 'bulk_insert' → BulkInsert frame, array shape
358
+ * - 'health' / 'version' → Ping frame
359
+ */
360
+ call(method: string, params?: Record<string, unknown>): Promise<unknown>
361
+ close(): Promise<void>
362
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@reddb-io/sdk",
3
+ "version": "1.0.1",
4
+ "description": "Official RedDB driver — talks the native RedWire TCP protocol (mTLS), HTTP, gRPC bridge, or embedded stdio JSON-RPC. Works in Node 18+, Bun and Deno.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.js",
10
+ "types": "./index.d.ts"
11
+ }
12
+ },
13
+ "types": "./index.d.ts",
14
+ "files": [
15
+ "src/",
16
+ "index.d.ts",
17
+ "postinstall.js",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "keywords": [
25
+ "reddb",
26
+ "database",
27
+ "client",
28
+ "driver",
29
+ "json-rpc",
30
+ "multi-model",
31
+ "vector",
32
+ "graph",
33
+ "document",
34
+ "key-value"
35
+ ],
36
+ "license": "MIT",
37
+ "homepage": "https://github.com/reddb-io/reddb",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/reddb-io/reddb.git",
41
+ "directory": "drivers/js"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/reddb-io/reddb/issues"
45
+ },
46
+ "scripts": {
47
+ "postinstall": "node postinstall.js",
48
+ "test": "node --test test/cache.test.mjs && node test/smoke.test.mjs"
49
+ }
50
+ }
package/postinstall.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * postinstall.js — download the matching `red` binary from GitHub Releases.
3
+ *
4
+ * Behavior:
5
+ * - Resolves `red-<platform>-<arch>` from `process.platform` + `process.arch`
6
+ * via the vendored asset fetcher.
7
+ * - Targets the GitHub release matching this package's version.
8
+ * - Drops the binary at `<package>/bin/red[.exe]` and chmods +x on Unix.
9
+ * - On any failure, prints a warning to stderr but exits 0 — `npm install`
10
+ * never breaks because of this script. The user gets a clear error
11
+ * later when they call `connect()` if the binary isn't present.
12
+ *
13
+ * Override hooks (env vars):
14
+ * REDDB_SKIP_POSTINSTALL=1 do nothing
15
+ * REDDB_POSTINSTALL_VERSION=… pull a different release tag
16
+ * REDDB_POSTINSTALL_REPO=… pull from a fork (default: reddb-io/reddb)
17
+ */
18
+
19
+ import { createRequire } from 'node:module'
20
+ import { fileURLToPath } from 'node:url'
21
+ import { dirname, join } from 'node:path'
22
+ import { existsSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs'
23
+
24
+ import { fetchReleaseAsset } from './src/internal/asset-fetcher/index.js'
25
+
26
+ const HERE = dirname(fileURLToPath(import.meta.url))
27
+ const require = createRequire(import.meta.url)
28
+ const pkg = require('./package.json')
29
+
30
+ const DEFAULT_REPO = 'reddb-io/reddb'
31
+
32
+ if (process.env.REDDB_SKIP_POSTINSTALL === '1') {
33
+ process.stdout.write('reddb: postinstall skipped (REDDB_SKIP_POSTINSTALL=1)\n')
34
+ process.exit(0)
35
+ }
36
+
37
+ main().catch((err) => {
38
+ process.stderr.write(
39
+ `reddb: postinstall could not download the binary (${err.message}).\n` +
40
+ ` The package will still install. To use the driver you can:\n` +
41
+ ` - set REDDB_BINARY_PATH=/path/to/red\n` +
42
+ ` - or install the binary manually from https://github.com/${DEFAULT_REPO}/releases\n`,
43
+ )
44
+ process.exit(0)
45
+ })
46
+
47
+ async function main() {
48
+ const repo = process.env.REDDB_POSTINSTALL_REPO || DEFAULT_REPO
49
+ const tag = process.env.REDDB_POSTINSTALL_VERSION
50
+ ? normalizeTag(process.env.REDDB_POSTINSTALL_VERSION)
51
+ : `v${pkg.version}`
52
+
53
+ const binDir = join(HERE, 'bin')
54
+ const binaryPath = join(binDir, defaultBinaryName())
55
+
56
+ if (existsSync(binaryPath)) {
57
+ process.stdout.write(`reddb: binary already present at ${binaryPath}\n`)
58
+ return
59
+ }
60
+
61
+ process.stdout.write(
62
+ `reddb: downloading red ${tag} for ${process.platform}/${process.arch} from ${repo}\n`,
63
+ )
64
+ const body = await fetchReleaseAsset({
65
+ repo,
66
+ tag,
67
+ platform: process.platform,
68
+ arch: process.arch,
69
+ binName: 'red',
70
+ })
71
+ mkdirSync(binDir, { recursive: true })
72
+ writeFileSync(binaryPath, body)
73
+ if (process.platform !== 'win32') {
74
+ chmodSync(binaryPath, 0o755)
75
+ }
76
+ process.stdout.write(`reddb: installed binary at ${binaryPath}\n`)
77
+ }
78
+
79
+ function defaultBinaryName() {
80
+ return process.platform === 'win32' ? 'red.exe' : 'red'
81
+ }
82
+
83
+ function normalizeTag(value) {
84
+ const v = String(value).trim()
85
+ return v.startsWith('v') ? v : `v${v}`
86
+ }