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