@tinycloud/node-sdk 3.4.0 → 3.5.0-beta.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.
package/dist/index.cjs CHANGED
@@ -17045,7 +17045,7 @@ function augmentSignInEntriesWithReplication(input) {
17045
17045
  for (const prefix of replication.prefixes) {
17046
17046
  (0, import_prefix.assertFolderPrefix)(prefix);
17047
17047
  const path = prefix === "/" ? "" : prefix;
17048
- if ((0, import_sdk_services.requiresSecretsOptIn)(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
17048
+ if ((0, import_sdk_services2.requiresSecretsOptIn)(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
17049
17049
  continue;
17050
17050
  }
17051
17051
  if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {
@@ -17071,12 +17071,12 @@ function augmentSignInEntriesWithReplication(input) {
17071
17071
  }
17072
17072
  return out;
17073
17073
  }
17074
- var import_sdk_core2, import_sdk_services, import_prefix, KV_GET, KV_SYNC, KV_SERVICE;
17074
+ var import_sdk_core2, import_sdk_services2, import_prefix, KV_GET, KV_SYNC, KV_SERVICE;
17075
17075
  var init_authority_sign_in = __esm({
17076
17076
  "src/replication/authority-sign-in.ts"() {
17077
17077
  "use strict";
17078
17078
  import_sdk_core2 = require("@tinycloud/sdk-core");
17079
- import_sdk_services = require("@tinycloud/sdk-services");
17079
+ import_sdk_services2 = require("@tinycloud/sdk-services");
17080
17080
  import_prefix = require("@tinycloud/sdk-services/kv/replication/prefix");
17081
17081
  KV_GET = import_sdk_core2.KV.GET;
17082
17082
  KV_SYNC = import_sdk_core2.KV.SYNC;
@@ -18069,6 +18069,167 @@ var PrivateKeySigner = class {
18069
18069
  }
18070
18070
  };
18071
18071
 
18072
+ // src/recordingServiceContext.ts
18073
+ var import_sdk_services = require("@tinycloud/sdk-services");
18074
+ var import_requestLog = require("@tinycloud/sdk-services/telemetry/requestLog");
18075
+
18076
+ // src/recordingRegistry.ts
18077
+ var unrecordedFetches = /* @__PURE__ */ new WeakMap();
18078
+ function registerRecording(context, unrecordedFetch) {
18079
+ unrecordedFetches.set(context, unrecordedFetch);
18080
+ }
18081
+ function unrecordedFetchOf(context) {
18082
+ const unrecordedFetch = unrecordedFetches.get(context);
18083
+ return unrecordedFetch ? unrecordedFetch() : context.fetch;
18084
+ }
18085
+
18086
+ // src/recordingServiceContext.ts
18087
+ var SYNC_ABILITY = "tinycloud.kv/sync";
18088
+ var INFO_PATH = "/info";
18089
+ function outcomeCodeOf(response) {
18090
+ return response === void 0 ? "ERROR" : response.ok ? "ok" : `HTTP_${response.status}`;
18091
+ }
18092
+ function replicationOp(url, meta) {
18093
+ if (meta !== void 0) return meta.action === SYNC_ABILITY ? `${meta.service}/sync` : void 0;
18094
+ return isInfoUrl(url) ? "sync/info" : void 0;
18095
+ }
18096
+ function isInfoUrl(url) {
18097
+ try {
18098
+ return new URL(url).pathname.replace(/\/+$/, "") === INFO_PATH;
18099
+ } catch {
18100
+ return false;
18101
+ }
18102
+ }
18103
+ function replicationEvent(sample, op) {
18104
+ return (0, import_requestLog.buildRequestEvent)({
18105
+ service: "sync",
18106
+ op,
18107
+ url: sample.url,
18108
+ bytes: (0, import_requestLog.requestBytesOf)(sample.init?.body, sample.response?.headers.get("content-length")),
18109
+ durationMs: performance.now() - sample.clock,
18110
+ attempts: sample.attempts ?? 1,
18111
+ ok: sample.response?.ok === true,
18112
+ code: outcomeCodeOf(sample.response),
18113
+ at: sample.at
18114
+ });
18115
+ }
18116
+ function createServiceContext(config) {
18117
+ const { requestSink, ...base2 } = config;
18118
+ return requestSink === void 0 ? new import_sdk_services.ServiceContext(base2) : new RecordingServiceContext(base2, requestSink);
18119
+ }
18120
+ var RecordingServiceContext = class extends import_sdk_services.ServiceContext {
18121
+ constructor(config, sink) {
18122
+ const pending = {};
18123
+ super({
18124
+ ...config,
18125
+ invoke: rememberInvoke(config.invoke, sink, pending),
18126
+ invokeAny: config.invokeAny ? rememberInvokeAny(config.invokeAny, sink, pending) : void 0
18127
+ });
18128
+ this.requestSink = sink;
18129
+ this.pending = pending;
18130
+ registerRecording(this, () => this.unrecordedFetch);
18131
+ }
18132
+ /** Per-call recording; `_fetch` is read at access time, so overrides apply. */
18133
+ get fetch() {
18134
+ const raw = super.fetch;
18135
+ return (url, init2) => this.recordCall(raw, url, init2);
18136
+ }
18137
+ /** Transport without per-call recording. */
18138
+ get unrecordedFetch() {
18139
+ return super.fetch;
18140
+ }
18141
+ /**
18142
+ * One event per logical request. The first resolved attempt's init is the logged request and
18143
+ * `attempts` counts resolved builds. A retired context throws before the first build and records
18144
+ * nothing, as master does.
18145
+ */
18146
+ async invokeWithRetry(url, build) {
18147
+ const at = Date.now();
18148
+ const clock = performance.now();
18149
+ let reached = false;
18150
+ let attempts = 0;
18151
+ let first;
18152
+ let firstMeta;
18153
+ let response;
18154
+ const attempt = async () => {
18155
+ reached = true;
18156
+ this.pending.meta = void 0;
18157
+ const built = build();
18158
+ const meta = this.pending.meta;
18159
+ const init2 = await built;
18160
+ attempts += 1;
18161
+ if (first === void 0) {
18162
+ first = init2;
18163
+ firstMeta = meta;
18164
+ }
18165
+ return init2;
18166
+ };
18167
+ try {
18168
+ response = await super.invokeWithRetry(url, attempt);
18169
+ return response;
18170
+ } finally {
18171
+ if (reached) {
18172
+ this.observeAttempt({ url, init: first, response, meta: firstMeta, at, clock, attempts });
18173
+ }
18174
+ }
18175
+ }
18176
+ async recordCall(fetchFn, url, init2) {
18177
+ const at = Date.now();
18178
+ const clock = performance.now();
18179
+ let response;
18180
+ try {
18181
+ response = await fetchFn(url, init2);
18182
+ return response;
18183
+ } finally {
18184
+ this.observeAttempt({ url, init: init2, response, at, clock });
18185
+ }
18186
+ }
18187
+ observeAttempt(sample) {
18188
+ try {
18189
+ const replication = replicationOp(sample.url, sample.meta ?? (0, import_requestLog.invocationMetaOf)(sample.init?.headers));
18190
+ if (replication !== void 0) {
18191
+ this.requestSink.record(replicationEvent(sample, replication));
18192
+ return;
18193
+ }
18194
+ const headers = sample.init?.headers;
18195
+ if (sample.meta !== void 0 && typeof headers === "object" && headers !== null) {
18196
+ this.requestSink.remember(headers, sample.meta);
18197
+ }
18198
+ this.requestSink.observe({
18199
+ url: sample.url,
18200
+ requestHeaders: headers,
18201
+ requestBody: sample.init?.body,
18202
+ responseContentLength: sample.response?.headers.get("content-length"),
18203
+ ok: sample.response?.ok === true,
18204
+ code: outcomeCodeOf(sample.response),
18205
+ durationMs: performance.now() - sample.clock,
18206
+ attempts: sample.attempts ?? 1,
18207
+ at: sample.at
18208
+ });
18209
+ } catch {
18210
+ }
18211
+ }
18212
+ };
18213
+ function rememberInvoke(invoke2, sink, pending) {
18214
+ return (session, service, path, action, facts) => {
18215
+ const headers = invoke2(session, service, path, action, facts);
18216
+ const meta = { service, path, action, spaceId: session.spaceId };
18217
+ pending.meta = meta;
18218
+ sink.remember(headers, meta);
18219
+ return headers;
18220
+ };
18221
+ }
18222
+ function rememberInvokeAny(invokeAny2, sink, pending) {
18223
+ return (session, entries, facts) => {
18224
+ const headers = invokeAny2(session, entries, facts);
18225
+ const first = entries[0];
18226
+ const meta = first === void 0 ? void 0 : { service: first.service, path: first.path, action: first.action, spaceId: first.spaceId ?? session.spaceId };
18227
+ pending.meta = meta;
18228
+ if (meta !== void 0) sink.remember(headers, meta);
18229
+ return headers;
18230
+ };
18231
+ }
18232
+
18072
18233
  // src/delegation.ts
18073
18234
  var import_sdk_core = require("@tinycloud/sdk-core");
18074
18235
  var RUNTIME_DELEGATION_REJECTIONS = [
@@ -18625,7 +18786,8 @@ function getNodeReplicationLoaders() {
18625
18786
 
18626
18787
  // src/TinyCloudNode.ts
18627
18788
  var import_sdk_core9 = require("@tinycloud/sdk-core");
18628
- var import_sdk_services2 = require("@tinycloud/sdk-services");
18789
+ var import_sdk_services3 = require("@tinycloud/sdk-services");
18790
+ var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
18629
18791
 
18630
18792
  // src/authorization/NodeUserAuthorization.ts
18631
18793
  var import_node_crypto = require("crypto");
@@ -20448,12 +20610,13 @@ var DelegatedAccess = class {
20448
20610
  this.session = session;
20449
20611
  this._delegation = delegation;
20450
20612
  this.host = host;
20451
- const context = new import_sdk_core6.ServiceContext({
20613
+ const context = createServiceContext({
20452
20614
  invoke: invoke2,
20453
20615
  invokeAny: invokeAny2,
20454
20616
  fetch: lifetime?.fetch ?? globalThis.fetch.bind(globalThis),
20455
20617
  hosts: [host],
20456
- telemetry
20618
+ telemetry,
20619
+ requestSink: lifetime?.requestSink
20457
20620
  });
20458
20621
  this._serviceContext = lifetime ? lifetime.track(context) : context;
20459
20622
  const prefix = this._delegation.path.replace(/\/$/, "");
@@ -21043,11 +21206,12 @@ function createDefaultSessionKey(wasm) {
21043
21206
  return { manager, keyId, jwk: JSON.parse(jwkStr) };
21044
21207
  }
21045
21208
  var ServiceGraphLifetime = class {
21046
- constructor(invokeFn, invokeAnyFn, fetchFn, observeContext) {
21209
+ constructor(invokeFn, invokeAnyFn, fetchFn, observeContext, requestSink) {
21047
21210
  this.invokeFn = invokeFn;
21048
21211
  this.invokeAnyFn = invokeAnyFn;
21049
21212
  this.fetchFn = fetchFn;
21050
21213
  this.observeContext = observeContext;
21214
+ this.requestSink = requestSink;
21051
21215
  this.abortController = new AbortController();
21052
21216
  this.contexts = /* @__PURE__ */ new Set();
21053
21217
  this.retired = false;
@@ -21618,6 +21782,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21618
21782
  this.sessionManager = sessionKey.manager;
21619
21783
  this.sessionKeyId = sessionKey.keyId;
21620
21784
  this.sessionKeyJwk = sessionKey.jwk;
21785
+ this._requestLog = config.requestLog === true ? new import_requestLog2.RequestLog() : void 0;
21621
21786
  this._serviceGraph = this.createServiceGraphLifetime();
21622
21787
  this._capabilityRegistry = new import_sdk_core9.CapabilityKeyRegistry();
21623
21788
  this._keyProvider = new WasmKeyProvider({
@@ -21789,7 +21954,8 @@ var _TinyCloudNode = class _TinyCloudNode {
21789
21954
  createKVService: (config) => {
21790
21955
  const prefix = config.pathPrefix?.replace(/\/$/, "");
21791
21956
  const kvService = new import_sdk_core9.KVService({ prefix });
21792
- const kvContext = receiveOnlyGraph.track(new import_sdk_core9.ServiceContext({
21957
+ const kvContext = receiveOnlyGraph.track(createServiceContext({
21958
+ requestSink: receiveOnlyGraph.requestSink,
21793
21959
  invoke: config.invoke,
21794
21960
  fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
21795
21961
  hosts: config.hosts,
@@ -21852,6 +22018,10 @@ var _TinyCloudNode = class _TinyCloudNode {
21852
22018
  shouldUseBootstrapSignInRequest(config) {
21853
22019
  return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
21854
22020
  }
22021
+ /** Sanitized developer request events; undefined unless the node was built with `requestLog: true`. */
22022
+ get requests() {
22023
+ return this._requestLog === void 0 ? void 0 : (0, import_requestLog2.readOnlyRequestLog)(this._requestLog);
22024
+ }
21855
22025
  createServiceGraphLifetime() {
21856
22026
  return new ServiceGraphLifetime(
21857
22027
  this.invokeWithRuntimePermissions,
@@ -21862,7 +22032,9 @@ var _TinyCloudNode = class _TinyCloudNode {
21862
22032
  (url, init2) => globalThis.fetch(url, init2),
21863
22033
  (context) => {
21864
22034
  this.storageFull.observe(context);
21865
- }
22035
+ if (this._requestLog) (0, import_requestLog2.observeReplicaReads)(context, this._requestLog);
22036
+ },
22037
+ this._requestLog
21866
22038
  );
21867
22039
  }
21868
22040
  syncResolvedHostFromAuth() {
@@ -22185,6 +22357,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22185
22357
  this.hostedOwnedSpaceIds.clear();
22186
22358
  this.cancelOwnedSpaceHosts();
22187
22359
  this.storageFull.clear();
22360
+ this._requestLog?.clear();
22188
22361
  this._bootstrapSkipped = false;
22189
22362
  this._bootstrapStatus = { skipped: false };
22190
22363
  this.replicationDelegateSession = void 0;
@@ -22276,7 +22449,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22276
22449
  const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
22277
22450
  this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
22278
22451
  }
22279
- if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
22452
+ if (address !== this._address || chainId !== this._chainId) {
22453
+ this.storageFull.clear();
22454
+ this._requestLog?.clear();
22455
+ }
22280
22456
  this._address = address;
22281
22457
  this._chainId = chainId;
22282
22458
  this._kv = void 0;
@@ -22855,10 +23031,11 @@ var _TinyCloudNode = class _TinyCloudNode {
22855
23031
  const contexts = [context];
22856
23032
  const accountSpaceId = this.ownedSpaceId(import_sdk_core9.ACCOUNT_REGISTRY_SPACE);
22857
23033
  const createScopedContext = (spaceId) => {
22858
- const scoped = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
23034
+ const scoped = this._serviceGraph.track(createServiceContext({
23035
+ requestSink: this._serviceGraph.requestSink,
22859
23036
  invoke: context.invoke,
22860
23037
  invokeAny: context.invokeAny,
22861
- fetch: context.fetch,
23038
+ fetch: unrecordedFetchOf(context),
22862
23039
  hosts: context.hosts,
22863
23040
  telemetry: this.config.telemetry,
22864
23041
  retryPolicy: this.config.retryPolicy
@@ -22910,7 +23087,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22910
23087
  if (!this.currentSessionCoversAccountRegistry()) {
22911
23088
  if (!_TinyCloudNode.accountRegistrySkipLogged) {
22912
23089
  _TinyCloudNode.accountRegistrySkipLogged = true;
22913
- import_sdk_services2.tinyCloudDebugLogger.log("registry.skip");
23090
+ import_sdk_services3.tinyCloudDebugLogger.log("registry.skip");
22914
23091
  }
22915
23092
  return;
22916
23093
  }
@@ -22922,10 +23099,11 @@ var _TinyCloudNode = class _TinyCloudNode {
22922
23099
  const promise = this.enqueueAccountRegistryOperation(session, async () => {
22923
23100
  const sharedContext = this._serviceContext;
22924
23101
  if (!sharedContext) return;
22925
- const context = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
23102
+ const context = this._serviceGraph.track(createServiceContext({
23103
+ requestSink: this._serviceGraph.requestSink,
22926
23104
  invoke: sharedContext.invoke,
22927
23105
  invokeAny: sharedContext.invokeAny,
22928
- fetch: sharedContext.fetch,
23106
+ fetch: unrecordedFetchOf(sharedContext),
22929
23107
  hosts: sharedContext.hosts,
22930
23108
  telemetry: this.config.telemetry,
22931
23109
  retryPolicy: this.config.retryPolicy
@@ -23659,6 +23837,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23659
23837
  if (resolvedHost) this.config.host = resolvedHost;
23660
23838
  if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
23661
23839
  this.storageFull.clear();
23840
+ this._requestLog?.clear();
23662
23841
  }
23663
23842
  this._address = stagedAddress;
23664
23843
  this._chainId = stagedChainId;
@@ -23778,7 +23957,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23778
23957
  const coreContext = graph.track(core.serviceContext);
23779
23958
  coreContext.setSession(input.serviceSession);
23780
23959
  }
23781
- const serviceContext = graph.track(new import_sdk_core9.ServiceContext({
23960
+ const serviceContext = graph.track(createServiceContext({
23961
+ requestSink: graph.requestSink,
23782
23962
  invoke: graph.invoke,
23783
23963
  invokeAny: graph.invokeAny,
23784
23964
  fetch: graph.fetch,
@@ -23866,7 +24046,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23866
24046
  },
23867
24047
  createKVService: (config) => {
23868
24048
  const service = new import_sdk_core9.KVService({ prefix: config.pathPrefix?.replace(/\/$/, "") });
23869
- const context = graph.track(new import_sdk_core9.ServiceContext({
24049
+ const context = graph.track(createServiceContext({
24050
+ requestSink: graph.requestSink,
23870
24051
  invoke: config.invoke,
23871
24052
  fetch: config.fetch ?? globalThis.fetch.bind(globalThis),
23872
24053
  hosts: config.hosts,
@@ -23906,7 +24087,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23906
24087
  createKVService: (spaceId) => {
23907
24088
  graph.assertActive();
23908
24089
  const scopedKv = new import_sdk_core9.KVService({});
23909
- const context = graph.track(new import_sdk_core9.ServiceContext({
24090
+ const context = graph.track(createServiceContext({
24091
+ requestSink: graph.requestSink,
23910
24092
  invoke: graph.invoke,
23911
24093
  invokeAny: graph.invokeAny,
23912
24094
  fetch: graph.fetch,
@@ -23922,7 +24104,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23922
24104
  createVaultService: (spaceId) => {
23923
24105
  graph.assertActive();
23924
24106
  const scopedKv = new import_sdk_core9.KVService({});
23925
- const context = graph.track(new import_sdk_core9.ServiceContext({
24107
+ const context = graph.track(createServiceContext({
24108
+ requestSink: graph.requestSink,
23926
24109
  invoke: graph.invoke,
23927
24110
  invokeAny: graph.invokeAny,
23928
24111
  fetch: graph.fetch,
@@ -24321,7 +24504,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24321
24504
  this._serviceGraph.fetch
24322
24505
  );
24323
24506
  this._serviceGraph.track(this.tc.serviceContext);
24324
- this._serviceContext = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
24507
+ this._serviceContext = this._serviceGraph.track(createServiceContext({
24508
+ requestSink: this._serviceGraph.requestSink,
24325
24509
  invoke: this._serviceGraph.invoke,
24326
24510
  invokeAny: this._serviceGraph.invokeAny,
24327
24511
  fetch: this._serviceGraph.fetch,
@@ -24369,10 +24553,11 @@ var _TinyCloudNode = class _TinyCloudNode {
24369
24553
  createSpaceScopedKVService(spaceId) {
24370
24554
  const kvService = new import_sdk_core9.KVService({});
24371
24555
  if (this._serviceContext) {
24372
- const spaceScopedContext = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
24556
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
24557
+ requestSink: this._serviceGraph.requestSink,
24373
24558
  invoke: this._serviceContext.invoke,
24374
24559
  invokeAny: this._serviceContext.invokeAny,
24375
- fetch: this._serviceContext.fetch,
24560
+ fetch: unrecordedFetchOf(this._serviceContext),
24376
24561
  hosts: this._serviceContext.hosts,
24377
24562
  telemetry: this.config.telemetry,
24378
24563
  retryPolicy: this.config.retryPolicy
@@ -24560,7 +24745,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24560
24745
  );
24561
24746
  graph.assertActive();
24562
24747
  if (!response.ok) {
24563
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services2.parsePermissionHint)({
24748
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
24564
24749
  service: "tinycloud.encryption",
24565
24750
  path: networkId,
24566
24751
  actions: ["tinycloud.encryption/decrypt"]
@@ -24838,7 +25023,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24838
25023
  createKVService: (config) => {
24839
25024
  graph.assertActive();
24840
25025
  const service = new import_sdk_core9.KVService({ prefix: config.pathPrefix?.replace(/\/$/, "") });
24841
- const context = graph.track(new import_sdk_core9.ServiceContext({
25026
+ const context = graph.track(createServiceContext({
25027
+ requestSink: graph.requestSink,
24842
25028
  invoke: config.invoke,
24843
25029
  fetch: config.fetch ?? graph.fetch,
24844
25030
  hosts: config.hosts,
@@ -25159,10 +25345,11 @@ var _TinyCloudNode = class _TinyCloudNode {
25159
25345
  throw new Error("Not signed in. Call signIn() first.");
25160
25346
  }
25161
25347
  const sql = new import_sdk_core9.SQLService({});
25162
- const spaceScopedContext = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
25348
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
25349
+ requestSink: this._serviceGraph.requestSink,
25163
25350
  invoke: this._serviceContext.invoke,
25164
25351
  invokeAny: this._serviceContext.invokeAny,
25165
- fetch: this._serviceContext.fetch,
25352
+ fetch: unrecordedFetchOf(this._serviceContext),
25166
25353
  hosts: this._serviceContext.hosts,
25167
25354
  telemetry: this.config.telemetry,
25168
25355
  retryPolicy: this.config.retryPolicy
@@ -25190,10 +25377,11 @@ var _TinyCloudNode = class _TinyCloudNode {
25190
25377
  throw new Error("Not signed in. Call signIn() first.");
25191
25378
  }
25192
25379
  const kv = new import_sdk_core9.KVService({});
25193
- const spaceScopedContext = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
25380
+ const spaceScopedContext = this._serviceGraph.track(createServiceContext({
25381
+ requestSink: this._serviceGraph.requestSink,
25194
25382
  invoke: this._serviceContext.invoke,
25195
25383
  invokeAny: this._serviceContext.invokeAny,
25196
- fetch: this._serviceContext.fetch,
25384
+ fetch: unrecordedFetchOf(this._serviceContext),
25197
25385
  hosts: this._serviceContext.hosts
25198
25386
  }));
25199
25387
  spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
@@ -25356,7 +25544,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25356
25544
  );
25357
25545
  if (result.status !== "ok") {
25358
25546
  if (result.status === "permission_required") {
25359
- const hint = (0, import_sdk_services2.parsePermissionHint)(result.hint);
25547
+ const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
25360
25548
  if (hint === void 0) return { status: "read_failed" };
25361
25549
  return {
25362
25550
  status: "permission_required",
@@ -25979,7 +26167,8 @@ var _TinyCloudNode = class _TinyCloudNode {
25979
26167
  }
25980
26168
  if (this._serviceContext) {
25981
26169
  const publicKV = new import_sdk_core9.KVService({ prefix: "" });
25982
- const publicContext = this._serviceGraph.track(new import_sdk_core9.ServiceContext({
26170
+ const publicContext = this._serviceGraph.track(createServiceContext({
26171
+ requestSink: this._serviceGraph.requestSink,
25983
26172
  invoke: this._serviceGraph.invoke,
25984
26173
  fetch: this._serviceGraph.fetch,
25985
26174
  hosts: this._serviceContext.hosts,
@@ -27699,7 +27888,7 @@ var import_node_fs = require("fs");
27699
27888
  var import_promises = require("fs/promises");
27700
27889
  var import_node_path = require("path");
27701
27890
  var import_sdk_core13 = require("@tinycloud/sdk-core");
27702
- var import_sdk_services3 = require("@tinycloud/sdk-services");
27891
+ var import_sdk_services4 = require("@tinycloud/sdk-services");
27703
27892
  var import_contractConstants = require("@tinycloud/sdk-services/kv/replication/contractConstants");
27704
27893
  init_authority();
27705
27894
  var REPLICA_LOGOUT_PENDING_FILE = "replica-logout-pending.json";
@@ -28261,7 +28450,7 @@ function sha256Base32(material) {
28261
28450
  return out.slice(0, 26);
28262
28451
  }
28263
28452
  function partitionDirOf(root, identity) {
28264
- return (0, import_node_path.join)(root, sha256Base32((0, import_sdk_services3.replicationIdentityKey)(identity)));
28453
+ return (0, import_node_path.join)(root, sha256Base32((0, import_sdk_services4.replicationIdentityKey)(identity)));
28265
28454
  }
28266
28455
  function replicaHashOf(prefix, deviceDid) {
28267
28456
  return sha256Base32(`${prefix}
@@ -28335,8 +28524,8 @@ function deviceRecordOf(value) {
28335
28524
  }
28336
28525
  function sameIdentity(a, b) {
28337
28526
  try {
28338
- const left = (0, import_sdk_services3.canonicalReplicationIdentity)(a);
28339
- const right = (0, import_sdk_services3.canonicalReplicationIdentity)(b);
28527
+ const left = (0, import_sdk_services4.canonicalReplicationIdentity)(a);
28528
+ const right = (0, import_sdk_services4.canonicalReplicationIdentity)(b);
28340
28529
  return left.host === right.host && left.space === right.space && left.principal === right.principal;
28341
28530
  } catch {
28342
28531
  return false;
@@ -28352,7 +28541,7 @@ async function ensurePartitionIdentity(dir, identity, dirSync) {
28352
28541
  const file = (0, import_node_path.join)(dir, "identity.json");
28353
28542
  const existing = await readJsonFile(file);
28354
28543
  if (existing === void 0) {
28355
- await writeJsonAtomic(file, { v: 1, ...(0, import_sdk_services3.canonicalReplicationIdentity)(identity) }, dirSync);
28544
+ await writeJsonAtomic(file, { v: 1, ...(0, import_sdk_services4.canonicalReplicationIdentity)(identity) }, dirSync);
28356
28545
  return;
28357
28546
  }
28358
28547
  const record = identityRecordOf(existing);
@@ -28451,7 +28640,7 @@ withGate_fn = async function(section) {
28451
28640
  if (snapshot === void 0) throw gated("gate_unavailable");
28452
28641
  const [principalHash, identityKeyHash] = await Promise.all([
28453
28642
  gate.principalHashOf(snapshot.salt, this.identity.principal),
28454
- gate.identityHashOf(snapshot.salt, (0, import_sdk_services3.replicationIdentityKey)(this.identity))
28643
+ gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(this.identity))
28455
28644
  ]);
28456
28645
  const access = {
28457
28646
  stamp: __privateGet(this, _stamp),
@@ -28951,7 +29140,7 @@ function createSqliteReplicaStorage(options) {
28951
29140
  if (snapshot === void 0) throw gated("gate_unavailable");
28952
29141
  const [principalHash, identityKeyHash] = await Promise.all([
28953
29142
  gate.principalHashOf(snapshot.salt, principal),
28954
- identity === void 0 ? Promise.resolve(void 0) : gate.identityHashOf(snapshot.salt, (0, import_sdk_services3.replicationIdentityKey)(identity))
29143
+ identity === void 0 ? Promise.resolve(void 0) : gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(identity))
28955
29144
  ]);
28956
29145
  return profileSection(async () => {
28957
29146
  assertLogoutClear(options.logoutPendingFile);
@@ -28974,7 +29163,7 @@ function createSqliteReplicaStorage(options) {
28974
29163
  if (snapshot === void 0) throw gated("gate_unavailable");
28975
29164
  const [principalHash, identityKeyHash] = await Promise.all([
28976
29165
  gate.principalHashOf(snapshot.salt, spec.identity.principal),
28977
- gate.identityHashOf(snapshot.salt, (0, import_sdk_services3.replicationIdentityKey)(spec.identity))
29166
+ gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(spec.identity))
28978
29167
  ]);
28979
29168
  const access = {
28980
29169
  stamp: spec.stamp,
@@ -29058,7 +29247,7 @@ function createSqliteReplicaStorage(options) {
29058
29247
  const identity = identityRecordOf(await readJsonFile((0, import_node_path.join)(idDir, "identity.json")));
29059
29248
  if (identity === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `Missing identity at ${idDir}.`);
29060
29249
  if (await gate.principalHashOf(salt, identity.principal) !== principalHash) continue;
29061
- const identityKeyHash = await gate.identityHashOf(salt, (0, import_sdk_services3.replicationIdentityKey)(identity));
29250
+ const identityKeyHash = await gate.identityHashOf(salt, (0, import_sdk_services4.replicationIdentityKey)(identity));
29062
29251
  const recordedAt = Date.now();
29063
29252
  if (await pathExists((0, import_node_path.join)(idDir, "pending.json")) || await pathExists((0, import_node_path.join)(idDir, "device.jwk"))) {
29064
29253
  intents.push({ id: `pending:${identityKeyHash}`, kind: "pending", identityKeyHash, principalHash, recordedAt });
@@ -29736,7 +29925,7 @@ function createSqliteReplicaStorage(options) {
29736
29925
  for (const dir of await identityDirs()) {
29737
29926
  const identity = identityRecordOf(await readJsonFile((0, import_node_path.join)(dir, "identity.json")));
29738
29927
  if (identity === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `Missing identity at ${dir}.`);
29739
- const hash3 = await gate.identityHashOf(state.salt, (0, import_sdk_services3.replicationIdentityKey)(identity));
29928
+ const hash3 = await gate.identityHashOf(state.salt, (0, import_sdk_services4.replicationIdentityKey)(identity));
29740
29929
  if (intent.kind === "pending" ? hash3 === intent.identityKeyHash : intent.partitionId.startsWith(`${hash3}:`)) {
29741
29930
  idDir = dir;
29742
29931
  break;
@@ -29882,7 +30071,7 @@ function createSqliteReplicaStorage(options) {
29882
30071
  const gate = await fileGate();
29883
30072
  const snapshot = await gate.readGateSnapshot(profileDir);
29884
30073
  if (snapshot === void 0) throw gated("gate_unavailable");
29885
- const identityKeyHash = await gate.identityHashOf(snapshot.salt, (0, import_sdk_services3.replicationIdentityKey)(spec.identity));
30074
+ const identityKeyHash = await gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(spec.identity));
29886
30075
  const resolvePolicy = async (at, recordFirst) => {
29887
30076
  const policy = spec.grantPolicy;
29888
30077
  if (!Number.isSafeInteger(policy.grantTtlMs) || policy.grantTtlMs <= 0 || policy.grantTtlMs > import_contractConstants.MAX_GRANT_TTL_MS) {
@@ -29904,7 +30093,7 @@ function createSqliteReplicaStorage(options) {
29904
30093
  let partitionId;
29905
30094
  const storeGuard = (section) => guarded(spec.identity.principal, spec.stamp, spec.identity, partitionId, section);
29906
30095
  return guarded(spec.identity.principal, spec.stamp, spec.identity, void 0, async () => {
29907
- if ((0, import_sdk_services3.requiresSecretsOptIn)(spec.space, spec.prefix) && spec.allowSecrets !== true) {
30096
+ if ((0, import_sdk_services4.requiresSecretsOptIn)(spec.space, spec.prefix) && spec.allowSecrets !== true) {
29908
30097
  throw new replica.ReplicaError(
29909
30098
  replica.ReplicaErrorCode.SECRETS_OPT_IN_REQUIRED,
29910
30099
  `Replicating ${spec.space}/kv/${spec.prefix} copies secret material to disk; pass allowSecrets to opt in.`
@@ -30150,7 +30339,7 @@ function createSqliteReplicaStorage(options) {
30150
30339
  const gate = await fileGate();
30151
30340
  const currentGate = await gate.readGateSnapshot(profileDir);
30152
30341
  if (currentGate !== void 0 && currentGate.principalHash !== null && currentGate.principalHash === await gate.principalHashOf(currentGate.salt, target.identity.principal)) {
30153
- const identityKeyHash = await gate.identityHashOf(currentGate.salt, (0, import_sdk_services3.replicationIdentityKey)(target.identity));
30342
+ const identityKeyHash = await gate.identityHashOf(currentGate.salt, (0, import_sdk_services4.replicationIdentityKey)(target.identity));
30154
30343
  await retainGrantInstalls(gate, currentGate, identityKeyHash, store);
30155
30344
  }
30156
30345
  if (state.revoked === null) {