@tinycloud/operations 0.3.4-beta.0 → 0.3.4-beta.2

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.
@@ -1256,10 +1256,14 @@ async function responseError(service, message, response) {
1256
1256
  }
1257
1257
  } catch {
1258
1258
  }
1259
- return wrapError(
1259
+ const error = wrapError(
1260
1260
  service,
1261
1261
  new Error(`${message}: ${response.status} ${detail}`)
1262
1262
  );
1263
+ return {
1264
+ ...error,
1265
+ meta: { ...error.meta, status: response.status, statusText: response.statusText }
1266
+ };
1263
1267
  }
1264
1268
  function isAbortError(error) {
1265
1269
  return error instanceof DOMException && error.name === "AbortError";
@@ -3079,7 +3083,7 @@ var init_dist2 = __esm({
3079
3083
  return err(
3080
3084
  storageQuotaExceededError(
3081
3085
  "kv",
3082
- `Storage quota exceeded for key "${key}": ${errorText}`,
3086
+ `Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
3083
3087
  {
3084
3088
  status: response.status,
3085
3089
  ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
@@ -3092,7 +3096,7 @@ var init_dist2 = __esm({
3092
3096
  return err(
3093
3097
  storageLimitReachedError(
3094
3098
  "kv",
3095
- `Storage limit reached for key "${key}": ${errorText}`,
3099
+ `Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
3096
3100
  {
3097
3101
  status: response.status,
3098
3102
  ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
@@ -3378,12 +3382,11 @@ var init_dist2 = __esm({
3378
3382
  if (!response.ok) {
3379
3383
  const errorText = await response.text();
3380
3384
  if (response.status === 401 || response.status === 403) {
3381
- const { resource, action: requiredAction } = parseAuthError(errorText);
3382
- return err(authUnauthorizedError("kv", errorText, {
3383
- status: response.status,
3384
- ...requiredAction && { requiredAction },
3385
- ...resource && { resource }
3386
- }));
3385
+ return this.authorizationFailure(
3386
+ `Failed to batch read ${keys.length} key(s)`,
3387
+ response,
3388
+ errorText
3389
+ );
3387
3390
  }
3388
3391
  if (response.status === 413) {
3389
3392
  return err(serviceError(
@@ -3482,23 +3485,39 @@ var init_dist2 = __esm({
3482
3485
  } catch {
3483
3486
  }
3484
3487
  if (response.status === 401 || response.status === 403) {
3485
- const { resource, action } = parseAuthError(errorText);
3486
- return err(authUnauthorizedError("kv", errorText, {
3487
- status: response.status,
3488
- ...action && { requiredAction: action },
3489
- ...resource && { resource }
3490
- }));
3488
+ return this.authorizationFailure(
3489
+ `Failed to create signed read URL for key ${JSON.stringify(key)}`,
3490
+ response,
3491
+ errorText
3492
+ );
3491
3493
  }
3492
3494
  const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
3493
3495
  return err(
3494
3496
  serviceError(
3495
3497
  code,
3496
- `Failed to create signed read URL for key "${key}": ${response.status} - ${errorText}`,
3498
+ `Failed to create signed read URL for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3497
3499
  "kv",
3498
3500
  { meta: { status: response.status, statusText: response.statusText } }
3499
3501
  )
3500
3502
  );
3501
3503
  }
3504
+ /**
3505
+ * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
3506
+ * status and the server text (`<context>: <status> - <text>`, the same
3507
+ * shape as every other KV failure) so callers that rethrow only the message
3508
+ * still see the status; `meta.status` carries it for typed callers.
3509
+ * Resource/ability hints are parsed from the raw body, never the message.
3510
+ */
3511
+ authorizationFailure(context, response, errorText, extraMeta = {}) {
3512
+ const { resource, action } = parseAuthError(errorText);
3513
+ const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
3514
+ return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
3515
+ status: response.status,
3516
+ ...action && { requiredAction: action },
3517
+ ...resource && { resource },
3518
+ ...extraMeta
3519
+ }));
3520
+ }
3502
3521
  normalizeSignedReadUrlResponse(data) {
3503
3522
  if (!data || typeof data !== "object") {
3504
3523
  return void 0;
@@ -3541,14 +3560,13 @@ var init_dist2 = __esm({
3541
3560
  if (!response.ok) {
3542
3561
  if (response.status === 401 || response.status === 403) {
3543
3562
  const errorText2 = await response.text();
3544
- const { resource, action } = parseAuthError(errorText2);
3545
3563
  const permissionHint = parsePermissionHintFromErrorText(errorText2);
3546
- return err(authUnauthorizedError("kv", errorText2, {
3547
- status: response.status,
3548
- ...action && { requiredAction: action },
3549
- ...resource && { resource },
3550
- ...permissionHint === void 0 ? {} : { permissionHint }
3551
- }));
3564
+ return this.authorizationFailure(
3565
+ `Failed to get key ${JSON.stringify(key)}`,
3566
+ response,
3567
+ errorText2,
3568
+ permissionHint === void 0 ? {} : { permissionHint }
3569
+ );
3552
3570
  }
3553
3571
  if (response.status === 404) {
3554
3572
  return this.classifyNotFound(response, key);
@@ -3557,7 +3575,7 @@ var init_dist2 = __esm({
3557
3575
  if (response.status === 413) {
3558
3576
  return err(serviceError(
3559
3577
  ErrorCodes.KV_RESPONSE_TOO_LARGE,
3560
- `KV value at key "${key}" exceeds the requested response limit`,
3578
+ `KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
3561
3579
  "kv",
3562
3580
  { meta: { status: response.status, statusText: response.statusText } }
3563
3581
  ));
@@ -3565,7 +3583,7 @@ var init_dist2 = __esm({
3565
3583
  return err(
3566
3584
  serviceError(
3567
3585
  ErrorCodes.NETWORK_ERROR,
3568
- `Failed to get key "${key}": ${response.status} - ${errorText}`,
3586
+ `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3569
3587
  "kv",
3570
3588
  { meta: { status: response.status, statusText: response.statusText } }
3571
3589
  )
@@ -3621,21 +3639,18 @@ var init_dist2 = __esm({
3621
3639
  }
3622
3640
  );
3623
3641
  if (response.status === 401 || response.status === 403) {
3624
- const errorText = await response.text();
3625
- const message = errorText.trim().length > 0 ? errorText : `Failed to put key "${key}": ${response.status} - ${response.statusText || "authorization failed"}`;
3626
- const { resource, action: requiredAction } = parseAuthError(errorText);
3627
- return err(authUnauthorizedError("kv", message, {
3628
- status: response.status,
3629
- ...requiredAction && { requiredAction },
3630
- ...resource && { resource }
3631
- }));
3642
+ return this.authorizationFailure(
3643
+ `Failed to put key ${JSON.stringify(key)}`,
3644
+ response,
3645
+ await response.text()
3646
+ );
3632
3647
  }
3633
3648
  if (!response.ok) {
3634
3649
  const errorText = await response.text();
3635
3650
  if (response.status === 412) {
3636
3651
  return err(serviceError(
3637
3652
  ErrorCodes.KV_PRECONDITION_FAILED,
3638
- `KV precondition failed for key "${key}"`,
3653
+ `KV precondition failed for key ${JSON.stringify(key)}`,
3639
3654
  "kv",
3640
3655
  { meta: { status: response.status, statusText: response.statusText } }
3641
3656
  ));
@@ -3643,7 +3658,7 @@ var init_dist2 = __esm({
3643
3658
  if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
3644
3659
  return err(serviceError(
3645
3660
  ErrorCodes.KV_CONFLICT,
3646
- `Concurrent KV update conflicted for key "${key}"`,
3661
+ `Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
3647
3662
  "kv",
3648
3663
  { meta: { status: response.status, statusText: response.statusText } }
3649
3664
  ));
@@ -3659,7 +3674,7 @@ var init_dist2 = __esm({
3659
3674
  return err(
3660
3675
  serviceError(
3661
3676
  ErrorCodes.KV_WRITE_FAILED,
3662
- `Failed to put key "${key}": ${response.status} - ${errorText}`,
3677
+ `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3663
3678
  "kv",
3664
3679
  { meta: { status: response.status, statusText: response.statusText } }
3665
3680
  )
@@ -3753,12 +3768,11 @@ var init_dist2 = __esm({
3753
3768
  );
3754
3769
  }
3755
3770
  if (response.status === 401 || response.status === 403) {
3756
- const { resource, action } = parseAuthError(errorText);
3757
- return err(authUnauthorizedError("kv", errorText, {
3758
- status: response.status,
3759
- ...action && { requiredAction: action },
3760
- ...resource && { resource }
3761
- }));
3771
+ return this.authorizationFailure(
3772
+ `Failed to batch put ${items.length} key(s)`,
3773
+ response,
3774
+ errorText
3775
+ );
3762
3776
  }
3763
3777
  const quotaError = this.handleQuotaErrorResponse(
3764
3778
  response,
@@ -3879,13 +3893,11 @@ var init_dist2 = __esm({
3879
3893
  );
3880
3894
  if (!response.ok) {
3881
3895
  if (response.status === 401) {
3882
- const errorText2 = await response.text();
3883
- const { resource, action } = parseAuthError(errorText2);
3884
- return err(authUnauthorizedError("kv", errorText2, {
3885
- status: response.status,
3886
- ...action && { requiredAction: action },
3887
- ...resource && { resource }
3888
- }));
3896
+ return this.authorizationFailure(
3897
+ "Failed to list keys",
3898
+ response,
3899
+ await response.text()
3900
+ );
3889
3901
  }
3890
3902
  const errorText = await response.text();
3891
3903
  return err(
@@ -3934,13 +3946,11 @@ var init_dist2 = __esm({
3934
3946
  );
3935
3947
  if (!response.ok) {
3936
3948
  if (response.status === 401) {
3937
- const errorText2 = await response.text();
3938
- const { resource, action } = parseAuthError(errorText2);
3939
- return err(authUnauthorizedError("kv", errorText2, {
3940
- status: response.status,
3941
- ...action && { requiredAction: action },
3942
- ...resource && { resource }
3943
- }));
3949
+ return this.authorizationFailure(
3950
+ `Failed to delete key ${JSON.stringify(key)}`,
3951
+ response,
3952
+ await response.text()
3953
+ );
3944
3954
  }
3945
3955
  if (response.status === 404) {
3946
3956
  return this.classifyNotFound(response, key);
@@ -3949,7 +3959,7 @@ var init_dist2 = __esm({
3949
3959
  if (response.status === 412) {
3950
3960
  return err(serviceError(
3951
3961
  ErrorCodes.KV_PRECONDITION_FAILED,
3952
- `KV precondition failed for key "${key}"`,
3962
+ `KV precondition failed for key ${JSON.stringify(key)}`,
3953
3963
  "kv",
3954
3964
  { meta: { status: response.status, statusText: response.statusText } }
3955
3965
  ));
@@ -3957,7 +3967,7 @@ var init_dist2 = __esm({
3957
3967
  if (response.status === 503 && options?.ifMatch !== void 0) {
3958
3968
  return err(serviceError(
3959
3969
  ErrorCodes.KV_CONFLICT,
3960
- `Concurrent KV delete conflicted for key "${key}"`,
3970
+ `Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
3961
3971
  "kv",
3962
3972
  { meta: { status: response.status, statusText: response.statusText } }
3963
3973
  ));
@@ -3965,7 +3975,7 @@ var init_dist2 = __esm({
3965
3975
  return err(
3966
3976
  serviceError(
3967
3977
  ErrorCodes.NETWORK_ERROR,
3968
- `Failed to delete key "${key}": ${response.status} - ${errorText}`,
3978
+ `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3969
3979
  "kv",
3970
3980
  { meta: { status: response.status, statusText: response.statusText } }
3971
3981
  )
@@ -3998,13 +4008,11 @@ var init_dist2 = __esm({
3998
4008
  );
3999
4009
  if (!response.ok) {
4000
4010
  if (response.status === 401) {
4001
- const errorText2 = await response.text();
4002
- const { resource, action } = parseAuthError(errorText2);
4003
- return err(authUnauthorizedError("kv", errorText2, {
4004
- status: response.status,
4005
- ...action && { requiredAction: action },
4006
- ...resource && { resource }
4007
- }));
4011
+ return this.authorizationFailure(
4012
+ `Failed to get metadata for key ${JSON.stringify(key)}`,
4013
+ response,
4014
+ await response.text()
4015
+ );
4008
4016
  }
4009
4017
  if (response.status === 404) {
4010
4018
  return this.classifyNotFound(response, key);
@@ -4013,7 +4021,7 @@ var init_dist2 = __esm({
4013
4021
  return err(
4014
4022
  serviceError(
4015
4023
  ErrorCodes.NETWORK_ERROR,
4016
- `Failed to get metadata for key "${key}": ${response.status} - ${errorText}`,
4024
+ `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
4017
4025
  "kv",
4018
4026
  { meta: { status: response.status, statusText: response.statusText } }
4019
4027
  )
@@ -7220,9 +7228,12 @@ async function acquireProfileLock(profile, options) {
7220
7228
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;
7221
7229
  const startedAt = Date.now();
7222
7230
  const lockPath = profileLockPath(profile);
7223
- await (0, import_promises.mkdir)(profilePath(profile), { recursive: true, mode: PRIVATE_DIR_MODE });
7224
- for (const directory of [tinycloudHomePath(), profilesPath(), profilePath(profile)]) {
7225
- await (0, import_promises.chmod)(directory, PRIVATE_DIR_MODE);
7231
+ const directory = profilePath(profile);
7232
+ await (0, import_promises.mkdir)(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
7233
+ for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
7234
+ await (0, import_promises.chmod)(path, PRIVATE_DIR_MODE).catch((error) => {
7235
+ if (path !== directory || !isErrno(error, "ENOENT")) throw error;
7236
+ });
7226
7237
  }
7227
7238
  while (true) {
7228
7239
  const token = (0, import_node_crypto.randomUUID)();
@@ -7245,7 +7256,9 @@ async function publishProfileLock(profile, lockPath, token) {
7245
7256
  await (0, import_promises.mkdir)(lockPath, { mode: PRIVATE_DIR_MODE });
7246
7257
  } catch (error) {
7247
7258
  if (isErrno(error, "EEXIST")) return false;
7248
- throw error;
7259
+ if (!isErrno(error, "ENOENT")) throw error;
7260
+ await (0, import_promises.mkdir)(profilePath(profile), { recursive: true, mode: PRIVATE_DIR_MODE });
7261
+ return false;
7249
7262
  }
7250
7263
  await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);
7251
7264
  const staged = (0, import_node_path.join)((0, import_node_path.dirname)(lockPath), `.lock-owner-${token}.tmp`);