@tinycloud/sdk-services 3.2.0-beta.5 → 3.2.0-beta.7

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.
Files changed (39) hide show
  1. package/dist/{BaseService-BDQXjWMx.d.cts → BaseService-4wwJq1Gn.d.cts} +1 -1
  2. package/dist/{BaseService-Di_hVfUV.d.ts → BaseService-BGr2slUz.d.ts} +1 -1
  3. package/dist/encryption/index.cjs +5 -1
  4. package/dist/encryption/index.cjs.map +1 -1
  5. package/dist/encryption/index.d.cts +2 -2
  6. package/dist/encryption/index.d.ts +2 -2
  7. package/dist/encryption/index.js +5 -1
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +176 -3
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +156 -6
  12. package/dist/index.d.ts +156 -6
  13. package/dist/index.js +170 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  16. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  17. package/dist/kv/index.cjs +42 -3
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +4 -4
  20. package/dist/kv/index.d.ts +4 -4
  21. package/dist/kv/index.js +42 -3
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/index.cjs +2 -0
  24. package/dist/kv/replication/index.cjs.map +1 -1
  25. package/dist/kv/replication/index.d.cts +3 -3
  26. package/dist/kv/replication/index.d.ts +3 -3
  27. package/dist/kv/replication/index.js +2 -0
  28. package/dist/kv/replication/index.js.map +1 -1
  29. package/dist/{scope-BKge7zDT.d.ts → scope-CoKKmMiI.d.ts} +1 -1
  30. package/dist/{scope-C05VVAXH.d.cts → scope-D7wmjbMH.d.cts} +1 -1
  31. package/dist/sql/index.cjs +44 -2
  32. package/dist/sql/index.cjs.map +1 -1
  33. package/dist/sql/index.d.cts +2 -2
  34. package/dist/sql/index.d.ts +2 -2
  35. package/dist/sql/index.js +44 -2
  36. package/dist/sql/index.js.map +1 -1
  37. package/dist/{types-7A5rHWRY.d.cts → types-D-9A3Sut.d.cts} +6 -0
  38. package/dist/{types-7A5rHWRY.d.ts → types-D-9A3Sut.d.ts} +6 -0
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -36,6 +36,8 @@ var ErrorCodes = {
36
36
  // Storage quota errors
37
37
  STORAGE_QUOTA_EXCEEDED: "STORAGE_QUOTA_EXCEEDED",
38
38
  STORAGE_LIMIT_REACHED: "STORAGE_LIMIT_REACHED",
39
+ /** The node answered the usage read without usage numbers (it predates the read). */
40
+ STORAGE_STATUS_UNAVAILABLE: "STORAGE_STATUS_UNAVAILABLE",
39
41
  // DuckDB-specific errors
40
42
  DUCKDB_ERROR: "DUCKDB_ERROR",
41
43
  DUCKDB_PERMISSION_DENIED: "DUCKDB_PERMISSION_DENIED",
@@ -65,7 +67,9 @@ var TelemetryEvents = {
65
67
  REPLICATION_WRITE: "replication.write",
66
68
  REPLICATION_SYNC: "replication.sync",
67
69
  REPLICATION_STATE: "replication.state",
68
- REPLICATION_DIVERGENCE: "replication.divergence"
70
+ REPLICATION_DIVERGENCE: "replication.divergence",
71
+ /** Emitted once, on the first write rejected because storage is full. */
72
+ STORAGE_FULL: "storage.full"
69
73
  };
70
74
  function ok(data) {
71
75
  return { ok: true, data };
@@ -1284,8 +1288,43 @@ function parseStorageQuotaBytes(responseText) {
1284
1288
  limitBytes: parseInt(match[2], 10)
1285
1289
  };
1286
1290
  }
1291
+ var STORAGE_REJECTION_BODY_ERRORS = {
1292
+ storage_quota_exceeded: true,
1293
+ storage_limit_reached: true
1294
+ };
1295
+ function isByteCount(value) {
1296
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1297
+ }
1298
+ function byteCountsOf(value) {
1299
+ if (typeof value !== "object" || value === null) return void 0;
1300
+ const { usedBytes, limitBytes } = value;
1301
+ return isByteCount(usedBytes) && isByteCount(limitBytes) ? { usedBytes, limitBytes } : void 0;
1302
+ }
1303
+ function parseStorageRejection(responseText) {
1304
+ let body;
1305
+ try {
1306
+ body = JSON.parse(responseText);
1307
+ } catch {
1308
+ return parseStorageQuotaBytes(responseText);
1309
+ }
1310
+ if (typeof body !== "object" || body === null) return void 0;
1311
+ const record = body;
1312
+ const message = typeof record.message === "string" ? record.message : "";
1313
+ const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
1314
+ const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
1315
+ if (!space && !isStorageBody) {
1316
+ return void 0;
1317
+ }
1318
+ const details = { ...space };
1319
+ const account = byteCountsOf(record.account);
1320
+ if (account) {
1321
+ const plan = record.account.plan;
1322
+ details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
1323
+ }
1324
+ return details;
1325
+ }
1287
1326
  function storageRejectionError(service, status, meta, responseText) {
1288
- const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
1327
+ const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
1289
1328
  return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
1290
1329
  }
1291
1330
  function isStorageFullError(error) {
@@ -1814,7 +1853,7 @@ var KVService = class extends BaseService {
1814
1853
  if (response.status !== 402 && response.status !== 413) {
1815
1854
  return void 0;
1816
1855
  }
1817
- if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
1856
+ if (response.status === 413 && !parseStorageRejection(errorText)) {
1818
1857
  return void 0;
1819
1858
  }
1820
1859
  return err(
@@ -3737,6 +3776,9 @@ var SQLService = class extends BaseService {
3737
3776
  if (response.status === 402) {
3738
3777
  return err(storageRejectionError("sql", 402, meta, errorText));
3739
3778
  }
3779
+ if (response.status === 413 && parseStorageRejection(errorText)) {
3780
+ return err(storageRejectionError("sql", 413, meta, errorText));
3781
+ }
3740
3782
  const errorBody = parseServiceErrorBody(errorText);
3741
3783
  const errorCode = this.mapHttpStatusToErrorCode(
3742
3784
  response.status,
@@ -4203,6 +4245,9 @@ var DuckDbService = class extends BaseService {
4203
4245
  if (response.status === 402) {
4204
4246
  return err(storageRejectionError("duckdb", 402, meta, errorText));
4205
4247
  }
4248
+ if (response.status === 413 && parseStorageRejection(errorText)) {
4249
+ return err(storageRejectionError("duckdb", 413, meta, errorText));
4250
+ }
4206
4251
  const errorBody = parseServiceErrorBody(errorText);
4207
4252
  const errorCode = this.mapHttpStatusToErrorCode(
4208
4253
  response.status,
@@ -5264,6 +5309,122 @@ function parsedStringArrayField(value, key) {
5264
5309
  }
5265
5310
  }
5266
5311
 
5312
+ // src/storage/StorageFullMonitor.ts
5313
+ var StorageFullMonitor = class {
5314
+ /**
5315
+ * @param ownerOf Owner key of a space id. Spaces with one key share one
5316
+ * storage budget. Defaults to the space id itself.
5317
+ */
5318
+ constructor(ownerOf = (spaceId) => spaceId) {
5319
+ this.ownerOf = ownerOf;
5320
+ /** First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown. */
5321
+ this.full = /* @__PURE__ */ new Map();
5322
+ this.handlers = /* @__PURE__ */ new Set();
5323
+ }
5324
+ /** True while any owner's storage is known to be full. */
5325
+ get isFull() {
5326
+ return this.full.size > 0;
5327
+ }
5328
+ /** The rejection `storage.full` announced, until the episode ends. */
5329
+ get rejection() {
5330
+ return this.event;
5331
+ }
5332
+ /**
5333
+ * True when a write to `spaceId`'s owner was rejected, or a write whose
5334
+ * space was unknown was: that one could have been any owner's.
5335
+ */
5336
+ isFullFor(spaceId) {
5337
+ return this.full.has(UNKNOWN_OWNER) || spaceId !== void 0 && this.full.has(this.ownerOf(spaceId));
5338
+ }
5339
+ /** Watch a context's service errors. Returns the unsubscribe function. */
5340
+ observe(context) {
5341
+ return context.on(TelemetryEvents.SERVICE_ERROR, (data) => {
5342
+ const { service, error } = data;
5343
+ if (!error || !isStorageFullError(error)) return;
5344
+ const spaceId = context.session?.spaceId;
5345
+ const owner = spaceId ? this.ownerOf(spaceId) : UNKNOWN_OWNER;
5346
+ if (this.full.has(owner)) return;
5347
+ const meta = error.meta ?? {};
5348
+ const event = { service: service ?? error.service, error };
5349
+ if (typeof meta.usedBytes === "number") event.usedBytes = meta.usedBytes;
5350
+ if (typeof meta.limitBytes === "number") event.limitBytes = meta.limitBytes;
5351
+ if (typeof meta.account === "object" && meta.account !== null) {
5352
+ event.account = meta.account;
5353
+ }
5354
+ if (spaceId) event.spaceId = spaceId;
5355
+ this.full.set(owner, event);
5356
+ if (this.event) return;
5357
+ this.event = event;
5358
+ context.emit(TelemetryEvents.STORAGE_FULL, event);
5359
+ for (const handler of this.handlers) {
5360
+ try {
5361
+ handler(event);
5362
+ } catch (handlerError) {
5363
+ console.error(`Error in "${TelemetryEvents.STORAGE_FULL}" handler:`, handlerError);
5364
+ }
5365
+ }
5366
+ });
5367
+ }
5368
+ /** Subscribe to the `storage.full` event. Returns the unsubscribe function. */
5369
+ on(handler) {
5370
+ this.handlers.add(handler);
5371
+ return () => {
5372
+ this.handlers.delete(handler);
5373
+ };
5374
+ }
5375
+ /** Forget that any owner's storage is full; the next rejection emits `storage.full` again. */
5376
+ clear() {
5377
+ this.full.clear();
5378
+ this.event = void 0;
5379
+ }
5380
+ /**
5381
+ * Forget that `spaceId`'s owner is full (`undefined`: the rejections whose
5382
+ * space was unknown). Once no owner is full, the next rejection emits
5383
+ * `storage.full` again.
5384
+ */
5385
+ clearFor(spaceId) {
5386
+ this.full.delete(spaceId === void 0 ? UNKNOWN_OWNER : this.ownerOf(spaceId));
5387
+ if (this.full.size === 0) this.event = void 0;
5388
+ }
5389
+ };
5390
+ var UNKNOWN_OWNER = "\0unknown";
5391
+
5392
+ // src/storage/status.ts
5393
+ var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/billing";
5394
+ var STORAGE_NEARLY_FULL_RATIO = 0.9;
5395
+ function storageUsageState(usedBytes, limitBytes) {
5396
+ if (limitBytes === null) return "ok";
5397
+ if (usedBytes >= limitBytes) return "full";
5398
+ return usedBytes >= limitBytes * STORAGE_NEARLY_FULL_RATIO ? "nearly_full" : "ok";
5399
+ }
5400
+ function storageUsageOf(value) {
5401
+ if (typeof value !== "object" || value === null) return void 0;
5402
+ const { usedBytes, limitBytes } = value;
5403
+ const isByteCount2 = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
5404
+ if (!isByteCount2(usedBytes)) return void 0;
5405
+ if (limitBytes !== null && !isByteCount2(limitBytes)) return void 0;
5406
+ return { usedBytes, limitBytes };
5407
+ }
5408
+ function parseStorageStatus(body) {
5409
+ if (typeof body !== "object" || body === null) return void 0;
5410
+ const record = body;
5411
+ const space = storageUsageOf(record.space);
5412
+ if (!space) return void 0;
5413
+ const status = {
5414
+ ...space,
5415
+ state: storageUsageState(space.usedBytes, space.limitBytes),
5416
+ manageUrl: typeof record.manageUrl === "string" && record.manageUrl.startsWith("https://") ? record.manageUrl : STORAGE_MANAGE_URL
5417
+ };
5418
+ const account = storageUsageOf(record.account);
5419
+ if (account) {
5420
+ const plan = record.account.plan;
5421
+ status.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
5422
+ if (status.account.plan) status.plan = status.account.plan;
5423
+ status.state = storageUsageState(account.usedBytes, account.limitBytes);
5424
+ }
5425
+ return status;
5426
+ }
5427
+
5267
5428
  // src/quota/TinyCloudQuota.ts
5268
5429
  var TinyCloudQuota = class {
5269
5430
  constructor(config = {}) {
@@ -8066,6 +8227,8 @@ export {
8066
8227
  SQLMigrations,
8067
8228
  SQLService,
8068
8229
  STORAGE_FULL_MESSAGE,
8230
+ STORAGE_MANAGE_URL,
8231
+ STORAGE_NEARLY_FULL_RATIO,
8069
8232
  STORAGE_WRITE_TOO_LARGE_MESSAGE,
8070
8233
  SecretsService,
8071
8234
  ServiceContext,
@@ -8075,6 +8238,7 @@ export {
8075
8238
  ServiceResponseEventSchema,
8076
8239
  ServiceRetryEventSchema,
8077
8240
  ServiceSessionSchema,
8241
+ StorageFullMonitor,
8078
8242
  TC832_RESERVED_SCOPES,
8079
8243
  TelemetryEvents,
8080
8244
  TelemetrySpanEventSchema,
@@ -8136,6 +8300,8 @@ export {
8136
8300
  parsePermissionHintFromErrorText,
8137
8301
  parseSecretCatalogKey,
8138
8302
  parseStorageQuotaBytes,
8303
+ parseStorageRejection,
8304
+ parseStorageStatus,
8139
8305
  permissionDeniedError,
8140
8306
  replicationIdentityKey,
8141
8307
  requiresSecretsOptIn,
@@ -8145,6 +8311,7 @@ export {
8145
8311
  storageLimitReachedError,
8146
8312
  storageQuotaExceededError,
8147
8313
  storageRejectionError,
8314
+ storageUsageState,
8148
8315
  timeoutError,
8149
8316
  tinyCloudDebugLogger,
8150
8317
  utf8Decode,