@tinycloud/node-sdk 3.2.6-beta.6 → 3.3.0-beta.10

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.
Files changed (43) hide show
  1. package/dist/{WasmKeyProvider-DEdSyQ0I.d.cts → TinyCloudNode-CcNh20QX.d.cts} +181 -286
  2. package/dist/{WasmKeyProvider-DEdSyQ0I.d.ts → TinyCloudNode-CcNh20QX.d.ts} +181 -286
  3. package/dist/WasmKeyProvider-5Ee7fcAw.d.cts +217 -0
  4. package/dist/WasmKeyProvider-5Ee7fcAw.d.ts +217 -0
  5. package/dist/authority-Cwotwza3.d.cts +118 -0
  6. package/dist/authority-D_u9a7Wl.d.ts +118 -0
  7. package/dist/core.cjs +400 -168
  8. package/dist/core.cjs.map +1 -1
  9. package/dist/core.d.cts +37 -4
  10. package/dist/core.d.ts +37 -4
  11. package/dist/core.js +407 -176
  12. package/dist/core.js.map +1 -1
  13. package/dist/gated-network-BEF2XSTC.d.cts +16 -0
  14. package/dist/gated-network-BEF2XSTC.d.ts +16 -0
  15. package/dist/index.cjs +2734 -545
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +35 -89
  18. package/dist/index.d.ts +35 -89
  19. package/dist/index.js +2729 -545
  20. package/dist/index.js.map +1 -1
  21. package/dist/replication/authority.cjs +153 -15
  22. package/dist/replication/authority.cjs.map +1 -1
  23. package/dist/replication/authority.d.cts +4 -1
  24. package/dist/replication/authority.d.ts +4 -1
  25. package/dist/replication/authority.js +147 -14
  26. package/dist/replication/authority.js.map +1 -1
  27. package/dist/replication/gated-network.cjs +90 -0
  28. package/dist/replication/gated-network.cjs.map +1 -0
  29. package/dist/replication/gated-network.d.cts +2 -0
  30. package/dist/replication/gated-network.d.ts +2 -0
  31. package/dist/replication/gated-network.js +65 -0
  32. package/dist/replication/gated-network.js.map +1 -0
  33. package/dist/replication/runtime.cjs +84 -6
  34. package/dist/replication/runtime.cjs.map +1 -1
  35. package/dist/replication/runtime.d.cts +2 -1
  36. package/dist/replication/runtime.d.ts +2 -1
  37. package/dist/replication/runtime.js +84 -6
  38. package/dist/replication/runtime.js.map +1 -1
  39. package/dist/{runtime-B5P5SRpC.d.ts → runtime-D1o9Eybt.d.cts} +14 -3
  40. package/dist/{runtime-B5P5SRpC.d.cts → runtime-v62FzQHz.d.ts} +14 -3
  41. package/package.json +14 -4
  42. package/dist/authority-DDGtccBK.d.cts +0 -96
  43. package/dist/authority-DDGtccBK.d.ts +0 -96
@@ -67,10 +67,23 @@ var ReplicationRuntime = class {
67
67
  this.control = {
68
68
  status: async () => {
69
69
  await this.binding;
70
+ if (this.gatedReason) return this.options.prefixes.map((prefix) => ({
71
+ prefix,
72
+ state: "gated",
73
+ gate: this.gatedReason,
74
+ errorCode: "REPLICA_GATED",
75
+ pending: { inFlight: 0, committed: 0, ambiguous: 0 },
76
+ pinned: [],
77
+ lagMs: null
78
+ }));
70
79
  return this.current ? this.current.status() : [];
71
80
  },
72
81
  sync: async (input) => {
73
82
  await this.binding;
83
+ if (this.gatedReason) throw Object.assign(new Error("Replica restore is gated."), {
84
+ code: "REPLICA_GATED",
85
+ gate: this.gatedReason
86
+ });
74
87
  if (this.purgedGeneration === this.generation) return;
75
88
  if (this.current) await this.current.sync(input);
76
89
  },
@@ -118,11 +131,59 @@ var ReplicationRuntime = class {
118
131
  for (const kv of this.attached.keys()) kv.setReadThrough(null);
119
132
  this.attached.clear();
120
133
  }
134
+ /** Observe a refused restore without opening replica state; loader failure leaves raw network KV usable. */
135
+ bindGatedNetworkOnly(input, loadGatedNetwork) {
136
+ if (this.closed || !this.options.enabled) return Promise.resolve();
137
+ const retired = this.unbind();
138
+ const generation = this.generation;
139
+ this.purgedGeneration = void 0;
140
+ this.pendingPurge = void 0;
141
+ this.pendingPurgeResult = void 0;
142
+ this.pending.clear();
143
+ this.lastController = void 0;
144
+ this.lastPending = void 0;
145
+ this.lastIdentity = void 0;
146
+ this.gatedReason = input.gate;
147
+ this.primarySpace = input.session.spaceId;
148
+ this.binding = retired.then(async () => {
149
+ let create;
150
+ try {
151
+ ({ createGatedNetworkReadThrough: create } = await loadGatedNetwork());
152
+ } catch {
153
+ return;
154
+ }
155
+ if (generation !== this.generation || this.closed || typeof create !== "function") return;
156
+ const networkOnly = create({
157
+ space: input.session.spaceId,
158
+ prefixes: this.options.prefixes,
159
+ now: () => this.clock.now(),
160
+ emit: (event) => {
161
+ if (generation !== this.generation || this.closed || this.gated !== networkOnly) return;
162
+ try {
163
+ this.options.onEvent?.(event);
164
+ } catch {
165
+ }
166
+ const { type, ...data } = event;
167
+ try {
168
+ input.context.emit(type, data);
169
+ } catch {
170
+ }
171
+ }
172
+ });
173
+ this.gated = networkOnly;
174
+ for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);
175
+ });
176
+ return this.binding;
177
+ }
121
178
  bind(input) {
122
179
  if (this.closed || !this.options.enabled) return Promise.resolve();
180
+ this.gated = void 0;
181
+ this.gatedReason = void 0;
123
182
  this.bindingActive = true;
124
183
  const generation = ++this.generation;
125
184
  this.purgedGeneration = void 0;
185
+ this.unsubscribeConsentExpired?.();
186
+ this.unsubscribeConsentExpired = void 0;
126
187
  const { context, session } = input;
127
188
  const emit = (event) => {
128
189
  try {
@@ -155,11 +216,17 @@ var ReplicationRuntime = class {
155
216
  return;
156
217
  }
157
218
  const key = services.replicationIdentityKey(identity);
158
- let pending = this.pending.get(key);
159
- if (!pending) {
160
- pending = this.options.storage.pendingWrites?.(identity) ?? services.createMemoryPendingStore(identity);
161
- this.pending.set(key, pending);
219
+ let cached = this.pending.get(key);
220
+ const stamp = input.stamp;
221
+ if (!cached || cached.stamp?.incarnation !== stamp?.incarnation || cached.stamp?.lifecycleEpoch !== stamp?.lifecycleEpoch) {
222
+ const store = this.options.storage.pendingWrites?.(identity, { stamp });
223
+ if (!store && this.options.storage.lifecycle) {
224
+ throw Object.assign(new Error("Gated replication requires a stamped pending store"), { code: "REPLICA_GATED", gate: "gate_unavailable" });
225
+ }
226
+ cached = { stamp, store: store ?? services.createMemoryPendingStore(identity) };
227
+ this.pending.set(key, cached);
162
228
  }
229
+ const pending = cached.store;
163
230
  this.lastPending = pending;
164
231
  const controller = services.createKVReplication({
165
232
  options: resolvedOptions(this.options),
@@ -167,6 +234,7 @@ var ReplicationRuntime = class {
167
234
  storage: this.options.storage,
168
235
  identity,
169
236
  session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },
237
+ stamp: input.stamp,
170
238
  authority: input.authority,
171
239
  pending,
172
240
  scheduler: this.clock,
@@ -176,6 +244,9 @@ var ReplicationRuntime = class {
176
244
  previous: previousClose
177
245
  });
178
246
  this.current = controller;
247
+ this.unsubscribeConsentExpired = this.options.storage.lifecycle?.subscribeConsentExpired?.(
248
+ (stamp2) => controller.observeConsentExpired(stamp2)
249
+ );
179
250
  this.lastController = controller;
180
251
  this.lastIdentity = identity;
181
252
  for (const [kv, space] of this.attached) {
@@ -196,17 +267,24 @@ var ReplicationRuntime = class {
196
267
  }
197
268
  attach(space, kv) {
198
269
  this.attached.set(kv, space);
199
- kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);
270
+ kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);
271
+ const generation = this.generation;
200
272
  return this.binding.then(() => {
201
- kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);
273
+ if (generation === this.generation && !this.closed && this.attached.get(kv) === space) {
274
+ kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);
275
+ }
202
276
  });
203
277
  }
204
278
  unbind() {
205
279
  this.generation++;
206
280
  this.bindingActive = false;
207
281
  this.pendingPurgeSet = false;
282
+ this.unsubscribeConsentExpired?.();
283
+ this.unsubscribeConsentExpired = void 0;
208
284
  const controller = this.current;
209
285
  this.current = void 0;
286
+ this.gated = void 0;
287
+ this.gatedReason = void 0;
210
288
  this.primarySpace = void 0;
211
289
  this.detachAll();
212
290
  const binding = this.binding;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/replication/runtime.ts"],"sourcesContent":["import { pkhDid } from \"@tinycloud/sdk-core\";\nimport type { FetchFunction, ServiceContext, ServiceSession } from \"@tinycloud/sdk-core\";\nimport type {\n KVReadThrough,\n KVReplicaStorage,\n KVReplicationController,\n PendingWriteStore,\n ReplicationAuthority,\n ReplicationControl,\n ReplicationEvent,\n ReplicationIdentity,\n ReplicationOptions,\n ReplicationScheduler,\n} from \"@tinycloud/sdk-services\";\ntype ReplicationServices = typeof import(\"@tinycloud/sdk-services/kv/replication\");\n\nexport interface ReplicationRuntimeOptions extends ReplicationOptions {\n storage: KVReplicaStorage;\n mode?: \"background\" | \"foreground\";\n}\n\nexport interface ReplicationRuntimeBinding {\n context: ServiceContext;\n session: ServiceSession;\n address?: string;\n chainId?: number;\n principal?: string;\n authority: ReplicationAuthority;\n nodeFeatures?: readonly string[];\n primaryKV: Array<{ space: string; kv: { setReadThrough(readThrough: KVReadThrough | null): void } }>;\n}\n\nconst scheduler: ReplicationScheduler = {\n now: () => Date.now(),\n setTimeout(fn, ms) {\n const timer = setTimeout(fn, ms);\n timer.unref?.();\n return () => clearTimeout(timer);\n },\n};\n\nfunction resolvedOptions(options: ReplicationRuntimeOptions) {\n return {\n ...options,\n syncIntervalMs: options.syncIntervalMs ?? 60_000,\n maxStalenessMs: options.maxStalenessMs ?? 120_000,\n staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5_000,\n allowSecrets: options.allowSecrets === true,\n verify: options.verify === true,\n };\n}\ntype ReadThroughKV = {\n setReadThrough(readThrough: KVReadThrough | null): void;\n setReadThroughReady?(ready: Promise<unknown>): void;\n};\n\nexport class ReplicationRuntime {\n readonly control: ReplicationControl;\n private readonly pending = new Map<string, PendingWriteStore>();\n private readonly attached = new Map<ReadThroughKV, string>();\n private current?: KVReplicationController;\n private lastController?: KVReplicationController;\n private previous: Promise<void> = Promise.resolve();\n private binding: Promise<void> = Promise.resolve();\n private services?: Promise<ReplicationServices>;\n private generation = 0;\n private lastIdentity?: ReplicationIdentity;\n private lastPending?: PendingWriteStore;\n private primarySpace?: string;\n private pendingPurge?: Parameters<KVReplicationController[\"purge\"]>[0];\n private pendingPurgeResult?: ReturnType<KVReplicationController[\"purge\"]>;\n private bindingActive = false;\n private pendingPurgeSet = false;\n private purgedGeneration?: number;\n private closed = false;\n constructor(\n private readonly options: ReplicationRuntimeOptions,\n private readonly clock: ReplicationScheduler = scheduler,\n ) {\n this.control = {\n status: async () => {\n await this.binding;\n return this.current ? this.current.status() : [];\n },\n sync: async (input) => {\n await this.binding;\n if (this.purgedGeneration === this.generation) return;\n if (this.current) await this.current.sync(input);\n },\n purge: (input) => {\n if (this.bindingActive) {\n this.pendingPurge = input;\n this.pendingPurgeSet = true;\n const existing = this.current ?? this.lastController;\n if (existing) {\n this.pendingPurgeResult = existing.purge(input);\n this.purgedGeneration = this.generation;\n }\n return this.binding.then(() => this.pendingPurgeResult ?? { purged: [], failed: [] });\n }\n const controller = this.current ?? this.lastController;\n if (!controller) return Promise.resolve({ purged: [], failed: [] });\n this.purgedGeneration = this.generation;\n return controller.purge(input);\n },\n clearPending: async () => {\n await this.binding;\n if (this.current) return this.current.clearPending();\n const pending = this.lastPending;\n if (!pending) return 0;\n return pending.update((state) => {\n const before = state.records.length;\n state.records = state.records.filter((record) =>\n record.state === \"committed\" ||\n (record.state === \"in_flight\" && this.clock.now() - Date.parse(record.at) <= 10 * 60_000),\n );\n return before - state.records.length;\n });\n },\n close: async () => {\n this.closed = true;\n this.unbind();\n await this.binding.catch(() => undefined);\n await this.previous;\n },\n };\n }\n\n private loadServices(): Promise<ReplicationServices> {\n return (this.services ??= import(\"@tinycloud/sdk-services/kv/replication\"));\n }\n\n private detachAll(): void {\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.attached.clear();\n }\n\n bind(input: ReplicationRuntimeBinding): Promise<void> {\n if (this.closed || !this.options.enabled) return Promise.resolve();\n this.bindingActive = true;\n const generation = ++this.generation;\n this.purgedGeneration = undefined;\n const { context, session } = input;\n const emit = (event: ReplicationEvent): void => {\n try { this.options.onEvent?.(event); } catch { /* Observability must not affect KV behavior. */ }\n const { type, ...data } = event;\n context.emit(type, data);\n };\n const previous = this.current;\n this.current = undefined;\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.primarySpace = session.spaceId;\n const close = previous ? previous.close().catch(() => undefined) : Promise.resolve();\n this.previous = this.previous.then(() => close);\n const previousClose = this.previous;\n this.binding = this.loadServices().then((services) => {\n if (generation !== this.generation || this.closed) return;\n let identity: ReplicationIdentity;\n try {\n identity = services.canonicalReplicationIdentity({\n host: context.hosts[0]!,\n space: session.spaceId,\n principal: input.principal ?? pkhDid(input.address!, input.chainId!),\n });\n } catch {\n for (const prefix of this.options.prefixes) {\n emit({ type: \"replication.state\", at: new Date(this.clock.now()).toISOString(), space: session.spaceId, replica: prefix, state: \"unavailable\", code: \"CONFIG_INVALID\" });\n }\n return;\n }\n const key = services.replicationIdentityKey(identity);\n let pending = this.pending.get(key);\n if (!pending) {\n pending = this.options.storage.pendingWrites?.(identity) ?? services.createMemoryPendingStore(identity);\n this.pending.set(key, pending);\n }\n this.lastPending = pending;\n const controller = services.createKVReplication({\n options: resolvedOptions(this.options),\n mode: this.options.mode ?? \"background\",\n storage: this.options.storage,\n identity,\n session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },\n authority: input.authority,\n pending,\n scheduler: this.clock,\n emit,\n fetch: context.fetch as FetchFunction,\n nodeFeatures: input.nodeFeatures,\n previous: previousClose,\n });\n this.current = controller;\n this.lastController = controller;\n this.lastIdentity = identity;\n for (const [kv, space] of this.attached) {\n kv.setReadThrough(space === this.primarySpace ? controller : null);\n }\n for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);\n if (this.pendingPurgeSet) {\n this.pendingPurgeResult = controller.purge(this.pendingPurge);\n this.purgedGeneration = generation;\n this.pendingPurge = undefined;\n this.pendingPurgeSet = false;\n }\n }).finally(() => {\n if (generation === this.generation) this.bindingActive = false;\n });\n for (const kv of this.attached.keys()) kv.setReadThroughReady?.(this.binding);\n return this.binding;\n }\n\n attach(space: string, kv: ReadThroughKV): Promise<void> {\n this.attached.set(kv, space);\n kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);\n return this.binding.then(() => {\n kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);\n });\n }\n\n unbind(): Promise<void> {\n this.generation++;\n this.bindingActive = false;\n this.pendingPurgeSet = false;\n const controller = this.current;\n this.current = undefined;\n this.primarySpace = undefined;\n this.detachAll();\n const binding = this.binding;\n const close = binding.then(() => controller?.close()).catch(() => undefined);\n this.previous = this.previous.then(() => close);\n return this.previous;\n }\n\n get boundIdentity(): ReplicationIdentity | undefined {\n return this.lastIdentity;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/replication/runtime.ts"],"sourcesContent":["import { pkhDid } from \"@tinycloud/sdk-core\";\nimport type { FetchFunction, ServiceContext, ServiceSession } from \"@tinycloud/sdk-core\";\nimport type {\n KVReadThrough,\n KVReplicaStorage,\n KVReplicationController,\n PendingWriteStore,\n ReplicationAuthority,\n ReplicationControl,\n ReplicationEvent,\n ReplicaSessionStamp,\n ReplicationIdentity,\n ReplicationOptions,\n ReplicaGateReason,\n ReplicationScheduler,\n} from \"@tinycloud/sdk-services\";\n\ntype ReplicationServices = typeof import(\"@tinycloud/sdk-services/kv/replication\");\n\nexport interface ReplicationRuntimeOptions extends ReplicationOptions {\n storage: KVReplicaStorage;\n mode?: \"background\" | \"foreground\";\n}\n\nexport interface ReplicationRuntimeBinding {\n context: ServiceContext;\n session: ServiceSession;\n address?: string;\n chainId?: number;\n principal?: string;\n stamp?: ReplicaSessionStamp;\n authority: ReplicationAuthority;\n nodeFeatures?: readonly string[];\n primaryKV: Array<{ space: string; kv: { setReadThrough(readThrough: KVReadThrough | null): void } }>;\n}\nexport type GatedReplicationRuntimeBinding = Pick<ReplicationRuntimeBinding, \"context\" | \"session\" | \"primaryKV\"> & {\n gate: ReplicaGateReason;\n};\n\nconst scheduler: ReplicationScheduler = {\n now: () => Date.now(),\n setTimeout(fn, ms) {\n const timer = setTimeout(fn, ms);\n timer.unref?.();\n return () => clearTimeout(timer);\n },\n};\n\nfunction resolvedOptions(options: ReplicationRuntimeOptions) {\n return {\n ...options,\n syncIntervalMs: options.syncIntervalMs ?? 60_000,\n maxStalenessMs: options.maxStalenessMs ?? 120_000,\n staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5_000,\n allowSecrets: options.allowSecrets === true,\n verify: options.verify === true,\n };\n}\ntype ReadThroughKV = {\n setReadThrough(readThrough: KVReadThrough | null): void;\n setReadThroughReady?(ready: Promise<unknown>): void;\n};\n\nexport class ReplicationRuntime {\n readonly control: ReplicationControl;\n private readonly pending = new Map<string, { stamp?: ReplicaSessionStamp; store: PendingWriteStore }>();\n private readonly attached = new Map<ReadThroughKV, string>();\n private current?: KVReplicationController;\n private gated?: KVReadThrough;\n private gatedReason?: ReplicaGateReason;\n private lastController?: KVReplicationController;\n private unsubscribeConsentExpired?: () => void;\n private previous: Promise<void> = Promise.resolve();\n private binding: Promise<void> = Promise.resolve();\n private services?: Promise<ReplicationServices>;\n private generation = 0;\n private lastIdentity?: ReplicationIdentity;\n private lastPending?: PendingWriteStore;\n private primarySpace?: string;\n private pendingPurge?: Parameters<KVReplicationController[\"purge\"]>[0];\n private pendingPurgeResult?: ReturnType<KVReplicationController[\"purge\"]>;\n private bindingActive = false;\n private pendingPurgeSet = false;\n private purgedGeneration?: number;\n private closed = false;\n constructor(\n private readonly options: ReplicationRuntimeOptions,\n private readonly clock: ReplicationScheduler = scheduler,\n ) {\n this.control = {\n status: async () => {\n await this.binding;\n if (this.gatedReason) return this.options.prefixes.map((prefix) => ({\n prefix, state: \"gated\" as const, gate: this.gatedReason, errorCode: \"REPLICA_GATED\",\n pending: { inFlight: 0, committed: 0, ambiguous: 0 }, pinned: [], lagMs: null,\n }));\n return this.current ? this.current.status() : [];\n },\n sync: async (input) => {\n await this.binding;\n if (this.gatedReason) throw Object.assign(new Error(\"Replica restore is gated.\"), {\n code: \"REPLICA_GATED\", gate: this.gatedReason,\n });\n if (this.purgedGeneration === this.generation) return;\n if (this.current) await this.current.sync(input);\n },\n purge: (input) => {\n if (this.bindingActive) {\n this.pendingPurge = input;\n this.pendingPurgeSet = true;\n const existing = this.current ?? this.lastController;\n if (existing) {\n this.pendingPurgeResult = existing.purge(input);\n this.purgedGeneration = this.generation;\n }\n return this.binding.then(() => this.pendingPurgeResult ?? { purged: [], failed: [] });\n }\n const controller = this.current ?? this.lastController;\n if (!controller) return Promise.resolve({ purged: [], failed: [] });\n this.purgedGeneration = this.generation;\n return controller.purge(input);\n },\n clearPending: async () => {\n await this.binding;\n if (this.current) return this.current.clearPending();\n const pending = this.lastPending;\n if (!pending) return 0;\n return pending.update((state) => {\n const before = state.records.length;\n state.records = state.records.filter((record) =>\n record.state === \"committed\" ||\n (record.state === \"in_flight\" && this.clock.now() - Date.parse(record.at) <= 10 * 60_000),\n );\n return before - state.records.length;\n });\n },\n close: async () => {\n this.closed = true;\n this.unbind();\n await this.binding.catch(() => undefined);\n await this.previous;\n },\n };\n }\n\n private loadServices(): Promise<ReplicationServices> {\n return (this.services ??= import(\"@tinycloud/sdk-services/kv/replication\"));\n }\n\n private detachAll(): void {\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.attached.clear();\n }\n\n /** Observe a refused restore without opening replica state; loader failure leaves raw network KV usable. */\n bindGatedNetworkOnly(\n input: GatedReplicationRuntimeBinding,\n loadGatedNetwork: () => Promise<typeof import(\"./gated-network\")>,\n ): Promise<void> {\n if (this.closed || !this.options.enabled) return Promise.resolve();\n const retired = this.unbind();\n const generation = this.generation;\n this.purgedGeneration = undefined;\n this.pendingPurge = undefined;\n this.pendingPurgeResult = undefined;\n this.pending.clear();\n this.lastController = undefined;\n this.lastPending = undefined;\n this.lastIdentity = undefined;\n this.gatedReason = input.gate;\n this.primarySpace = input.session.spaceId;\n this.binding = retired.then(async () => {\n let create: typeof import(\"./gated-network\").createGatedNetworkReadThrough;\n try {\n ({ createGatedNetworkReadThrough: create } = await loadGatedNetwork());\n } catch {\n // Refusal already gates local delivery; a missing observer cannot end a network session.\n return;\n }\n if (generation !== this.generation || this.closed || typeof create !== \"function\") return;\n const networkOnly = create({\n space: input.session.spaceId,\n prefixes: this.options.prefixes,\n now: () => this.clock.now(),\n emit: (event) => {\n if (generation !== this.generation || this.closed || this.gated !== networkOnly) return;\n try { this.options.onEvent?.(event); } catch { /* Telemetry cannot change network results. */ }\n const { type, ...data } = event;\n try { input.context.emit(type, data); } catch { /* Telemetry cannot change network results. */ }\n },\n });\n this.gated = networkOnly;\n for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);\n });\n return this.binding;\n }\n\n bind(input: ReplicationRuntimeBinding): Promise<void> {\n if (this.closed || !this.options.enabled) return Promise.resolve();\n this.gated = undefined;\n this.gatedReason = undefined;\n this.bindingActive = true;\n const generation = ++this.generation;\n this.purgedGeneration = undefined;\n this.unsubscribeConsentExpired?.();\n this.unsubscribeConsentExpired = undefined;\n const { context, session } = input;\n const emit = (event: ReplicationEvent): void => {\n try { this.options.onEvent?.(event); } catch { /* Observability must not affect KV behavior. */ }\n const { type, ...data } = event;\n context.emit(type, data);\n };\n const previous = this.current;\n this.current = undefined;\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.primarySpace = session.spaceId;\n const close = previous ? previous.close().catch(() => undefined) : Promise.resolve();\n this.previous = this.previous.then(() => close);\n const previousClose = this.previous;\n this.binding = this.loadServices().then((services) => {\n if (generation !== this.generation || this.closed) return;\n let identity: ReplicationIdentity;\n try {\n identity = services.canonicalReplicationIdentity({\n host: context.hosts[0]!,\n space: session.spaceId,\n principal: input.principal ?? pkhDid(input.address!, input.chainId!),\n });\n } catch {\n for (const prefix of this.options.prefixes) {\n emit({ type: \"replication.state\", at: new Date(this.clock.now()).toISOString(), space: session.spaceId, replica: prefix, state: \"unavailable\", code: \"CONFIG_INVALID\" });\n }\n return;\n }\n const key = services.replicationIdentityKey(identity);\n let cached = this.pending.get(key);\n const stamp = input.stamp;\n if (!cached || cached.stamp?.incarnation !== stamp?.incarnation || cached.stamp?.lifecycleEpoch !== stamp?.lifecycleEpoch) {\n const store = this.options.storage.pendingWrites?.(identity, { stamp });\n if (!store && this.options.storage.lifecycle) {\n throw Object.assign(new Error(\"Gated replication requires a stamped pending store\"), { code: \"REPLICA_GATED\", gate: \"gate_unavailable\" });\n }\n cached = { stamp, store: store ?? services.createMemoryPendingStore(identity) };\n this.pending.set(key, cached);\n }\n const pending = cached.store;\n this.lastPending = pending;\n const controller = services.createKVReplication({\n options: resolvedOptions(this.options),\n mode: this.options.mode ?? \"background\",\n storage: this.options.storage,\n identity,\n session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },\n stamp: input.stamp,\n authority: input.authority,\n pending,\n scheduler: this.clock,\n emit,\n fetch: context.fetch as FetchFunction,\n nodeFeatures: input.nodeFeatures,\n previous: previousClose,\n });\n this.current = controller;\n this.unsubscribeConsentExpired = this.options.storage.lifecycle?.subscribeConsentExpired?.(\n (stamp) => controller.observeConsentExpired(stamp),\n );\n this.lastController = controller;\n this.lastIdentity = identity;\n for (const [kv, space] of this.attached) {\n kv.setReadThrough(space === this.primarySpace ? controller : null);\n }\n for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);\n if (this.pendingPurgeSet) {\n this.pendingPurgeResult = controller.purge(this.pendingPurge);\n this.purgedGeneration = generation;\n this.pendingPurge = undefined;\n this.pendingPurgeSet = false;\n }\n }).finally(() => {\n if (generation === this.generation) this.bindingActive = false;\n });\n for (const kv of this.attached.keys()) kv.setReadThroughReady?.(this.binding);\n return this.binding;\n }\n\n attach(space: string, kv: ReadThroughKV): Promise<void> {\n this.attached.set(kv, space);\n kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);\n const generation = this.generation;\n return this.binding.then(() => {\n if (generation === this.generation && !this.closed && this.attached.get(kv) === space) {\n kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);\n }\n });\n }\n\n unbind(): Promise<void> {\n this.generation++;\n this.bindingActive = false;\n this.pendingPurgeSet = false;\n this.unsubscribeConsentExpired?.();\n this.unsubscribeConsentExpired = undefined;\n const controller = this.current;\n this.current = undefined;\n this.gated = undefined;\n this.gatedReason = undefined;\n this.primarySpace = undefined;\n this.detachAll();\n const binding = this.binding;\n const close = binding.then(() => controller?.close()).catch(() => undefined);\n this.previous = this.previous.then(() => close);\n return this.previous;\n }\n\n get boundIdentity(): ReplicationIdentity | undefined {\n return this.lastIdentity;\n }\n}\n"],"mappings":"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","names":["stamp"]}
@@ -1,3 +1,4 @@
1
+ import '../gated-network-BEF2XSTC.cjs';
1
2
  import '@tinycloud/sdk-core';
2
3
  import '@tinycloud/sdk-services';
3
- export { R as ReplicationRuntime, a as ReplicationRuntimeBinding, b as ReplicationRuntimeOptions } from '../runtime-B5P5SRpC.cjs';
4
+ export { G as GatedReplicationRuntimeBinding, R as ReplicationRuntime, a as ReplicationRuntimeBinding, b as ReplicationRuntimeOptions } from '../runtime-D1o9Eybt.cjs';
@@ -1,3 +1,4 @@
1
+ import '../gated-network-BEF2XSTC.js';
1
2
  import '@tinycloud/sdk-core';
2
3
  import '@tinycloud/sdk-services';
3
- export { R as ReplicationRuntime, a as ReplicationRuntimeBinding, b as ReplicationRuntimeOptions } from '../runtime-B5P5SRpC.js';
4
+ export { G as GatedReplicationRuntimeBinding, R as ReplicationRuntime, a as ReplicationRuntimeBinding, b as ReplicationRuntimeOptions } from '../runtime-v62FzQHz.js';
@@ -33,10 +33,23 @@ var ReplicationRuntime = class {
33
33
  this.control = {
34
34
  status: async () => {
35
35
  await this.binding;
36
+ if (this.gatedReason) return this.options.prefixes.map((prefix) => ({
37
+ prefix,
38
+ state: "gated",
39
+ gate: this.gatedReason,
40
+ errorCode: "REPLICA_GATED",
41
+ pending: { inFlight: 0, committed: 0, ambiguous: 0 },
42
+ pinned: [],
43
+ lagMs: null
44
+ }));
36
45
  return this.current ? this.current.status() : [];
37
46
  },
38
47
  sync: async (input) => {
39
48
  await this.binding;
49
+ if (this.gatedReason) throw Object.assign(new Error("Replica restore is gated."), {
50
+ code: "REPLICA_GATED",
51
+ gate: this.gatedReason
52
+ });
40
53
  if (this.purgedGeneration === this.generation) return;
41
54
  if (this.current) await this.current.sync(input);
42
55
  },
@@ -84,11 +97,59 @@ var ReplicationRuntime = class {
84
97
  for (const kv of this.attached.keys()) kv.setReadThrough(null);
85
98
  this.attached.clear();
86
99
  }
100
+ /** Observe a refused restore without opening replica state; loader failure leaves raw network KV usable. */
101
+ bindGatedNetworkOnly(input, loadGatedNetwork) {
102
+ if (this.closed || !this.options.enabled) return Promise.resolve();
103
+ const retired = this.unbind();
104
+ const generation = this.generation;
105
+ this.purgedGeneration = void 0;
106
+ this.pendingPurge = void 0;
107
+ this.pendingPurgeResult = void 0;
108
+ this.pending.clear();
109
+ this.lastController = void 0;
110
+ this.lastPending = void 0;
111
+ this.lastIdentity = void 0;
112
+ this.gatedReason = input.gate;
113
+ this.primarySpace = input.session.spaceId;
114
+ this.binding = retired.then(async () => {
115
+ let create;
116
+ try {
117
+ ({ createGatedNetworkReadThrough: create } = await loadGatedNetwork());
118
+ } catch {
119
+ return;
120
+ }
121
+ if (generation !== this.generation || this.closed || typeof create !== "function") return;
122
+ const networkOnly = create({
123
+ space: input.session.spaceId,
124
+ prefixes: this.options.prefixes,
125
+ now: () => this.clock.now(),
126
+ emit: (event) => {
127
+ if (generation !== this.generation || this.closed || this.gated !== networkOnly) return;
128
+ try {
129
+ this.options.onEvent?.(event);
130
+ } catch {
131
+ }
132
+ const { type, ...data } = event;
133
+ try {
134
+ input.context.emit(type, data);
135
+ } catch {
136
+ }
137
+ }
138
+ });
139
+ this.gated = networkOnly;
140
+ for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);
141
+ });
142
+ return this.binding;
143
+ }
87
144
  bind(input) {
88
145
  if (this.closed || !this.options.enabled) return Promise.resolve();
146
+ this.gated = void 0;
147
+ this.gatedReason = void 0;
89
148
  this.bindingActive = true;
90
149
  const generation = ++this.generation;
91
150
  this.purgedGeneration = void 0;
151
+ this.unsubscribeConsentExpired?.();
152
+ this.unsubscribeConsentExpired = void 0;
92
153
  const { context, session } = input;
93
154
  const emit = (event) => {
94
155
  try {
@@ -121,11 +182,17 @@ var ReplicationRuntime = class {
121
182
  return;
122
183
  }
123
184
  const key = services.replicationIdentityKey(identity);
124
- let pending = this.pending.get(key);
125
- if (!pending) {
126
- pending = this.options.storage.pendingWrites?.(identity) ?? services.createMemoryPendingStore(identity);
127
- this.pending.set(key, pending);
185
+ let cached = this.pending.get(key);
186
+ const stamp = input.stamp;
187
+ if (!cached || cached.stamp?.incarnation !== stamp?.incarnation || cached.stamp?.lifecycleEpoch !== stamp?.lifecycleEpoch) {
188
+ const store = this.options.storage.pendingWrites?.(identity, { stamp });
189
+ if (!store && this.options.storage.lifecycle) {
190
+ throw Object.assign(new Error("Gated replication requires a stamped pending store"), { code: "REPLICA_GATED", gate: "gate_unavailable" });
191
+ }
192
+ cached = { stamp, store: store ?? services.createMemoryPendingStore(identity) };
193
+ this.pending.set(key, cached);
128
194
  }
195
+ const pending = cached.store;
129
196
  this.lastPending = pending;
130
197
  const controller = services.createKVReplication({
131
198
  options: resolvedOptions(this.options),
@@ -133,6 +200,7 @@ var ReplicationRuntime = class {
133
200
  storage: this.options.storage,
134
201
  identity,
135
202
  session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },
203
+ stamp: input.stamp,
136
204
  authority: input.authority,
137
205
  pending,
138
206
  scheduler: this.clock,
@@ -142,6 +210,9 @@ var ReplicationRuntime = class {
142
210
  previous: previousClose
143
211
  });
144
212
  this.current = controller;
213
+ this.unsubscribeConsentExpired = this.options.storage.lifecycle?.subscribeConsentExpired?.(
214
+ (stamp2) => controller.observeConsentExpired(stamp2)
215
+ );
145
216
  this.lastController = controller;
146
217
  this.lastIdentity = identity;
147
218
  for (const [kv, space] of this.attached) {
@@ -162,17 +233,24 @@ var ReplicationRuntime = class {
162
233
  }
163
234
  attach(space, kv) {
164
235
  this.attached.set(kv, space);
165
- kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);
236
+ kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);
237
+ const generation = this.generation;
166
238
  return this.binding.then(() => {
167
- kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);
239
+ if (generation === this.generation && !this.closed && this.attached.get(kv) === space) {
240
+ kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);
241
+ }
168
242
  });
169
243
  }
170
244
  unbind() {
171
245
  this.generation++;
172
246
  this.bindingActive = false;
173
247
  this.pendingPurgeSet = false;
248
+ this.unsubscribeConsentExpired?.();
249
+ this.unsubscribeConsentExpired = void 0;
174
250
  const controller = this.current;
175
251
  this.current = void 0;
252
+ this.gated = void 0;
253
+ this.gatedReason = void 0;
176
254
  this.primarySpace = void 0;
177
255
  this.detachAll();
178
256
  const binding = this.binding;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/replication/runtime.ts"],"sourcesContent":["import { pkhDid } from \"@tinycloud/sdk-core\";\nimport type { FetchFunction, ServiceContext, ServiceSession } from \"@tinycloud/sdk-core\";\nimport type {\n KVReadThrough,\n KVReplicaStorage,\n KVReplicationController,\n PendingWriteStore,\n ReplicationAuthority,\n ReplicationControl,\n ReplicationEvent,\n ReplicationIdentity,\n ReplicationOptions,\n ReplicationScheduler,\n} from \"@tinycloud/sdk-services\";\ntype ReplicationServices = typeof import(\"@tinycloud/sdk-services/kv/replication\");\n\nexport interface ReplicationRuntimeOptions extends ReplicationOptions {\n storage: KVReplicaStorage;\n mode?: \"background\" | \"foreground\";\n}\n\nexport interface ReplicationRuntimeBinding {\n context: ServiceContext;\n session: ServiceSession;\n address?: string;\n chainId?: number;\n principal?: string;\n authority: ReplicationAuthority;\n nodeFeatures?: readonly string[];\n primaryKV: Array<{ space: string; kv: { setReadThrough(readThrough: KVReadThrough | null): void } }>;\n}\n\nconst scheduler: ReplicationScheduler = {\n now: () => Date.now(),\n setTimeout(fn, ms) {\n const timer = setTimeout(fn, ms);\n timer.unref?.();\n return () => clearTimeout(timer);\n },\n};\n\nfunction resolvedOptions(options: ReplicationRuntimeOptions) {\n return {\n ...options,\n syncIntervalMs: options.syncIntervalMs ?? 60_000,\n maxStalenessMs: options.maxStalenessMs ?? 120_000,\n staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5_000,\n allowSecrets: options.allowSecrets === true,\n verify: options.verify === true,\n };\n}\ntype ReadThroughKV = {\n setReadThrough(readThrough: KVReadThrough | null): void;\n setReadThroughReady?(ready: Promise<unknown>): void;\n};\n\nexport class ReplicationRuntime {\n readonly control: ReplicationControl;\n private readonly pending = new Map<string, PendingWriteStore>();\n private readonly attached = new Map<ReadThroughKV, string>();\n private current?: KVReplicationController;\n private lastController?: KVReplicationController;\n private previous: Promise<void> = Promise.resolve();\n private binding: Promise<void> = Promise.resolve();\n private services?: Promise<ReplicationServices>;\n private generation = 0;\n private lastIdentity?: ReplicationIdentity;\n private lastPending?: PendingWriteStore;\n private primarySpace?: string;\n private pendingPurge?: Parameters<KVReplicationController[\"purge\"]>[0];\n private pendingPurgeResult?: ReturnType<KVReplicationController[\"purge\"]>;\n private bindingActive = false;\n private pendingPurgeSet = false;\n private purgedGeneration?: number;\n private closed = false;\n constructor(\n private readonly options: ReplicationRuntimeOptions,\n private readonly clock: ReplicationScheduler = scheduler,\n ) {\n this.control = {\n status: async () => {\n await this.binding;\n return this.current ? this.current.status() : [];\n },\n sync: async (input) => {\n await this.binding;\n if (this.purgedGeneration === this.generation) return;\n if (this.current) await this.current.sync(input);\n },\n purge: (input) => {\n if (this.bindingActive) {\n this.pendingPurge = input;\n this.pendingPurgeSet = true;\n const existing = this.current ?? this.lastController;\n if (existing) {\n this.pendingPurgeResult = existing.purge(input);\n this.purgedGeneration = this.generation;\n }\n return this.binding.then(() => this.pendingPurgeResult ?? { purged: [], failed: [] });\n }\n const controller = this.current ?? this.lastController;\n if (!controller) return Promise.resolve({ purged: [], failed: [] });\n this.purgedGeneration = this.generation;\n return controller.purge(input);\n },\n clearPending: async () => {\n await this.binding;\n if (this.current) return this.current.clearPending();\n const pending = this.lastPending;\n if (!pending) return 0;\n return pending.update((state) => {\n const before = state.records.length;\n state.records = state.records.filter((record) =>\n record.state === \"committed\" ||\n (record.state === \"in_flight\" && this.clock.now() - Date.parse(record.at) <= 10 * 60_000),\n );\n return before - state.records.length;\n });\n },\n close: async () => {\n this.closed = true;\n this.unbind();\n await this.binding.catch(() => undefined);\n await this.previous;\n },\n };\n }\n\n private loadServices(): Promise<ReplicationServices> {\n return (this.services ??= import(\"@tinycloud/sdk-services/kv/replication\"));\n }\n\n private detachAll(): void {\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.attached.clear();\n }\n\n bind(input: ReplicationRuntimeBinding): Promise<void> {\n if (this.closed || !this.options.enabled) return Promise.resolve();\n this.bindingActive = true;\n const generation = ++this.generation;\n this.purgedGeneration = undefined;\n const { context, session } = input;\n const emit = (event: ReplicationEvent): void => {\n try { this.options.onEvent?.(event); } catch { /* Observability must not affect KV behavior. */ }\n const { type, ...data } = event;\n context.emit(type, data);\n };\n const previous = this.current;\n this.current = undefined;\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.primarySpace = session.spaceId;\n const close = previous ? previous.close().catch(() => undefined) : Promise.resolve();\n this.previous = this.previous.then(() => close);\n const previousClose = this.previous;\n this.binding = this.loadServices().then((services) => {\n if (generation !== this.generation || this.closed) return;\n let identity: ReplicationIdentity;\n try {\n identity = services.canonicalReplicationIdentity({\n host: context.hosts[0]!,\n space: session.spaceId,\n principal: input.principal ?? pkhDid(input.address!, input.chainId!),\n });\n } catch {\n for (const prefix of this.options.prefixes) {\n emit({ type: \"replication.state\", at: new Date(this.clock.now()).toISOString(), space: session.spaceId, replica: prefix, state: \"unavailable\", code: \"CONFIG_INVALID\" });\n }\n return;\n }\n const key = services.replicationIdentityKey(identity);\n let pending = this.pending.get(key);\n if (!pending) {\n pending = this.options.storage.pendingWrites?.(identity) ?? services.createMemoryPendingStore(identity);\n this.pending.set(key, pending);\n }\n this.lastPending = pending;\n const controller = services.createKVReplication({\n options: resolvedOptions(this.options),\n mode: this.options.mode ?? \"background\",\n storage: this.options.storage,\n identity,\n session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },\n authority: input.authority,\n pending,\n scheduler: this.clock,\n emit,\n fetch: context.fetch as FetchFunction,\n nodeFeatures: input.nodeFeatures,\n previous: previousClose,\n });\n this.current = controller;\n this.lastController = controller;\n this.lastIdentity = identity;\n for (const [kv, space] of this.attached) {\n kv.setReadThrough(space === this.primarySpace ? controller : null);\n }\n for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);\n if (this.pendingPurgeSet) {\n this.pendingPurgeResult = controller.purge(this.pendingPurge);\n this.purgedGeneration = generation;\n this.pendingPurge = undefined;\n this.pendingPurgeSet = false;\n }\n }).finally(() => {\n if (generation === this.generation) this.bindingActive = false;\n });\n for (const kv of this.attached.keys()) kv.setReadThroughReady?.(this.binding);\n return this.binding;\n }\n\n attach(space: string, kv: ReadThroughKV): Promise<void> {\n this.attached.set(kv, space);\n kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);\n return this.binding.then(() => {\n kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);\n });\n }\n\n unbind(): Promise<void> {\n this.generation++;\n this.bindingActive = false;\n this.pendingPurgeSet = false;\n const controller = this.current;\n this.current = undefined;\n this.primarySpace = undefined;\n this.detachAll();\n const binding = this.binding;\n const close = binding.then(() => controller?.close()).catch(() => undefined);\n this.previous = this.previous.then(() => close);\n return this.previous;\n }\n\n get boundIdentity(): ReplicationIdentity | undefined {\n return this.lastIdentity;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/replication/runtime.ts"],"sourcesContent":["import { pkhDid } from \"@tinycloud/sdk-core\";\nimport type { FetchFunction, ServiceContext, ServiceSession } from \"@tinycloud/sdk-core\";\nimport type {\n KVReadThrough,\n KVReplicaStorage,\n KVReplicationController,\n PendingWriteStore,\n ReplicationAuthority,\n ReplicationControl,\n ReplicationEvent,\n ReplicaSessionStamp,\n ReplicationIdentity,\n ReplicationOptions,\n ReplicaGateReason,\n ReplicationScheduler,\n} from \"@tinycloud/sdk-services\";\n\ntype ReplicationServices = typeof import(\"@tinycloud/sdk-services/kv/replication\");\n\nexport interface ReplicationRuntimeOptions extends ReplicationOptions {\n storage: KVReplicaStorage;\n mode?: \"background\" | \"foreground\";\n}\n\nexport interface ReplicationRuntimeBinding {\n context: ServiceContext;\n session: ServiceSession;\n address?: string;\n chainId?: number;\n principal?: string;\n stamp?: ReplicaSessionStamp;\n authority: ReplicationAuthority;\n nodeFeatures?: readonly string[];\n primaryKV: Array<{ space: string; kv: { setReadThrough(readThrough: KVReadThrough | null): void } }>;\n}\nexport type GatedReplicationRuntimeBinding = Pick<ReplicationRuntimeBinding, \"context\" | \"session\" | \"primaryKV\"> & {\n gate: ReplicaGateReason;\n};\n\nconst scheduler: ReplicationScheduler = {\n now: () => Date.now(),\n setTimeout(fn, ms) {\n const timer = setTimeout(fn, ms);\n timer.unref?.();\n return () => clearTimeout(timer);\n },\n};\n\nfunction resolvedOptions(options: ReplicationRuntimeOptions) {\n return {\n ...options,\n syncIntervalMs: options.syncIntervalMs ?? 60_000,\n maxStalenessMs: options.maxStalenessMs ?? 120_000,\n staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5_000,\n allowSecrets: options.allowSecrets === true,\n verify: options.verify === true,\n };\n}\ntype ReadThroughKV = {\n setReadThrough(readThrough: KVReadThrough | null): void;\n setReadThroughReady?(ready: Promise<unknown>): void;\n};\n\nexport class ReplicationRuntime {\n readonly control: ReplicationControl;\n private readonly pending = new Map<string, { stamp?: ReplicaSessionStamp; store: PendingWriteStore }>();\n private readonly attached = new Map<ReadThroughKV, string>();\n private current?: KVReplicationController;\n private gated?: KVReadThrough;\n private gatedReason?: ReplicaGateReason;\n private lastController?: KVReplicationController;\n private unsubscribeConsentExpired?: () => void;\n private previous: Promise<void> = Promise.resolve();\n private binding: Promise<void> = Promise.resolve();\n private services?: Promise<ReplicationServices>;\n private generation = 0;\n private lastIdentity?: ReplicationIdentity;\n private lastPending?: PendingWriteStore;\n private primarySpace?: string;\n private pendingPurge?: Parameters<KVReplicationController[\"purge\"]>[0];\n private pendingPurgeResult?: ReturnType<KVReplicationController[\"purge\"]>;\n private bindingActive = false;\n private pendingPurgeSet = false;\n private purgedGeneration?: number;\n private closed = false;\n constructor(\n private readonly options: ReplicationRuntimeOptions,\n private readonly clock: ReplicationScheduler = scheduler,\n ) {\n this.control = {\n status: async () => {\n await this.binding;\n if (this.gatedReason) return this.options.prefixes.map((prefix) => ({\n prefix, state: \"gated\" as const, gate: this.gatedReason, errorCode: \"REPLICA_GATED\",\n pending: { inFlight: 0, committed: 0, ambiguous: 0 }, pinned: [], lagMs: null,\n }));\n return this.current ? this.current.status() : [];\n },\n sync: async (input) => {\n await this.binding;\n if (this.gatedReason) throw Object.assign(new Error(\"Replica restore is gated.\"), {\n code: \"REPLICA_GATED\", gate: this.gatedReason,\n });\n if (this.purgedGeneration === this.generation) return;\n if (this.current) await this.current.sync(input);\n },\n purge: (input) => {\n if (this.bindingActive) {\n this.pendingPurge = input;\n this.pendingPurgeSet = true;\n const existing = this.current ?? this.lastController;\n if (existing) {\n this.pendingPurgeResult = existing.purge(input);\n this.purgedGeneration = this.generation;\n }\n return this.binding.then(() => this.pendingPurgeResult ?? { purged: [], failed: [] });\n }\n const controller = this.current ?? this.lastController;\n if (!controller) return Promise.resolve({ purged: [], failed: [] });\n this.purgedGeneration = this.generation;\n return controller.purge(input);\n },\n clearPending: async () => {\n await this.binding;\n if (this.current) return this.current.clearPending();\n const pending = this.lastPending;\n if (!pending) return 0;\n return pending.update((state) => {\n const before = state.records.length;\n state.records = state.records.filter((record) =>\n record.state === \"committed\" ||\n (record.state === \"in_flight\" && this.clock.now() - Date.parse(record.at) <= 10 * 60_000),\n );\n return before - state.records.length;\n });\n },\n close: async () => {\n this.closed = true;\n this.unbind();\n await this.binding.catch(() => undefined);\n await this.previous;\n },\n };\n }\n\n private loadServices(): Promise<ReplicationServices> {\n return (this.services ??= import(\"@tinycloud/sdk-services/kv/replication\"));\n }\n\n private detachAll(): void {\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.attached.clear();\n }\n\n /** Observe a refused restore without opening replica state; loader failure leaves raw network KV usable. */\n bindGatedNetworkOnly(\n input: GatedReplicationRuntimeBinding,\n loadGatedNetwork: () => Promise<typeof import(\"./gated-network\")>,\n ): Promise<void> {\n if (this.closed || !this.options.enabled) return Promise.resolve();\n const retired = this.unbind();\n const generation = this.generation;\n this.purgedGeneration = undefined;\n this.pendingPurge = undefined;\n this.pendingPurgeResult = undefined;\n this.pending.clear();\n this.lastController = undefined;\n this.lastPending = undefined;\n this.lastIdentity = undefined;\n this.gatedReason = input.gate;\n this.primarySpace = input.session.spaceId;\n this.binding = retired.then(async () => {\n let create: typeof import(\"./gated-network\").createGatedNetworkReadThrough;\n try {\n ({ createGatedNetworkReadThrough: create } = await loadGatedNetwork());\n } catch {\n // Refusal already gates local delivery; a missing observer cannot end a network session.\n return;\n }\n if (generation !== this.generation || this.closed || typeof create !== \"function\") return;\n const networkOnly = create({\n space: input.session.spaceId,\n prefixes: this.options.prefixes,\n now: () => this.clock.now(),\n emit: (event) => {\n if (generation !== this.generation || this.closed || this.gated !== networkOnly) return;\n try { this.options.onEvent?.(event); } catch { /* Telemetry cannot change network results. */ }\n const { type, ...data } = event;\n try { input.context.emit(type, data); } catch { /* Telemetry cannot change network results. */ }\n },\n });\n this.gated = networkOnly;\n for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);\n });\n return this.binding;\n }\n\n bind(input: ReplicationRuntimeBinding): Promise<void> {\n if (this.closed || !this.options.enabled) return Promise.resolve();\n this.gated = undefined;\n this.gatedReason = undefined;\n this.bindingActive = true;\n const generation = ++this.generation;\n this.purgedGeneration = undefined;\n this.unsubscribeConsentExpired?.();\n this.unsubscribeConsentExpired = undefined;\n const { context, session } = input;\n const emit = (event: ReplicationEvent): void => {\n try { this.options.onEvent?.(event); } catch { /* Observability must not affect KV behavior. */ }\n const { type, ...data } = event;\n context.emit(type, data);\n };\n const previous = this.current;\n this.current = undefined;\n for (const kv of this.attached.keys()) kv.setReadThrough(null);\n this.primarySpace = session.spaceId;\n const close = previous ? previous.close().catch(() => undefined) : Promise.resolve();\n this.previous = this.previous.then(() => close);\n const previousClose = this.previous;\n this.binding = this.loadServices().then((services) => {\n if (generation !== this.generation || this.closed) return;\n let identity: ReplicationIdentity;\n try {\n identity = services.canonicalReplicationIdentity({\n host: context.hosts[0]!,\n space: session.spaceId,\n principal: input.principal ?? pkhDid(input.address!, input.chainId!),\n });\n } catch {\n for (const prefix of this.options.prefixes) {\n emit({ type: \"replication.state\", at: new Date(this.clock.now()).toISOString(), space: session.spaceId, replica: prefix, state: \"unavailable\", code: \"CONFIG_INVALID\" });\n }\n return;\n }\n const key = services.replicationIdentityKey(identity);\n let cached = this.pending.get(key);\n const stamp = input.stamp;\n if (!cached || cached.stamp?.incarnation !== stamp?.incarnation || cached.stamp?.lifecycleEpoch !== stamp?.lifecycleEpoch) {\n const store = this.options.storage.pendingWrites?.(identity, { stamp });\n if (!store && this.options.storage.lifecycle) {\n throw Object.assign(new Error(\"Gated replication requires a stamped pending store\"), { code: \"REPLICA_GATED\", gate: \"gate_unavailable\" });\n }\n cached = { stamp, store: store ?? services.createMemoryPendingStore(identity) };\n this.pending.set(key, cached);\n }\n const pending = cached.store;\n this.lastPending = pending;\n const controller = services.createKVReplication({\n options: resolvedOptions(this.options),\n mode: this.options.mode ?? \"background\",\n storage: this.options.storage,\n identity,\n session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },\n stamp: input.stamp,\n authority: input.authority,\n pending,\n scheduler: this.clock,\n emit,\n fetch: context.fetch as FetchFunction,\n nodeFeatures: input.nodeFeatures,\n previous: previousClose,\n });\n this.current = controller;\n this.unsubscribeConsentExpired = this.options.storage.lifecycle?.subscribeConsentExpired?.(\n (stamp) => controller.observeConsentExpired(stamp),\n );\n this.lastController = controller;\n this.lastIdentity = identity;\n for (const [kv, space] of this.attached) {\n kv.setReadThrough(space === this.primarySpace ? controller : null);\n }\n for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);\n if (this.pendingPurgeSet) {\n this.pendingPurgeResult = controller.purge(this.pendingPurge);\n this.purgedGeneration = generation;\n this.pendingPurge = undefined;\n this.pendingPurgeSet = false;\n }\n }).finally(() => {\n if (generation === this.generation) this.bindingActive = false;\n });\n for (const kv of this.attached.keys()) kv.setReadThroughReady?.(this.binding);\n return this.binding;\n }\n\n attach(space: string, kv: ReadThroughKV): Promise<void> {\n this.attached.set(kv, space);\n kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);\n const generation = this.generation;\n return this.binding.then(() => {\n if (generation === this.generation && !this.closed && this.attached.get(kv) === space) {\n kv.setReadThrough(space === this.primarySpace ? this.current ?? this.gated ?? null : null);\n }\n });\n }\n\n unbind(): Promise<void> {\n this.generation++;\n this.bindingActive = false;\n this.pendingPurgeSet = false;\n this.unsubscribeConsentExpired?.();\n this.unsubscribeConsentExpired = undefined;\n const controller = this.current;\n this.current = undefined;\n this.gated = undefined;\n this.gatedReason = undefined;\n this.primarySpace = undefined;\n this.detachAll();\n const binding = this.binding;\n const close = binding.then(() => controller?.close()).catch(() => undefined);\n this.previous = this.previous.then(() => close);\n return this.previous;\n }\n\n get boundIdentity(): ReplicationIdentity | undefined {\n return this.lastIdentity;\n }\n}\n"],"mappings":"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","names":["stamp"]}
@@ -1,5 +1,6 @@
1
+ import { _ as __gated_network } from './gated-network-BEF2XSTC.cjs';
1
2
  import { ServiceContext, ServiceSession } from '@tinycloud/sdk-core';
2
- import { ReplicationControl, ReplicationOptions, KVReplicaStorage, ReplicationScheduler, ReplicationAuthority, KVReadThrough, ReplicationIdentity } from '@tinycloud/sdk-services';
3
+ import { ReplicaSessionStamp, ReplicationAuthority, KVReadThrough, ReplicaGateReason, ReplicationControl, ReplicationOptions, KVReplicaStorage, ReplicationScheduler, ReplicationIdentity } from '@tinycloud/sdk-services';
3
4
 
4
5
  interface ReplicationRuntimeOptions extends ReplicationOptions {
5
6
  storage: KVReplicaStorage;
@@ -11,6 +12,7 @@ interface ReplicationRuntimeBinding {
11
12
  address?: string;
12
13
  chainId?: number;
13
14
  principal?: string;
15
+ stamp?: ReplicaSessionStamp;
14
16
  authority: ReplicationAuthority;
15
17
  nodeFeatures?: readonly string[];
16
18
  primaryKV: Array<{
@@ -20,6 +22,9 @@ interface ReplicationRuntimeBinding {
20
22
  };
21
23
  }>;
22
24
  }
25
+ type GatedReplicationRuntimeBinding = Pick<ReplicationRuntimeBinding, "context" | "session" | "primaryKV"> & {
26
+ gate: ReplicaGateReason;
27
+ };
23
28
  type ReadThroughKV = {
24
29
  setReadThrough(readThrough: KVReadThrough | null): void;
25
30
  setReadThroughReady?(ready: Promise<unknown>): void;
@@ -31,7 +36,10 @@ declare class ReplicationRuntime {
31
36
  private readonly pending;
32
37
  private readonly attached;
33
38
  private current?;
39
+ private gated?;
40
+ private gatedReason?;
34
41
  private lastController?;
42
+ private unsubscribeConsentExpired?;
35
43
  private previous;
36
44
  private binding;
37
45
  private services?;
@@ -48,18 +56,21 @@ declare class ReplicationRuntime {
48
56
  constructor(options: ReplicationRuntimeOptions, clock?: ReplicationScheduler);
49
57
  private loadServices;
50
58
  private detachAll;
59
+ /** Observe a refused restore without opening replica state; loader failure leaves raw network KV usable. */
60
+ bindGatedNetworkOnly(input: GatedReplicationRuntimeBinding, loadGatedNetwork: () => Promise<typeof __gated_network>): Promise<void>;
51
61
  bind(input: ReplicationRuntimeBinding): Promise<void>;
52
62
  attach(space: string, kv: ReadThroughKV): Promise<void>;
53
63
  unbind(): Promise<void>;
54
64
  get boundIdentity(): ReplicationIdentity | undefined;
55
65
  }
56
66
 
67
+ type __runtime_GatedReplicationRuntimeBinding = GatedReplicationRuntimeBinding;
57
68
  type __runtime_ReplicationRuntime = ReplicationRuntime;
58
69
  declare const __runtime_ReplicationRuntime: typeof ReplicationRuntime;
59
70
  type __runtime_ReplicationRuntimeBinding = ReplicationRuntimeBinding;
60
71
  type __runtime_ReplicationRuntimeOptions = ReplicationRuntimeOptions;
61
72
  declare namespace __runtime {
62
- export { __runtime_ReplicationRuntime as ReplicationRuntime, type __runtime_ReplicationRuntimeBinding as ReplicationRuntimeBinding, type __runtime_ReplicationRuntimeOptions as ReplicationRuntimeOptions };
73
+ export { type __runtime_GatedReplicationRuntimeBinding as GatedReplicationRuntimeBinding, __runtime_ReplicationRuntime as ReplicationRuntime, type __runtime_ReplicationRuntimeBinding as ReplicationRuntimeBinding, type __runtime_ReplicationRuntimeOptions as ReplicationRuntimeOptions };
63
74
  }
64
75
 
65
- export { ReplicationRuntime as R, __runtime as _, type ReplicationRuntimeBinding as a, type ReplicationRuntimeOptions as b };
76
+ export { type GatedReplicationRuntimeBinding as G, ReplicationRuntime as R, __runtime as _, type ReplicationRuntimeBinding as a, type ReplicationRuntimeOptions as b };
@@ -1,5 +1,6 @@
1
+ import { _ as __gated_network } from './gated-network-BEF2XSTC.js';
1
2
  import { ServiceContext, ServiceSession } from '@tinycloud/sdk-core';
2
- import { ReplicationControl, ReplicationOptions, KVReplicaStorage, ReplicationScheduler, ReplicationAuthority, KVReadThrough, ReplicationIdentity } from '@tinycloud/sdk-services';
3
+ import { ReplicaSessionStamp, ReplicationAuthority, KVReadThrough, ReplicaGateReason, ReplicationControl, ReplicationOptions, KVReplicaStorage, ReplicationScheduler, ReplicationIdentity } from '@tinycloud/sdk-services';
3
4
 
4
5
  interface ReplicationRuntimeOptions extends ReplicationOptions {
5
6
  storage: KVReplicaStorage;
@@ -11,6 +12,7 @@ interface ReplicationRuntimeBinding {
11
12
  address?: string;
12
13
  chainId?: number;
13
14
  principal?: string;
15
+ stamp?: ReplicaSessionStamp;
14
16
  authority: ReplicationAuthority;
15
17
  nodeFeatures?: readonly string[];
16
18
  primaryKV: Array<{
@@ -20,6 +22,9 @@ interface ReplicationRuntimeBinding {
20
22
  };
21
23
  }>;
22
24
  }
25
+ type GatedReplicationRuntimeBinding = Pick<ReplicationRuntimeBinding, "context" | "session" | "primaryKV"> & {
26
+ gate: ReplicaGateReason;
27
+ };
23
28
  type ReadThroughKV = {
24
29
  setReadThrough(readThrough: KVReadThrough | null): void;
25
30
  setReadThroughReady?(ready: Promise<unknown>): void;
@@ -31,7 +36,10 @@ declare class ReplicationRuntime {
31
36
  private readonly pending;
32
37
  private readonly attached;
33
38
  private current?;
39
+ private gated?;
40
+ private gatedReason?;
34
41
  private lastController?;
42
+ private unsubscribeConsentExpired?;
35
43
  private previous;
36
44
  private binding;
37
45
  private services?;
@@ -48,18 +56,21 @@ declare class ReplicationRuntime {
48
56
  constructor(options: ReplicationRuntimeOptions, clock?: ReplicationScheduler);
49
57
  private loadServices;
50
58
  private detachAll;
59
+ /** Observe a refused restore without opening replica state; loader failure leaves raw network KV usable. */
60
+ bindGatedNetworkOnly(input: GatedReplicationRuntimeBinding, loadGatedNetwork: () => Promise<typeof __gated_network>): Promise<void>;
51
61
  bind(input: ReplicationRuntimeBinding): Promise<void>;
52
62
  attach(space: string, kv: ReadThroughKV): Promise<void>;
53
63
  unbind(): Promise<void>;
54
64
  get boundIdentity(): ReplicationIdentity | undefined;
55
65
  }
56
66
 
67
+ type __runtime_GatedReplicationRuntimeBinding = GatedReplicationRuntimeBinding;
57
68
  type __runtime_ReplicationRuntime = ReplicationRuntime;
58
69
  declare const __runtime_ReplicationRuntime: typeof ReplicationRuntime;
59
70
  type __runtime_ReplicationRuntimeBinding = ReplicationRuntimeBinding;
60
71
  type __runtime_ReplicationRuntimeOptions = ReplicationRuntimeOptions;
61
72
  declare namespace __runtime {
62
- export { __runtime_ReplicationRuntime as ReplicationRuntime, type __runtime_ReplicationRuntimeBinding as ReplicationRuntimeBinding, type __runtime_ReplicationRuntimeOptions as ReplicationRuntimeOptions };
73
+ export { type __runtime_GatedReplicationRuntimeBinding as GatedReplicationRuntimeBinding, __runtime_ReplicationRuntime as ReplicationRuntime, type __runtime_ReplicationRuntimeBinding as ReplicationRuntimeBinding, type __runtime_ReplicationRuntimeOptions as ReplicationRuntimeOptions };
63
74
  }
64
75
 
65
- export { ReplicationRuntime as R, __runtime as _, type ReplicationRuntimeBinding as a, type ReplicationRuntimeOptions as b };
76
+ export { type GatedReplicationRuntimeBinding as G, ReplicationRuntime as R, __runtime as _, type ReplicationRuntimeBinding as a, type ReplicationRuntimeOptions as b };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinycloud/node-sdk",
3
- "version": "3.2.6-beta.6",
3
+ "version": "3.3.0-beta.10",
4
4
  "description": "TinyCloud SDK for Node.js with configurable authorization strategies",
5
5
  "author": "TinyCloud, Inc.",
6
6
  "license": "EGPL",
@@ -38,6 +38,16 @@
38
38
  "types": "./dist/replication/authority.d.cts",
39
39
  "default": "./dist/replication/authority.cjs"
40
40
  }
41
+ },
42
+ "./replication/gated-network": {
43
+ "import": {
44
+ "types": "./dist/replication/gated-network.d.ts",
45
+ "default": "./dist/replication/gated-network.js"
46
+ },
47
+ "require": {
48
+ "types": "./dist/replication/gated-network.d.cts",
49
+ "default": "./dist/replication/gated-network.cjs"
50
+ }
41
51
  }
42
52
  },
43
53
  "engines": {
@@ -54,9 +64,9 @@
54
64
  "test:integration": "echo 'Integration tests live in tests/node-sdk workspace'"
55
65
  },
56
66
  "dependencies": {
57
- "@tinycloud/replica": "0.2.4-beta.3",
58
- "@tinycloud/sdk-services": "3.2.6-beta.4",
59
- "@tinycloud/sdk-core": "3.2.6-beta.4",
67
+ "@tinycloud/replica": "0.3.0-beta.4",
68
+ "@tinycloud/sdk-services": "3.3.0-beta.7",
69
+ "@tinycloud/sdk-core": "3.3.0-beta.10",
60
70
  "@tinycloud/node-sdk-wasm": "1.7.7",
61
71
  "siwe": "^3.0.0"
62
72
  },