@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/LICENSE +661 -0
- package/README.md +199 -0
- package/index.d.ts +362 -0
- package/package.json +50 -0
- package/postinstall.js +86 -0
- package/src/binary.js +66 -0
- package/src/cache.js +137 -0
- package/src/cli.js +25 -0
- package/src/config.js +66 -0
- package/src/http.js +200 -0
- package/src/index.js +432 -0
- package/src/internal/asset-fetcher/asset-name.js +37 -0
- package/src/internal/asset-fetcher/checksum.js +23 -0
- package/src/internal/asset-fetcher/download.js +89 -0
- package/src/internal/asset-fetcher/index.js +52 -0
- package/src/internal/bin-resolver/index.js +57 -0
- package/src/internal/version-compare/index.js +163 -0
- package/src/kv.js +70 -0
- package/src/protocol.js +157 -0
- package/src/redwire.js +723 -0
- package/src/spawn.js +177 -0
- package/src/url.js +271 -0
- package/src/vault.js +58 -0
package/src/binary.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate the `red` binary for SDK / CLI use.
|
|
3
|
+
*
|
|
4
|
+
* SDK lookup (`resolveSdkBinary`):
|
|
5
|
+
* 1. `REDDB_BIN` env var (the canonical override per ADR 0006).
|
|
6
|
+
* 2. `REDDB_BINARY_PATH` env var (legacy alias, deprecation window).
|
|
7
|
+
* 3. `<package>/bin/red[.exe]` — where postinstall.js dropped it.
|
|
8
|
+
* 4. Otherwise throw an actionable error.
|
|
9
|
+
*
|
|
10
|
+
* PATH is **never** consulted. The wire-format coupling between the
|
|
11
|
+
* SDK and the embedded engine is too tight to silently bind to
|
|
12
|
+
* whatever `red` happens to be on PATH (see ADR 0006).
|
|
13
|
+
*
|
|
14
|
+
* CLI lookup (`resolveCliBinary`):
|
|
15
|
+
* 1. `REDDB_BIN` env var.
|
|
16
|
+
* 2. `<package>/bin/red[.exe]`.
|
|
17
|
+
* 3. PATH-resolved bare `red[.exe]` — appropriate for the CLI which
|
|
18
|
+
* *targets* PATH.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync } from 'node:fs'
|
|
22
|
+
import { fileURLToPath } from 'node:url'
|
|
23
|
+
import { dirname, resolve, join } from 'node:path'
|
|
24
|
+
|
|
25
|
+
import { resolveBin } from './internal/bin-resolver/index.js'
|
|
26
|
+
|
|
27
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
28
|
+
const PACKAGE_ROOT = resolve(HERE, '..')
|
|
29
|
+
|
|
30
|
+
function defaultBinaryName() {
|
|
31
|
+
if (typeof process !== 'undefined' && process.platform === 'win32') {
|
|
32
|
+
return 'red.exe'
|
|
33
|
+
}
|
|
34
|
+
return 'red'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** SDK runtime lookup. Throws actionable error when binary cannot be located. */
|
|
38
|
+
export function resolveSdkBinary() {
|
|
39
|
+
const legacy = process.env?.REDDB_BINARY_PATH
|
|
40
|
+
if (typeof legacy === 'string' && legacy !== '' && !process.env?.REDDB_BIN) {
|
|
41
|
+
return legacy
|
|
42
|
+
}
|
|
43
|
+
return resolveBin({
|
|
44
|
+
name: defaultBinaryName(),
|
|
45
|
+
packageRoot: PACKAGE_ROOT,
|
|
46
|
+
envVar: 'REDDB_BIN',
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** CLI runtime lookup. Allowed to fall back to PATH per ADR 0006. */
|
|
51
|
+
export function resolveCliBinary() {
|
|
52
|
+
const override = process.env?.REDDB_BIN
|
|
53
|
+
if (typeof override === 'string' && override !== '') {
|
|
54
|
+
return override
|
|
55
|
+
}
|
|
56
|
+
const local = join(PACKAGE_ROOT, 'bin', defaultBinaryName())
|
|
57
|
+
if (existsSync(local)) {
|
|
58
|
+
return local
|
|
59
|
+
}
|
|
60
|
+
return defaultBinaryName()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Used by postinstall.js to know where to drop the downloaded binary. */
|
|
64
|
+
export function packageBinaryDir() {
|
|
65
|
+
return join(PACKAGE_ROOT, 'bin')
|
|
66
|
+
}
|
package/src/cache.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache client — exposes cache.{get,put,exists,invalidate,invalidatePrefix,
|
|
3
|
+
* invalidateTags,flushNamespace} via the underlying HTTP transport.
|
|
4
|
+
*
|
|
5
|
+
* NOTE: These methods require server-side HTTP endpoints under /cache/ns/*.
|
|
6
|
+
* flushNamespace routes to the existing POST /admin/blob_cache/flush_namespace.
|
|
7
|
+
* All others target endpoints planned for a future server release.
|
|
8
|
+
*
|
|
9
|
+
* Values are base64-encoded in transit so binary payloads survive JSON.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class CacheClient {
|
|
13
|
+
/** @param {{ call: Function }} client */
|
|
14
|
+
constructor(client) {
|
|
15
|
+
this._client = client
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch a cached value. Returns a Uint8Array on hit, null on miss.
|
|
20
|
+
* @param {string} namespace
|
|
21
|
+
* @param {string} key
|
|
22
|
+
* @returns {Promise<Uint8Array | null>}
|
|
23
|
+
*/
|
|
24
|
+
async get(namespace, key) {
|
|
25
|
+
const result = await this._client.call('cache.get', { namespace, key })
|
|
26
|
+
if (result == null || result.value == null) return null
|
|
27
|
+
return base64ToBytes(result.value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Store a value in the cache.
|
|
32
|
+
* @param {string} namespace
|
|
33
|
+
* @param {string} key
|
|
34
|
+
* @param {Uint8Array | Buffer | string} value String is UTF-8 encoded.
|
|
35
|
+
* @param {object} [opts]
|
|
36
|
+
* @param {number} [opts.ttl_ms]
|
|
37
|
+
* @param {string[]} [opts.tags]
|
|
38
|
+
* @param {object} [opts.policy]
|
|
39
|
+
* @returns {Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
async put(namespace, key, value, opts = {}) {
|
|
42
|
+
const encoded = bytesToBase64(value)
|
|
43
|
+
await this._client.call('cache.put', {
|
|
44
|
+
namespace,
|
|
45
|
+
key,
|
|
46
|
+
value: encoded,
|
|
47
|
+
...opts,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check whether a key is present.
|
|
53
|
+
* @param {string} namespace
|
|
54
|
+
* @param {string} key
|
|
55
|
+
* @returns {Promise<'present' | 'absent' | 'maybe'>}
|
|
56
|
+
*/
|
|
57
|
+
async exists(namespace, key) {
|
|
58
|
+
const result = await this._client.call('cache.exists', { namespace, key })
|
|
59
|
+
return result?.status ?? 'maybe'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Remove a single entry.
|
|
64
|
+
* @param {string} namespace
|
|
65
|
+
* @param {string} key
|
|
66
|
+
* @returns {Promise<void>}
|
|
67
|
+
*/
|
|
68
|
+
async invalidate(namespace, key) {
|
|
69
|
+
await this._client.call('cache.invalidate', { namespace, key })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Remove all entries whose key starts with `prefix`.
|
|
74
|
+
* @param {string} namespace
|
|
75
|
+
* @param {string} prefix
|
|
76
|
+
* @returns {Promise<number>} Number of entries removed.
|
|
77
|
+
*/
|
|
78
|
+
async invalidatePrefix(namespace, prefix) {
|
|
79
|
+
const result = await this._client.call('cache.invalidate_prefix', { namespace, prefix })
|
|
80
|
+
return result?.removed ?? 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Remove all entries tagged with any of the given tags.
|
|
85
|
+
* @param {string} namespace
|
|
86
|
+
* @param {string[]} tags
|
|
87
|
+
* @returns {Promise<number>} Number of entries removed.
|
|
88
|
+
*/
|
|
89
|
+
async invalidateTags(namespace, tags) {
|
|
90
|
+
const result = await this._client.call('cache.invalidate_tags', { namespace, tags })
|
|
91
|
+
return result?.removed ?? 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Remove all entries in a namespace.
|
|
96
|
+
* Routes to POST /admin/blob_cache/flush_namespace (live endpoint).
|
|
97
|
+
* @param {string} namespace
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
100
|
+
async flushNamespace(namespace) {
|
|
101
|
+
await this._client.call('cache.flush_namespace', { namespace })
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Helpers
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
function bytesToBase64(value) {
|
|
110
|
+
if (typeof value === 'string') {
|
|
111
|
+
const bytes = new TextEncoder().encode(value)
|
|
112
|
+
return bufToBase64(bytes)
|
|
113
|
+
}
|
|
114
|
+
if (value instanceof Uint8Array || (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))) {
|
|
115
|
+
return bufToBase64(value)
|
|
116
|
+
}
|
|
117
|
+
throw new TypeError('cache value must be a string, Uint8Array, or Buffer')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function bufToBase64(bytes) {
|
|
121
|
+
if (typeof Buffer !== 'undefined') {
|
|
122
|
+
return Buffer.from(bytes).toString('base64')
|
|
123
|
+
}
|
|
124
|
+
let bin = ''
|
|
125
|
+
for (const b of bytes) bin += String.fromCharCode(b)
|
|
126
|
+
return btoa(bin)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function base64ToBytes(b64) {
|
|
130
|
+
if (typeof Buffer !== 'undefined') {
|
|
131
|
+
return new Uint8Array(Buffer.from(b64, 'base64'))
|
|
132
|
+
}
|
|
133
|
+
const bin = atob(b64)
|
|
134
|
+
const out = new Uint8Array(bin.length)
|
|
135
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)
|
|
136
|
+
return out
|
|
137
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process'
|
|
4
|
+
import { resolveCliBinary } from './binary.js'
|
|
5
|
+
|
|
6
|
+
const binary = resolveCliBinary()
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
|
|
9
|
+
const child = spawn(binary, args, {
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
env: process.env,
|
|
12
|
+
stdio: 'inherit',
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
child.on('error', (err) => {
|
|
16
|
+
process.stderr.write(`reddb-cli: ${err.message}\n`)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
child.on('exit', (code, signal) => {
|
|
21
|
+
if (signal) {
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
process.exit(code ?? 0)
|
|
25
|
+
})
|
package/src/config.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export class ConfigClient {
|
|
2
|
+
constructor(client, collection = 'red.config') {
|
|
3
|
+
this.client = client
|
|
4
|
+
this.collection = collection
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
put(key, value, options = {}) {
|
|
8
|
+
rejectVolatileOptions(options, 'config')
|
|
9
|
+
const collection = options.collection ?? this.collection
|
|
10
|
+
const tags = Array.isArray(options.tags) && options.tags.length > 0
|
|
11
|
+
? ` TAGS [${options.tags.map(keyedStringLiteral).join(', ')}]`
|
|
12
|
+
: ''
|
|
13
|
+
return this.client.call('query', {
|
|
14
|
+
sql: `PUT CONFIG ${keyedIdentifier(collection)} ${keyedIdentifier(key)} = ${configValueLiteral(value, options)}${tags}`,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(key, options = {}) {
|
|
19
|
+
const collection = options.collection ?? this.collection
|
|
20
|
+
return this.client.call('query', {
|
|
21
|
+
sql: `GET CONFIG ${keyedIdentifier(collection)} ${keyedIdentifier(key)}`,
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolve(key, options = {}) {
|
|
26
|
+
const collection = options.collection ?? this.collection
|
|
27
|
+
return this.client.call('query', {
|
|
28
|
+
sql: `RESOLVE CONFIG ${keyedIdentifier(collection)} ${keyedIdentifier(key)}`,
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function configValueLiteral(value, options) {
|
|
34
|
+
if (options.secretRef) {
|
|
35
|
+
const { collection, key } = options.secretRef
|
|
36
|
+
return `SECRET_REF(vault, ${keyedIdentifier(collection)}.${keyedIdentifier(key)})`
|
|
37
|
+
}
|
|
38
|
+
return keyedValueLiteral(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function rejectVolatileOptions(options, domain) {
|
|
42
|
+
for (const field of ['ttl', 'ttlMs', 'ttl_ms', 'expireMs', 'expire_ms', 'expiresAt']) {
|
|
43
|
+
if (options[field] != null) {
|
|
44
|
+
throw new TypeError(`${domain} does not support TTL or expiration options`)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function keyedIdentifier(value) {
|
|
50
|
+
const out = String(value)
|
|
51
|
+
if (!/^[A-Za-z0-9_.]+$/.test(out)) {
|
|
52
|
+
throw new TypeError('keyed collection and key names must use letters, numbers, underscores, or dots')
|
|
53
|
+
}
|
|
54
|
+
return out
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function keyedValueLiteral(value) {
|
|
58
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
|
59
|
+
if (value == null) return 'NULL'
|
|
60
|
+
if (Array.isArray(value) || typeof value === 'object') return JSON.stringify(value)
|
|
61
|
+
return keyedStringLiteral(value)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function keyedStringLiteral(value) {
|
|
65
|
+
return `'${String(value).replace(/'/g, "''")}'`
|
|
66
|
+
}
|
package/src/http.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP transport for the JS driver.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the public surface of `RpcClient` (call, close) but talks
|
|
5
|
+
* straight to the RedDB HTTP server via fetch() — no binary spawn.
|
|
6
|
+
* Each `RedDB` method is mapped to a REST endpoint; method names
|
|
7
|
+
* stay identical to the JSON-RPC ones so `RedDB` doesn't need to
|
|
8
|
+
* know which transport it's using.
|
|
9
|
+
*
|
|
10
|
+
* Endpoint mapping (server-side defined in src/server/routing.rs):
|
|
11
|
+
*
|
|
12
|
+
* query / explain → POST /query, POST /query/explain
|
|
13
|
+
* insert → POST /collections/:name/rows
|
|
14
|
+
* bulk_insert → POST /collections/:name/bulk/rows
|
|
15
|
+
* get → GET /collections/:name/{id} (entity scan + filter)
|
|
16
|
+
* delete → DELETE /collections/:name/{id}
|
|
17
|
+
* health → GET /health
|
|
18
|
+
* version → GET /admin/version
|
|
19
|
+
* auth.login → POST /auth/login
|
|
20
|
+
* auth.whoami → GET /auth/whoami
|
|
21
|
+
* auth.create_api_key → POST /auth/api-keys
|
|
22
|
+
* auth.revoke_api_key → DELETE /auth/api-keys/:key
|
|
23
|
+
* auth.change_password→ POST /auth/change-password
|
|
24
|
+
*
|
|
25
|
+
* cache.get → GET /cache/ns/:ns/:key
|
|
26
|
+
* cache.put → PUT /cache/ns/:ns/:key
|
|
27
|
+
* cache.exists → GET /cache/ns/:ns/:key/exists
|
|
28
|
+
* cache.invalidate → DELETE /cache/ns/:ns/:key
|
|
29
|
+
* cache.invalidate_prefix → DELETE /cache/ns/:ns?prefix=...
|
|
30
|
+
* cache.invalidate_tags → DELETE /cache/ns/:ns/tags (body: {tags})
|
|
31
|
+
* cache.flush_namespace → POST /admin/blob_cache/flush_namespace
|
|
32
|
+
*
|
|
33
|
+
* Auth: every request after construction carries `Authorization:
|
|
34
|
+
* Bearer <token>` (when set). `setToken(token)` updates it in
|
|
35
|
+
* place — used by the login flow that exchanges credentials for
|
|
36
|
+
* a fresh bearer.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { RedDBError } from './protocol.js'
|
|
40
|
+
|
|
41
|
+
export class HttpRpcClient {
|
|
42
|
+
/**
|
|
43
|
+
* @param {object} opts
|
|
44
|
+
* @param {string} opts.baseUrl Server origin, e.g. 'https://reddb.example.com:8443'
|
|
45
|
+
* @param {string} [opts.token] Bearer token / API key
|
|
46
|
+
*/
|
|
47
|
+
constructor({ baseUrl, token }) {
|
|
48
|
+
if (typeof baseUrl !== 'string' || baseUrl.length === 0) {
|
|
49
|
+
throw new TypeError('HttpRpcClient: baseUrl required')
|
|
50
|
+
}
|
|
51
|
+
this.baseUrl = baseUrl.replace(/\/$/, '')
|
|
52
|
+
this.token = token ?? null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setToken(token) {
|
|
56
|
+
this.token = token
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async close() {
|
|
60
|
+
// HTTP is stateless — nothing to close.
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generic RPC entry point. Routes the named method to the
|
|
65
|
+
* corresponding HTTP endpoint and returns the parsed JSON body.
|
|
66
|
+
*/
|
|
67
|
+
async call(method, params = {}) {
|
|
68
|
+
const route = ROUTES[method]
|
|
69
|
+
if (!route) {
|
|
70
|
+
throw new RedDBError(
|
|
71
|
+
'UNKNOWN_METHOD',
|
|
72
|
+
`HTTP transport has no route for method '${method}'`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
const { url, init } = route(this.baseUrl, params)
|
|
76
|
+
const response = await fetch(url, this.attachAuth(init))
|
|
77
|
+
return parseResponse(response)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
attachAuth(init) {
|
|
81
|
+
const headers = new Headers(init.headers || {})
|
|
82
|
+
if (this.token && !headers.has('authorization')) {
|
|
83
|
+
headers.set('authorization', `Bearer ${this.token}`)
|
|
84
|
+
}
|
|
85
|
+
if (init.body && !headers.has('content-type')) {
|
|
86
|
+
headers.set('content-type', 'application/json')
|
|
87
|
+
}
|
|
88
|
+
return { ...init, headers }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function parseResponse(response) {
|
|
93
|
+
const text = await response.text()
|
|
94
|
+
let body = null
|
|
95
|
+
if (text) {
|
|
96
|
+
try {
|
|
97
|
+
body = JSON.parse(text)
|
|
98
|
+
} catch {
|
|
99
|
+
body = { raw: text }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
const code = body?.error_code || `HTTP_${response.status}`
|
|
104
|
+
const message = body?.error || body?.message || `request failed with status ${response.status}`
|
|
105
|
+
throw new RedDBError(code, message, body)
|
|
106
|
+
}
|
|
107
|
+
// RedDB envelope is `{ ok, result, error? }` for some endpoints
|
|
108
|
+
// and bare JSON for others. Unwrap the envelope when present.
|
|
109
|
+
if (body && typeof body === 'object' && 'ok' in body) {
|
|
110
|
+
if (body.ok === false) {
|
|
111
|
+
const code = body.error_code || 'RPC_ERROR'
|
|
112
|
+
throw new RedDBError(code, body.error || 'unknown error', body)
|
|
113
|
+
}
|
|
114
|
+
return body.result ?? body
|
|
115
|
+
}
|
|
116
|
+
return body
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Method → HTTP route mapping
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
const ROUTES = {
|
|
124
|
+
health: (base) => ({ url: `${base}/health`, init: { method: 'GET' } }),
|
|
125
|
+
version: (base) => ({ url: `${base}/admin/version`, init: { method: 'GET' } }),
|
|
126
|
+
query: (base, { sql }) => ({
|
|
127
|
+
url: `${base}/query`,
|
|
128
|
+
init: { method: 'POST', body: JSON.stringify({ query: sql }) },
|
|
129
|
+
}),
|
|
130
|
+
insert: (base, { collection, payload }) => ({
|
|
131
|
+
url: `${base}/collections/${encodeURIComponent(collection)}/rows`,
|
|
132
|
+
init: { method: 'POST', body: JSON.stringify(payload) },
|
|
133
|
+
}),
|
|
134
|
+
bulk_insert: (base, { collection, payloads }) => ({
|
|
135
|
+
url: `${base}/collections/${encodeURIComponent(collection)}/bulk/rows`,
|
|
136
|
+
init: { method: 'POST', body: JSON.stringify({ rows: payloads }) },
|
|
137
|
+
}),
|
|
138
|
+
get: (base, { collection, id }) => ({
|
|
139
|
+
url: `${base}/collections/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,
|
|
140
|
+
init: { method: 'GET' },
|
|
141
|
+
}),
|
|
142
|
+
delete: (base, { collection, id }) => ({
|
|
143
|
+
url: `${base}/collections/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`,
|
|
144
|
+
init: { method: 'DELETE' },
|
|
145
|
+
}),
|
|
146
|
+
'auth.login': (base, { username, password }) => ({
|
|
147
|
+
url: `${base}/auth/login`,
|
|
148
|
+
init: { method: 'POST', body: JSON.stringify({ username, password }) },
|
|
149
|
+
}),
|
|
150
|
+
'auth.whoami': (base) => ({
|
|
151
|
+
url: `${base}/auth/whoami`,
|
|
152
|
+
init: { method: 'GET' },
|
|
153
|
+
}),
|
|
154
|
+
'auth.create_api_key': (base, params = {}) => ({
|
|
155
|
+
url: `${base}/auth/api-keys`,
|
|
156
|
+
init: { method: 'POST', body: JSON.stringify(params) },
|
|
157
|
+
}),
|
|
158
|
+
'auth.revoke_api_key': (base, { key }) => ({
|
|
159
|
+
url: `${base}/auth/api-keys/${encodeURIComponent(key)}`,
|
|
160
|
+
init: { method: 'DELETE' },
|
|
161
|
+
}),
|
|
162
|
+
'auth.change_password': (base, { current_password, new_password }) => ({
|
|
163
|
+
url: `${base}/auth/change-password`,
|
|
164
|
+
init: {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body: JSON.stringify({
|
|
167
|
+
current_password,
|
|
168
|
+
new_password,
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
'cache.get': (base, { namespace, key }) => ({
|
|
173
|
+
url: `${base}/cache/ns/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`,
|
|
174
|
+
init: { method: 'GET' },
|
|
175
|
+
}),
|
|
176
|
+
'cache.put': (base, { namespace, key, value, ttl_ms, tags, policy }) => ({
|
|
177
|
+
url: `${base}/cache/ns/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`,
|
|
178
|
+
init: { method: 'PUT', body: JSON.stringify({ value, ttl_ms, tags, policy }) },
|
|
179
|
+
}),
|
|
180
|
+
'cache.exists': (base, { namespace, key }) => ({
|
|
181
|
+
url: `${base}/cache/ns/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}/exists`,
|
|
182
|
+
init: { method: 'GET' },
|
|
183
|
+
}),
|
|
184
|
+
'cache.invalidate': (base, { namespace, key }) => ({
|
|
185
|
+
url: `${base}/cache/ns/${encodeURIComponent(namespace)}/${encodeURIComponent(key)}`,
|
|
186
|
+
init: { method: 'DELETE' },
|
|
187
|
+
}),
|
|
188
|
+
'cache.invalidate_prefix': (base, { namespace, prefix }) => ({
|
|
189
|
+
url: `${base}/cache/ns/${encodeURIComponent(namespace)}?prefix=${encodeURIComponent(prefix)}`,
|
|
190
|
+
init: { method: 'DELETE' },
|
|
191
|
+
}),
|
|
192
|
+
'cache.invalidate_tags': (base, { namespace, tags }) => ({
|
|
193
|
+
url: `${base}/cache/ns/${encodeURIComponent(namespace)}/tags`,
|
|
194
|
+
init: { method: 'DELETE', body: JSON.stringify({ tags }) },
|
|
195
|
+
}),
|
|
196
|
+
'cache.flush_namespace': (base, { namespace }) => ({
|
|
197
|
+
url: `${base}/admin/blob_cache/flush_namespace`,
|
|
198
|
+
init: { method: 'POST', body: JSON.stringify({ namespace }) },
|
|
199
|
+
}),
|
|
200
|
+
}
|