@tinycloud/sdk-services 3.1.0-beta.9 → 3.2.0-beta.2

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
@@ -1713,6 +1713,24 @@ var MAX_KV_BATCH_READ_ITEMS = 100;
1713
1713
  function isJsonObject(value) {
1714
1714
  return typeof value === "object" && value !== null && !Array.isArray(value);
1715
1715
  }
1716
+ var pathByteEncoder = new TextEncoder();
1717
+ function comparePathsByBytes(a, b) {
1718
+ const left = pathByteEncoder.encode(a);
1719
+ const right = pathByteEncoder.encode(b);
1720
+ const length = Math.min(left.length, right.length);
1721
+ for (let index = 0; index < length; index++) {
1722
+ if (left[index] !== right[index]) {
1723
+ return left[index] - right[index];
1724
+ }
1725
+ }
1726
+ return left.length - right.length;
1727
+ }
1728
+ var REVOKED_BODY = /^(?:Invalid invocation: )?delegation-(?:(revoked): [A-Za-z0-9]+|(ancestor-revoked): ancestor=[A-Za-z0-9]+ invoked=[A-Za-z0-9]+)$/;
1729
+ function revocationCodeOf(body) {
1730
+ const match = REVOKED_BODY.exec(body.trim());
1731
+ if (match === null) return void 0;
1732
+ return match[1] !== void 0 ? ErrorCodes.AUTH_DELEGATION_REVOKED : ErrorCodes.AUTH_DELEGATION_ANCESTOR_REVOKED;
1733
+ }
1716
1734
  function encodeKvBatchPartName(path) {
1717
1735
  return encodeURIComponent(path).replace(
1718
1736
  /[!'()*]/g,
@@ -1940,15 +1958,25 @@ var KVService = class extends BaseService {
1940
1958
  )?.[1];
1941
1959
  return contentType?.includes("application/json") ? JSON.parse(text) : text;
1942
1960
  }
1961
+ /**
1962
+ * Validate a batch read response and return its items in the caller's
1963
+ * request order. The node flattens invocation abilities from a `BTreeMap`,
1964
+ * so results arrive sorted by UTF-8 byte order of the path, not in request
1965
+ * order. Items that carry their path in `key` are matched by path; if no
1966
+ * item carries a key, items are assumed to be in the node's byte-sorted
1967
+ * order and the request is aligned the same way. Any mix of keyed and
1968
+ * keyless items, a missing requested path, an unrequested path, or a
1969
+ * malformed item fails closed (returns undefined).
1970
+ */
1943
1971
  normalizeBatchReadResponse(data, paths, requireData) {
1944
1972
  if (!data || typeof data !== "object") return void 0;
1945
1973
  const response = data;
1946
- if (!Array.isArray(response.results) || response.results.length !== paths.length) {
1974
+ const items = response.results;
1975
+ if (!Array.isArray(items) || items.length !== paths.length) {
1947
1976
  return void 0;
1948
1977
  }
1949
- for (let index = 0; index < response.results.length; index++) {
1950
- const item = response.results[index];
1951
- if (!item || typeof item !== "object" || item.key !== paths[index] || typeof item.ok !== "boolean") {
1978
+ for (const item of items) {
1979
+ if (!item || typeof item !== "object" || typeof item.ok !== "boolean") {
1952
1980
  return void 0;
1953
1981
  }
1954
1982
  if (item.ok && (!item.headers || typeof item.headers !== "object" || Object.values(item.headers).some((value) => typeof value !== "string") || requireData && typeof item.dataBase64 !== "string")) {
@@ -1958,7 +1986,24 @@ var KVService = class extends BaseService {
1958
1986
  return void 0;
1959
1987
  }
1960
1988
  }
1961
- return response;
1989
+ if (items.every((item) => typeof item.key === "string")) {
1990
+ const byPath = new Map(items.map((item) => [item.key, item]));
1991
+ if (byPath.size !== items.length) return void 0;
1992
+ const aligned2 = [];
1993
+ for (const path of paths) {
1994
+ const item = byPath.get(path);
1995
+ if (!item) return void 0;
1996
+ aligned2.push(item);
1997
+ }
1998
+ return aligned2;
1999
+ }
2000
+ if (items.some((item) => item.key !== void 0)) return void 0;
2001
+ const requestOrder = paths.map((_, index) => index).sort((a, b) => comparePathsByBytes(paths[a], paths[b]));
2002
+ const aligned = new Array(items.length);
2003
+ for (let position = 0; position < requestOrder.length; position++) {
2004
+ aligned[requestOrder[position]] = items[position];
2005
+ }
2006
+ return aligned;
1962
2007
  }
1963
2008
  async batchRead(keys, action, options) {
1964
2009
  if (!this.requireAuth()) return err(authRequiredError("kv"));
@@ -2051,19 +2096,19 @@ var KVService = class extends BaseService {
2051
2096
  { meta: { status: response.status, statusText: response.statusText } }
2052
2097
  ));
2053
2098
  }
2054
- const payload = this.normalizeBatchReadResponse(
2099
+ const items = this.normalizeBatchReadResponse(
2055
2100
  await response.json(),
2056
2101
  paths,
2057
2102
  action === KVAction.GET
2058
2103
  );
2059
- if (!payload) {
2104
+ if (!items) {
2060
2105
  return err(serviceError(
2061
2106
  ErrorCodes.NETWORK_ERROR,
2062
2107
  "KV batch read response did not match the requested keys",
2063
2108
  "kv"
2064
2109
  ));
2065
2110
  }
2066
- const results = payload.results.map((item, index) => {
2111
+ const results = items.map((item, index) => {
2067
2112
  if (!item.ok) {
2068
2113
  return {
2069
2114
  key: keys[index],
@@ -2254,7 +2299,7 @@ var KVService = class extends BaseService {
2254
2299
  );
2255
2300
  }
2256
2301
  if (response.status === 404) {
2257
- return this.classifyNotFound(response, key);
2302
+ return await this.classifyNotFound(response, key);
2258
2303
  }
2259
2304
  const errorText = await response.text();
2260
2305
  if (response.status === 413) {
@@ -2697,8 +2742,8 @@ var KVService = class extends BaseService {
2697
2742
  }
2698
2743
  /**
2699
2744
  * Map a failed `kv/sync` response. 410 and the retention 403 carry
2700
- * `{"error":{"code","reason"}}`; a revoked grant is a 401 whose text names
2701
- * `delegation-revoked` or `delegation-ancestor-revoked`. A body read that
2745
+ * `{"error":{"code","reason"}}`; a revoked grant is a 401 whose whole body
2746
+ * is the node's revocation error (see REVOKED_BODY). A body read that
2702
2747
  * fails because the request was cancelled or timed out rethrows, so the
2703
2748
  * caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
2704
2749
  */
@@ -2743,7 +2788,7 @@ var KVService = class extends BaseService {
2743
2788
  ));
2744
2789
  }
2745
2790
  if (response.status === 401) {
2746
- const revoked = /\bdelegation-ancestor-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_ANCESTOR_REVOKED : /\bdelegation-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_REVOKED : void 0;
2791
+ const revoked = revocationCodeOf(errorText);
2747
2792
  if (revoked !== void 0) {
2748
2793
  return err(serviceError(revoked, `${context}: 401 - ${errorText}`, "kv", { meta }));
2749
2794
  }
@@ -2823,7 +2868,7 @@ var KVService = class extends BaseService {
2823
2868
  );
2824
2869
  }
2825
2870
  if (response.status === 404) {
2826
- return this.classifyNotFound(response, key);
2871
+ return await this.classifyNotFound(response, key);
2827
2872
  }
2828
2873
  const errorText = await response.text();
2829
2874
  if (response.status === 412) {
@@ -2890,7 +2935,7 @@ var KVService = class extends BaseService {
2890
2935
  );
2891
2936
  }
2892
2937
  if (response.status === 404) {
2893
- return this.classifyNotFound(response, key);
2938
+ return await this.classifyNotFound(response, key);
2894
2939
  }
2895
2940
  const errorText = await response.text();
2896
2941
  return err(
@@ -2956,7 +3001,7 @@ var KVService = class extends BaseService {
2956
3001
  signal: request.signal
2957
3002
  });
2958
3003
  if (!response.ok) {
2959
- return this.createSignedReadUrlError(response, key, path);
3004
+ return await this.createSignedReadUrlError(response, key, path);
2960
3005
  }
2961
3006
  const signedUrl = this.normalizeSignedReadUrlResponse(
2962
3007
  await response.json()
@@ -3990,7 +4035,7 @@ var AsyncQueue = class {
3990
4035
  }
3991
4036
  const waiter = this.waiters.shift();
3992
4037
  if (waiter) {
3993
- waiter({ value, done: false });
4038
+ waiter.resolve({ value, done: false });
3994
4039
  return;
3995
4040
  }
3996
4041
  this.values.push(value);
@@ -4002,7 +4047,22 @@ var AsyncQueue = class {
4002
4047
  this.closed = true;
4003
4048
  while (this.waiters.length > 0) {
4004
4049
  const waiter = this.waiters.shift();
4005
- waiter?.({ value: void 0, done: true });
4050
+ waiter?.resolve({ value: void 0, done: true });
4051
+ }
4052
+ }
4053
+ /**
4054
+ * Fail the queue: pending and future `next()` calls reject with `error`,
4055
+ * so a `for await` consumer sees the iterator throw.
4056
+ */
4057
+ fail(error) {
4058
+ if (this.closed) {
4059
+ return;
4060
+ }
4061
+ this.closed = true;
4062
+ this.failure = error;
4063
+ while (this.waiters.length > 0) {
4064
+ const waiter = this.waiters.shift();
4065
+ waiter?.reject(error);
4006
4066
  }
4007
4067
  }
4008
4068
  next() {
@@ -4010,20 +4070,28 @@ var AsyncQueue = class {
4010
4070
  const value = this.values.shift();
4011
4071
  return Promise.resolve({ value, done: false });
4012
4072
  }
4073
+ if (this.failure !== void 0) {
4074
+ return Promise.reject(this.failure);
4075
+ }
4013
4076
  if (this.closed) {
4014
4077
  return Promise.resolve({ value: void 0, done: true });
4015
4078
  }
4016
- return new Promise((resolve) => {
4017
- this.waiters.push(resolve);
4079
+ return new Promise((resolve, reject) => {
4080
+ this.waiters.push({ resolve, reject });
4018
4081
  });
4019
4082
  }
4020
4083
  };
4084
+ var HOOK_STREAM_RETRY_BASE_DELAY_MS = 250;
4085
+ var HOOK_STREAM_RETRY_MAX_DELAY_MS = 3e4;
4086
+ var HOOK_STREAM_HEALTHY_MS = 5e3;
4021
4087
  var HooksService = class extends BaseService {
4022
4088
  constructor(config = {}) {
4023
4089
  super();
4024
4090
  this._subscribers = /* @__PURE__ */ new Set();
4025
- this._refreshChain = Promise.resolve();
4026
- this._activeSignature = "";
4091
+ /** True while the one supervisor loop runs. Set only by wake(), cleared only at loop exit. */
4092
+ this._supervising = false;
4093
+ /** Bumped on every session change; part of the plan key. */
4094
+ this._sessionGeneration = 0;
4027
4095
  this._config = config;
4028
4096
  }
4029
4097
  get config() {
@@ -4039,35 +4107,32 @@ var HooksService = class extends BaseService {
4039
4107
  if (subscriptions.length === 0) {
4040
4108
  throw new Error("At least one hook subscription is required");
4041
4109
  }
4042
- const normalized = subscriptions.map(normalizeSubscription);
4110
+ const requested = subscriptions.map(normalizeSubscription);
4043
4111
  const subscriber = {
4044
- requested: normalized,
4112
+ requested,
4113
+ signatures: requested.map(subscriptionSignature),
4045
4114
  ttlSeconds: options.ttlSeconds,
4046
4115
  queue: new AsyncQueue()
4047
4116
  };
4048
- this._subscribers.add(subscriber);
4049
- const abortHandler = () => {
4117
+ const leave = () => {
4050
4118
  this._subscribers.delete(subscriber);
4051
4119
  subscriber.queue.close();
4052
- void this.scheduleSharedStreamRefresh();
4120
+ this.wake();
4053
4121
  };
4054
- if (options.signal) {
4055
- if (options.signal.aborted) {
4056
- abortHandler();
4057
- } else {
4058
- options.signal.addEventListener("abort", abortHandler, { once: true });
4059
- }
4122
+ const { signal } = options;
4123
+ if (signal?.aborted) {
4124
+ return;
4060
4125
  }
4061
- void this.scheduleSharedStreamRefresh();
4126
+ signal?.addEventListener("abort", leave, { once: true });
4127
+ this._subscribers.add(subscriber);
4128
+ this.wake();
4062
4129
  try {
4063
4130
  for await (const event of subscriber.queue) {
4064
4131
  yield event;
4065
4132
  }
4066
4133
  } finally {
4067
- if (options.signal) {
4068
- options.signal.removeEventListener("abort", abortHandler);
4069
- }
4070
- abortHandler();
4134
+ signal?.removeEventListener("abort", leave);
4135
+ leave();
4071
4136
  }
4072
4137
  }
4073
4138
  async register(webhook) {
@@ -4208,146 +4273,292 @@ var HooksService = class extends BaseService {
4208
4273
  return err(wrapError("hooks", error));
4209
4274
  }
4210
4275
  }
4211
- async scheduleSharedStreamRefresh() {
4212
- this._refreshChain = this._refreshChain.then(() => this.refreshSharedStream()).catch(() => void 0);
4213
- await this._refreshChain;
4214
- }
4215
- async refreshSharedStream() {
4216
- if (!this.requireAuth() || this._subscribers.size === 0) {
4217
- this.abortSharedStream();
4218
- this._activeSignature = "";
4219
- return;
4220
- }
4221
- const state = this.collectSharedStreamState();
4222
- if (state.signature !== this._activeSignature) {
4223
- this._activeSignature = state.signature;
4224
- this.abortSharedStream();
4276
+ /**
4277
+ * Called after anything that may change what the supervisor should do:
4278
+ * subscribe, unsubscribe, session change, sign-out. Interrupts the running
4279
+ * step if the plan moved away from it, and starts the supervisor if none
4280
+ * runs. Touches only SDK-owned state, so it never throws into callers such
4281
+ * as `ServiceContext.setSession`.
4282
+ */
4283
+ wake() {
4284
+ const phase = this._phase;
4285
+ if (phase && phase.key !== this.plan()?.key) {
4286
+ phase.interrupt.abort();
4225
4287
  }
4226
- if (!this._sharedStreamTask) {
4227
- this._sharedStreamTask = this.runSharedStream(state).catch((error) => {
4228
- if (!isAbortError(error)) {
4229
- throw error;
4230
- }
4231
- }).finally(() => {
4232
- this._sharedStreamTask = void 0;
4233
- this._sharedStreamAbort = void 0;
4234
- if (this._subscribers.size > 0) {
4235
- void this.scheduleSharedStreamRefresh();
4236
- }
4237
- });
4288
+ if (this._supervising || this._subscribers.size === 0) {
4289
+ return;
4238
4290
  }
4291
+ this._supervising = true;
4292
+ this.supervise().catch(() => this.collapse());
4239
4293
  }
4240
- collectSharedStreamState() {
4241
- const merged = /* @__PURE__ */ new Map();
4242
- const ttlCandidates = [];
4243
- for (const subscriber of this._subscribers) {
4244
- if (typeof subscriber.ttlSeconds === "number") {
4245
- ttlCandidates.push(subscriber.ttlSeconds);
4294
+ /**
4295
+ * The only owner of the shared stream. Each iteration is one attempt
4296
+ * (mint → open → drain), then a classification and, unless the attempt was
4297
+ * superseded or released its subscribers, a backoff. Every await is a
4298
+ * `phase` that wake() can interrupt; an interrupted phase yields
4299
+ * `undefined`, so a superseded mint can never act. Outside `attempt` and
4300
+ * `pause`, which each own one catch, the loop touches only SDK state.
4301
+ */
4302
+ async supervise() {
4303
+ let streak = 0;
4304
+ for (; ; ) {
4305
+ const plan = this.plan();
4306
+ if (!plan) {
4307
+ this._supervising = false;
4308
+ return;
4309
+ }
4310
+ const health = { openedAt: 0, delivered: false };
4311
+ const outcome = await this.phase(
4312
+ plan.key,
4313
+ (signal) => this.attempt(plan, signal, health)
4314
+ );
4315
+ if (health.delivered || health.openedAt > 0 && performance.now() - health.openedAt >= HOOK_STREAM_HEALTHY_MS) {
4316
+ streak = 0;
4317
+ }
4318
+ if (!outcome) {
4319
+ continue;
4320
+ }
4321
+ if (this.plan()?.key !== plan.key) {
4322
+ continue;
4323
+ }
4324
+ if (outcome.kind === "stopped") {
4325
+ this.release();
4326
+ continue;
4327
+ }
4328
+ if (outcome.kind === "refused" && outcome.terminal) {
4329
+ this.release(outcome.error);
4330
+ this.report(outcome.error);
4331
+ continue;
4246
4332
  }
4247
- for (const subscription of subscriber.requested) {
4248
- merged.set(subscriptionSignature(subscription), subscription);
4333
+ if (outcome.kind !== "ended") {
4334
+ this.report(outcome.error);
4249
4335
  }
4336
+ streak += 1;
4337
+ await this.phase(
4338
+ plan.key,
4339
+ (signal) => this.pause(streak, signal)
4340
+ );
4250
4341
  }
4251
- const subscriptions = [...merged.values()].sort(
4252
- (left, right) => subscriptionSignature(left).localeCompare(subscriptionSignature(right))
4253
- );
4254
- const ttlSeconds = ttlCandidates.length > 0 ? Math.min(...ttlCandidates) : void 0;
4255
- const signature = JSON.stringify({
4256
- subscriptions: subscriptions.map(subscriptionSignature),
4257
- ttlSeconds
4258
- });
4259
- return {
4260
- subscriptions,
4261
- ttlSeconds,
4262
- signature
4263
- };
4264
4342
  }
4265
- async runSharedStream(state) {
4266
- const abortController = new AbortController();
4267
- this._sharedStreamAbort = abortController;
4343
+ /**
4344
+ * Run one step under an interrupt that wake() fires when the plan key moves
4345
+ * on. An interrupted step's result is discarded. The interrupt is also
4346
+ * fired when the step returns, which releases whatever it left open: the
4347
+ * request signal and its listeners, an unread response body, a timer.
4348
+ */
4349
+ async phase(key, step) {
4350
+ const interrupt = new AbortController();
4351
+ this._phase = { key, interrupt };
4352
+ try {
4353
+ if (this.plan()?.key !== key) {
4354
+ interrupt.abort();
4355
+ }
4356
+ const result = await step(interrupt.signal);
4357
+ return interrupt.signal.aborted ? void 0 : result;
4358
+ } finally {
4359
+ this._phase = void 0;
4360
+ interrupt.abort();
4361
+ }
4362
+ }
4363
+ /**
4364
+ * One mint → open → drain. The single catch site for everything the
4365
+ * attempt touches that the SDK does not own: context accessors, invoke,
4366
+ * fetch, response bodies, the SSE parser. Never rejects.
4367
+ */
4368
+ async attempt(plan, signal, health) {
4369
+ let phase = "mint";
4370
+ let request;
4371
+ const owns = () => !signal.aborted && !request?.signal.aborted && this.plan()?.key === plan.key;
4268
4372
  try {
4269
- const host = this._config.host ?? this.context.hosts[0];
4270
- const ticketResponse = await this.mintHookTicket(
4271
- state.subscriptions,
4272
- state.ttlSeconds,
4273
- abortController.signal
4373
+ const context = this.context;
4374
+ const contextSignal = context.abortSignal;
4375
+ this._lastContextSignal = contextSignal;
4376
+ if (!context.isAuthenticated || contextSignal?.aborted) {
4377
+ return { kind: "stopped" };
4378
+ }
4379
+ request = this.createRequestSignal(signal, 0);
4380
+ const host = this.host;
4381
+ const minted = await awaitOwned(
4382
+ request.signal,
4383
+ () => context.fetch(`${host}/hooks/tickets`, {
4384
+ method: "POST",
4385
+ headers: {
4386
+ ...serviceHeadersToRecord(this.createInvokeHeaders(plan.subscriptions)),
4387
+ "content-type": "application/json"
4388
+ },
4389
+ body: JSON.stringify({
4390
+ subscriptions: plan.subscriptions,
4391
+ ttlSeconds: plan.ttlSeconds
4392
+ }),
4393
+ signal: request.signal
4394
+ })
4274
4395
  );
4275
- const streamResponse = await this.openHookStream(
4276
- host,
4277
- ticketResponse.ticket,
4278
- abortController.signal
4396
+ if (!owns()) return { kind: "stopped" };
4397
+ if (!minted.ok) {
4398
+ const status = minted.status;
4399
+ const error = await awaitOwned(
4400
+ request.signal,
4401
+ () => responseError("hooks", "failed to mint hook ticket", minted)
4402
+ );
4403
+ if (!owns()) return { kind: "stopped" };
4404
+ return { kind: "refused", terminal: isTerminalMintRefusal(status), error };
4405
+ }
4406
+ const body = await awaitOwned(request.signal, () => minted.json());
4407
+ if (!owns()) return { kind: "stopped" };
4408
+ const ticket = body !== null && typeof body === "object" && "ticket" in body ? body.ticket : void 0;
4409
+ if (typeof ticket !== "string" || ticket.length === 0) {
4410
+ throw new Error("Hook ticket response did not include a ticket");
4411
+ }
4412
+ phase = "open";
4413
+ if (!owns()) return { kind: "stopped" };
4414
+ const stream = await awaitOwned(
4415
+ request.signal,
4416
+ () => context.fetch(`${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`, {
4417
+ method: "GET",
4418
+ headers: { accept: "text/event-stream" },
4419
+ signal: request.signal
4420
+ })
4279
4421
  );
4280
- for await (const message of parseSseStream(
4281
- streamResponse.body,
4282
- abortController.signal
4283
- )) {
4284
- if (!message.data) {
4285
- continue;
4286
- }
4287
- const event = parseHookEvent(message);
4288
- for (const subscriber of this._subscribers) {
4289
- if (matchesAnySubscription(event, subscriber.requested)) {
4290
- subscriber.queue.push(event);
4291
- }
4422
+ if (!owns()) return { kind: "stopped" };
4423
+ if (!stream.ok) {
4424
+ return { kind: "refused", terminal: false, error: hookStreamRefusal(stream.status) };
4425
+ }
4426
+ health.openedAt = performance.now();
4427
+ const iterator = parseSseStream(stream.body, request.signal)[Symbol.asyncIterator]();
4428
+ try {
4429
+ for (; ; ) {
4430
+ const next = await awaitOwned(request.signal, () => iterator.next());
4431
+ if (!owns()) return { kind: "stopped" };
4432
+ if (next.done) break;
4433
+ const message = next.value;
4434
+ if (!message.data) continue;
4435
+ const event = parseHookEvent(message);
4436
+ if (!owns()) return { kind: "stopped" };
4437
+ this.dispatch(event);
4438
+ health.delivered = true;
4292
4439
  }
4440
+ } finally {
4441
+ const closing = iterator.return?.();
4442
+ if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
4293
4443
  }
4444
+ return owns() ? { kind: "ended" } : { kind: "stopped" };
4445
+ } catch (thrown) {
4446
+ return request?.signal.aborted || thrown === SUPERSEDED ? { kind: "stopped" } : { kind: "failed", error: hookStreamFailure(phase, thrown) };
4447
+ }
4448
+ }
4449
+ /**
4450
+ * Back off before the next attempt. `config.streamRetry` overrides the
4451
+ * delay and the clock; whatever they do, the pause ends by the delay or the
4452
+ * interrupt, then yields one timer turn so an instant clock cannot starve
4453
+ * due timers or the process.
4454
+ */
4455
+ async pause(attempt, signal) {
4456
+ try {
4457
+ this._lastContextSignal = this.context.abortSignal;
4458
+ } catch {
4459
+ }
4460
+ const contextSignal = this._lastContextSignal;
4461
+ const waits = [];
4462
+ const aborts = [];
4463
+ try {
4464
+ const retry = this._config.streamRetry;
4465
+ const delayMs = retry?.delay?.(attempt) ?? hookStreamRetryDelay(attempt);
4466
+ waits.push((retry?.wait ?? sleep)(delayMs, attempt, signal));
4467
+ } catch {
4468
+ waits.push(Promise.resolve());
4469
+ }
4470
+ aborts.push(abortPromise(signal));
4471
+ if (contextSignal) aborts.push(abortPromise(contextSignal));
4472
+ try {
4473
+ await Promise.race([...waits, ...aborts.map(({ promise }) => promise)]);
4474
+ } catch {
4294
4475
  } finally {
4295
- if (this._sharedStreamAbort === abortController) {
4296
- this._sharedStreamAbort = void 0;
4476
+ for (const abort of aborts) abort.dispose();
4477
+ }
4478
+ await new Promise((resolve) => setTimeout(resolve, 0));
4479
+ }
4480
+ /** The stream the current subscribers need, or undefined when none remain. */
4481
+ plan() {
4482
+ if (this._subscribers.size === 0) {
4483
+ return void 0;
4484
+ }
4485
+ const merged = /* @__PURE__ */ new Map();
4486
+ let ttlSeconds;
4487
+ for (const subscriber of this._subscribers) {
4488
+ subscriber.requested.forEach(
4489
+ (subscription, index) => merged.set(subscriber.signatures[index], subscription)
4490
+ );
4491
+ if (typeof subscriber.ttlSeconds === "number") {
4492
+ ttlSeconds = Math.min(subscriber.ttlSeconds, ttlSeconds ?? Infinity);
4297
4493
  }
4298
4494
  }
4495
+ const signatures = [...merged.keys()].sort();
4496
+ return {
4497
+ key: JSON.stringify([this._sessionGeneration, signatures, ttlSeconds ?? null]),
4498
+ subscriptions: signatures.map((signature) => merged.get(signature)),
4499
+ ttlSeconds
4500
+ };
4299
4501
  }
4300
- abortSharedStream() {
4301
- this._sharedStreamAbort?.abort();
4502
+ dispatch(event) {
4503
+ for (const subscriber of this._subscribers) {
4504
+ if (matchesAnySubscription(event, subscriber.requested)) {
4505
+ subscriber.queue.push(event);
4506
+ }
4507
+ }
4302
4508
  }
4303
- createHookHeaders(action, path) {
4304
- return this.context.invoke(this.session, "hooks", path, action);
4509
+ /**
4510
+ * Detach every subscriber. With `error`, their iterators throw it after
4511
+ * draining buffered events; without, they complete.
4512
+ */
4513
+ release(error) {
4514
+ const subscribers = [...this._subscribers];
4515
+ this._subscribers.clear();
4516
+ for (const { queue } of subscribers) {
4517
+ if (error) {
4518
+ queue.fail(error);
4519
+ } else {
4520
+ queue.close();
4521
+ }
4522
+ }
4305
4523
  }
4306
- async mintHookTicket(subscriptions, ttlSeconds, signal) {
4307
- const host = this._config.host ?? this.context.hosts[0];
4308
- const headers = this.createInvokeHeaders(subscriptions);
4309
- const ticketResponse = await this.context.fetch(`${host}/hooks/tickets`, {
4310
- method: "POST",
4311
- headers: {
4312
- ...serviceHeadersToRecord(headers),
4313
- "content-type": "application/json"
4314
- },
4315
- body: JSON.stringify({
4316
- subscriptions,
4317
- ttlSeconds
4318
- }),
4319
- signal
4320
- });
4321
- if (!ticketResponse.ok) {
4322
- throw await responseError(
4323
- "hooks",
4324
- "failed to mint hook ticket",
4325
- ticketResponse
4326
- );
4524
+ report(error) {
4525
+ if (error.meta && typeof error.meta === "object") {
4526
+ Object.freeze(error.meta);
4327
4527
  }
4328
- const ticketJson = await ticketResponse.json();
4329
- if (!ticketJson?.ticket) {
4330
- throw new Error("Hook ticket response did not include a ticket");
4528
+ Object.freeze(error);
4529
+ try {
4530
+ this.emitError(error, "stream");
4531
+ } catch {
4331
4532
  }
4332
- return ticketJson;
4333
4533
  }
4334
- async openHookStream(host, ticket, signal) {
4335
- const streamResponse = await this.context.fetch(
4336
- `${host}/hooks/events?ticket=${encodeURIComponent(ticket)}`,
4337
- {
4338
- method: "GET",
4339
- headers: { accept: "text/event-stream" },
4340
- signal
4341
- }
4534
+ /**
4535
+ * Unreachable by construction. If the loop ever rejects anyway, fail its
4536
+ * subscribers visibly instead of stranding them or crashing the process.
4537
+ */
4538
+ collapse() {
4539
+ this._supervising = false;
4540
+ this._phase?.interrupt.abort();
4541
+ this._phase = void 0;
4542
+ const error = serviceError(
4543
+ ErrorCodes.NETWORK_ERROR,
4544
+ "hook stream supervisor stopped unexpectedly",
4545
+ "hooks"
4342
4546
  );
4343
- if (!streamResponse.ok) {
4344
- throw await responseError(
4345
- "hooks",
4346
- "failed to open hook stream",
4347
- streamResponse
4348
- );
4349
- }
4350
- return streamResponse;
4547
+ this.report(error);
4548
+ this.release(error);
4549
+ }
4550
+ onSessionChange(session) {
4551
+ super.onSessionChange(session);
4552
+ this._sessionGeneration += 1;
4553
+ this.wake();
4554
+ }
4555
+ onSignOut() {
4556
+ super.onSignOut();
4557
+ this.release();
4558
+ this.wake();
4559
+ }
4560
+ createHookHeaders(action, path) {
4561
+ return this.context.invoke(this.session, "hooks", path, action);
4351
4562
  }
4352
4563
  createInvokeHeaders(subscriptions) {
4353
4564
  const entries = subscriptions.map((subscription) => ({
@@ -4378,11 +4589,168 @@ function buildScopePath(service, pathPrefix) {
4378
4589
  const normalized = normalizePathPrefix(pathPrefix);
4379
4590
  return normalized ? `${service}/${normalized}` : service;
4380
4591
  }
4592
+ function hookStreamRetryDelay(attempt) {
4593
+ const cap = Math.min(
4594
+ HOOK_STREAM_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1),
4595
+ HOOK_STREAM_RETRY_MAX_DELAY_MS
4596
+ );
4597
+ return cap / 2 + Math.random() * (cap / 2);
4598
+ }
4599
+ function isTerminalMintRefusal(status) {
4600
+ return typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
4601
+ }
4602
+ var SUPERSEDED = /* @__PURE__ */ Symbol("hooks attempt superseded");
4603
+ async function awaitOwned(signal, operation) {
4604
+ if (signal.aborted) {
4605
+ if (typeof operation !== "function") {
4606
+ void operation.then(cancelLateResponse, () => void 0);
4607
+ }
4608
+ throw SUPERSEDED;
4609
+ }
4610
+ const promise = typeof operation === "function" ? operation() : operation;
4611
+ return new Promise((resolve, reject) => {
4612
+ let settled = false;
4613
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
4614
+ const onAbort = () => {
4615
+ if (settled) return;
4616
+ settled = true;
4617
+ cleanup();
4618
+ reject(SUPERSEDED);
4619
+ };
4620
+ signal.addEventListener("abort", onAbort, { once: true });
4621
+ promise.then(
4622
+ (value) => {
4623
+ if (settled) {
4624
+ cancelLateResponse(value);
4625
+ return;
4626
+ }
4627
+ settled = true;
4628
+ cleanup();
4629
+ resolve(value);
4630
+ },
4631
+ (error) => {
4632
+ if (settled) return;
4633
+ settled = true;
4634
+ cleanup();
4635
+ reject(error);
4636
+ }
4637
+ );
4638
+ });
4639
+ }
4640
+ function cancelLateResponse(value) {
4641
+ if (!(value instanceof Response) || !value.body) return;
4642
+ try {
4643
+ const cancellation = value.body.cancel();
4644
+ void cancellation.then(() => void 0, () => void 0);
4645
+ } catch {
4646
+ }
4647
+ }
4648
+ function hookStreamRefusal(status) {
4649
+ const code = typeof status === "number" ? status : void 0;
4650
+ return serviceError(
4651
+ ErrorCodes.NETWORK_ERROR,
4652
+ `hook stream open refused: ${code ?? "unknown status"}`,
4653
+ "hooks",
4654
+ { meta: { phase: "open", status: code } }
4655
+ );
4656
+ }
4657
+ var STREAM_ERROR_NAMES = /* @__PURE__ */ new Set([
4658
+ "Error",
4659
+ "TypeError",
4660
+ "AbortError",
4661
+ "TimeoutError",
4662
+ "SyntaxError",
4663
+ "RangeError",
4664
+ "NetworkError"
4665
+ ]);
4666
+ function hookStreamFailure(phase, thrown) {
4667
+ const candidateName = readProperty(thrown, "name");
4668
+ const name = typeof candidateName === "string" && STREAM_ERROR_NAMES.has(candidateName) ? candidateName : "Error";
4669
+ const code = identifier(readProperty(thrown, "code")) ?? identifier(readProperty(readProperty(thrown, "cause"), "code"));
4670
+ return serviceError(
4671
+ ErrorCodes.NETWORK_ERROR,
4672
+ `hook stream ${phase} failed: ${name}${code ? ` (${code})` : ""}`,
4673
+ "hooks",
4674
+ { meta: { phase, errorName: name, ...code ? { errorCode: code } : {} } }
4675
+ );
4676
+ }
4677
+ var STREAM_ERROR_CODES = /* @__PURE__ */ new Set([
4678
+ "ECONNREFUSED",
4679
+ "ECONNRESET",
4680
+ "ECONNABORTED",
4681
+ "ETIMEDOUT",
4682
+ "EPIPE",
4683
+ "ENOTFOUND",
4684
+ "EAI_AGAIN",
4685
+ "EHOSTUNREACH",
4686
+ "ENETUNREACH",
4687
+ "ENETDOWN",
4688
+ "EADDRNOTAVAIL",
4689
+ "UND_ERR_SOCKET",
4690
+ "UND_ERR_CONNECT_TIMEOUT",
4691
+ "UND_ERR_HEADERS_TIMEOUT",
4692
+ "UND_ERR_BODY_TIMEOUT",
4693
+ "UND_ERR_CLOSED",
4694
+ "UND_ERR_ABORTED",
4695
+ "UND_ERR_RESPONSE_STATUS_CODE",
4696
+ "ERR_TLS_CERT_ALTNAME_INVALID",
4697
+ "CERT_HAS_EXPIRED",
4698
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
4699
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
4700
+ "SELF_SIGNED_CERT_IN_CHAIN",
4701
+ "ERR_SSL_WRONG_VERSION_NUMBER"
4702
+ ]);
4703
+ function identifier(value) {
4704
+ return typeof value === "string" && STREAM_ERROR_CODES.has(value) ? value : void 0;
4705
+ }
4706
+ function readProperty(value, key) {
4707
+ if (value === null || typeof value !== "object" && typeof value !== "function") {
4708
+ return void 0;
4709
+ }
4710
+ try {
4711
+ return Reflect.get(value, key);
4712
+ } catch {
4713
+ return void 0;
4714
+ }
4715
+ }
4716
+ function sleep(delayMs, _attempt, signal) {
4717
+ return new Promise((resolve) => {
4718
+ const finish = () => {
4719
+ clearTimeout(timer);
4720
+ signal.removeEventListener("abort", stop);
4721
+ resolve();
4722
+ };
4723
+ const timer = setTimeout(finish, delayMs);
4724
+ timer.unref?.();
4725
+ const stop = () => finish();
4726
+ if (signal.aborted) {
4727
+ finish();
4728
+ } else {
4729
+ signal.addEventListener("abort", stop, { once: true });
4730
+ }
4731
+ });
4732
+ }
4733
+ function abortPromise(signal) {
4734
+ let resolve;
4735
+ const promise = new Promise((done) => {
4736
+ resolve = done;
4737
+ });
4738
+ const onAbort = () => resolve();
4739
+ if (signal.aborted) {
4740
+ resolve();
4741
+ } else {
4742
+ signal.addEventListener("abort", onAbort, { once: true });
4743
+ }
4744
+ return {
4745
+ promise,
4746
+ dispose: () => signal.removeEventListener("abort", onAbort)
4747
+ };
4748
+ }
4381
4749
  function normalizeSubscription(subscription) {
4382
4750
  return {
4383
4751
  ...subscription,
4384
4752
  pathPrefix: normalizePathPrefix(subscription.pathPrefix),
4385
- abilities: subscription.abilities ?? []
4753
+ abilities: subscription.abilities ? [...subscription.abilities] : []
4386
4754
  };
4387
4755
  }
4388
4756
  function subscriptionSignature(subscription) {
@@ -4448,9 +4816,6 @@ async function responseError(service, message, response) {
4448
4816
  meta: { ...error.meta, status: response.status, statusText: response.statusText }
4449
4817
  };
4450
4818
  }
4451
- function isAbortError(error) {
4452
- return error instanceof DOMException && error.name === "AbortError";
4453
- }
4454
4819
  async function* parseSseStream(body, signal) {
4455
4820
  if (!body) {
4456
4821
  throw new Error("Hook stream response does not expose a readable body");
@@ -4479,11 +4844,24 @@ async function* parseSseStream(body, signal) {
4479
4844
  async function* readBodyChunks(body, signal) {
4480
4845
  const asyncIterable = body;
4481
4846
  if (typeof asyncIterable?.[Symbol.asyncIterator] === "function") {
4482
- for await (const chunk of asyncIterable) {
4483
- if (signal?.aborted) {
4484
- break;
4847
+ const iterator = asyncIterable[Symbol.asyncIterator]();
4848
+ const cancel = () => {
4849
+ try {
4850
+ const closing = iterator.return?.();
4851
+ if (closing) void Promise.resolve(closing).then(() => void 0, () => void 0);
4852
+ } catch {
4853
+ }
4854
+ };
4855
+ signal?.addEventListener("abort", cancel, { once: true });
4856
+ try {
4857
+ while (!signal?.aborted) {
4858
+ const next = await iterator.next();
4859
+ if (next.done) break;
4860
+ yield next.value;
4485
4861
  }
4486
- yield chunk;
4862
+ } finally {
4863
+ signal?.removeEventListener("abort", cancel);
4864
+ cancel();
4487
4865
  }
4488
4866
  return;
4489
4867
  }
@@ -4492,21 +4870,23 @@ async function* readBodyChunks(body, signal) {
4492
4870
  throw new Error("Unsupported hook stream body type");
4493
4871
  }
4494
4872
  const reader = stream.getReader();
4873
+ const cancelReader = () => {
4874
+ try {
4875
+ const cancellation = reader.cancel?.();
4876
+ if (cancellation) void cancellation.then(() => void 0, () => void 0);
4877
+ } catch {
4878
+ }
4879
+ };
4880
+ signal?.addEventListener("abort", cancelReader, { once: true });
4495
4881
  try {
4496
4882
  while (!signal?.aborted) {
4497
4883
  const { done, value } = await reader.read();
4498
- if (done) {
4499
- break;
4500
- }
4501
- if (value) {
4502
- yield value;
4503
- }
4884
+ if (done) break;
4885
+ if (value) yield value;
4504
4886
  }
4505
4887
  } finally {
4506
- try {
4507
- await reader.cancel?.();
4508
- } catch {
4509
- }
4888
+ signal?.removeEventListener("abort", cancelReader);
4889
+ cancelReader();
4510
4890
  reader.releaseLock?.();
4511
4891
  }
4512
4892
  }
@@ -4912,6 +5292,12 @@ function defaultVaultMessage(input) {
4912
5292
  return input.message ?? `Public key not found for ${input.did}`;
4913
5293
  case "STORAGE_ERROR":
4914
5294
  return input.message ?? input.cause.message;
5295
+ case "ALREADY_EXISTS":
5296
+ return input.message ?? `A value already exists at ${input.key}`;
5297
+ case "AUTH_UNAUTHORIZED":
5298
+ return input.message;
5299
+ case "PERMISSION_DENIED":
5300
+ return input.message;
4915
5301
  case "STORAGE_QUOTA_EXCEEDED":
4916
5302
  case "STORAGE_LIMIT_REACHED":
4917
5303
  return input.message;
@@ -4925,13 +5311,33 @@ function vaultError(input) {
4925
5311
  };
4926
5312
  return { ok: false, error };
4927
5313
  }
5314
+ function typedKvCause(error) {
5315
+ return Object.assign(new Error(error.message), {
5316
+ name: "ServiceError",
5317
+ code: error.code,
5318
+ service: error.service,
5319
+ ...error.meta !== void 0 ? { meta: error.meta } : {},
5320
+ ...error.cause !== void 0 ? { cause: error.cause } : {}
5321
+ });
5322
+ }
4928
5323
  function kvWriteError(context, error) {
4929
5324
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
4930
5325
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
4931
5326
  }
5327
+ const message = `${context}: ${error.message}`;
5328
+ if (error.code === ErrorCodes.AUTH_UNAUTHORIZED) {
5329
+ const code = authorizationVerdictOf(error) === "unauthenticated" ? "AUTH_UNAUTHORIZED" : "PERMISSION_DENIED";
5330
+ return vaultError({
5331
+ code,
5332
+ message,
5333
+ ...error.meta !== void 0 ? { meta: error.meta } : {},
5334
+ cause: typedKvCause(error)
5335
+ });
5336
+ }
4932
5337
  return vaultError({
4933
5338
  code: "STORAGE_ERROR",
4934
- cause: new Error(`${context}: ${error.message}`)
5339
+ cause: new Error(message, { cause: typedKvCause(error) }),
5340
+ ...error.meta !== void 0 ? { meta: error.meta } : {}
4935
5341
  });
4936
5342
  }
4937
5343
  var DataVaultService = class extends BaseService {
@@ -5211,11 +5617,18 @@ var DataVaultService = class extends BaseService {
5211
5617
  cause: new Error(envelopeResult.error.message)
5212
5618
  });
5213
5619
  }
5214
- const valuePutResult = await this.tc.kv.put(
5215
- `vault/${key}`,
5216
- JSON.stringify(envelopeResult.data)
5217
- );
5620
+ const valuePath = `vault/${key}`;
5621
+ const valueBody = JSON.stringify(envelopeResult.data);
5622
+ const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
5218
5623
  if (!valuePutResult.ok) {
5624
+ if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
5625
+ return vaultError({
5626
+ code: "ALREADY_EXISTS",
5627
+ key,
5628
+ ...valuePutResult.error.meta !== void 0 ? { meta: valuePutResult.error.meta } : {},
5629
+ cause: typedKvCause(valuePutResult.error)
5630
+ });
5631
+ }
5219
5632
  return kvWriteError("Failed to store encrypted value", valuePutResult.error);
5220
5633
  }
5221
5634
  return { ok: true, data: void 0 };
@@ -5383,6 +5796,14 @@ var DataVaultService = class extends BaseService {
5383
5796
  if (this.usesNetworkEncryption) {
5384
5797
  return this.putNetworkEncrypted(key, value, options);
5385
5798
  }
5799
+ if (options?.ifNoneMatch !== void 0) {
5800
+ return vaultError({
5801
+ code: "STORAGE_ERROR",
5802
+ cause: new Error(
5803
+ "ifNoneMatch is only supported by network-encrypted vaults"
5804
+ )
5805
+ });
5806
+ }
5386
5807
  if (!this._isUnlocked || !this.masterKey) {
5387
5808
  return vaultError({
5388
5809
  code: "VAULT_LOCKED",
@@ -6194,6 +6615,35 @@ var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
6194
6615
  var SECRET_PREFIX = "secrets/";
6195
6616
  var SCOPED_SECRET_PREFIX = "secrets/scoped/";
6196
6617
  var RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
6618
+ var SECRET_SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
6619
+ var MAX_SECRET_SCOPE_LENGTH = 64;
6620
+ var TC832_RESERVED_SCOPES = Object.freeze([
6621
+ "default",
6622
+ "global",
6623
+ "scoped"
6624
+ ]);
6625
+ function appSecretScope(appId) {
6626
+ if (appId.trim() === "") {
6627
+ throw new Error("App id must be non-empty to derive a secret scope.");
6628
+ }
6629
+ const scope = appId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6630
+ if (scope === "") {
6631
+ throw new Error(
6632
+ `App id ${JSON.stringify(appId)} must contain at least one letter or number.`
6633
+ );
6634
+ }
6635
+ if (scope.length > MAX_SECRET_SCOPE_LENGTH) {
6636
+ throw new Error(
6637
+ `App id ${JSON.stringify(appId)} yields a secret scope longer than ${MAX_SECRET_SCOPE_LENGTH} characters.`
6638
+ );
6639
+ }
6640
+ if (TC832_RESERVED_SCOPES.includes(scope)) {
6641
+ throw new Error(
6642
+ `App id ${JSON.stringify(appId)} yields the reserved secret scope ${JSON.stringify(scope)}.`
6643
+ );
6644
+ }
6645
+ return scope;
6646
+ }
6197
6647
  function canonicalizeSecretScope(scope) {
6198
6648
  if (scope === void 0) {
6199
6649
  return void 0;
@@ -6306,11 +6756,12 @@ var SecretsService = class {
6306
6756
  const secretPath = resolveSecretPathResult(name, options);
6307
6757
  if ("ok" in secretPath) return secretPath;
6308
6758
  const now = (/* @__PURE__ */ new Date()).toISOString();
6309
- return this.vault.put(secretPath.vaultKey, {
6759
+ const payload = {
6310
6760
  value,
6311
6761
  createdAt: now,
6312
6762
  updatedAt: now
6313
- });
6763
+ };
6764
+ return options?.ifAbsent === true ? this.vault.put(secretPath.vaultKey, payload, { ifNoneMatch: "*" }) : this.vault.put(secretPath.vaultKey, payload);
6314
6765
  }
6315
6766
  async delete(name, options) {
6316
6767
  const secretPath = resolveSecretPathResult(name, options);
@@ -7150,10 +7601,10 @@ var EncryptionService = class extends BaseService {
7150
7601
  get crypto() {
7151
7602
  return this._config.crypto;
7152
7603
  }
7153
- async discoverNetwork(identifier, ownerDid) {
7604
+ async discoverNetwork(identifier2, ownerDid) {
7154
7605
  this.assertActive();
7155
7606
  const result = await discoverNetwork({
7156
- identifier,
7607
+ identifier: identifier2,
7157
7608
  ...ownerDid !== void 0 ? { ownerDid } : {},
7158
7609
  ...this._config.node !== void 0 ? { node: this._config.node } : {},
7159
7610
  ...this._config.wellKnown !== void 0 ? { wellKnown: this._config.wellKnown } : {}
@@ -7365,11 +7816,13 @@ export {
7365
7816
  KVListResultSchema,
7366
7817
  KVResponseHeadersSchema,
7367
7818
  KVService,
7819
+ MAX_SECRET_SCOPE_LENGTH,
7368
7820
  NETWORK_NAME_PATTERN,
7369
7821
  NetworkIdError,
7370
7822
  PrefixedKVService,
7371
7823
  RetryPolicySchema,
7372
7824
  SECRET_NAME_RE,
7825
+ SECRET_SCOPE_RE,
7373
7826
  SQLAction,
7374
7827
  SQLMigrations,
7375
7828
  SQLService,
@@ -7383,6 +7836,7 @@ export {
7383
7836
  ServiceResponseEventSchema,
7384
7837
  ServiceRetryEventSchema,
7385
7838
  ServiceSessionSchema,
7839
+ TC832_RESERVED_SCOPES,
7386
7840
  TelemetryEvents,
7387
7841
  TelemetrySpanEventSchema,
7388
7842
  TinyCloudDebugLogger,
@@ -7390,6 +7844,7 @@ export {
7390
7844
  VaultHeaders,
7391
7845
  VaultPublicSpaceKVActions,
7392
7846
  abortedError,
7847
+ appSecretScope,
7393
7848
  authExpiredError,
7394
7849
  authRequiredError,
7395
7850
  authUnauthorizedError,