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/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
- const creds = { token };
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
- if (typeof Buffer !== "undefined") {
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
- if (typeof Buffer !== "undefined") {
334
- return new Uint8Array(Buffer.from(b64, "base64"));
335
- }
336
- const binary = atob(b64);
337
- const bytes = new Uint8Array(binary.length);
338
- for (let i = 0; i < binary.length; i++) {
339
- bytes[i] = binary.charCodeAt(i);
340
- }
341
- return bytes;
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
- /** Write (overwrite) a file. Returns bytes written. */
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.shared.aws.tidbcloud.com/api";
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
- const reg = await publicClient.post(
563
- "/customer/anonymous-register"
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
- try {
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
  }