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

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.
@@ -1266,6 +1266,20 @@ function storageLimitReachedError(service, message, meta) {
1266
1266
  meta
1267
1267
  };
1268
1268
  }
1269
+ var STORAGE_FULL_MESSAGE = "TinyCloud storage is full, so this change was not saved. Reading still works. Free up space or upgrade your plan to save again.";
1270
+ var STORAGE_WRITE_TOO_LARGE_MESSAGE = "This change is larger than the TinyCloud storage you have left, so it was not saved. Reading still works. Free up space or upgrade your plan to save it.";
1271
+ function parseStorageQuotaBytes(responseText) {
1272
+ const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
1273
+ if (!match) return void 0;
1274
+ return {
1275
+ usedBytes: parseInt(match[1], 10),
1276
+ limitBytes: parseInt(match[2], 10)
1277
+ };
1278
+ }
1279
+ function storageRejectionError(service, status, meta, responseText) {
1280
+ const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
1281
+ return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
1282
+ }
1269
1283
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1270
1284
  if (error instanceof Error) {
1271
1285
  if (error.name === "AbortError") {
@@ -1641,47 +1655,21 @@ var KVService = class extends BaseService {
1641
1655
  get config() {
1642
1656
  return this._config;
1643
1657
  }
1644
- // Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
1645
- parseQuotaInfo(errorText) {
1646
- const match = errorText.match(
1647
- /Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
1648
- );
1649
- if (match) {
1650
- return {
1651
- usedBytes: parseInt(match[1], 10),
1652
- limitBytes: parseInt(match[2], 10)
1653
- };
1654
- }
1655
- return void 0;
1656
- }
1657
1658
  handleQuotaErrorResponse(response, errorText, key) {
1658
- if (response.status === 402) {
1659
- const quotaInfo = this.parseQuotaInfo(errorText);
1660
- return err(
1661
- storageQuotaExceededError(
1662
- "kv",
1663
- `Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
1664
- {
1665
- status: response.status,
1666
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
1667
- }
1668
- )
1669
- );
1659
+ if (response.status !== 402 && response.status !== 413) {
1660
+ return void 0;
1670
1661
  }
1671
- if (response.status === 413) {
1672
- const quotaInfo = this.parseQuotaInfo(errorText);
1673
- return err(
1674
- storageLimitReachedError(
1675
- "kv",
1676
- `Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
1677
- {
1678
- status: response.status,
1679
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
1680
- }
1681
- )
1682
- );
1662
+ if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
1663
+ return void 0;
1683
1664
  }
1684
- return void 0;
1665
+ return err(
1666
+ storageRejectionError(
1667
+ "kv",
1668
+ response.status,
1669
+ { status: response.status, statusText: response.statusText, key },
1670
+ errorText
1671
+ )
1672
+ );
1685
1673
  }
1686
1674
  /**
1687
1675
  * Classify a KV 404 by reading the response body once.
@@ -3004,21 +2992,24 @@ var SQLService = class extends BaseService {
3004
2992
  }
3005
2993
  async applyMigrationsOnDbUnlocked(dbName, options) {
3006
2994
  return this.withTelemetry("migrations.apply", dbName, async () => {
3007
- const created = await this.ensureMigrationsTable(dbName, options.signal);
3008
- if (!created.ok) return created;
3009
2995
  const listed = await this.queryOnDb(
3010
2996
  dbName,
3011
2997
  `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
3012
2998
  [options.namespace],
3013
2999
  { signal: options.signal }
3014
3000
  );
3015
- if (!listed.ok) return listed;
3001
+ const tableMissing = !listed.ok && (listed.error.code === ErrorCodes.SQL_DATABASE_NOT_FOUND || isMissingTableError(listed.error.message));
3002
+ if (!listed.ok && !tableMissing) return listed;
3016
3003
  const appliedIds = new Set(
3017
- listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
3004
+ (listed.ok ? listed.data.rows : []).map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
3018
3005
  );
3019
3006
  const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
3020
3007
  const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
3021
3008
  const applied = [];
3009
+ if (pending.length > 0 && tableMissing) {
3010
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
3011
+ if (!created.ok) return created;
3012
+ }
3022
3013
  for (const migration of pending) {
3023
3014
  const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
3024
3015
  if (!result.ok) return result;
@@ -3121,6 +3112,10 @@ var SQLService = class extends BaseService {
3121
3112
  }
3122
3113
  async handleErrorResponse(response, operation) {
3123
3114
  const errorText = await response.text();
3115
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
3116
+ if (response.status === 402) {
3117
+ return err(storageRejectionError("sql", 402, meta, errorText));
3118
+ }
3124
3119
  const errorBody = parseServiceErrorBody(errorText);
3125
3120
  const errorCode = this.mapHttpStatusToErrorCode(
3126
3121
  response.status,
@@ -3133,7 +3128,6 @@ var SQLService = class extends BaseService {
3133
3128
  errorText,
3134
3129
  errorBody
3135
3130
  );
3136
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
3137
3131
  if (response.status === 401) {
3138
3132
  const { resource, action } = parseAuthError(errorText);
3139
3133
  if (action) meta.requiredAction = action;
@@ -3234,6 +3228,9 @@ function serializeSqlValues(values) {
3234
3228
  (value) => value instanceof Uint8Array ? Array.from(value) : value
3235
3229
  );
3236
3230
  }
3231
+ function isMissingTableError(message) {
3232
+ return /no such table/i.test(message);
3233
+ }
3237
3234
  function migrationKey(namespace, id) {
3238
3235
  return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
3239
3236
  }
@@ -3548,6 +3545,10 @@ var DuckDbService = class extends BaseService {
3548
3545
  }
3549
3546
  async handleErrorResponse(response, operation) {
3550
3547
  const errorText = await response.text();
3548
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
3549
+ if (response.status === 402) {
3550
+ return err(storageRejectionError("duckdb", 402, meta, errorText));
3551
+ }
3551
3552
  const errorBody = parseServiceErrorBody(errorText);
3552
3553
  const errorCode = this.mapHttpStatusToErrorCode(
3553
3554
  response.status,
@@ -3560,7 +3561,6 @@ var DuckDbService = class extends BaseService {
3560
3561
  errorText,
3561
3562
  errorBody
3562
3563
  );
3563
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
3564
3564
  if (response.status === 401) {
3565
3565
  const { resource, action } = parseAuthError(errorText);
3566
3566
  if (action) meta.requiredAction = action;
@@ -4487,6 +4487,9 @@ function defaultVaultMessage(input) {
4487
4487
  return input.message ?? `Public key not found for ${input.did}`;
4488
4488
  case "STORAGE_ERROR":
4489
4489
  return input.message ?? input.cause.message;
4490
+ case "STORAGE_QUOTA_EXCEEDED":
4491
+ case "STORAGE_LIMIT_REACHED":
4492
+ return input.message;
4490
4493
  }
4491
4494
  }
4492
4495
  function vaultError(input) {
@@ -4497,6 +4500,15 @@ function vaultError(input) {
4497
4500
  };
4498
4501
  return { ok: false, error };
4499
4502
  }
4503
+ function kvWriteError(context, error) {
4504
+ if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
4505
+ return vaultError({ code: error.code, message: error.message, meta: error.meta });
4506
+ }
4507
+ return vaultError({
4508
+ code: "STORAGE_ERROR",
4509
+ cause: new Error(`${context}: ${error.message}`)
4510
+ });
4511
+ }
4500
4512
  var DataVaultService = class extends BaseService {
4501
4513
  /**
4502
4514
  * Create a new DataVaultService instance.
@@ -4779,12 +4791,7 @@ var DataVaultService = class extends BaseService {
4779
4791
  JSON.stringify(envelopeResult.data)
4780
4792
  );
4781
4793
  if (!valuePutResult.ok) {
4782
- return vaultError({
4783
- code: "STORAGE_ERROR",
4784
- cause: new Error(
4785
- `Failed to store encrypted value: ${valuePutResult.error.message}`
4786
- )
4787
- });
4794
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
4788
4795
  }
4789
4796
  return { ok: true, data: void 0 };
4790
4797
  } catch (error) {
@@ -5002,12 +5009,7 @@ var DataVaultService = class extends BaseService {
5002
5009
  keyPayload
5003
5010
  );
5004
5011
  if (!keyPutResult.ok) {
5005
- return vaultError({
5006
- code: "STORAGE_ERROR",
5007
- cause: new Error(
5008
- `Failed to store key blob: ${keyPutResult.error.message}`
5009
- )
5010
- });
5012
+ return kvWriteError("Failed to store key blob", keyPutResult.error);
5011
5013
  }
5012
5014
  const valuePayload = JSON.stringify({
5013
5015
  data: base64Encode(encrypted),
@@ -5018,12 +5020,7 @@ var DataVaultService = class extends BaseService {
5018
5020
  valuePayload
5019
5021
  );
5020
5022
  if (!valuePutResult.ok) {
5021
- return vaultError({
5022
- code: "STORAGE_ERROR",
5023
- cause: new Error(
5024
- `Failed to store encrypted value: ${valuePutResult.error.message}`
5025
- )
5026
- });
5023
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
5027
5024
  }
5028
5025
  return ok(void 0);
5029
5026
  } catch (error) {
@@ -5354,12 +5351,7 @@ var DataVaultService = class extends BaseService {
5354
5351
  grantPayload
5355
5352
  );
5356
5353
  if (!grantPutResult.ok) {
5357
- return vaultError({
5358
- code: "STORAGE_ERROR",
5359
- cause: new Error(
5360
- `Failed to store grant: ${grantPutResult.error.message}`
5361
- )
5362
- });
5354
+ return kvWriteError("Failed to store grant", grantPutResult.error);
5363
5355
  }
5364
5356
  return ok(void 0);
5365
5357
  } catch (error) {
@@ -5704,12 +5696,7 @@ var DataVaultService = class extends BaseService {
5704
5696
  keyPayload
5705
5697
  );
5706
5698
  if (!keyPutResult.ok) {
5707
- return vaultError({
5708
- code: "STORAGE_ERROR",
5709
- cause: new Error(
5710
- `Failed to store rotated key blob: ${keyPutResult.error.message}`
5711
- )
5712
- });
5699
+ return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
5713
5700
  }
5714
5701
  const valuePayload = JSON.stringify({
5715
5702
  data: base64Encode(encrypted),
@@ -5720,12 +5707,7 @@ var DataVaultService = class extends BaseService {
5720
5707
  valuePayload
5721
5708
  );
5722
5709
  if (!valuePutResult.ok) {
5723
- return vaultError({
5724
- code: "STORAGE_ERROR",
5725
- cause: new Error(
5726
- `Failed to store re-encrypted value: ${valuePutResult.error.message}`
5727
- )
5728
- });
5710
+ return kvWriteError("Failed to store re-encrypted value", valuePutResult.error);
5729
5711
  }
5730
5712
  for (const did of remainingGrantees) {
5731
5713
  const grantResult = await this.reencrypt(key, did);