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

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/artifacts.js CHANGED
@@ -1242,6 +1242,20 @@ function storageLimitReachedError(service, message, meta) {
1242
1242
  meta
1243
1243
  };
1244
1244
  }
1245
+ 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.";
1246
+ 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.";
1247
+ function parseStorageQuotaBytes(responseText) {
1248
+ const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
1249
+ if (!match) return void 0;
1250
+ return {
1251
+ usedBytes: parseInt(match[1], 10),
1252
+ limitBytes: parseInt(match[2], 10)
1253
+ };
1254
+ }
1255
+ function storageRejectionError(service, status, meta, responseText) {
1256
+ const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
1257
+ return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
1258
+ }
1245
1259
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1246
1260
  if (error instanceof Error) {
1247
1261
  if (error.name === "AbortError") {
@@ -1617,47 +1631,21 @@ var KVService = class extends BaseService {
1617
1631
  get config() {
1618
1632
  return this._config;
1619
1633
  }
1620
- // Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
1621
- parseQuotaInfo(errorText) {
1622
- const match = errorText.match(
1623
- /Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
1624
- );
1625
- if (match) {
1626
- return {
1627
- usedBytes: parseInt(match[1], 10),
1628
- limitBytes: parseInt(match[2], 10)
1629
- };
1630
- }
1631
- return void 0;
1632
- }
1633
1634
  handleQuotaErrorResponse(response, errorText, key) {
1634
- if (response.status === 402) {
1635
- const quotaInfo = this.parseQuotaInfo(errorText);
1636
- return err(
1637
- storageQuotaExceededError(
1638
- "kv",
1639
- `Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
1640
- {
1641
- status: response.status,
1642
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
1643
- }
1644
- )
1645
- );
1635
+ if (response.status !== 402 && response.status !== 413) {
1636
+ return void 0;
1646
1637
  }
1647
- if (response.status === 413) {
1648
- const quotaInfo = this.parseQuotaInfo(errorText);
1649
- return err(
1650
- storageLimitReachedError(
1651
- "kv",
1652
- `Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
1653
- {
1654
- status: response.status,
1655
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
1656
- }
1657
- )
1658
- );
1638
+ if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
1639
+ return void 0;
1659
1640
  }
1660
- return void 0;
1641
+ return err(
1642
+ storageRejectionError(
1643
+ "kv",
1644
+ response.status,
1645
+ { status: response.status, statusText: response.statusText, key },
1646
+ errorText
1647
+ )
1648
+ );
1661
1649
  }
1662
1650
  /**
1663
1651
  * Classify a KV 404 by reading the response body once.
@@ -2980,21 +2968,24 @@ var SQLService = class extends BaseService {
2980
2968
  }
2981
2969
  async applyMigrationsOnDbUnlocked(dbName, options) {
2982
2970
  return this.withTelemetry("migrations.apply", dbName, async () => {
2983
- const created = await this.ensureMigrationsTable(dbName, options.signal);
2984
- if (!created.ok) return created;
2985
2971
  const listed = await this.queryOnDb(
2986
2972
  dbName,
2987
2973
  `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
2988
2974
  [options.namespace],
2989
2975
  { signal: options.signal }
2990
2976
  );
2991
- if (!listed.ok) return listed;
2977
+ const tableMissing = !listed.ok && (listed.error.code === ErrorCodes.SQL_DATABASE_NOT_FOUND || isMissingTableError(listed.error.message));
2978
+ if (!listed.ok && !tableMissing) return listed;
2992
2979
  const appliedIds = new Set(
2993
- listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
2980
+ (listed.ok ? listed.data.rows : []).map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
2994
2981
  );
2995
2982
  const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
2996
2983
  const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
2997
2984
  const applied = [];
2985
+ if (pending.length > 0 && tableMissing) {
2986
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
2987
+ if (!created.ok) return created;
2988
+ }
2998
2989
  for (const migration of pending) {
2999
2990
  const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
3000
2991
  if (!result.ok) return result;
@@ -3097,6 +3088,10 @@ var SQLService = class extends BaseService {
3097
3088
  }
3098
3089
  async handleErrorResponse(response, operation) {
3099
3090
  const errorText = await response.text();
3091
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
3092
+ if (response.status === 402) {
3093
+ return err(storageRejectionError("sql", 402, meta, errorText));
3094
+ }
3100
3095
  const errorBody = parseServiceErrorBody(errorText);
3101
3096
  const errorCode = this.mapHttpStatusToErrorCode(
3102
3097
  response.status,
@@ -3109,7 +3104,6 @@ var SQLService = class extends BaseService {
3109
3104
  errorText,
3110
3105
  errorBody
3111
3106
  );
3112
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
3113
3107
  if (response.status === 401) {
3114
3108
  const { resource, action } = parseAuthError(errorText);
3115
3109
  if (action) meta.requiredAction = action;
@@ -3210,6 +3204,9 @@ function serializeSqlValues(values) {
3210
3204
  (value) => value instanceof Uint8Array ? Array.from(value) : value
3211
3205
  );
3212
3206
  }
3207
+ function isMissingTableError(message) {
3208
+ return /no such table/i.test(message);
3209
+ }
3213
3210
  function migrationKey(namespace, id) {
3214
3211
  return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
3215
3212
  }
@@ -3524,6 +3521,10 @@ var DuckDbService = class extends BaseService {
3524
3521
  }
3525
3522
  async handleErrorResponse(response, operation) {
3526
3523
  const errorText = await response.text();
3524
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
3525
+ if (response.status === 402) {
3526
+ return err(storageRejectionError("duckdb", 402, meta, errorText));
3527
+ }
3527
3528
  const errorBody = parseServiceErrorBody(errorText);
3528
3529
  const errorCode = this.mapHttpStatusToErrorCode(
3529
3530
  response.status,
@@ -3536,7 +3537,6 @@ var DuckDbService = class extends BaseService {
3536
3537
  errorText,
3537
3538
  errorBody
3538
3539
  );
3539
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
3540
3540
  if (response.status === 401) {
3541
3541
  const { resource, action } = parseAuthError(errorText);
3542
3542
  if (action) meta.requiredAction = action;
@@ -4463,6 +4463,9 @@ function defaultVaultMessage(input) {
4463
4463
  return input.message ?? `Public key not found for ${input.did}`;
4464
4464
  case "STORAGE_ERROR":
4465
4465
  return input.message ?? input.cause.message;
4466
+ case "STORAGE_QUOTA_EXCEEDED":
4467
+ case "STORAGE_LIMIT_REACHED":
4468
+ return input.message;
4466
4469
  }
4467
4470
  }
4468
4471
  function vaultError(input) {
@@ -4473,6 +4476,15 @@ function vaultError(input) {
4473
4476
  };
4474
4477
  return { ok: false, error };
4475
4478
  }
4479
+ function kvWriteError(context, error) {
4480
+ if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
4481
+ return vaultError({ code: error.code, message: error.message, meta: error.meta });
4482
+ }
4483
+ return vaultError({
4484
+ code: "STORAGE_ERROR",
4485
+ cause: new Error(`${context}: ${error.message}`)
4486
+ });
4487
+ }
4476
4488
  var DataVaultService = class extends BaseService {
4477
4489
  /**
4478
4490
  * Create a new DataVaultService instance.
@@ -4755,12 +4767,7 @@ var DataVaultService = class extends BaseService {
4755
4767
  JSON.stringify(envelopeResult.data)
4756
4768
  );
4757
4769
  if (!valuePutResult.ok) {
4758
- return vaultError({
4759
- code: "STORAGE_ERROR",
4760
- cause: new Error(
4761
- `Failed to store encrypted value: ${valuePutResult.error.message}`
4762
- )
4763
- });
4770
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
4764
4771
  }
4765
4772
  return { ok: true, data: void 0 };
4766
4773
  } catch (error) {
@@ -4978,12 +4985,7 @@ var DataVaultService = class extends BaseService {
4978
4985
  keyPayload
4979
4986
  );
4980
4987
  if (!keyPutResult.ok) {
4981
- return vaultError({
4982
- code: "STORAGE_ERROR",
4983
- cause: new Error(
4984
- `Failed to store key blob: ${keyPutResult.error.message}`
4985
- )
4986
- });
4988
+ return kvWriteError("Failed to store key blob", keyPutResult.error);
4987
4989
  }
4988
4990
  const valuePayload = JSON.stringify({
4989
4991
  data: base64Encode(encrypted),
@@ -4994,12 +4996,7 @@ var DataVaultService = class extends BaseService {
4994
4996
  valuePayload
4995
4997
  );
4996
4998
  if (!valuePutResult.ok) {
4997
- return vaultError({
4998
- code: "STORAGE_ERROR",
4999
- cause: new Error(
5000
- `Failed to store encrypted value: ${valuePutResult.error.message}`
5001
- )
5002
- });
4999
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
5003
5000
  }
5004
5001
  return ok(void 0);
5005
5002
  } catch (error) {
@@ -5330,12 +5327,7 @@ var DataVaultService = class extends BaseService {
5330
5327
  grantPayload
5331
5328
  );
5332
5329
  if (!grantPutResult.ok) {
5333
- return vaultError({
5334
- code: "STORAGE_ERROR",
5335
- cause: new Error(
5336
- `Failed to store grant: ${grantPutResult.error.message}`
5337
- )
5338
- });
5330
+ return kvWriteError("Failed to store grant", grantPutResult.error);
5339
5331
  }
5340
5332
  return ok(void 0);
5341
5333
  } catch (error) {
@@ -5680,12 +5672,7 @@ var DataVaultService = class extends BaseService {
5680
5672
  keyPayload
5681
5673
  );
5682
5674
  if (!keyPutResult.ok) {
5683
- return vaultError({
5684
- code: "STORAGE_ERROR",
5685
- cause: new Error(
5686
- `Failed to store rotated key blob: ${keyPutResult.error.message}`
5687
- )
5688
- });
5675
+ return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
5689
5676
  }
5690
5677
  const valuePayload = JSON.stringify({
5691
5678
  data: base64Encode(encrypted),
@@ -5696,12 +5683,7 @@ var DataVaultService = class extends BaseService {
5696
5683
  valuePayload
5697
5684
  );
5698
5685
  if (!valuePutResult.ok) {
5699
- return vaultError({
5700
- code: "STORAGE_ERROR",
5701
- cause: new Error(
5702
- `Failed to store re-encrypted value: ${valuePutResult.error.message}`
5703
- )
5704
- });
5686
+ return kvWriteError("Failed to store re-encrypted value", valuePutResult.error);
5705
5687
  }
5706
5688
  for (const did of remainingGrantees) {
5707
5689
  const grantResult = await this.reencrypt(key, did);