@tinycloud/sdk-services 2.2.0-beta.7 → 2.2.0

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.
@@ -1,4 +1,4 @@
1
- import { e as Result, c as IService, B as BaseService } from '../BaseService-BiS6HRwE.cjs';
1
+ import { e as Result, c as IService, B as BaseService } from '../BaseService-C_iXlTeN.cjs';
2
2
 
3
3
  /**
4
4
  * KV Service Types
@@ -140,6 +140,47 @@ interface KVHeadOptions {
140
140
  */
141
141
  signal?: AbortSignal;
142
142
  }
143
+ /**
144
+ * Default lifetime for signed KV read URLs when a caller omits expiresInSeconds.
145
+ * SDK duration defaults are stored in milliseconds; createSignedReadUrl converts
146
+ * this to the node endpoint's ttl_seconds field.
147
+ *
148
+ * Keep this in sync with EXPIRY.SIGNED_READ_URL_MS in @tinycloud/sdk-core.
149
+ * sdk-services cannot import sdk-core because sdk-core depends on sdk-services.
150
+ */
151
+ declare const DEFAULT_SIGNED_READ_URL_EXPIRY_MS: number;
152
+ /**
153
+ * Options for creating a signed KV read URL.
154
+ */
155
+ interface KVCreateSignedReadUrlOptions {
156
+ /**
157
+ * Override the default prefix for this operation.
158
+ */
159
+ prefix?: string;
160
+ /**
161
+ * Requested URL lifetime in seconds.
162
+ * Defaults to {@link DEFAULT_SIGNED_READ_URL_EXPIRY_MS} converted to seconds.
163
+ * The node may cap this by its configured maximum, the invocation expiry,
164
+ * or the parent delegation expiry.
165
+ */
166
+ expiresInSeconds?: number;
167
+ /**
168
+ * Optional blake3 content hash to bind the signed URL to a specific object.
169
+ */
170
+ contentHash?: string;
171
+ /**
172
+ * Optional ETag to bind the signed URL to a specific object version.
173
+ */
174
+ etag?: string;
175
+ /**
176
+ * Custom timeout for this operation in milliseconds.
177
+ */
178
+ timeout?: number;
179
+ /**
180
+ * Custom abort signal for this operation.
181
+ */
182
+ signal?: AbortSignal;
183
+ }
143
184
  /**
144
185
  * Response headers from KV operations.
145
186
  */
@@ -192,6 +233,27 @@ interface KVListResponse {
192
233
  */
193
234
  keys: string[];
194
235
  }
236
+ /**
237
+ * Response from signed KV read URL creation.
238
+ */
239
+ interface KVSignedReadUrlResponse {
240
+ /**
241
+ * Absolute URL suitable for passing to external readers.
242
+ */
243
+ url: string;
244
+ /**
245
+ * Opaque URL returned by tinycloud-node, usually relative to the node host.
246
+ */
247
+ relativeUrl: string;
248
+ /**
249
+ * Opaque signed KV ticket identifier.
250
+ */
251
+ ticketId: string;
252
+ /**
253
+ * Expiry timestamp as returned by tinycloud-node.
254
+ */
255
+ expiresAt: string;
256
+ }
195
257
  /**
196
258
  * KV service action types.
197
259
  */
@@ -333,6 +395,16 @@ interface IPrefixedKVService {
333
395
  * ```
334
396
  */
335
397
  head(key: string, options?: Omit<KVHeadOptions, 'prefix'>): Promise<Result<KVResponse<void>>>;
398
+ /**
399
+ * Create a short-lived signed URL for reading a KV object.
400
+ *
401
+ * The key is automatically prefixed with this service's prefix.
402
+ *
403
+ * @param key - The key to expose via a signed read URL (will be prefixed)
404
+ * @param options - Optional signed URL configuration
405
+ * @returns Result with URL and expiry metadata
406
+ */
407
+ createSignedReadUrl(key: string, options?: Omit<KVCreateSignedReadUrlOptions, 'prefix'>): Promise<Result<KVSignedReadUrlResponse>>;
336
408
  /**
337
409
  * Create a nested prefix-scoped view.
338
410
  *
@@ -362,6 +434,7 @@ interface IKVServiceLike {
362
434
  list(options?: KVListOptions): Promise<Result<KVListResponse>>;
363
435
  delete(key: string, options?: KVDeleteOptions): Promise<Result<void>>;
364
436
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
437
+ createSignedReadUrl(key: string, options?: KVCreateSignedReadUrlOptions): Promise<Result<KVSignedReadUrlResponse>>;
365
438
  }
366
439
  /**
367
440
  * PrefixedKVService - Implementation of prefix-scoped KV operations.
@@ -442,6 +515,10 @@ declare class PrefixedKVService implements IPrefixedKVService {
442
515
  * Get metadata for a key without retrieving the value.
443
516
  */
444
517
  head(key: string, options?: Omit<KVHeadOptions, 'prefix'>): Promise<Result<KVResponse<void>>>;
518
+ /**
519
+ * Create a short-lived signed URL for reading a KV object.
520
+ */
521
+ createSignedReadUrl(key: string, options?: Omit<KVCreateSignedReadUrlOptions, 'prefix'>): Promise<Result<KVSignedReadUrlResponse>>;
445
522
  /**
446
523
  * Create a nested prefix-scoped view.
447
524
  */
@@ -564,6 +641,20 @@ interface IKVService extends IService {
564
641
  * ```
565
642
  */
566
643
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
644
+ /**
645
+ * Create a short-lived signed URL for reading a KV object.
646
+ *
647
+ * The request is authorized with the current session's `tinycloud.kv/get`
648
+ * capability for the resolved key path. The returned `url` is absolute and
649
+ * can be passed to external services that need bearer read access.
650
+ *
651
+ * Requires tinycloud-node with the `/signed/kv` endpoint from TC-1368.
652
+ *
653
+ * @param key - The key to expose via a signed read URL
654
+ * @param options - Optional signed URL configuration
655
+ * @returns Result with URL and expiry metadata
656
+ */
657
+ createSignedReadUrl(key: string, options?: KVCreateSignedReadUrlOptions): Promise<Result<KVSignedReadUrlResponse>>;
567
658
  /**
568
659
  * Create a prefix-scoped view of this KV service.
569
660
  *
@@ -655,6 +746,7 @@ declare class KVService extends BaseService implements IKVService {
655
746
  * Get the host URL.
656
747
  */
657
748
  private get host();
749
+ private withJsonContentType;
658
750
  /**
659
751
  * Execute an invoke operation.
660
752
  *
@@ -680,6 +772,8 @@ declare class KVService extends BaseService implements IKVService {
680
772
  * @returns Parsed data
681
773
  */
682
774
  private parseResponse;
775
+ private createSignedReadUrlError;
776
+ private normalizeSignedReadUrlResponse;
683
777
  /**
684
778
  * Get a value by key.
685
779
  */
@@ -700,6 +794,10 @@ declare class KVService extends BaseService implements IKVService {
700
794
  * Get metadata for a key without retrieving the value.
701
795
  */
702
796
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
797
+ /**
798
+ * Create a short-lived signed URL for reading a KV object.
799
+ */
800
+ createSignedReadUrl(key: string, options?: KVCreateSignedReadUrlOptions): Promise<Result<KVSignedReadUrlResponse>>;
703
801
  /**
704
802
  * Create a prefix-scoped view of this KV service.
705
803
  *
@@ -745,4 +843,4 @@ declare class KVService extends BaseService implements IKVService {
745
843
  withPrefix(prefix: string): IPrefixedKVService;
746
844
  }
747
845
 
748
- export { type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, PrefixedKVService };
846
+ export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVCreateSignedReadUrlOptions, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, type KVSignedReadUrlResponse, PrefixedKVService };
@@ -1,4 +1,4 @@
1
- import { e as Result, c as IService, B as BaseService } from '../BaseService-BiS6HRwE.js';
1
+ import { e as Result, c as IService, B as BaseService } from '../BaseService-C_iXlTeN.js';
2
2
 
3
3
  /**
4
4
  * KV Service Types
@@ -140,6 +140,47 @@ interface KVHeadOptions {
140
140
  */
141
141
  signal?: AbortSignal;
142
142
  }
143
+ /**
144
+ * Default lifetime for signed KV read URLs when a caller omits expiresInSeconds.
145
+ * SDK duration defaults are stored in milliseconds; createSignedReadUrl converts
146
+ * this to the node endpoint's ttl_seconds field.
147
+ *
148
+ * Keep this in sync with EXPIRY.SIGNED_READ_URL_MS in @tinycloud/sdk-core.
149
+ * sdk-services cannot import sdk-core because sdk-core depends on sdk-services.
150
+ */
151
+ declare const DEFAULT_SIGNED_READ_URL_EXPIRY_MS: number;
152
+ /**
153
+ * Options for creating a signed KV read URL.
154
+ */
155
+ interface KVCreateSignedReadUrlOptions {
156
+ /**
157
+ * Override the default prefix for this operation.
158
+ */
159
+ prefix?: string;
160
+ /**
161
+ * Requested URL lifetime in seconds.
162
+ * Defaults to {@link DEFAULT_SIGNED_READ_URL_EXPIRY_MS} converted to seconds.
163
+ * The node may cap this by its configured maximum, the invocation expiry,
164
+ * or the parent delegation expiry.
165
+ */
166
+ expiresInSeconds?: number;
167
+ /**
168
+ * Optional blake3 content hash to bind the signed URL to a specific object.
169
+ */
170
+ contentHash?: string;
171
+ /**
172
+ * Optional ETag to bind the signed URL to a specific object version.
173
+ */
174
+ etag?: string;
175
+ /**
176
+ * Custom timeout for this operation in milliseconds.
177
+ */
178
+ timeout?: number;
179
+ /**
180
+ * Custom abort signal for this operation.
181
+ */
182
+ signal?: AbortSignal;
183
+ }
143
184
  /**
144
185
  * Response headers from KV operations.
145
186
  */
@@ -192,6 +233,27 @@ interface KVListResponse {
192
233
  */
193
234
  keys: string[];
194
235
  }
236
+ /**
237
+ * Response from signed KV read URL creation.
238
+ */
239
+ interface KVSignedReadUrlResponse {
240
+ /**
241
+ * Absolute URL suitable for passing to external readers.
242
+ */
243
+ url: string;
244
+ /**
245
+ * Opaque URL returned by tinycloud-node, usually relative to the node host.
246
+ */
247
+ relativeUrl: string;
248
+ /**
249
+ * Opaque signed KV ticket identifier.
250
+ */
251
+ ticketId: string;
252
+ /**
253
+ * Expiry timestamp as returned by tinycloud-node.
254
+ */
255
+ expiresAt: string;
256
+ }
195
257
  /**
196
258
  * KV service action types.
197
259
  */
@@ -333,6 +395,16 @@ interface IPrefixedKVService {
333
395
  * ```
334
396
  */
335
397
  head(key: string, options?: Omit<KVHeadOptions, 'prefix'>): Promise<Result<KVResponse<void>>>;
398
+ /**
399
+ * Create a short-lived signed URL for reading a KV object.
400
+ *
401
+ * The key is automatically prefixed with this service's prefix.
402
+ *
403
+ * @param key - The key to expose via a signed read URL (will be prefixed)
404
+ * @param options - Optional signed URL configuration
405
+ * @returns Result with URL and expiry metadata
406
+ */
407
+ createSignedReadUrl(key: string, options?: Omit<KVCreateSignedReadUrlOptions, 'prefix'>): Promise<Result<KVSignedReadUrlResponse>>;
336
408
  /**
337
409
  * Create a nested prefix-scoped view.
338
410
  *
@@ -362,6 +434,7 @@ interface IKVServiceLike {
362
434
  list(options?: KVListOptions): Promise<Result<KVListResponse>>;
363
435
  delete(key: string, options?: KVDeleteOptions): Promise<Result<void>>;
364
436
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
437
+ createSignedReadUrl(key: string, options?: KVCreateSignedReadUrlOptions): Promise<Result<KVSignedReadUrlResponse>>;
365
438
  }
366
439
  /**
367
440
  * PrefixedKVService - Implementation of prefix-scoped KV operations.
@@ -442,6 +515,10 @@ declare class PrefixedKVService implements IPrefixedKVService {
442
515
  * Get metadata for a key without retrieving the value.
443
516
  */
444
517
  head(key: string, options?: Omit<KVHeadOptions, 'prefix'>): Promise<Result<KVResponse<void>>>;
518
+ /**
519
+ * Create a short-lived signed URL for reading a KV object.
520
+ */
521
+ createSignedReadUrl(key: string, options?: Omit<KVCreateSignedReadUrlOptions, 'prefix'>): Promise<Result<KVSignedReadUrlResponse>>;
445
522
  /**
446
523
  * Create a nested prefix-scoped view.
447
524
  */
@@ -564,6 +641,20 @@ interface IKVService extends IService {
564
641
  * ```
565
642
  */
566
643
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
644
+ /**
645
+ * Create a short-lived signed URL for reading a KV object.
646
+ *
647
+ * The request is authorized with the current session's `tinycloud.kv/get`
648
+ * capability for the resolved key path. The returned `url` is absolute and
649
+ * can be passed to external services that need bearer read access.
650
+ *
651
+ * Requires tinycloud-node with the `/signed/kv` endpoint from TC-1368.
652
+ *
653
+ * @param key - The key to expose via a signed read URL
654
+ * @param options - Optional signed URL configuration
655
+ * @returns Result with URL and expiry metadata
656
+ */
657
+ createSignedReadUrl(key: string, options?: KVCreateSignedReadUrlOptions): Promise<Result<KVSignedReadUrlResponse>>;
567
658
  /**
568
659
  * Create a prefix-scoped view of this KV service.
569
660
  *
@@ -655,6 +746,7 @@ declare class KVService extends BaseService implements IKVService {
655
746
  * Get the host URL.
656
747
  */
657
748
  private get host();
749
+ private withJsonContentType;
658
750
  /**
659
751
  * Execute an invoke operation.
660
752
  *
@@ -680,6 +772,8 @@ declare class KVService extends BaseService implements IKVService {
680
772
  * @returns Parsed data
681
773
  */
682
774
  private parseResponse;
775
+ private createSignedReadUrlError;
776
+ private normalizeSignedReadUrlResponse;
683
777
  /**
684
778
  * Get a value by key.
685
779
  */
@@ -700,6 +794,10 @@ declare class KVService extends BaseService implements IKVService {
700
794
  * Get metadata for a key without retrieving the value.
701
795
  */
702
796
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
797
+ /**
798
+ * Create a short-lived signed URL for reading a KV object.
799
+ */
800
+ createSignedReadUrl(key: string, options?: KVCreateSignedReadUrlOptions): Promise<Result<KVSignedReadUrlResponse>>;
703
801
  /**
704
802
  * Create a prefix-scoped view of this KV service.
705
803
  *
@@ -745,4 +843,4 @@ declare class KVService extends BaseService implements IKVService {
745
843
  withPrefix(prefix: string): IPrefixedKVService;
746
844
  }
747
845
 
748
- export { type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, PrefixedKVService };
846
+ export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVCreateSignedReadUrlOptions, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, type KVSignedReadUrlResponse, PrefixedKVService };
package/dist/kv/index.js CHANGED
@@ -386,6 +386,13 @@ var PrefixedKVService = class _PrefixedKVService {
386
386
  const fullKey = this.getFullKey(key);
387
387
  return this._kv.head(fullKey, { ...options, prefix: "" });
388
388
  }
389
+ /**
390
+ * Create a short-lived signed URL for reading a KV object.
391
+ */
392
+ async createSignedReadUrl(key, options) {
393
+ const fullKey = this.getFullKey(key);
394
+ return this._kv.createSignedReadUrl(fullKey, { ...options, prefix: "" });
395
+ }
389
396
  /**
390
397
  * Create a nested prefix-scoped view.
391
398
  */
@@ -397,6 +404,7 @@ var PrefixedKVService = class _PrefixedKVService {
397
404
  };
398
405
 
399
406
  // src/kv/types.ts
407
+ var DEFAULT_SIGNED_READ_URL_EXPIRY_MS = 5 * 60 * 1e3;
400
408
  var KVAction = {
401
409
  GET: "tinycloud.kv/get",
402
410
  PUT: "tinycloud.kv/put",
@@ -481,6 +489,15 @@ var KVService = class extends BaseService {
481
489
  get host() {
482
490
  return this.context.hosts[0];
483
491
  }
492
+ withJsonContentType(headers) {
493
+ if (Array.isArray(headers)) {
494
+ return [...headers, ["content-type", "application/json"]];
495
+ }
496
+ return {
497
+ ...headers,
498
+ "content-type": "application/json"
499
+ };
500
+ }
484
501
  /**
485
502
  * Execute an invoke operation.
486
503
  *
@@ -550,6 +567,48 @@ var KVService = class extends BaseService {
550
567
  return text;
551
568
  }
552
569
  }
570
+ async createSignedReadUrlError(response, key) {
571
+ let errorText = response.statusText;
572
+ try {
573
+ const text = await response.text();
574
+ if (text) {
575
+ errorText = text;
576
+ }
577
+ } catch {
578
+ }
579
+ if (response.status === 401 || response.status === 403) {
580
+ const { resource, action } = parseAuthError(errorText);
581
+ return err(authUnauthorizedError("kv", errorText, {
582
+ status: response.status,
583
+ ...action && { requiredAction: action },
584
+ ...resource && { resource }
585
+ }));
586
+ }
587
+ const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
588
+ return err(
589
+ serviceError(
590
+ code,
591
+ `Failed to create signed read URL for key "${key}": ${response.status} - ${errorText}`,
592
+ "kv",
593
+ { meta: { status: response.status, statusText: response.statusText } }
594
+ )
595
+ );
596
+ }
597
+ normalizeSignedReadUrlResponse(data) {
598
+ if (!data || typeof data !== "object") {
599
+ return void 0;
600
+ }
601
+ const response = data;
602
+ if (typeof response.url !== "string" || typeof response.ticketId !== "string" || typeof response.expiresAt !== "string") {
603
+ return void 0;
604
+ }
605
+ return {
606
+ url: new URL(response.url, this.host).toString(),
607
+ relativeUrl: response.url,
608
+ ticketId: response.ticketId,
609
+ expiresAt: response.expiresAt
610
+ };
611
+ }
553
612
  /**
554
613
  * Get a value by key.
555
614
  */
@@ -822,6 +881,61 @@ var KVService = class extends BaseService {
822
881
  }
823
882
  });
824
883
  }
884
+ /**
885
+ * Create a short-lived signed URL for reading a KV object.
886
+ */
887
+ async createSignedReadUrl(key, options) {
888
+ return this.withTelemetry("createSignedReadUrl", key, async () => {
889
+ if (!this.requireAuth()) {
890
+ return err(authRequiredError("kv"));
891
+ }
892
+ const path = this.getFullPath(key, options?.prefix);
893
+ const session = this.context.session;
894
+ const headers = this.context.invoke(
895
+ session,
896
+ "kv",
897
+ path,
898
+ KVAction.GET
899
+ );
900
+ const body = {
901
+ space: session.spaceId,
902
+ path,
903
+ ttl_seconds: options?.expiresInSeconds ?? Math.ceil(DEFAULT_SIGNED_READ_URL_EXPIRY_MS / 1e3)
904
+ };
905
+ if (options?.contentHash !== void 0) {
906
+ body.content_hash = options.contentHash;
907
+ }
908
+ if (options?.etag !== void 0) {
909
+ body.etag = options.etag;
910
+ }
911
+ try {
912
+ const response = await this.context.fetch(`${this.host}/signed/kv`, {
913
+ method: "POST",
914
+ headers: this.withJsonContentType(headers),
915
+ body: JSON.stringify(body),
916
+ signal: this.combineSignals(options?.signal)
917
+ });
918
+ if (!response.ok) {
919
+ return this.createSignedReadUrlError(response, key);
920
+ }
921
+ const signedUrl = this.normalizeSignedReadUrlResponse(
922
+ await response.json()
923
+ );
924
+ if (!signedUrl) {
925
+ return err(
926
+ serviceError(
927
+ ErrorCodes.NETWORK_ERROR,
928
+ "Signed read URL response did not include url, ticketId, and expiresAt",
929
+ "kv"
930
+ )
931
+ );
932
+ }
933
+ return ok(signedUrl);
934
+ } catch (error) {
935
+ return err(wrapError("kv", error));
936
+ }
937
+ });
938
+ }
825
939
  /**
826
940
  * Create a prefix-scoped view of this KV service.
827
941
  *
@@ -873,6 +987,7 @@ var KVService = class extends BaseService {
873
987
  */
874
988
  KVService.serviceName = "kv";
875
989
  export {
990
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
876
991
  KVAction,
877
992
  KVService,
878
993
  PrefixedKVService