@fairfox/polly 0.57.0 → 0.59.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.
@@ -23,6 +23,7 @@ import { Repo, type StorageAdapterInterface } from "@automerge/automerge-repo/sl
23
23
  import type { KeyringStorage } from "./keyring-storage";
24
24
  import { type MeshKeyring, MeshNetworkAdapter } from "./mesh-network-adapter";
25
25
  import { MeshSignalingClient, type MeshSignalingClientOptions } from "./mesh-signaling-client";
26
+ import { type MeshStateLoadedRejectionBreadcrumb } from "./mesh-state";
26
27
  import { MeshWebRTCAdapter, type MeshWebRTCAdapterOptions } from "./mesh-webrtc-adapter";
27
28
  /** Options for {@link createMeshClient}. */
28
29
  export interface CreateMeshClientOptions {
@@ -197,6 +198,64 @@ export interface MeshClientHandleSnapshot {
197
198
  * asking. */
198
199
  lastSyncMessageOutType: string | undefined;
199
200
  }
201
+ /** Polly#107 H5 diagnostics: surfaces the `mesh-state` module
202
+ * instance identity so a single snapshot read tells the operator
203
+ * whether the consumer's `$meshState` wrappers are resolving against
204
+ * the same module instance `createMeshClient` configured. A mismatch
205
+ * here IS the bundle-time module duplication bug. */
206
+ export interface MeshStateModuleDiagnostics {
207
+ /** The id stamped at import time on the `mesh-state` module
208
+ * instance THIS mesh client was constructed against. Compare to
209
+ * the id seen at the `$meshState` call site (also importable as
210
+ * `MESH_STATE_MODULE_ID` from `@fairfox/polly/mesh`). Two different
211
+ * ids means the consumer is reaching a duplicated copy of
212
+ * `mesh-state.ts` — wrappers register handles against a Repo this
213
+ * mesh client never saw. */
214
+ moduleId: string;
215
+ /** `true` iff THIS module instance has a configured `defaultRepo`.
216
+ * In the H5 scenario, this is `true` for the mesh-client-side
217
+ * snapshot (because `createMeshClient` always calls
218
+ * `configureMeshState` on the module it imports), but the same
219
+ * field read from the consumer's `$meshState` call site would
220
+ * be `false`. */
221
+ configured: boolean;
222
+ /** `peerId` of the most recent Repo wired through
223
+ * `configureMeshState` against THIS module instance. Compared to
224
+ * `client.repo.peerId` and the consumer's wrappers' resolved Repo
225
+ * tells the full story. */
226
+ lastConfiguredRepoPeerId: string | undefined;
227
+ /** `true` if any `$meshState`-family wrapper has been called
228
+ * against THIS module instance at any point. The polly#107 H5
229
+ * fingerprint pair: `configured: true, wasResolved: false` on
230
+ * the mesh-client-side snapshot (the wrappers never reached us);
231
+ * the consumer's wrapper code reading the same field from THEIR
232
+ * `$meshState` import would see `configured: false, wasResolved:
233
+ * true` (they're using a module instance no mesh client ever
234
+ * configured). */
235
+ wasResolved: boolean;
236
+ /** Polly#107 post-H5 instrumentation. Count of `$meshState`-family
237
+ * lazy factory invocations since module load. Once the consumer's
238
+ * pre-warm pass completes, this equals the number of distinct
239
+ * `$mesh*` wrappers whose handles the loader actually tried to
240
+ * resolve. Compared to {@link lazyReachedRepo}: gap = throws
241
+ * before any Repo work. */
242
+ lazyInvocations: number;
243
+ /** Polly#107 post-H5 instrumentation. Count of factory invocations
244
+ * that reached the Repo subsystem (`repo.handles[...]`, `repo.find`
245
+ * or `repo.import`) before returning or throwing. If
246
+ * {@link lazyInvocations} is N and this is N, every wrapper
247
+ * touched the Repo — any missing handles are downstream of Repo
248
+ * registration. If this is < N, the gap is the call site to
249
+ * instrument next. */
250
+ lazyReachedRepo: number;
251
+ /** Polly#107 post-H5 instrumentation. Breadcrumb for the most
252
+ * recent rejection from a `$meshState`-family `loaded` promise
253
+ * (or its factory). Captured even when the consumer's wrapper
254
+ * never awaits `loaded` and the rejection would otherwise vanish
255
+ * silently. `undefined` means no rejection has escaped any
256
+ * wrapper on THIS module instance since module load. */
257
+ lastLoadedRejection: MeshStateLoadedRejectionBreadcrumb | undefined;
258
+ }
200
259
  /** The mesh client's enriched per-peer state snapshot. Mirrors the
201
260
  * underlying {@link MeshWebRTCAdapter.getPeerStateSnapshot} shape but
202
261
  * replaces the slot's `handles` map with the Repo-enriched view
@@ -211,6 +270,19 @@ export interface MeshClientPeerStateSnapshot {
211
270
  handles: Record<string, MeshClientHandleSnapshot>;
212
271
  });
213
272
  }>;
273
+ /** polly#107 H5 diagnostics. See {@link MeshStateModuleDiagnostics}. */
274
+ meshStateModule: MeshStateModuleDiagnostics;
275
+ /** Count of handles known to the Repo this client owns. Compared
276
+ * to the count the consumer's `$meshState` wrappers should have
277
+ * registered: a mismatch (e.g. consumer pre-warmed 14 wrappers,
278
+ * snapshot reports 1) confirms the H5 module-duplication
279
+ * fingerprint without needing a separate read. */
280
+ repoHandleCount: number;
281
+ /** All handle ids the Repo this client owns currently caches.
282
+ * Together with `repoHandleCount` and `meshStateModule.moduleId`,
283
+ * this answers "did the consumer's wrappers land their handles in
284
+ * THIS Repo?" in one read. */
285
+ repoHandleIds: string[];
214
286
  }
215
287
  /** Handle returned by {@link createMeshClient}. */
216
288
  export interface MeshClient {
@@ -56,6 +56,41 @@ export interface MeshStateOptions {
56
56
  * a public-key set used by the signing layer to verify incoming ops. */
57
57
  access?: Access;
58
58
  }
59
+ /** Per-module-instance identifier stamped at import time. Two distinct
60
+ * module instances (e.g. duplicated under a bundler that hoists
61
+ * differently for the consumer's app code and `@fairfox/polly`'s own
62
+ * imports) produce two different ids — and therefore two different
63
+ * `defaultRepo` globals.
64
+ *
65
+ * The polly#107 fingerprint reading is exactly that: `configureMeshState`
66
+ * runs on one module instance (the one `createMeshClient` imports);
67
+ * the consumer's `$meshState(key, initial)` calls run on a different
68
+ * instance, whose `defaultRepo` was never set. `resolveRepo` either
69
+ * throws (caught silently by consumer wrappers) or returns the
70
+ * unconfigured fallback — either way, the handle that should land
71
+ * in the mesh `Repo` lands somewhere else (or nowhere), Automerge's
72
+ * NetworkSubsystem never sees it, and inbound sync from the daemon
73
+ * has nowhere to go.
74
+ *
75
+ * Exported so consumers can compare the id seen at the `$meshState`
76
+ * call site against the id seen at the `configureMeshState` call
77
+ * site. A mismatch IS the bundle-duplication bug; a match rules it
78
+ * out. The id is also surfaced via {@link getMeshStateModuleId} on
79
+ * every `MeshClient.getPeerStateSnapshot()` so a single one-line
80
+ * read from the failing tab tells the operator which module
81
+ * instance the snapshot is rendered from.
82
+ *
83
+ * Polly issue #107 H5 (mesh-state module duplication). */
84
+ export declare const MESH_STATE_MODULE_ID: string;
85
+ /** Returns the per-module-instance id stamped at import time. See
86
+ * {@link MESH_STATE_MODULE_ID}. */
87
+ export declare function getMeshStateModuleId(): string;
88
+ /** Returns the `peerId` of the most recent Repo wired through
89
+ * {@link configureMeshState} against this module instance. Surfaced
90
+ * via the mesh client snapshot so a consumer can answer "did
91
+ * configureMeshState run on the same module instance the wrappers
92
+ * are using" in one read. */
93
+ export declare function getLastConfiguredRepoPeerId(): string | undefined;
59
94
  /**
60
95
  * Set the default Repo that the $mesh* primitives use when no `repo` option
61
96
  * is supplied. Production code typically calls this once at application
@@ -68,6 +103,43 @@ export declare function configureMeshState(repo: Repo): void;
68
103
  * Intended for tests; production code should not call this.
69
104
  */
70
105
  export declare function resetMeshState(): void;
106
+ /** Returns whether this module instance's `defaultRepo` was ever
107
+ * set via {@link configureMeshState}. Distinct from "was set and
108
+ * then reset" — `resetMeshState` clears it. The polly#107 H5
109
+ * fingerprint is `false` here on the module instance the consumer's
110
+ * `$meshState` calls resolve from. */
111
+ export declare function isMeshStateConfigured(): boolean;
112
+ /** Returns `true` once any `$meshState`-family wrapper has been
113
+ * called against this module instance. See
114
+ * {@link meshStateEverResolved}. */
115
+ export declare function wasMeshStateResolved(): boolean;
116
+ /** Returns the count of factory invocations since module load. See
117
+ * {@link lazyInvocations}. */
118
+ export declare function getLazyInvocations(): number;
119
+ /** Returns the count of factory invocations that reached
120
+ * `repo.find` / `repo.import` since module load. See
121
+ * {@link lazyReachedRepo}. */
122
+ export declare function getLazyReachedRepo(): number;
123
+ /** Polly#107 post-H5 instrumentation. Records the most recent
124
+ * rejection (or synchronous throw) escaping the factory body — the
125
+ * `loaded` promise's rejection path. Today an unawaited rejection
126
+ * inside a consumer wrapper vanishes without trace; capturing the
127
+ * message + stack here on the module and exposing it via the
128
+ * snapshot leaves a breadcrumb the operator can read in one paste.
129
+ *
130
+ * The format is the JSON-safe `{ name, message, stack, at }` shape
131
+ * so the snapshot read does not have to traffic in `Error`
132
+ * instances. `at` is `Date.now()` at the time the rejection was
133
+ * captured. */
134
+ export interface MeshStateLoadedRejectionBreadcrumb {
135
+ name: string;
136
+ message: string;
137
+ stack: string | undefined;
138
+ at: number;
139
+ }
140
+ /** Returns the most recent rejection escaping a `$meshState` factory
141
+ * invocation since module load. See {@link lastLoadedRejection}. */
142
+ export declare function getLastLoadedRejection(): MeshStateLoadedRejectionBreadcrumb | undefined;
71
143
  /**
72
144
  * Create a peer-replicated state primitive backed by Automerge with a mesh
73
145
  * transport. Every device holds a full replica; no central server holds a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fairfox/polly",
3
- "version": "0.57.0",
3
+ "version": "0.59.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Multi-execution-context framework with reactive state and cross-context messaging for Chrome extensions, PWAs, and worker-based applications",