get-db9 0.6.0 → 0.6.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 +21 -57
- package/dist/{client-B3NUYqOc.d.cts → client-sRIN-o-Q.d.cts} +34 -87
- package/dist/{client-B3NUYqOc.d.ts → client-sRIN-o-Q.d.ts} +34 -87
- package/dist/client.cjs +87 -139
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +87 -139
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +88 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +88 -145
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ var Db9Error = class _Db9Error extends Error {
|
|
|
12
12
|
let message;
|
|
13
13
|
try {
|
|
14
14
|
const body = await response.json();
|
|
15
|
-
message = body.message || response.statusText;
|
|
15
|
+
message = body.message || body.detail || body.error_description || body.error || response.statusText;
|
|
16
16
|
} catch {
|
|
17
17
|
message = response.statusText;
|
|
18
18
|
}
|
|
@@ -183,17 +183,7 @@ var FileCredentialStore = class {
|
|
|
183
183
|
const parsed = parseToml(content);
|
|
184
184
|
const token = parsed["token"];
|
|
185
185
|
if (typeof token !== "string") return null;
|
|
186
|
-
|
|
187
|
-
if (typeof parsed["is_anonymous"] === "boolean") {
|
|
188
|
-
creds.is_anonymous = parsed["is_anonymous"];
|
|
189
|
-
}
|
|
190
|
-
if (typeof parsed["anonymous_id"] === "string") {
|
|
191
|
-
creds.anonymous_id = parsed["anonymous_id"];
|
|
192
|
-
}
|
|
193
|
-
if (typeof parsed["anonymous_secret"] === "string") {
|
|
194
|
-
creds.anonymous_secret = parsed["anonymous_secret"];
|
|
195
|
-
}
|
|
196
|
-
return creds;
|
|
186
|
+
return { token };
|
|
197
187
|
}
|
|
198
188
|
async save(credentials) {
|
|
199
189
|
const fs = await import("fs/promises");
|
|
@@ -214,15 +204,6 @@ var FileCredentialStore = class {
|
|
|
214
204
|
if (err.code !== "ENOENT") throw err;
|
|
215
205
|
}
|
|
216
206
|
data["token"] = credentials.token;
|
|
217
|
-
if (credentials.is_anonymous !== void 0) {
|
|
218
|
-
data["is_anonymous"] = credentials.is_anonymous;
|
|
219
|
-
}
|
|
220
|
-
if (credentials.anonymous_id !== void 0) {
|
|
221
|
-
data["anonymous_id"] = credentials.anonymous_id;
|
|
222
|
-
}
|
|
223
|
-
if (credentials.anonymous_secret !== void 0) {
|
|
224
|
-
data["anonymous_secret"] = credentials.anonymous_secret;
|
|
225
|
-
}
|
|
226
207
|
const toml = stringifyToml(
|
|
227
208
|
data
|
|
228
209
|
);
|
|
@@ -244,12 +225,7 @@ var MemoryCredentialStore = class {
|
|
|
244
225
|
return this.credentials ? { ...this.credentials } : null;
|
|
245
226
|
}
|
|
246
227
|
async save(credentials) {
|
|
247
|
-
this.credentials = {
|
|
248
|
-
token: credentials.token,
|
|
249
|
-
is_anonymous: credentials.is_anonymous ?? this.credentials?.is_anonymous,
|
|
250
|
-
anonymous_id: credentials.anonymous_id ?? this.credentials?.anonymous_id,
|
|
251
|
-
anonymous_secret: credentials.anonymous_secret ?? this.credentials?.anonymous_secret
|
|
252
|
-
};
|
|
228
|
+
this.credentials = { token: credentials.token };
|
|
253
229
|
}
|
|
254
230
|
async clear() {
|
|
255
231
|
this.credentials = null;
|
|
@@ -260,6 +236,7 @@ function defaultCredentialStore() {
|
|
|
260
236
|
}
|
|
261
237
|
|
|
262
238
|
// src/ws.ts
|
|
239
|
+
import { createHash } from "crypto";
|
|
263
240
|
var FsError = class extends Error {
|
|
264
241
|
code;
|
|
265
242
|
constructor(code, message) {
|
|
@@ -268,31 +245,28 @@ var FsError = class extends Error {
|
|
|
268
245
|
this.code = code;
|
|
269
246
|
}
|
|
270
247
|
};
|
|
248
|
+
var STREAMING_THRESHOLD = 1024 * 1024;
|
|
249
|
+
var DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|
271
250
|
var nextId = 1;
|
|
272
251
|
function nextRequestId() {
|
|
273
252
|
return String(nextId++);
|
|
274
253
|
}
|
|
275
254
|
function toBase64(data) {
|
|
276
255
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
277
|
-
|
|
278
|
-
return Buffer.from(bytes).toString("base64");
|
|
279
|
-
}
|
|
280
|
-
let binary = "";
|
|
281
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
282
|
-
binary += String.fromCharCode(bytes[i]);
|
|
283
|
-
}
|
|
284
|
-
return btoa(binary);
|
|
256
|
+
return Buffer.from(bytes).toString("base64");
|
|
285
257
|
}
|
|
286
258
|
function fromBase64(b64) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
259
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
260
|
+
}
|
|
261
|
+
function computeChecksum(data) {
|
|
262
|
+
const hash = createHash("sha256").update(data).digest("hex");
|
|
263
|
+
return `sha256:${hash}`;
|
|
264
|
+
}
|
|
265
|
+
function encodeBinaryFrame(streamId, chunk) {
|
|
266
|
+
const frame = Buffer.alloc(8 + chunk.length);
|
|
267
|
+
frame.writeBigUInt64BE(BigInt(streamId), 0);
|
|
268
|
+
frame.set(chunk, 8);
|
|
269
|
+
return frame;
|
|
296
270
|
}
|
|
297
271
|
var FsClient = class _FsClient {
|
|
298
272
|
ws;
|
|
@@ -410,9 +384,17 @@ var FsClient = class _FsClient {
|
|
|
410
384
|
}
|
|
411
385
|
return fromBase64(content);
|
|
412
386
|
}
|
|
413
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* Write (overwrite) a file. Returns bytes written.
|
|
389
|
+
*
|
|
390
|
+
* Automatically uses streaming mode for files >= 1 MB to avoid base64
|
|
391
|
+
* overhead and bypass the 2 MB JSON frame limit.
|
|
392
|
+
*/
|
|
414
393
|
async writeFile(path, data) {
|
|
415
394
|
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
395
|
+
if (bytes.byteLength >= STREAMING_THRESHOLD) {
|
|
396
|
+
return this.writeFileStreaming(path, bytes);
|
|
397
|
+
}
|
|
416
398
|
const resp = await this.sendAndRecv({
|
|
417
399
|
id: nextRequestId(),
|
|
418
400
|
op: "write",
|
|
@@ -461,6 +443,61 @@ var FsClient = class _FsClient {
|
|
|
461
443
|
this.ws.close();
|
|
462
444
|
}
|
|
463
445
|
}
|
|
446
|
+
// ── streaming internals ────────────────────────────────────────
|
|
447
|
+
/**
|
|
448
|
+
* Write a file using streaming mode (binary frames, no base64).
|
|
449
|
+
*
|
|
450
|
+
* Protocol:
|
|
451
|
+
* 1. Send streaming write request with file size
|
|
452
|
+
* 2. Receive ready response with stream_id and chunk_size
|
|
453
|
+
* 3. Send binary frames: [8-byte stream_id BE][chunk_data]
|
|
454
|
+
* 4. Send stream end with checksum
|
|
455
|
+
* 5. Receive final write confirmation
|
|
456
|
+
*/
|
|
457
|
+
async writeFileStreaming(path, bytes) {
|
|
458
|
+
const requestId = nextRequestId();
|
|
459
|
+
const readyResp = await this.sendAndRecv({
|
|
460
|
+
id: requestId,
|
|
461
|
+
op: "write",
|
|
462
|
+
path,
|
|
463
|
+
streaming: true,
|
|
464
|
+
size: bytes.byteLength
|
|
465
|
+
});
|
|
466
|
+
this.expectOk(readyResp);
|
|
467
|
+
const ready = readyResp.data;
|
|
468
|
+
if (!ready?.ready) {
|
|
469
|
+
throw new FsError("PROTOCOL", "server not ready for streaming");
|
|
470
|
+
}
|
|
471
|
+
const streamId = ready.stream_id;
|
|
472
|
+
const chunkSize = ready.chunk_size > 0 ? ready.chunk_size : DEFAULT_CHUNK_SIZE;
|
|
473
|
+
try {
|
|
474
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
475
|
+
const chunk = bytes.subarray(offset, offset + chunkSize);
|
|
476
|
+
const frame = encodeBinaryFrame(streamId, chunk);
|
|
477
|
+
this.ws.send(frame);
|
|
478
|
+
}
|
|
479
|
+
} catch (err) {
|
|
480
|
+
this.tryAbortStream(streamId);
|
|
481
|
+
throw new FsError("CONNECTION_ERROR", `send chunk: ${err}`);
|
|
482
|
+
}
|
|
483
|
+
const checksum = computeChecksum(bytes);
|
|
484
|
+
const finalResp = await this.sendAndRecv({
|
|
485
|
+
id: requestId,
|
|
486
|
+
stream: "end",
|
|
487
|
+
stream_id: streamId,
|
|
488
|
+
checksum
|
|
489
|
+
});
|
|
490
|
+
this.expectOk(finalResp);
|
|
491
|
+
const result = finalResp.data;
|
|
492
|
+
return result?.written ?? bytes.byteLength;
|
|
493
|
+
}
|
|
494
|
+
/** Best-effort abort of an in-progress stream so the server can clean up. */
|
|
495
|
+
tryAbortStream(streamId) {
|
|
496
|
+
try {
|
|
497
|
+
this.ws.send(JSON.stringify({ stream: "abort", stream_id: streamId }));
|
|
498
|
+
} catch {
|
|
499
|
+
}
|
|
500
|
+
}
|
|
464
501
|
// ── internals ──────────────────────────────────────────────────
|
|
465
502
|
sendAndRecv(request) {
|
|
466
503
|
if (this.closed) {
|
|
@@ -494,7 +531,7 @@ var FsClient = class _FsClient {
|
|
|
494
531
|
|
|
495
532
|
// src/client.ts
|
|
496
533
|
function createDb9Client(options = {}) {
|
|
497
|
-
const baseUrl = options.baseUrl ?? "https://db9.
|
|
534
|
+
const baseUrl = options.baseUrl ?? "https://db9.ai/api";
|
|
498
535
|
let token = options.token;
|
|
499
536
|
let tokenLoaded = !!token;
|
|
500
537
|
const store = options.credentialStore ?? defaultCredentialStore();
|
|
@@ -513,16 +550,10 @@ function createDb9Client(options = {}) {
|
|
|
513
550
|
tokenLoaded = true;
|
|
514
551
|
}
|
|
515
552
|
if (!token) {
|
|
516
|
-
|
|
517
|
-
"
|
|
553
|
+
throw new Db9Error(
|
|
554
|
+
"No token available. Run `db9 login` or pass a token via Db9ClientOptions.",
|
|
555
|
+
401
|
|
518
556
|
);
|
|
519
|
-
token = reg.token;
|
|
520
|
-
await store.save({
|
|
521
|
-
token: reg.token,
|
|
522
|
-
is_anonymous: reg.is_anonymous,
|
|
523
|
-
anonymous_id: reg.anonymous_id,
|
|
524
|
-
anonymous_secret: reg.anonymous_secret
|
|
525
|
-
});
|
|
526
557
|
}
|
|
527
558
|
return createHttpClient({
|
|
528
559
|
baseUrl,
|
|
@@ -533,43 +564,9 @@ function createDb9Client(options = {}) {
|
|
|
533
564
|
retryDelay: options.retryDelay
|
|
534
565
|
});
|
|
535
566
|
}
|
|
536
|
-
let refreshPromise = null;
|
|
537
|
-
async function refreshAnonymousToken() {
|
|
538
|
-
const creds = await store.load();
|
|
539
|
-
if (!creds?.anonymous_id || !creds?.anonymous_secret) {
|
|
540
|
-
throw new Error("Not an anonymous session");
|
|
541
|
-
}
|
|
542
|
-
const resp = await publicClient.post(
|
|
543
|
-
"/customer/anonymous-refresh",
|
|
544
|
-
{
|
|
545
|
-
anonymous_id: creds.anonymous_id,
|
|
546
|
-
anonymous_secret: creds.anonymous_secret
|
|
547
|
-
}
|
|
548
|
-
);
|
|
549
|
-
token = resp.token;
|
|
550
|
-
await store.save({ ...creds, token: resp.token });
|
|
551
|
-
}
|
|
552
567
|
async function withAuthRetry(operation) {
|
|
553
568
|
const client = await getAuthClient();
|
|
554
|
-
|
|
555
|
-
return await operation(client);
|
|
556
|
-
} catch (err) {
|
|
557
|
-
if (!(err instanceof Db9Error) || err.statusCode !== 401) {
|
|
558
|
-
throw err;
|
|
559
|
-
}
|
|
560
|
-
try {
|
|
561
|
-
if (!refreshPromise) {
|
|
562
|
-
refreshPromise = refreshAnonymousToken();
|
|
563
|
-
}
|
|
564
|
-
await refreshPromise;
|
|
565
|
-
} catch {
|
|
566
|
-
throw err;
|
|
567
|
-
} finally {
|
|
568
|
-
refreshPromise = null;
|
|
569
|
-
}
|
|
570
|
-
const newClient = await getAuthClient();
|
|
571
|
-
return operation(newClient);
|
|
572
|
-
}
|
|
569
|
+
return operation(client);
|
|
573
570
|
}
|
|
574
571
|
const fsWsPort = options.wsPort ?? 5480;
|
|
575
572
|
async function resolveFsConn(dbId) {
|
|
@@ -627,44 +624,11 @@ function createDb9Client(options = {}) {
|
|
|
627
624
|
}
|
|
628
625
|
return { message: raw };
|
|
629
626
|
}
|
|
630
|
-
async function fetchAnonymousSecret() {
|
|
631
|
-
return withAuthRetry(
|
|
632
|
-
(client) => client.post("/customer/anonymous-secret", {})
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
627
|
return {
|
|
636
628
|
auth: {
|
|
637
|
-
// Public endpoints (no token required)
|
|
638
|
-
register: (req) => publicClient.post("/customer/register", req),
|
|
639
|
-
login: (req) => publicClient.post("/customer/login", req),
|
|
640
|
-
anonymousRegister: () => publicClient.post(
|
|
641
|
-
"/customer/anonymous-register"
|
|
642
|
-
),
|
|
643
|
-
anonymousRefresh: (req) => publicClient.post(
|
|
644
|
-
"/customer/anonymous-refresh",
|
|
645
|
-
req
|
|
646
|
-
),
|
|
647
|
-
// Authenticated endpoints
|
|
648
629
|
me: async () => withAuthRetry(
|
|
649
630
|
(client) => client.get("/customer/me")
|
|
650
|
-
)
|
|
651
|
-
getAnonymousSecret: () => {
|
|
652
|
-
return fetchAnonymousSecret();
|
|
653
|
-
},
|
|
654
|
-
claim: async (req) => withAuthRetry(
|
|
655
|
-
(client) => client.post("/customer/claim", req)
|
|
656
|
-
),
|
|
657
|
-
ensureAnonymousSecret: async () => {
|
|
658
|
-
const creds = await store.load();
|
|
659
|
-
if (!creds?.anonymous_id || creds.anonymous_secret) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
const resp = await fetchAnonymousSecret();
|
|
663
|
-
await store.save({
|
|
664
|
-
...creds,
|
|
665
|
-
anonymous_secret: resp.anonymous_secret
|
|
666
|
-
});
|
|
667
|
-
}
|
|
631
|
+
)
|
|
668
632
|
},
|
|
669
633
|
tokens: {
|
|
670
634
|
list: async () => withAuthRetry(
|
|
@@ -844,27 +808,6 @@ function createDb9Client(options = {}) {
|
|
|
844
808
|
rename: async (dbId, oldPath, newPath) => {
|
|
845
809
|
await withFsClient(dbId, (client) => client.rename(oldPath, newPath));
|
|
846
810
|
}
|
|
847
|
-
},
|
|
848
|
-
// ── Device Auth Flow ─────────────────────────────────────────
|
|
849
|
-
deviceAuth: {
|
|
850
|
-
/** Start device code flow. Returns codes for user to authorize. */
|
|
851
|
-
createDeviceCode: () => publicClient.post("/customer/device-code"),
|
|
852
|
-
/** Poll for device token after user authorizes. Returns token or error status. */
|
|
853
|
-
pollDeviceToken: async (req) => {
|
|
854
|
-
try {
|
|
855
|
-
return await publicClient.post("/customer/device-token", req);
|
|
856
|
-
} catch (err) {
|
|
857
|
-
if (err instanceof Db9Error && err.statusCode === 400) {
|
|
858
|
-
const errorType = err.message;
|
|
859
|
-
if (errorType === "authorization_pending" || errorType === "expired_token" || errorType === "access_denied") {
|
|
860
|
-
return { error: errorType };
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
throw err;
|
|
864
|
-
}
|
|
865
|
-
},
|
|
866
|
-
/** Submit device verification with user credentials. */
|
|
867
|
-
verifyDevice: (req) => publicClient.post("/customer/device-verify", req)
|
|
868
811
|
}
|
|
869
812
|
};
|
|
870
813
|
}
|