@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/index.js CHANGED
@@ -1050,6 +1050,18 @@ function storageLimitReachedError(service, message, meta) {
1050
1050
  meta
1051
1051
  };
1052
1052
  }
1053
+ function parseStorageQuotaBytes(responseText) {
1054
+ const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
1055
+ if (!match) return void 0;
1056
+ return {
1057
+ usedBytes: parseInt(match[1], 10),
1058
+ limitBytes: parseInt(match[2], 10)
1059
+ };
1060
+ }
1061
+ function storageRejectionError(service, status, meta, responseText) {
1062
+ const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
1063
+ return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
1064
+ }
1053
1065
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1054
1066
  if (error instanceof Error) {
1055
1067
  if (error.name === "AbortError") {
@@ -1179,6 +1191,9 @@ function serializeSqlValues(values) {
1179
1191
  (value) => value instanceof Uint8Array ? Array.from(value) : value
1180
1192
  );
1181
1193
  }
1194
+ function isMissingTableError(message) {
1195
+ return /no such table/i.test(message);
1196
+ }
1182
1197
  function migrationKey(namespace, id) {
1183
1198
  return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
1184
1199
  }
@@ -1664,6 +1679,9 @@ function defaultVaultMessage(input) {
1664
1679
  return input.message ?? `Public key not found for ${input.did}`;
1665
1680
  case "STORAGE_ERROR":
1666
1681
  return input.message ?? input.cause.message;
1682
+ case "STORAGE_QUOTA_EXCEEDED":
1683
+ case "STORAGE_LIMIT_REACHED":
1684
+ return input.message;
1667
1685
  }
1668
1686
  }
1669
1687
  function vaultError(input) {
@@ -1674,6 +1692,15 @@ function vaultError(input) {
1674
1692
  };
1675
1693
  return { ok: false, error };
1676
1694
  }
1695
+ function kvWriteError(context, error) {
1696
+ if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
1697
+ return vaultError({ code: error.code, message: error.message, meta: error.meta });
1698
+ }
1699
+ return vaultError({
1700
+ code: "STORAGE_ERROR",
1701
+ cause: new Error(`${context}: ${error.message}`)
1702
+ });
1703
+ }
1677
1704
  function canonicalize(value) {
1678
1705
  if (value === void 0) {
1679
1706
  return "";
@@ -2432,7 +2459,7 @@ function encOk(data) {
2432
2459
  function encErr(error) {
2433
2460
  return { ok: false, error };
2434
2461
  }
2435
- var import_decrypt_transport_response_error, ErrorCodes, defaultRetryPolicy, TelemetryEvents, REDACTED, SAFE_NUMBER_FIELDS, SAFE_BOOLEAN_FIELDS, DEBUG_FLAG, MAX_EVENTS, TinyCloudDebugLogger, tinyCloudDebugLogger, ServiceErrorSchema, GenericResultSchema, KVResponseHeadersSchema, GenericKVResponseSchema, KVListResponseSchema, KVListResultSchema, ServiceRequestEventSchema, ServiceResponseEventSchema, ServiceErrorEventSchema, ServiceRetryEventSchema, TelemetrySpanEventSchema, RetryPolicySchema, ServiceSessionSchema, BaseService, PrefixedKVService, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, KVAction, MAX_KV_BATCH_READ_ITEMS, KVService, SQLMigrations, DatabaseHandle, SQLAction, DDL_TOKENS, MIGRATIONS_TABLE, MIGRATIONS_SCHEMA, MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID, SQLService, DuckDbDatabaseHandle, DuckDbAction, DuckDbService, AsyncQueue, HooksService, VaultVersionConfig, CURRENT_VAULT_VERSION, VaultHeaders, DB_NAME, DB_VERSION, STORE_NAME, WRAP_KEY_ID, DataVaultService, HEX, URN_PREFIX, NETWORK_NAME_RE, PKH_EIP155_DID_RE, NetworkIdError, DEFAULT_ENCRYPTION_ALG, ENVELOPE_VERSION, DEFAULT_KEY_VERSION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, EncryptionService;
2462
+ var import_decrypt_transport_response_error, ErrorCodes, defaultRetryPolicy, TelemetryEvents, REDACTED, SAFE_NUMBER_FIELDS, SAFE_BOOLEAN_FIELDS, DEBUG_FLAG, MAX_EVENTS, TinyCloudDebugLogger, tinyCloudDebugLogger, ServiceErrorSchema, GenericResultSchema, KVResponseHeadersSchema, GenericKVResponseSchema, KVListResponseSchema, KVListResultSchema, ServiceRequestEventSchema, ServiceResponseEventSchema, ServiceErrorEventSchema, ServiceRetryEventSchema, TelemetrySpanEventSchema, RetryPolicySchema, ServiceSessionSchema, STORAGE_FULL_MESSAGE, STORAGE_WRITE_TOO_LARGE_MESSAGE, BaseService, PrefixedKVService, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, KVAction, MAX_KV_BATCH_READ_ITEMS, KVService, SQLMigrations, DatabaseHandle, SQLAction, DDL_TOKENS, MIGRATIONS_TABLE, MIGRATIONS_SCHEMA, MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID, SQLService, DuckDbDatabaseHandle, DuckDbAction, DuckDbService, AsyncQueue, HooksService, VaultVersionConfig, CURRENT_VAULT_VERSION, VaultHeaders, DB_NAME, DB_VERSION, STORE_NAME, WRAP_KEY_ID, DataVaultService, HEX, URN_PREFIX, NETWORK_NAME_RE, PKH_EIP155_DID_RE, NetworkIdError, DEFAULT_ENCRYPTION_ALG, ENVELOPE_VERSION, DEFAULT_KEY_VERSION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, EncryptionService;
2436
2463
  var init_dist2 = __esm({
2437
2464
  "../sdk-services/dist/index.js"() {
2438
2465
  "use strict";
@@ -2714,6 +2741,8 @@ var init_dist2 = __esm({
2714
2741
  /** The session key JWK (required for invoke) */
2715
2742
  jwk: z.object({}).passthrough()
2716
2743
  });
2744
+ 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.";
2745
+ 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.";
2717
2746
  BaseService = class {
2718
2747
  constructor() {
2719
2748
  this.abortController = new AbortController();
@@ -3062,47 +3091,21 @@ var init_dist2 = __esm({
3062
3091
  get config() {
3063
3092
  return this._config;
3064
3093
  }
3065
- // Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
3066
- parseQuotaInfo(errorText) {
3067
- const match = errorText.match(
3068
- /Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
3069
- );
3070
- if (match) {
3071
- return {
3072
- usedBytes: parseInt(match[1], 10),
3073
- limitBytes: parseInt(match[2], 10)
3074
- };
3075
- }
3076
- return void 0;
3077
- }
3078
3094
  handleQuotaErrorResponse(response, errorText, key) {
3079
- if (response.status === 402) {
3080
- const quotaInfo = this.parseQuotaInfo(errorText);
3081
- return err(
3082
- storageQuotaExceededError(
3083
- "kv",
3084
- `Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
3085
- {
3086
- status: response.status,
3087
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
3088
- }
3089
- )
3090
- );
3095
+ if (response.status !== 402 && response.status !== 413) {
3096
+ return void 0;
3091
3097
  }
3092
- if (response.status === 413) {
3093
- const quotaInfo = this.parseQuotaInfo(errorText);
3094
- return err(
3095
- storageLimitReachedError(
3096
- "kv",
3097
- `Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
3098
- {
3099
- status: response.status,
3100
- ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
3101
- }
3102
- )
3103
- );
3098
+ if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
3099
+ return void 0;
3104
3100
  }
3105
- return void 0;
3101
+ return err(
3102
+ storageRejectionError(
3103
+ "kv",
3104
+ response.status,
3105
+ { status: response.status, statusText: response.statusText, key },
3106
+ errorText
3107
+ )
3108
+ );
3106
3109
  }
3107
3110
  /**
3108
3111
  * Classify a KV 404 by reading the response body once.
@@ -4391,21 +4394,24 @@ var init_dist2 = __esm({
4391
4394
  }
4392
4395
  async applyMigrationsOnDbUnlocked(dbName, options) {
4393
4396
  return this.withTelemetry("migrations.apply", dbName, async () => {
4394
- const created = await this.ensureMigrationsTable(dbName, options.signal);
4395
- if (!created.ok) return created;
4396
4397
  const listed = await this.queryOnDb(
4397
4398
  dbName,
4398
4399
  `SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
4399
4400
  [options.namespace],
4400
4401
  { signal: options.signal }
4401
4402
  );
4402
- if (!listed.ok) return listed;
4403
+ const tableMissing = !listed.ok && (listed.error.code === ErrorCodes.SQL_DATABASE_NOT_FOUND || isMissingTableError(listed.error.message));
4404
+ if (!listed.ok && !tableMissing) return listed;
4403
4405
  const appliedIds = new Set(
4404
- listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
4406
+ (listed.ok ? listed.data.rows : []).map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
4405
4407
  );
4406
4408
  const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
4407
4409
  const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
4408
4410
  const applied = [];
4411
+ if (pending.length > 0 && tableMissing) {
4412
+ const created = await this.ensureMigrationsTable(dbName, options.signal);
4413
+ if (!created.ok) return created;
4414
+ }
4409
4415
  for (const migration of pending) {
4410
4416
  const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
4411
4417
  if (!result.ok) return result;
@@ -4508,6 +4514,10 @@ var init_dist2 = __esm({
4508
4514
  }
4509
4515
  async handleErrorResponse(response, operation) {
4510
4516
  const errorText = await response.text();
4517
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
4518
+ if (response.status === 402) {
4519
+ return err(storageRejectionError("sql", 402, meta, errorText));
4520
+ }
4511
4521
  const errorBody = parseServiceErrorBody(errorText);
4512
4522
  const errorCode = this.mapHttpStatusToErrorCode(
4513
4523
  response.status,
@@ -4520,7 +4530,6 @@ var init_dist2 = __esm({
4520
4530
  errorText,
4521
4531
  errorBody
4522
4532
  );
4523
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
4524
4533
  if (response.status === 401) {
4525
4534
  const { resource, action } = parseAuthError(errorText);
4526
4535
  if (action) meta.requiredAction = action;
@@ -4856,6 +4865,10 @@ var init_dist2 = __esm({
4856
4865
  }
4857
4866
  async handleErrorResponse(response, operation) {
4858
4867
  const errorText = await response.text();
4868
+ const meta = responseErrorMeta(response.status, response.statusText, errorText);
4869
+ if (response.status === 402) {
4870
+ return err(storageRejectionError("duckdb", 402, meta, errorText));
4871
+ }
4859
4872
  const errorBody = parseServiceErrorBody(errorText);
4860
4873
  const errorCode = this.mapHttpStatusToErrorCode(
4861
4874
  response.status,
@@ -4868,7 +4881,6 @@ var init_dist2 = __esm({
4868
4881
  errorText,
4869
4882
  errorBody
4870
4883
  );
4871
- const meta = responseErrorMeta(response.status, response.statusText, errorText);
4872
4884
  if (response.status === 401) {
4873
4885
  const { resource, action } = parseAuthError(errorText);
4874
4886
  if (action) meta.requiredAction = action;
@@ -5603,12 +5615,7 @@ var init_dist2 = __esm({
5603
5615
  JSON.stringify(envelopeResult.data)
5604
5616
  );
5605
5617
  if (!valuePutResult.ok) {
5606
- return vaultError({
5607
- code: "STORAGE_ERROR",
5608
- cause: new Error(
5609
- `Failed to store encrypted value: ${valuePutResult.error.message}`
5610
- )
5611
- });
5618
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
5612
5619
  }
5613
5620
  return { ok: true, data: void 0 };
5614
5621
  } catch (error) {
@@ -5826,12 +5833,7 @@ var init_dist2 = __esm({
5826
5833
  keyPayload
5827
5834
  );
5828
5835
  if (!keyPutResult.ok) {
5829
- return vaultError({
5830
- code: "STORAGE_ERROR",
5831
- cause: new Error(
5832
- `Failed to store key blob: ${keyPutResult.error.message}`
5833
- )
5834
- });
5836
+ return kvWriteError("Failed to store key blob", keyPutResult.error);
5835
5837
  }
5836
5838
  const valuePayload = JSON.stringify({
5837
5839
  data: base64Encode(encrypted),
@@ -5842,12 +5844,7 @@ var init_dist2 = __esm({
5842
5844
  valuePayload
5843
5845
  );
5844
5846
  if (!valuePutResult.ok) {
5845
- return vaultError({
5846
- code: "STORAGE_ERROR",
5847
- cause: new Error(
5848
- `Failed to store encrypted value: ${valuePutResult.error.message}`
5849
- )
5850
- });
5847
+ return kvWriteError("Failed to store encrypted value", valuePutResult.error);
5851
5848
  }
5852
5849
  return ok(void 0);
5853
5850
  } catch (error) {
@@ -6178,12 +6175,7 @@ var init_dist2 = __esm({
6178
6175
  grantPayload
6179
6176
  );
6180
6177
  if (!grantPutResult.ok) {
6181
- return vaultError({
6182
- code: "STORAGE_ERROR",
6183
- cause: new Error(
6184
- `Failed to store grant: ${grantPutResult.error.message}`
6185
- )
6186
- });
6178
+ return kvWriteError("Failed to store grant", grantPutResult.error);
6187
6179
  }
6188
6180
  return ok(void 0);
6189
6181
  } catch (error) {
@@ -6528,12 +6520,7 @@ var init_dist2 = __esm({
6528
6520
  keyPayload
6529
6521
  );
6530
6522
  if (!keyPutResult.ok) {
6531
- return vaultError({
6532
- code: "STORAGE_ERROR",
6533
- cause: new Error(
6534
- `Failed to store rotated key blob: ${keyPutResult.error.message}`
6535
- )
6536
- });
6523
+ return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
6537
6524
  }
6538
6525
  const valuePayload = JSON.stringify({
6539
6526
  data: base64Encode(encrypted),
@@ -6544,12 +6531,7 @@ var init_dist2 = __esm({
6544
6531
  valuePayload
6545
6532
  );
6546
6533
  if (!valuePutResult.ok) {
6547
- return vaultError({
6548
- code: "STORAGE_ERROR",
6549
- cause: new Error(
6550
- `Failed to store re-encrypted value: ${valuePutResult.error.message}`
6551
- )
6552
- });
6534
+ return kvWriteError("Failed to store re-encrypted value", valuePutResult.error);
6553
6535
  }
6554
6536
  for (const did of remainingGrantees) {
6555
6537
  const grantResult = await this.reencrypt(key, did);
@@ -7833,12 +7815,12 @@ function canonicalizeSpaceUri(space) {
7833
7815
  }
7834
7816
  function buildSecretSetupUrl(reference) {
7835
7817
  const parameters = [
7836
- ["name", reference.name],
7818
+ ["secret", reference.name],
7837
7819
  ...reference.scope === void 0 ? [] : [["scope", reference.scope]],
7838
7820
  ["space", reference.space]
7839
7821
  ];
7840
7822
  const query = parameters.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
7841
- return `${SECRET_MANAGER_BASE_URL}?${query}`;
7823
+ return `${SECRET_MANAGER_BASE_URL}/app?${query}`;
7842
7824
  }
7843
7825
  function canonicalizeSecretScope(scope) {
7844
7826
  if (scope === void 0 || scope === "") return void 0;