@tinycloud/sdk-services 3.1.0-beta.1 → 3.1.0-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.
package/dist/index.cjs CHANGED
@@ -55,6 +55,8 @@ __export(src_exports, {
55
55
  SQLAction: () => SQLAction,
56
56
  SQLMigrations: () => SQLMigrations,
57
57
  SQLService: () => SQLService,
58
+ STORAGE_FULL_MESSAGE: () => STORAGE_FULL_MESSAGE,
59
+ STORAGE_WRITE_TOO_LARGE_MESSAGE: () => STORAGE_WRITE_TOO_LARGE_MESSAGE,
58
60
  SecretsService: () => SecretsService,
59
61
  ServiceContext: () => ServiceContext,
60
62
  ServiceErrorEventSchema: () => ServiceErrorEventSchema,
@@ -107,6 +109,7 @@ __export(src_exports, {
107
109
  hexEncode: () => hexEncode2,
108
110
  installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
109
111
  isNetworkId: () => isNetworkId,
112
+ isStorageFullError: () => isStorageFullError,
110
113
  networkDiscoveryKey: () => networkDiscoveryKey,
111
114
  networkError: () => networkError,
112
115
  notFoundError: () => notFoundError,
@@ -117,12 +120,14 @@ __export(src_exports, {
117
120
  parsePermissionHint: () => parsePermissionHint,
118
121
  parsePermissionHintFromErrorText: () => parsePermissionHintFromErrorText,
119
122
  parseSecretCatalogKey: () => parseSecretCatalogKey,
123
+ parseStorageQuotaBytes: () => parseStorageQuotaBytes,
120
124
  permissionDeniedError: () => permissionDeniedError,
121
125
  resolveSecretListPrefix: () => resolveSecretListPrefix,
122
126
  resolveSecretPath: () => resolveSecretPath,
123
127
  serviceError: () => serviceError,
124
128
  storageLimitReachedError: () => storageLimitReachedError,
125
129
  storageQuotaExceededError: () => storageQuotaExceededError,
130
+ storageRejectionError: () => storageRejectionError,
126
131
  timeoutError: () => timeoutError,
127
132
  tinyCloudDebugLogger: () => tinyCloudDebugLogger,
128
133
  utf8Decode: () => utf8Decode,
@@ -1183,6 +1188,23 @@ function storageLimitReachedError(service, message, meta) {
1183
1188
  meta
1184
1189
  };
1185
1190
  }
1191
+ 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.";
1192
+ 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.";
1193
+ function parseStorageQuotaBytes(responseText) {
1194
+ const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
1195
+ if (!match) return void 0;
1196
+ return {
1197
+ usedBytes: parseInt(match[1], 10),
1198
+ limitBytes: parseInt(match[2], 10)
1199
+ };
1200
+ }
1201
+ function storageRejectionError(service, status, meta, responseText) {
1202
+ const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
1203
+ return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
1204
+ }
1205
+ function isStorageFullError(error) {
1206
+ return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
1207
+ }
1186
1208
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1187
1209
  if (error instanceof Error) {
1188
1210
  if (error.name === "AbortError") {
@@ -1576,47 +1598,21 @@ var KVService = class extends BaseService {
1576
1598
  get config() {
1577
1599
  return this._config;
1578
1600
  }
1579
- // Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
1580
- parseQuotaInfo(errorText) {
1581
- const match = errorText.match(
1582
- /Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
1583
- );
1584
- if (match) {
1585
- return {
1586
- usedBytes: parseInt(match[1], 10),
1587
- limitBytes: parseInt(match[2], 10)
1588
- };
1589
- }
1590
- return void 0;
1591
- }
1592
1601
  handleQuotaErrorResponse(response, errorText, key) {
1593
- if (response.status === 402) {
1594
- const quotaInfo = this.parseQuotaInfo(errorText);
1595
- return err(
1596
- storageQuotaExceededError(
1597
- "kv",
1598
- `Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
1599
- {
1600
- status: response.status,
1601
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
1602
- }
1603
- )
1604
- );
1602
+ if (response.status !== 402 && response.status !== 413) {
1603
+ return void 0;
1605
1604
  }
1606
- if (response.status === 413) {
1607
- const quotaInfo = this.parseQuotaInfo(errorText);
1608
- return err(
1609
- storageLimitReachedError(
1610
- "kv",
1611
- `Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
1612
- {
1613
- status: response.status,
1614
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
1615
- }
1616
- )
1617
- );
1605
+ if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
1606
+ return void 0;
1618
1607
  }
1619
- return void 0;
1608
+ return err(
1609
+ storageRejectionError(
1610
+ "kv",
1611
+ response.status,
1612
+ { status: response.status, statusText: response.statusText, key },
1613
+ errorText
1614
+ )
1615
+ );
1620
1616
  }
1621
1617
  /**
1622
1618
  * Classify a KV 404 by reading the response body once.
@@ -2953,21 +2949,24 @@ var SQLService = class extends BaseService {
2953
2949
  }
2954
2950
  async applyMigrationsOnDbUnlocked(dbName, options) {
2955
2951
  return this.withTelemetry("migrations.apply", dbName, async () => {
2956
- const created = await this.ensureMigrationsTable(dbName, options.signal);
2957
- if (!created.ok) return created;
2958
2952
  const listed = await this.queryOnDb(
2959
2953
  dbName,
2960
2954
  `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
2961
2955
  [options.namespace],
2962
2956
  { signal: options.signal }
2963
2957
  );
2964
- if (!listed.ok) return listed;
2958
+ const tableMissing = !listed.ok && (listed.error.code === ErrorCodes.SQL_DATABASE_NOT_FOUND || isMissingTableError(listed.error.message));
2959
+ if (!listed.ok && !tableMissing) return listed;
2965
2960
  const appliedIds = new Set(
2966
- listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
2961
+ (listed.ok ? listed.data.rows : []).map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
2967
2962
  );
2968
2963
  const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
2969
2964
  const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
2970
2965
  const applied = [];
2966
+ if (pending.length > 0 && tableMissing) {
2967
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
2968
+ if (!created.ok) return created;
2969
+ }
2971
2970
  for (const migration of pending) {
2972
2971
  const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
2973
2972
  if (!result.ok) return result;
@@ -3070,6 +3069,10 @@ var SQLService = class extends BaseService {
3070
3069
  }
3071
3070
  async handleErrorResponse(response, operation) {
3072
3071
  const errorText = await response.text();
3072
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
3073
+ if (response.status === 402) {
3074
+ return err(storageRejectionError("sql", 402, meta, errorText));
3075
+ }
3073
3076
  const errorBody = parseServiceErrorBody(errorText);
3074
3077
  const errorCode = this.mapHttpStatusToErrorCode(
3075
3078
  response.status,
@@ -3082,7 +3085,6 @@ var SQLService = class extends BaseService {
3082
3085
  errorText,
3083
3086
  errorBody
3084
3087
  );
3085
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
3086
3088
  if (response.status === 401) {
3087
3089
  const { resource, action } = parseAuthError(errorText);
3088
3090
  if (action) meta.requiredAction = action;
@@ -3183,6 +3185,9 @@ function serializeSqlValues(values) {
3183
3185
  (value) => value instanceof Uint8Array ? Array.from(value) : value
3184
3186
  );
3185
3187
  }
3188
+ function isMissingTableError(message) {
3189
+ return /no such table/i.test(message);
3190
+ }
3186
3191
  function migrationKey(namespace, id) {
3187
3192
  return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
3188
3193
  }
@@ -3504,6 +3509,10 @@ var DuckDbService = class extends BaseService {
3504
3509
  }
3505
3510
  async handleErrorResponse(response, operation) {
3506
3511
  const errorText = await response.text();
3512
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
3513
+ if (response.status === 402) {
3514
+ return err(storageRejectionError("duckdb", 402, meta, errorText));
3515
+ }
3507
3516
  const errorBody = parseServiceErrorBody(errorText);
3508
3517
  const errorCode = this.mapHttpStatusToErrorCode(
3509
3518
  response.status,
@@ -3516,7 +3525,6 @@ var DuckDbService = class extends BaseService {
3516
3525
  errorText,
3517
3526
  errorBody
3518
3527
  );
3519
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
3520
3528
  if (response.status === 401) {
3521
3529
  const { resource, action } = parseAuthError(errorText);
3522
3530
  if (action) meta.requiredAction = action;
@@ -4488,6 +4496,9 @@ function defaultVaultMessage(input) {
4488
4496
  return input.message ?? `Public key not found for ${input.did}`;
4489
4497
  case "STORAGE_ERROR":
4490
4498
  return input.message ?? input.cause.message;
4499
+ case "STORAGE_QUOTA_EXCEEDED":
4500
+ case "STORAGE_LIMIT_REACHED":
4501
+ return input.message;
4491
4502
  }
4492
4503
  }
4493
4504
  function vaultError(input) {
@@ -4498,6 +4509,15 @@ function vaultError(input) {
4498
4509
  };
4499
4510
  return { ok: false, error };
4500
4511
  }
4512
+ function kvWriteError(context, error) {
4513
+ if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
4514
+ return vaultError({ code: error.code, message: error.message, meta: error.meta });
4515
+ }
4516
+ return vaultError({
4517
+ code: "STORAGE_ERROR",
4518
+ cause: new Error(`${context}: ${error.message}`)
4519
+ });
4520
+ }
4501
4521
  var DataVaultService = class extends BaseService {
4502
4522
  /**
4503
4523
  * Create a new DataVaultService instance.
@@ -4780,12 +4800,7 @@ var DataVaultService = class extends BaseService {
4780
4800
  JSON.stringify(envelopeResult.data)
4781
4801
  );
4782
4802
  if (!valuePutResult.ok) {
4783
- return vaultError({
4784
- code: "STORAGE_ERROR",
4785
- cause: new Error(
4786
- `Failed to store encrypted value: ${valuePutResult.error.message}`
4787
- )
4788
- });
4803
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
4789
4804
  }
4790
4805
  return { ok: true, data: void 0 };
4791
4806
  } catch (error) {
@@ -5003,12 +5018,7 @@ var DataVaultService = class extends BaseService {
5003
5018
  keyPayload
5004
5019
  );
5005
5020
  if (!keyPutResult.ok) {
5006
- return vaultError({
5007
- code: "STORAGE_ERROR",
5008
- cause: new Error(
5009
- `Failed to store key blob: ${keyPutResult.error.message}`
5010
- )
5011
- });
5021
+ return kvWriteError("Failed to store key blob", keyPutResult.error);
5012
5022
  }
5013
5023
  const valuePayload = JSON.stringify({
5014
5024
  data: base64Encode(encrypted),
@@ -5019,12 +5029,7 @@ var DataVaultService = class extends BaseService {
5019
5029
  valuePayload
5020
5030
  );
5021
5031
  if (!valuePutResult.ok) {
5022
- return vaultError({
5023
- code: "STORAGE_ERROR",
5024
- cause: new Error(
5025
- `Failed to store encrypted value: ${valuePutResult.error.message}`
5026
- )
5027
- });
5032
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
5028
5033
  }
5029
5034
  return ok(void 0);
5030
5035
  } catch (error) {
@@ -5355,12 +5360,7 @@ var DataVaultService = class extends BaseService {
5355
5360
  grantPayload
5356
5361
  );
5357
5362
  if (!grantPutResult.ok) {
5358
- return vaultError({
5359
- code: "STORAGE_ERROR",
5360
- cause: new Error(
5361
- `Failed to store grant: ${grantPutResult.error.message}`
5362
- )
5363
- });
5363
+ return kvWriteError("Failed to store grant", grantPutResult.error);
5364
5364
  }
5365
5365
  return ok(void 0);
5366
5366
  } catch (error) {
@@ -5705,12 +5705,7 @@ var DataVaultService = class extends BaseService {
5705
5705
  keyPayload
5706
5706
  );
5707
5707
  if (!keyPutResult.ok) {
5708
- return vaultError({
5709
- code: "STORAGE_ERROR",
5710
- cause: new Error(
5711
- `Failed to store rotated key blob: ${keyPutResult.error.message}`
5712
- )
5713
- });
5708
+ return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
5714
5709
  }
5715
5710
  const valuePayload = JSON.stringify({
5716
5711
  data: base64Encode(encrypted),
@@ -5721,12 +5716,7 @@ var DataVaultService = class extends BaseService {
5721
5716
  valuePayload
5722
5717
  );
5723
5718
  if (!valuePutResult.ok) {
5724
- return vaultError({
5725
- code: "STORAGE_ERROR",
5726
- cause: new Error(
5727
- `Failed to store re-encrypted value: ${valuePutResult.error.message}`
5728
- )
5729
- });
5719
+ return kvWriteError("Failed to store re-encrypted value", valuePutResult.error);
5730
5720
  }
5731
5721
  for (const did of remainingGrantees) {
5732
5722
  const grantResult = await this.reencrypt(key, did);
@@ -7056,6 +7046,8 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7056
7046
  SQLAction,
7057
7047
  SQLMigrations,
7058
7048
  SQLService,
7049
+ STORAGE_FULL_MESSAGE,
7050
+ STORAGE_WRITE_TOO_LARGE_MESSAGE,
7059
7051
  SecretsService,
7060
7052
  ServiceContext,
7061
7053
  ServiceErrorEventSchema,
@@ -7108,6 +7100,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7108
7100
  hexEncode,
7109
7101
  installTinyCloudDebugGlobals,
7110
7102
  isNetworkId,
7103
+ isStorageFullError,
7111
7104
  networkDiscoveryKey,
7112
7105
  networkError,
7113
7106
  notFoundError,
@@ -7118,12 +7111,14 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7118
7111
  parsePermissionHint,
7119
7112
  parsePermissionHintFromErrorText,
7120
7113
  parseSecretCatalogKey,
7114
+ parseStorageQuotaBytes,
7121
7115
  permissionDeniedError,
7122
7116
  resolveSecretListPrefix,
7123
7117
  resolveSecretPath,
7124
7118
  serviceError,
7125
7119
  storageLimitReachedError,
7126
7120
  storageQuotaExceededError,
7121
+ storageRejectionError,
7127
7122
  timeoutError,
7128
7123
  tinyCloudDebugLogger,
7129
7124
  utf8Decode,