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.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
- const creds = { token };
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
- if (typeof Buffer !== "undefined") {
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
- if (typeof Buffer !== "undefined") {
288
- return new Uint8Array(Buffer.from(b64, "base64"));
289
- }
290
- const binary = atob(b64);
291
- const bytes = new Uint8Array(binary.length);
292
- for (let i = 0; i < binary.length; i++) {
293
- bytes[i] = binary.charCodeAt(i);
294
- }
295
- return bytes;
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
- /** Write (overwrite) a file. Returns bytes written. */
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.shared.aws.tidbcloud.com/api";
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
- const reg = await publicClient.post(
517
- "/customer/anonymous-register"
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
- try {
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
  }