@tinycloud/sdk-services 3.2.0-beta.3 → 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.
- package/dist/{BaseService-BDQXjWMx.d.cts → BaseService-4wwJq1Gn.d.cts} +1 -1
- package/dist/{BaseService-Di_hVfUV.d.ts → BaseService-BGr2slUz.d.ts} +1 -1
- package/dist/encryption/index.cjs +5 -1
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +2 -2
- package/dist/encryption/index.d.ts +2 -2
- package/dist/encryption/index.js +5 -1
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +190 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +157 -6
- package/dist/index.d.ts +157 -6
- package/dist/index.js +184 -11
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +56 -11
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +5 -4
- package/dist/kv/index.d.ts +5 -4
- package/dist/kv/index.js +56 -11
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +44 -19
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +4 -3
- package/dist/kv/replication/index.d.ts +4 -3
- package/dist/kv/replication/index.js +44 -19
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/kv/replication/prefix.cjs +43 -0
- package/dist/kv/replication/prefix.cjs.map +1 -0
- package/dist/kv/replication/prefix.d.cts +6 -0
- package/dist/kv/replication/prefix.d.ts +6 -0
- package/dist/kv/replication/prefix.js +17 -0
- package/dist/kv/replication/prefix.js.map +1 -0
- package/dist/{scope-BmLxwyl-.d.ts → scope-CoKKmMiI.d.ts} +5 -5
- package/dist/{scope-D855kRDx.d.cts → scope-D7wmjbMH.d.cts} +5 -5
- package/dist/sql/index.cjs +44 -2
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +2 -2
- package/dist/sql/index.d.ts +2 -2
- package/dist/sql/index.js +44 -2
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-7A5rHWRY.d.cts → types-D-9A3Sut.d.cts} +6 -0
- package/dist/{types-7A5rHWRY.d.ts → types-D-9A3Sut.d.ts} +6 -0
- package/package.json +14 -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, ...
|
|
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 && !
|
|
1856
|
+
if (response.status === 413 && !parseStorageRejection(errorText)) {
|
|
1818
1857
|
return void 0;
|
|
1819
1858
|
}
|
|
1820
1859
|
return err(
|
|
@@ -2854,6 +2893,7 @@ var KVService = class extends BaseService {
|
|
|
2854
2893
|
"kv"
|
|
2855
2894
|
));
|
|
2856
2895
|
}
|
|
2896
|
+
const requestPrefix = options.prefix === "/" ? "" : options.prefix;
|
|
2857
2897
|
const headers = {};
|
|
2858
2898
|
if (options.limit !== void 0) headers["x-tinycloud-limit"] = String(options.limit);
|
|
2859
2899
|
if (options.cursor !== void 0) headers["x-tinycloud-cursor"] = options.cursor;
|
|
@@ -2863,7 +2903,7 @@ var KVService = class extends BaseService {
|
|
|
2863
2903
|
const request = this.createRequestSignal(options.signal, options.timeout);
|
|
2864
2904
|
try {
|
|
2865
2905
|
const response = await this.invokeOperation(
|
|
2866
|
-
|
|
2906
|
+
requestPrefix,
|
|
2867
2907
|
KVAction.SYNC,
|
|
2868
2908
|
void 0,
|
|
2869
2909
|
request.signal,
|
|
@@ -2879,7 +2919,7 @@ var KVService = class extends BaseService {
|
|
|
2879
2919
|
} catch {
|
|
2880
2920
|
body = void 0;
|
|
2881
2921
|
}
|
|
2882
|
-
const page = this.normalizeChangesResponse(body);
|
|
2922
|
+
const page = this.normalizeChangesResponse(body, options.prefix);
|
|
2883
2923
|
if (!page) {
|
|
2884
2924
|
return err(serviceError(
|
|
2885
2925
|
ErrorCodes.NETWORK_ERROR,
|
|
@@ -2959,7 +2999,7 @@ var KVService = class extends BaseService {
|
|
|
2959
2999
|
{ meta }
|
|
2960
3000
|
));
|
|
2961
3001
|
}
|
|
2962
|
-
normalizeChangesResponse(data) {
|
|
3002
|
+
normalizeChangesResponse(data, requestedPrefix) {
|
|
2963
3003
|
if (!isJsonObject(data)) return void 0;
|
|
2964
3004
|
const { changes, more, cursor, source, authority } = data;
|
|
2965
3005
|
if (!Array.isArray(changes) || typeof more !== "boolean" || typeof cursor !== "string" || !isJsonObject(source) || typeof source.nodeDid !== "string" || typeof source.space !== "string" || typeof source.prefix !== "string" || !isJsonObject(authority)) {
|
|
@@ -2991,7 +3031,11 @@ var KVService = class extends BaseService {
|
|
|
2991
3031
|
changes: parsed,
|
|
2992
3032
|
more,
|
|
2993
3033
|
cursor,
|
|
2994
|
-
source: {
|
|
3034
|
+
source: {
|
|
3035
|
+
nodeDid: source.nodeDid,
|
|
3036
|
+
space: source.space,
|
|
3037
|
+
prefix: requestedPrefix === "/" && source.prefix === "" ? "/" : source.prefix
|
|
3038
|
+
},
|
|
2995
3039
|
authority: { notBefore, expiresAt, retainUntil }
|
|
2996
3040
|
};
|
|
2997
3041
|
}
|
|
@@ -3286,13 +3330,14 @@ ${canonical.space}
|
|
|
3286
3330
|
${canonical.principal}`;
|
|
3287
3331
|
}
|
|
3288
3332
|
|
|
3289
|
-
// src/kv/replication/
|
|
3333
|
+
// src/kv/replication/prefix.ts
|
|
3290
3334
|
function kvPrefixCovers(prefix, key) {
|
|
3291
|
-
|
|
3292
|
-
if (prefix.endsWith("/")) return key.startsWith(prefix);
|
|
3293
|
-
return key === prefix || key.startsWith(`${prefix}/`);
|
|
3335
|
+
return prefix === "/" || key.startsWith(prefix);
|
|
3294
3336
|
}
|
|
3337
|
+
|
|
3338
|
+
// src/kv/replication/scope.ts
|
|
3295
3339
|
function requiresSecretsOptIn(space, prefix) {
|
|
3340
|
+
if (prefix === "/") return true;
|
|
3296
3341
|
if (space === "secrets" || space.endsWith(":secrets")) return true;
|
|
3297
3342
|
return prefix.split("/", 1)[0] === "vault";
|
|
3298
3343
|
}
|
|
@@ -3731,6 +3776,9 @@ var SQLService = class extends BaseService {
|
|
|
3731
3776
|
if (response.status === 402) {
|
|
3732
3777
|
return err(storageRejectionError("sql", 402, meta, errorText));
|
|
3733
3778
|
}
|
|
3779
|
+
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
3780
|
+
return err(storageRejectionError("sql", 413, meta, errorText));
|
|
3781
|
+
}
|
|
3734
3782
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3735
3783
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3736
3784
|
response.status,
|
|
@@ -4197,6 +4245,9 @@ var DuckDbService = class extends BaseService {
|
|
|
4197
4245
|
if (response.status === 402) {
|
|
4198
4246
|
return err(storageRejectionError("duckdb", 402, meta, errorText));
|
|
4199
4247
|
}
|
|
4248
|
+
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
4249
|
+
return err(storageRejectionError("duckdb", 413, meta, errorText));
|
|
4250
|
+
}
|
|
4200
4251
|
const errorBody = parseServiceErrorBody(errorText);
|
|
4201
4252
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
4202
4253
|
response.status,
|
|
@@ -5258,6 +5309,122 @@ function parsedStringArrayField(value, key) {
|
|
|
5258
5309
|
}
|
|
5259
5310
|
}
|
|
5260
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
|
+
|
|
5261
5428
|
// src/quota/TinyCloudQuota.ts
|
|
5262
5429
|
var TinyCloudQuota = class {
|
|
5263
5430
|
constructor(config = {}) {
|
|
@@ -8060,6 +8227,8 @@ export {
|
|
|
8060
8227
|
SQLMigrations,
|
|
8061
8228
|
SQLService,
|
|
8062
8229
|
STORAGE_FULL_MESSAGE,
|
|
8230
|
+
STORAGE_MANAGE_URL,
|
|
8231
|
+
STORAGE_NEARLY_FULL_RATIO,
|
|
8063
8232
|
STORAGE_WRITE_TOO_LARGE_MESSAGE,
|
|
8064
8233
|
SecretsService,
|
|
8065
8234
|
ServiceContext,
|
|
@@ -8069,6 +8238,7 @@ export {
|
|
|
8069
8238
|
ServiceResponseEventSchema,
|
|
8070
8239
|
ServiceRetryEventSchema,
|
|
8071
8240
|
ServiceSessionSchema,
|
|
8241
|
+
StorageFullMonitor,
|
|
8072
8242
|
TC832_RESERVED_SCOPES,
|
|
8073
8243
|
TelemetryEvents,
|
|
8074
8244
|
TelemetrySpanEventSchema,
|
|
@@ -8130,6 +8300,8 @@ export {
|
|
|
8130
8300
|
parsePermissionHintFromErrorText,
|
|
8131
8301
|
parseSecretCatalogKey,
|
|
8132
8302
|
parseStorageQuotaBytes,
|
|
8303
|
+
parseStorageRejection,
|
|
8304
|
+
parseStorageStatus,
|
|
8133
8305
|
permissionDeniedError,
|
|
8134
8306
|
replicationIdentityKey,
|
|
8135
8307
|
requiresSecretsOptIn,
|
|
@@ -8139,6 +8311,7 @@ export {
|
|
|
8139
8311
|
storageLimitReachedError,
|
|
8140
8312
|
storageQuotaExceededError,
|
|
8141
8313
|
storageRejectionError,
|
|
8314
|
+
storageUsageState,
|
|
8142
8315
|
timeoutError,
|
|
8143
8316
|
tinyCloudDebugLogger,
|
|
8144
8317
|
utf8Decode,
|