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