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.cjs
CHANGED
|
@@ -58,7 +58,7 @@ var Db9Error = class _Db9Error extends Error {
|
|
|
58
58
|
let message;
|
|
59
59
|
try {
|
|
60
60
|
const body = await response.json();
|
|
61
|
-
message = body.message || response.statusText;
|
|
61
|
+
message = body.message || body.detail || body.error_description || body.error || response.statusText;
|
|
62
62
|
} catch {
|
|
63
63
|
message = response.statusText;
|
|
64
64
|
}
|
|
@@ -229,17 +229,7 @@ var FileCredentialStore = class {
|
|
|
229
229
|
const parsed = (0, import_toml.parse)(content);
|
|
230
230
|
const token = parsed["token"];
|
|
231
231
|
if (typeof token !== "string") return null;
|
|
232
|
-
|
|
233
|
-
if (typeof parsed["is_anonymous"] === "boolean") {
|
|
234
|
-
creds.is_anonymous = parsed["is_anonymous"];
|
|
235
|
-
}
|
|
236
|
-
if (typeof parsed["anonymous_id"] === "string") {
|
|
237
|
-
creds.anonymous_id = parsed["anonymous_id"];
|
|
238
|
-
}
|
|
239
|
-
if (typeof parsed["anonymous_secret"] === "string") {
|
|
240
|
-
creds.anonymous_secret = parsed["anonymous_secret"];
|
|
241
|
-
}
|
|
242
|
-
return creds;
|
|
232
|
+
return { token };
|
|
243
233
|
}
|
|
244
234
|
async save(credentials) {
|
|
245
235
|
const fs = await import("fs/promises");
|
|
@@ -260,15 +250,6 @@ var FileCredentialStore = class {
|
|
|
260
250
|
if (err.code !== "ENOENT") throw err;
|
|
261
251
|
}
|
|
262
252
|
data["token"] = credentials.token;
|
|
263
|
-
if (credentials.is_anonymous !== void 0) {
|
|
264
|
-
data["is_anonymous"] = credentials.is_anonymous;
|
|
265
|
-
}
|
|
266
|
-
if (credentials.anonymous_id !== void 0) {
|
|
267
|
-
data["anonymous_id"] = credentials.anonymous_id;
|
|
268
|
-
}
|
|
269
|
-
if (credentials.anonymous_secret !== void 0) {
|
|
270
|
-
data["anonymous_secret"] = credentials.anonymous_secret;
|
|
271
|
-
}
|
|
272
253
|
const toml = (0, import_toml.stringify)(
|
|
273
254
|
data
|
|
274
255
|
);
|
|
@@ -290,12 +271,7 @@ var MemoryCredentialStore = class {
|
|
|
290
271
|
return this.credentials ? { ...this.credentials } : null;
|
|
291
272
|
}
|
|
292
273
|
async save(credentials) {
|
|
293
|
-
this.credentials = {
|
|
294
|
-
token: credentials.token,
|
|
295
|
-
is_anonymous: credentials.is_anonymous ?? this.credentials?.is_anonymous,
|
|
296
|
-
anonymous_id: credentials.anonymous_id ?? this.credentials?.anonymous_id,
|
|
297
|
-
anonymous_secret: credentials.anonymous_secret ?? this.credentials?.anonymous_secret
|
|
298
|
-
};
|
|
274
|
+
this.credentials = { token: credentials.token };
|
|
299
275
|
}
|
|
300
276
|
async clear() {
|
|
301
277
|
this.credentials = null;
|
|
@@ -306,6 +282,7 @@ function defaultCredentialStore() {
|
|
|
306
282
|
}
|
|
307
283
|
|
|
308
284
|
// src/ws.ts
|
|
285
|
+
var import_node_crypto = require("crypto");
|
|
309
286
|
var FsError = class extends Error {
|
|
310
287
|
code;
|
|
311
288
|
constructor(code, message) {
|
|
@@ -314,31 +291,28 @@ var FsError = class extends Error {
|
|
|
314
291
|
this.code = code;
|
|
315
292
|
}
|
|
316
293
|
};
|
|
294
|
+
var STREAMING_THRESHOLD = 1024 * 1024;
|
|
295
|
+
var DEFAULT_CHUNK_SIZE = 64 * 1024;
|
|
317
296
|
var nextId = 1;
|
|
318
297
|
function nextRequestId() {
|
|
319
298
|
return String(nextId++);
|
|
320
299
|
}
|
|
321
300
|
function toBase64(data) {
|
|
322
301
|
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
323
|
-
|
|
324
|
-
return Buffer.from(bytes).toString("base64");
|
|
325
|
-
}
|
|
326
|
-
let binary = "";
|
|
327
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
328
|
-
binary += String.fromCharCode(bytes[i]);
|
|
329
|
-
}
|
|
330
|
-
return btoa(binary);
|
|
302
|
+
return Buffer.from(bytes).toString("base64");
|
|
331
303
|
}
|
|
332
304
|
function fromBase64(b64) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
305
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
306
|
+
}
|
|
307
|
+
function computeChecksum(data) {
|
|
308
|
+
const hash = (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
309
|
+
return `sha256:${hash}`;
|
|
310
|
+
}
|
|
311
|
+
function encodeBinaryFrame(streamId, chunk) {
|
|
312
|
+
const frame = Buffer.alloc(8 + chunk.length);
|
|
313
|
+
frame.writeBigUInt64BE(BigInt(streamId), 0);
|
|
314
|
+
frame.set(chunk, 8);
|
|
315
|
+
return frame;
|
|
342
316
|
}
|
|
343
317
|
var FsClient = class _FsClient {
|
|
344
318
|
ws;
|
|
@@ -456,9 +430,17 @@ var FsClient = class _FsClient {
|
|
|
456
430
|
}
|
|
457
431
|
return fromBase64(content);
|
|
458
432
|
}
|
|
459
|
-
/**
|
|
433
|
+
/**
|
|
434
|
+
* Write (overwrite) a file. Returns bytes written.
|
|
435
|
+
*
|
|
436
|
+
* Automatically uses streaming mode for files >= 1 MB to avoid base64
|
|
437
|
+
* overhead and bypass the 2 MB JSON frame limit.
|
|
438
|
+
*/
|
|
460
439
|
async writeFile(path, data) {
|
|
461
440
|
const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
441
|
+
if (bytes.byteLength >= STREAMING_THRESHOLD) {
|
|
442
|
+
return this.writeFileStreaming(path, bytes);
|
|
443
|
+
}
|
|
462
444
|
const resp = await this.sendAndRecv({
|
|
463
445
|
id: nextRequestId(),
|
|
464
446
|
op: "write",
|
|
@@ -507,6 +489,61 @@ var FsClient = class _FsClient {
|
|
|
507
489
|
this.ws.close();
|
|
508
490
|
}
|
|
509
491
|
}
|
|
492
|
+
// ── streaming internals ────────────────────────────────────────
|
|
493
|
+
/**
|
|
494
|
+
* Write a file using streaming mode (binary frames, no base64).
|
|
495
|
+
*
|
|
496
|
+
* Protocol:
|
|
497
|
+
* 1. Send streaming write request with file size
|
|
498
|
+
* 2. Receive ready response with stream_id and chunk_size
|
|
499
|
+
* 3. Send binary frames: [8-byte stream_id BE][chunk_data]
|
|
500
|
+
* 4. Send stream end with checksum
|
|
501
|
+
* 5. Receive final write confirmation
|
|
502
|
+
*/
|
|
503
|
+
async writeFileStreaming(path, bytes) {
|
|
504
|
+
const requestId = nextRequestId();
|
|
505
|
+
const readyResp = await this.sendAndRecv({
|
|
506
|
+
id: requestId,
|
|
507
|
+
op: "write",
|
|
508
|
+
path,
|
|
509
|
+
streaming: true,
|
|
510
|
+
size: bytes.byteLength
|
|
511
|
+
});
|
|
512
|
+
this.expectOk(readyResp);
|
|
513
|
+
const ready = readyResp.data;
|
|
514
|
+
if (!ready?.ready) {
|
|
515
|
+
throw new FsError("PROTOCOL", "server not ready for streaming");
|
|
516
|
+
}
|
|
517
|
+
const streamId = ready.stream_id;
|
|
518
|
+
const chunkSize = ready.chunk_size > 0 ? ready.chunk_size : DEFAULT_CHUNK_SIZE;
|
|
519
|
+
try {
|
|
520
|
+
for (let offset = 0; offset < bytes.byteLength; offset += chunkSize) {
|
|
521
|
+
const chunk = bytes.subarray(offset, offset + chunkSize);
|
|
522
|
+
const frame = encodeBinaryFrame(streamId, chunk);
|
|
523
|
+
this.ws.send(frame);
|
|
524
|
+
}
|
|
525
|
+
} catch (err) {
|
|
526
|
+
this.tryAbortStream(streamId);
|
|
527
|
+
throw new FsError("CONNECTION_ERROR", `send chunk: ${err}`);
|
|
528
|
+
}
|
|
529
|
+
const checksum = computeChecksum(bytes);
|
|
530
|
+
const finalResp = await this.sendAndRecv({
|
|
531
|
+
id: requestId,
|
|
532
|
+
stream: "end",
|
|
533
|
+
stream_id: streamId,
|
|
534
|
+
checksum
|
|
535
|
+
});
|
|
536
|
+
this.expectOk(finalResp);
|
|
537
|
+
const result = finalResp.data;
|
|
538
|
+
return result?.written ?? bytes.byteLength;
|
|
539
|
+
}
|
|
540
|
+
/** Best-effort abort of an in-progress stream so the server can clean up. */
|
|
541
|
+
tryAbortStream(streamId) {
|
|
542
|
+
try {
|
|
543
|
+
this.ws.send(JSON.stringify({ stream: "abort", stream_id: streamId }));
|
|
544
|
+
} catch {
|
|
545
|
+
}
|
|
546
|
+
}
|
|
510
547
|
// ── internals ──────────────────────────────────────────────────
|
|
511
548
|
sendAndRecv(request) {
|
|
512
549
|
if (this.closed) {
|
|
@@ -540,7 +577,7 @@ var FsClient = class _FsClient {
|
|
|
540
577
|
|
|
541
578
|
// src/client.ts
|
|
542
579
|
function createDb9Client(options = {}) {
|
|
543
|
-
const baseUrl = options.baseUrl ?? "https://db9.
|
|
580
|
+
const baseUrl = options.baseUrl ?? "https://db9.ai/api";
|
|
544
581
|
let token = options.token;
|
|
545
582
|
let tokenLoaded = !!token;
|
|
546
583
|
const store = options.credentialStore ?? defaultCredentialStore();
|
|
@@ -559,16 +596,10 @@ function createDb9Client(options = {}) {
|
|
|
559
596
|
tokenLoaded = true;
|
|
560
597
|
}
|
|
561
598
|
if (!token) {
|
|
562
|
-
|
|
563
|
-
"
|
|
599
|
+
throw new Db9Error(
|
|
600
|
+
"No token available. Run `db9 login` or pass a token via Db9ClientOptions.",
|
|
601
|
+
401
|
|
564
602
|
);
|
|
565
|
-
token = reg.token;
|
|
566
|
-
await store.save({
|
|
567
|
-
token: reg.token,
|
|
568
|
-
is_anonymous: reg.is_anonymous,
|
|
569
|
-
anonymous_id: reg.anonymous_id,
|
|
570
|
-
anonymous_secret: reg.anonymous_secret
|
|
571
|
-
});
|
|
572
603
|
}
|
|
573
604
|
return createHttpClient({
|
|
574
605
|
baseUrl,
|
|
@@ -579,43 +610,9 @@ function createDb9Client(options = {}) {
|
|
|
579
610
|
retryDelay: options.retryDelay
|
|
580
611
|
});
|
|
581
612
|
}
|
|
582
|
-
let refreshPromise = null;
|
|
583
|
-
async function refreshAnonymousToken() {
|
|
584
|
-
const creds = await store.load();
|
|
585
|
-
if (!creds?.anonymous_id || !creds?.anonymous_secret) {
|
|
586
|
-
throw new Error("Not an anonymous session");
|
|
587
|
-
}
|
|
588
|
-
const resp = await publicClient.post(
|
|
589
|
-
"/customer/anonymous-refresh",
|
|
590
|
-
{
|
|
591
|
-
anonymous_id: creds.anonymous_id,
|
|
592
|
-
anonymous_secret: creds.anonymous_secret
|
|
593
|
-
}
|
|
594
|
-
);
|
|
595
|
-
token = resp.token;
|
|
596
|
-
await store.save({ ...creds, token: resp.token });
|
|
597
|
-
}
|
|
598
613
|
async function withAuthRetry(operation) {
|
|
599
614
|
const client = await getAuthClient();
|
|
600
|
-
|
|
601
|
-
return await operation(client);
|
|
602
|
-
} catch (err) {
|
|
603
|
-
if (!(err instanceof Db9Error) || err.statusCode !== 401) {
|
|
604
|
-
throw err;
|
|
605
|
-
}
|
|
606
|
-
try {
|
|
607
|
-
if (!refreshPromise) {
|
|
608
|
-
refreshPromise = refreshAnonymousToken();
|
|
609
|
-
}
|
|
610
|
-
await refreshPromise;
|
|
611
|
-
} catch {
|
|
612
|
-
throw err;
|
|
613
|
-
} finally {
|
|
614
|
-
refreshPromise = null;
|
|
615
|
-
}
|
|
616
|
-
const newClient = await getAuthClient();
|
|
617
|
-
return operation(newClient);
|
|
618
|
-
}
|
|
615
|
+
return operation(client);
|
|
619
616
|
}
|
|
620
617
|
const fsWsPort = options.wsPort ?? 5480;
|
|
621
618
|
async function resolveFsConn(dbId) {
|
|
@@ -673,44 +670,11 @@ function createDb9Client(options = {}) {
|
|
|
673
670
|
}
|
|
674
671
|
return { message: raw };
|
|
675
672
|
}
|
|
676
|
-
async function fetchAnonymousSecret() {
|
|
677
|
-
return withAuthRetry(
|
|
678
|
-
(client) => client.post("/customer/anonymous-secret", {})
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
673
|
return {
|
|
682
674
|
auth: {
|
|
683
|
-
// Public endpoints (no token required)
|
|
684
|
-
register: (req) => publicClient.post("/customer/register", req),
|
|
685
|
-
login: (req) => publicClient.post("/customer/login", req),
|
|
686
|
-
anonymousRegister: () => publicClient.post(
|
|
687
|
-
"/customer/anonymous-register"
|
|
688
|
-
),
|
|
689
|
-
anonymousRefresh: (req) => publicClient.post(
|
|
690
|
-
"/customer/anonymous-refresh",
|
|
691
|
-
req
|
|
692
|
-
),
|
|
693
|
-
// Authenticated endpoints
|
|
694
675
|
me: async () => withAuthRetry(
|
|
695
676
|
(client) => client.get("/customer/me")
|
|
696
|
-
)
|
|
697
|
-
getAnonymousSecret: () => {
|
|
698
|
-
return fetchAnonymousSecret();
|
|
699
|
-
},
|
|
700
|
-
claim: async (req) => withAuthRetry(
|
|
701
|
-
(client) => client.post("/customer/claim", req)
|
|
702
|
-
),
|
|
703
|
-
ensureAnonymousSecret: async () => {
|
|
704
|
-
const creds = await store.load();
|
|
705
|
-
if (!creds?.anonymous_id || creds.anonymous_secret) {
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const resp = await fetchAnonymousSecret();
|
|
709
|
-
await store.save({
|
|
710
|
-
...creds,
|
|
711
|
-
anonymous_secret: resp.anonymous_secret
|
|
712
|
-
});
|
|
713
|
-
}
|
|
677
|
+
)
|
|
714
678
|
},
|
|
715
679
|
tokens: {
|
|
716
680
|
list: async () => withAuthRetry(
|
|
@@ -890,27 +854,6 @@ function createDb9Client(options = {}) {
|
|
|
890
854
|
rename: async (dbId, oldPath, newPath) => {
|
|
891
855
|
await withFsClient(dbId, (client) => client.rename(oldPath, newPath));
|
|
892
856
|
}
|
|
893
|
-
},
|
|
894
|
-
// ── Device Auth Flow ─────────────────────────────────────────
|
|
895
|
-
deviceAuth: {
|
|
896
|
-
/** Start device code flow. Returns codes for user to authorize. */
|
|
897
|
-
createDeviceCode: () => publicClient.post("/customer/device-code"),
|
|
898
|
-
/** Poll for device token after user authorizes. Returns token or error status. */
|
|
899
|
-
pollDeviceToken: async (req) => {
|
|
900
|
-
try {
|
|
901
|
-
return await publicClient.post("/customer/device-token", req);
|
|
902
|
-
} catch (err) {
|
|
903
|
-
if (err instanceof Db9Error && err.statusCode === 400) {
|
|
904
|
-
const errorType = err.message;
|
|
905
|
-
if (errorType === "authorization_pending" || errorType === "expired_token" || errorType === "access_denied") {
|
|
906
|
-
return { error: errorType };
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
throw err;
|
|
910
|
-
}
|
|
911
|
-
},
|
|
912
|
-
/** Submit device verification with user credentials. */
|
|
913
|
-
verifyDevice: (req) => publicClient.post("/customer/device-verify", req)
|
|
914
857
|
}
|
|
915
858
|
};
|
|
916
859
|
}
|