@tinycloud/operations 0.3.0 → 0.3.1-beta.1

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
@@ -2396,7 +2396,7 @@ function encOk(data) {
2396
2396
  function encErr(error) {
2397
2397
  return { ok: false, error };
2398
2398
  }
2399
- var import_decrypt_transport_response_error, import_decrypt_transport_response_error2, import_decrypt_transport_response_error3, 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, 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;
2399
+ var import_decrypt_transport_response_error, import_decrypt_transport_response_error2, import_decrypt_transport_response_error3, 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;
2400
2400
  var init_dist2 = __esm({
2401
2401
  "../sdk-services/dist/index.js"() {
2402
2402
  "use strict";
@@ -2613,7 +2613,9 @@ var init_dist2 = __esm({
2613
2613
  GenericKVResponseSchema = createKVResponseSchema(z.unknown());
2614
2614
  KVListResponseSchema = z.object({
2615
2615
  /** Array of keys matching the list criteria */
2616
- keys: z.array(z.string())
2616
+ keys: z.array(z.string()),
2617
+ truncated: z.boolean().optional(),
2618
+ nextCursor: z.string().optional()
2617
2619
  });
2618
2620
  KVListResultSchema = createResultSchema(KVListResponseSchema);
2619
2621
  ServiceRequestEventSchema = z.object({
@@ -2913,6 +2915,20 @@ var init_dist2 = __esm({
2913
2915
  const fullKey = this.getFullKey(key);
2914
2916
  return this._kv.get(fullKey, { ...options, prefix: "" });
2915
2917
  }
2918
+ async batchGet(keys, options) {
2919
+ const response = await this._kv.batchGet(
2920
+ keys.map((key) => this.getFullKey(key)),
2921
+ { ...options, prefix: "" }
2922
+ );
2923
+ if (!response.ok) return response;
2924
+ return ok({
2925
+ ...response.data,
2926
+ results: response.data.results.map((item, index) => ({
2927
+ ...item,
2928
+ key: keys[index]
2929
+ }))
2930
+ });
2931
+ }
2916
2932
  /**
2917
2933
  * Store a value at a key.
2918
2934
  */
@@ -2957,6 +2973,20 @@ var init_dist2 = __esm({
2957
2973
  const fullKey = this.getFullKey(key);
2958
2974
  return this._kv.head(fullKey, { ...options, prefix: "" });
2959
2975
  }
2976
+ async batchHead(keys, options) {
2977
+ const response = await this._kv.batchHead(
2978
+ keys.map((key) => this.getFullKey(key)),
2979
+ { ...options, prefix: "" }
2980
+ );
2981
+ if (!response.ok) return response;
2982
+ return ok({
2983
+ ...response.data,
2984
+ results: response.data.results.map((item, index) => ({
2985
+ ...item,
2986
+ key: keys[index]
2987
+ }))
2988
+ });
2989
+ }
2960
2990
  /**
2961
2991
  * Create a short-lived signed URL for reading a KV object.
2962
2992
  */
@@ -2981,6 +3011,7 @@ var init_dist2 = __esm({
2981
3011
  DELETE: "tinycloud.kv/del",
2982
3012
  HEAD: "tinycloud.kv/metadata"
2983
3013
  };
3014
+ MAX_KV_BATCH_READ_ITEMS = 100;
2984
3015
  KVService = class extends BaseService {
2985
3016
  /**
2986
3017
  * Create a new KVService instance.
@@ -3209,6 +3240,173 @@ var init_dist2 = __esm({
3209
3240
  get: (name) => headers.get(name)
3210
3241
  };
3211
3242
  }
3243
+ createBatchResponseHeaders(values) {
3244
+ const normalized = new Map(
3245
+ Object.entries(values).map(([name, value]) => [name.toLowerCase(), value])
3246
+ );
3247
+ return this.createResponseHeaders({
3248
+ get: (name) => normalized.get(name.toLowerCase()) ?? null
3249
+ });
3250
+ }
3251
+ parseBatchValue(dataBase64, headers, raw = false, binary = false) {
3252
+ const encoded = globalThis.atob(dataBase64);
3253
+ const bytes = Uint8Array.from(encoded, (character) => character.charCodeAt(0));
3254
+ if (binary) return bytes;
3255
+ const text = new TextDecoder().decode(bytes);
3256
+ if (raw) return text;
3257
+ const contentType = Object.entries(headers).find(
3258
+ ([name]) => name.toLowerCase() === "content-type"
3259
+ )?.[1];
3260
+ return contentType?.includes("application/json") ? JSON.parse(text) : text;
3261
+ }
3262
+ normalizeBatchReadResponse(data, paths, requireData) {
3263
+ if (!data || typeof data !== "object") return void 0;
3264
+ const response = data;
3265
+ if (!Array.isArray(response.results) || response.results.length !== paths.length) {
3266
+ return void 0;
3267
+ }
3268
+ for (let index = 0; index < response.results.length; index++) {
3269
+ const item = response.results[index];
3270
+ if (!item || typeof item !== "object" || item.key !== paths[index] || typeof item.ok !== "boolean") {
3271
+ return void 0;
3272
+ }
3273
+ if (item.ok && (!item.headers || typeof item.headers !== "object" || Object.values(item.headers).some((value) => typeof value !== "string") || requireData && typeof item.dataBase64 !== "string")) {
3274
+ return void 0;
3275
+ }
3276
+ if (!item.ok && (!item.error || typeof item.error.code !== "string" || typeof item.error.message !== "string")) {
3277
+ return void 0;
3278
+ }
3279
+ }
3280
+ return response;
3281
+ }
3282
+ async batchRead(keys, action, options) {
3283
+ if (!this.requireAuth()) return err(authRequiredError("kv"));
3284
+ if (keys.length === 0) return ok({ results: [], count: 0 });
3285
+ if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
3286
+ return err(serviceError(
3287
+ ErrorCodes.INVALID_INPUT,
3288
+ `KV batch reads accept at most ${MAX_KV_BATCH_READ_ITEMS} keys`,
3289
+ "kv"
3290
+ ));
3291
+ }
3292
+ if (keys.length === 1) {
3293
+ const key = keys[0];
3294
+ const result = action === KVAction.GET ? await this.get(key, options) : await this.head(key, options);
3295
+ return ok({
3296
+ results: [{ key, result }],
3297
+ count: 1
3298
+ });
3299
+ }
3300
+ if (!this.context.invokeAny) {
3301
+ return err(serviceError(
3302
+ ErrorCodes.INVALID_INPUT,
3303
+ "KV batch reads require SDK runtime support for multi-resource invocations",
3304
+ "kv"
3305
+ ));
3306
+ }
3307
+ const getOptions = action === KVAction.GET ? options : void 0;
3308
+ if (getOptions?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(getOptions.maxResponseBytes) || getOptions.maxResponseBytes <= 0)) {
3309
+ return err(serviceError(
3310
+ ErrorCodes.INVALID_INPUT,
3311
+ "KV maxResponseBytes must be a positive safe integer",
3312
+ "kv"
3313
+ ));
3314
+ }
3315
+ const paths = keys.map((key) => this.getFullPath(key, options?.prefix));
3316
+ if (new Set(paths).size !== paths.length) {
3317
+ return err(serviceError(
3318
+ ErrorCodes.INVALID_INPUT,
3319
+ "KV batch read received duplicate keys after prefix resolution",
3320
+ "kv"
3321
+ ));
3322
+ }
3323
+ try {
3324
+ const session = this.context.session;
3325
+ const invocationHeaders = this.context.invokeAny(
3326
+ session,
3327
+ paths.map((path) => ({
3328
+ spaceId: session.spaceId,
3329
+ service: "kv",
3330
+ path,
3331
+ action
3332
+ }))
3333
+ );
3334
+ const limitHeaders = {};
3335
+ if (getOptions?.maxResponseBytes !== void 0) {
3336
+ limitHeaders["x-tinycloud-max-response-bytes"] = String(
3337
+ getOptions.maxResponseBytes
3338
+ );
3339
+ }
3340
+ const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
3341
+ const response = await this.context.fetch(`${this.host}/invoke`, {
3342
+ method: "POST",
3343
+ headers,
3344
+ signal: this.combineSignals(options?.signal)
3345
+ });
3346
+ if (!response.ok) {
3347
+ const errorText = await response.text();
3348
+ if (response.status === 401 || response.status === 403) {
3349
+ const { resource, action: requiredAction } = parseAuthError(errorText);
3350
+ return err(authUnauthorizedError("kv", errorText, {
3351
+ status: response.status,
3352
+ ...requiredAction && { requiredAction },
3353
+ ...resource && { resource }
3354
+ }));
3355
+ }
3356
+ if (response.status === 413) {
3357
+ return err(serviceError(
3358
+ ErrorCodes.KV_RESPONSE_TOO_LARGE,
3359
+ "A KV batch value exceeds the requested response limit",
3360
+ "kv",
3361
+ { meta: { status: response.status, statusText: response.statusText } }
3362
+ ));
3363
+ }
3364
+ return err(serviceError(
3365
+ ErrorCodes.NETWORK_ERROR,
3366
+ `Failed to batch read ${keys.length} key(s): ${response.status} - ${errorText}`,
3367
+ "kv",
3368
+ { meta: { status: response.status, statusText: response.statusText } }
3369
+ ));
3370
+ }
3371
+ const payload = this.normalizeBatchReadResponse(
3372
+ await response.json(),
3373
+ paths,
3374
+ action === KVAction.GET
3375
+ );
3376
+ if (!payload) {
3377
+ return err(serviceError(
3378
+ ErrorCodes.NETWORK_ERROR,
3379
+ "KV batch read response did not match the requested keys",
3380
+ "kv"
3381
+ ));
3382
+ }
3383
+ const results = payload.results.map((item, index) => {
3384
+ if (!item.ok) {
3385
+ return {
3386
+ key: keys[index],
3387
+ result: err(serviceError(item.error.code, item.error.message, "kv"))
3388
+ };
3389
+ }
3390
+ const headers2 = item.headers;
3391
+ const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
3392
+ item.dataBase64,
3393
+ headers2,
3394
+ getOptions?.raw,
3395
+ getOptions?.binary
3396
+ );
3397
+ return {
3398
+ key: keys[index],
3399
+ result: ok({
3400
+ data,
3401
+ headers: this.createBatchResponseHeaders(headers2)
3402
+ })
3403
+ };
3404
+ });
3405
+ return ok({ results, count: results.length });
3406
+ } catch (error) {
3407
+ return err(wrapError("kv", error));
3408
+ }
3409
+ }
3212
3410
  /**
3213
3411
  * Parse response body based on content type.
3214
3412
  *
@@ -3355,6 +3553,13 @@ var init_dist2 = __esm({
3355
3553
  }
3356
3554
  });
3357
3555
  }
3556
+ async batchGet(keys, options) {
3557
+ return this.withTelemetry(
3558
+ "batchGet",
3559
+ String(keys.length),
3560
+ () => this.batchRead(keys, KVAction.GET, options)
3561
+ );
3562
+ }
3358
3563
  /**
3359
3564
  * Store a value at a key.
3360
3565
  */
@@ -3562,7 +3767,10 @@ var init_dist2 = __esm({
3562
3767
  KVAction.LIST,
3563
3768
  void 0,
3564
3769
  options?.signal,
3565
- options?.limit === void 0 ? void 0 : { "x-tinycloud-limit": String(options.limit) }
3770
+ options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
3771
+ ...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
3772
+ ...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
3773
+ }
3566
3774
  );
3567
3775
  if (!response.ok) {
3568
3776
  if (response.status === 401) {
@@ -3594,7 +3802,8 @@ var init_dist2 = __esm({
3594
3802
  }
3595
3803
  return ok({
3596
3804
  keys,
3597
- ...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" }
3805
+ ...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
3806
+ ...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
3598
3807
  });
3599
3808
  } catch (error) {
3600
3809
  return err(wrapError("kv", error));
@@ -3714,6 +3923,13 @@ var init_dist2 = __esm({
3714
3923
  }
3715
3924
  });
3716
3925
  }
3926
+ async batchHead(keys, options) {
3927
+ return this.withTelemetry(
3928
+ "batchHead",
3929
+ String(keys.length),
3930
+ () => this.batchRead(keys, KVAction.HEAD, options)
3931
+ );
3932
+ }
3717
3933
  /**
3718
3934
  * Create a short-lived signed URL for reading a KV object.
3719
3935
  */
@@ -5653,6 +5869,10 @@ var init_dist2 = __esm({
5653
5869
  * @returns Result with array of key names (vault/ prefix stripped)
5654
5870
  */
5655
5871
  async list(options) {
5872
+ const page = await this.listPage(options);
5873
+ return page.ok ? { ok: true, data: page.data.keys } : page;
5874
+ }
5875
+ async listPage(options) {
5656
5876
  return this.withTelemetry("list", options?.prefix, async () => {
5657
5877
  if (!this.isUnlocked) {
5658
5878
  return vaultError({
@@ -5670,7 +5890,9 @@ var init_dist2 = __esm({
5670
5890
  const listPrefix = options?.prefix ? `vault/${options.prefix}` : "vault/";
5671
5891
  const listResult = await this.tc.kv.list({
5672
5892
  prefix: listPrefix,
5673
- removePrefix: true
5893
+ removePrefix: true,
5894
+ ...options?.limit === void 0 ? {} : { limit: options.limit },
5895
+ ...options?.cursor === void 0 ? {} : { cursor: options.cursor }
5674
5896
  });
5675
5897
  if (!listResult.ok) {
5676
5898
  return vaultError({
@@ -5687,7 +5909,11 @@ var init_dist2 = __esm({
5687
5909
  (k) => k.startsWith(userPrefix) ? k.slice(userPrefix.length) : k
5688
5910
  );
5689
5911
  }
5690
- return ok(keys);
5912
+ return ok({
5913
+ keys,
5914
+ truncated: listResult.data.truncated === true,
5915
+ ...listResult.data.nextCursor === void 0 ? {} : { nextCursor: listResult.data.nextCursor }
5916
+ });
5691
5917
  } catch (error) {
5692
5918
  return vaultError({
5693
5919
  code: "STORAGE_ERROR",