@tinycloud/sdk-services 2.2.0-beta.10 → 2.2.0-beta.13

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.d.cts CHANGED
@@ -2,7 +2,7 @@ import { I as IServiceContext, a as InvokeFunction, b as InvokeAnyFunction, F as
2
2
  export { E as ErrorCode, g as ErrorCodes, h as EventHandler, i as FetchRequestInit, j as FetchResponse, k as InvocationFact, l as InvocationFacts, m as InvokeAnyEntry, n as ServiceErrorEvent, o as ServiceHeaders, p as ServiceRequestEvent, q as ServiceResponseEvent, r as ServiceRetryEvent, T as TelemetryEvents, s as defaultRetryPolicy, t as err, u as ok, v as serviceError } from './BaseService-BiS6HRwE.cjs';
3
3
  import { z } from 'zod';
4
4
  import { IKVService } from './kv/index.cjs';
5
- export { IPrefixedKVService, KVAction, KVActionType, KVDeleteOptions, KVGetOptions, KVHeadOptions, KVListOptions, KVListResponse, KVPutOptions, KVResponse, KVResponseHeaders, KVService, KVServiceConfig, PrefixedKVService } from './kv/index.cjs';
5
+ export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, IPrefixedKVService, KVAction, KVActionType, KVCreateSignedReadUrlOptions, KVDeleteOptions, KVGetOptions, KVHeadOptions, KVListOptions, KVListResponse, KVPutOptions, KVResponse, KVResponseHeaders, KVService, KVServiceConfig, KVSignedReadUrlResponse, PrefixedKVService } from './kv/index.cjs';
6
6
  export { BatchOptions, BatchResponse, DatabaseHandle, ExecuteOptions, ExecuteResponse, IDatabaseHandle, ISQLService, QueryOptions, QueryResponse, SQLAction, SQLActionType, SQLService, SQLServiceConfig, SqlStatement, SqlValue } from './sql/index.cjs';
7
7
 
8
8
  /**
@@ -1747,6 +1747,7 @@ declare class DataVaultService extends BaseService implements IDataVaultService
1747
1747
  private encryptionIdentity;
1748
1748
  private _isUnlocked;
1749
1749
  private vaultConfig;
1750
+ private unlockInFlight;
1750
1751
  /**
1751
1752
  * Create a new DataVaultService instance.
1752
1753
  *
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { I as IServiceContext, a as InvokeFunction, b as InvokeAnyFunction, F as
2
2
  export { E as ErrorCode, g as ErrorCodes, h as EventHandler, i as FetchRequestInit, j as FetchResponse, k as InvocationFact, l as InvocationFacts, m as InvokeAnyEntry, n as ServiceErrorEvent, o as ServiceHeaders, p as ServiceRequestEvent, q as ServiceResponseEvent, r as ServiceRetryEvent, T as TelemetryEvents, s as defaultRetryPolicy, t as err, u as ok, v as serviceError } from './BaseService-BiS6HRwE.js';
3
3
  import { z } from 'zod';
4
4
  import { IKVService } from './kv/index.js';
5
- export { IPrefixedKVService, KVAction, KVActionType, KVDeleteOptions, KVGetOptions, KVHeadOptions, KVListOptions, KVListResponse, KVPutOptions, KVResponse, KVResponseHeaders, KVService, KVServiceConfig, PrefixedKVService } from './kv/index.js';
5
+ export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, IPrefixedKVService, KVAction, KVActionType, KVCreateSignedReadUrlOptions, KVDeleteOptions, KVGetOptions, KVHeadOptions, KVListOptions, KVListResponse, KVPutOptions, KVResponse, KVResponseHeaders, KVService, KVServiceConfig, KVSignedReadUrlResponse, PrefixedKVService } from './kv/index.js';
6
6
  export { BatchOptions, BatchResponse, DatabaseHandle, ExecuteOptions, ExecuteResponse, IDatabaseHandle, ISQLService, QueryOptions, QueryResponse, SQLAction, SQLActionType, SQLService, SQLServiceConfig, SqlStatement, SqlValue } from './sql/index.js';
7
7
 
8
8
  /**
@@ -1747,6 +1747,7 @@ declare class DataVaultService extends BaseService implements IDataVaultService
1747
1747
  private encryptionIdentity;
1748
1748
  private _isUnlocked;
1749
1749
  private vaultConfig;
1750
+ private unlockInFlight;
1750
1751
  /**
1751
1752
  * Create a new DataVaultService instance.
1752
1753
  *
package/dist/index.js CHANGED
@@ -818,6 +818,13 @@ var PrefixedKVService = class _PrefixedKVService {
818
818
  const fullKey = this.getFullKey(key);
819
819
  return this._kv.head(fullKey, { ...options, prefix: "" });
820
820
  }
821
+ /**
822
+ * Create a short-lived signed URL for reading a KV object.
823
+ */
824
+ async createSignedReadUrl(key, options) {
825
+ const fullKey = this.getFullKey(key);
826
+ return this._kv.createSignedReadUrl(fullKey, { ...options, prefix: "" });
827
+ }
821
828
  /**
822
829
  * Create a nested prefix-scoped view.
823
830
  */
@@ -829,6 +836,7 @@ var PrefixedKVService = class _PrefixedKVService {
829
836
  };
830
837
 
831
838
  // src/kv/types.ts
839
+ var DEFAULT_SIGNED_READ_URL_EXPIRY_MS = 5 * 60 * 1e3;
832
840
  var KVAction = {
833
841
  GET: "tinycloud.kv/get",
834
842
  PUT: "tinycloud.kv/put",
@@ -913,6 +921,15 @@ var KVService = class extends BaseService {
913
921
  get host() {
914
922
  return this.context.hosts[0];
915
923
  }
924
+ withJsonContentType(headers) {
925
+ if (Array.isArray(headers)) {
926
+ return [...headers, ["content-type", "application/json"]];
927
+ }
928
+ return {
929
+ ...headers,
930
+ "content-type": "application/json"
931
+ };
932
+ }
916
933
  /**
917
934
  * Execute an invoke operation.
918
935
  *
@@ -982,6 +999,48 @@ var KVService = class extends BaseService {
982
999
  return text;
983
1000
  }
984
1001
  }
1002
+ async createSignedReadUrlError(response, key) {
1003
+ let errorText = response.statusText;
1004
+ try {
1005
+ const text = await response.text();
1006
+ if (text) {
1007
+ errorText = text;
1008
+ }
1009
+ } catch {
1010
+ }
1011
+ if (response.status === 401 || response.status === 403) {
1012
+ const { resource, action } = parseAuthError(errorText);
1013
+ return err(authUnauthorizedError("kv", errorText, {
1014
+ status: response.status,
1015
+ ...action && { requiredAction: action },
1016
+ ...resource && { resource }
1017
+ }));
1018
+ }
1019
+ const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
1020
+ return err(
1021
+ serviceError(
1022
+ code,
1023
+ `Failed to create signed read URL for key "${key}": ${response.status} - ${errorText}`,
1024
+ "kv",
1025
+ { meta: { status: response.status, statusText: response.statusText } }
1026
+ )
1027
+ );
1028
+ }
1029
+ normalizeSignedReadUrlResponse(data) {
1030
+ if (!data || typeof data !== "object") {
1031
+ return void 0;
1032
+ }
1033
+ const response = data;
1034
+ if (typeof response.url !== "string" || typeof response.ticketId !== "string" || typeof response.expiresAt !== "string") {
1035
+ return void 0;
1036
+ }
1037
+ return {
1038
+ url: new URL(response.url, this.host).toString(),
1039
+ relativeUrl: response.url,
1040
+ ticketId: response.ticketId,
1041
+ expiresAt: response.expiresAt
1042
+ };
1043
+ }
985
1044
  /**
986
1045
  * Get a value by key.
987
1046
  */
@@ -1254,6 +1313,61 @@ var KVService = class extends BaseService {
1254
1313
  }
1255
1314
  });
1256
1315
  }
1316
+ /**
1317
+ * Create a short-lived signed URL for reading a KV object.
1318
+ */
1319
+ async createSignedReadUrl(key, options) {
1320
+ return this.withTelemetry("createSignedReadUrl", key, async () => {
1321
+ if (!this.requireAuth()) {
1322
+ return err(authRequiredError("kv"));
1323
+ }
1324
+ const path = this.getFullPath(key, options?.prefix);
1325
+ const session = this.context.session;
1326
+ const headers = this.context.invoke(
1327
+ session,
1328
+ "kv",
1329
+ path,
1330
+ KVAction.GET
1331
+ );
1332
+ const body = {
1333
+ space: session.spaceId,
1334
+ path,
1335
+ ttl_seconds: options?.expiresInSeconds ?? Math.ceil(DEFAULT_SIGNED_READ_URL_EXPIRY_MS / 1e3)
1336
+ };
1337
+ if (options?.contentHash !== void 0) {
1338
+ body.content_hash = options.contentHash;
1339
+ }
1340
+ if (options?.etag !== void 0) {
1341
+ body.etag = options.etag;
1342
+ }
1343
+ try {
1344
+ const response = await this.context.fetch(`${this.host}/signed/kv`, {
1345
+ method: "POST",
1346
+ headers: this.withJsonContentType(headers),
1347
+ body: JSON.stringify(body),
1348
+ signal: this.combineSignals(options?.signal)
1349
+ });
1350
+ if (!response.ok) {
1351
+ return this.createSignedReadUrlError(response, key);
1352
+ }
1353
+ const signedUrl = this.normalizeSignedReadUrlResponse(
1354
+ await response.json()
1355
+ );
1356
+ if (!signedUrl) {
1357
+ return err(
1358
+ serviceError(
1359
+ ErrorCodes.NETWORK_ERROR,
1360
+ "Signed read URL response did not include url, ticketId, and expiresAt",
1361
+ "kv"
1362
+ )
1363
+ );
1364
+ }
1365
+ return ok(signedUrl);
1366
+ } catch (error) {
1367
+ return err(wrapError("kv", error));
1368
+ }
1369
+ });
1370
+ }
1257
1371
  /**
1258
1372
  * Create a prefix-scoped view of this KV service.
1259
1373
  *
@@ -2823,6 +2937,9 @@ function base64Decode(str) {
2823
2937
  }
2824
2938
  return bytes;
2825
2939
  }
2940
+ function isUnlockSigner(signer) {
2941
+ return typeof signer === "object" && signer !== null && typeof signer.signMessage === "function";
2942
+ }
2826
2943
  function defaultVaultMessage(input) {
2827
2944
  switch (input.code) {
2828
2945
  case "DECRYPTION_FAILED":
@@ -2860,6 +2977,7 @@ var DataVaultService = class extends BaseService {
2860
2977
  this.masterKey = null;
2861
2978
  this.encryptionIdentity = null;
2862
2979
  this._isUnlocked = false;
2980
+ this.unlockInFlight = null;
2863
2981
  this.vaultConfig = config;
2864
2982
  this._config = config;
2865
2983
  }
@@ -2919,30 +3037,40 @@ var DataVaultService = class extends BaseService {
2919
3037
  * signatures exist (browser only).
2920
3038
  */
2921
3039
  async unlock(signer) {
2922
- return this.withTelemetry("unlock", void 0, async () => {
3040
+ const unlockSigner = isUnlockSigner(signer) ? signer : void 0;
3041
+ if (this._isUnlocked && this.masterKey && (this.encryptionIdentity || !unlockSigner)) {
3042
+ return { ok: true, data: void 0 };
3043
+ }
3044
+ if (this.unlockInFlight) {
3045
+ return this.unlockInFlight;
3046
+ }
3047
+ this.unlockInFlight = this.withTelemetry("unlock", void 0, async () => {
2923
3048
  const spaceId = this.vaultConfig.spaceId;
2924
3049
  const versionConfig = VaultVersionConfig[CURRENT_VAULT_VERSION];
2925
3050
  const masterCacheKey = `vault-master:${spaceId}`;
2926
3051
  const identityCacheKey = `vault-identity:${this.tc.address}`;
2927
3052
  try {
2928
- let masterSigBytes = await loadCachedSignature(masterCacheKey);
2929
- if (!masterSigBytes) {
2930
- if (!signer) {
2931
- return vaultError({
2932
- code: "VAULT_LOCKED",
2933
- message: "Signer is required when no cached master signature exists"
2934
- });
3053
+ if (!this.masterKey) {
3054
+ let masterSigBytes = await loadCachedSignature(masterCacheKey);
3055
+ if (!masterSigBytes) {
3056
+ if (!unlockSigner) {
3057
+ return vaultError({
3058
+ code: "VAULT_LOCKED",
3059
+ message: "Signer is required when no cached master signature exists"
3060
+ });
3061
+ }
3062
+ const sig = await unlockSigner.signMessage(
3063
+ versionConfig.masterMessage(spaceId)
3064
+ );
3065
+ masterSigBytes = toBytes(sig);
3066
+ await cacheSignature(masterCacheKey, masterSigBytes);
2935
3067
  }
2936
- const s = signer;
2937
- const sig = await s.signMessage(versionConfig.masterMessage(spaceId));
2938
- masterSigBytes = toBytes(sig);
2939
- await cacheSignature(masterCacheKey, masterSigBytes);
2940
- }
2941
- this.masterKey = this.crypto.deriveKey(
2942
- masterSigBytes,
2943
- this.crypto.sha256(toBytes(spaceId)),
2944
- toBytes("vault-master")
2945
- );
3068
+ this.masterKey = this.crypto.deriveKey(
3069
+ masterSigBytes,
3070
+ this.crypto.sha256(toBytes(spaceId)),
3071
+ toBytes("vault-master")
3072
+ );
3073
+ }
2946
3074
  const publicSpaceId = this.tc.makePublicSpaceId(this.tc.address, this.tc.chainId);
2947
3075
  let existingPubKey = null;
2948
3076
  try {
@@ -2965,13 +3093,14 @@ var DataVaultService = class extends BaseService {
2965
3093
  } else {
2966
3094
  let identitySigBytes = await loadCachedSignature(identityCacheKey);
2967
3095
  if (!identitySigBytes) {
2968
- if (!signer) {
3096
+ if (!unlockSigner) {
2969
3097
  this.encryptionIdentity = null;
2970
3098
  this._isUnlocked = true;
2971
3099
  return ok(void 0);
2972
3100
  }
2973
- const s = signer;
2974
- const sig = await s.signMessage(versionConfig.identityMessage);
3101
+ const sig = await unlockSigner.signMessage(
3102
+ versionConfig.identityMessage
3103
+ );
2975
3104
  identitySigBytes = toBytes(sig);
2976
3105
  await cacheSignature(identityCacheKey, identitySigBytes);
2977
3106
  }
@@ -3001,6 +3130,11 @@ var DataVaultService = class extends BaseService {
3001
3130
  });
3002
3131
  }
3003
3132
  });
3133
+ try {
3134
+ return await this.unlockInFlight;
3135
+ } finally {
3136
+ this.unlockInFlight = null;
3137
+ }
3004
3138
  }
3005
3139
  /**
3006
3140
  * Clear the cached vault signatures.
@@ -3885,6 +4019,7 @@ var SecretsService = class {
3885
4019
  };
3886
4020
  export {
3887
4021
  BaseService,
4022
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
3888
4023
  DataVaultService,
3889
4024
  DatabaseHandle,
3890
4025
  DuckDbAction,