@capxul/sdk 4.20.0-beta.3 → 4.20.0-beta.31

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.
@@ -0,0 +1,4882 @@
1
+ import { c as deserializeCapxulError, n as makeOrgMethods, o as ConvexOrganizationAdapter, s as OrgPortTag, t as ConvexOrganizationSetupAdapter } from "./organizations-wucUfgJr.mjs";
2
+ import { $r as ActorFailure, $t as version, A as registerProductTelemetryInvocation, Ai as toEpochMs, An as details, Bi as isFailureMode, Br as runIdentityFacade, Bt as failureEvidenceProps, C as AccountReadPortTag, Ci as detectAuthCacheAdapter, Cn as CLI_INVOCATION_KINDS, D as convexCallErrorFromCapxul, Dn as isCapxulOperation, E as ConvexCallPortTag, En as CAPXUL_OPERATIONS, Gt as observationContextProps, H as makePaymentsMethods, Hi as readField, Hr as sessionFromActor, Ht as postHogFailureObservation, Ii as Errors, It as callOperation, J as makeTargetsMethods, Jn as ORGANIZATION_SETUP_REASONS, K as makeCheckoutMethods, Kt as withCapturedAttemptContext, L as makeAccountMethods, Li as CapxulError, Lr as makeAuthMethods, Lt as asCapxulError, M as makeApprovalsMethods, Mi as toAuthUserId, Mr as toAddress, Ni as toEmail, Nt as BASE_SEPOLIA_CHAIN_ID, On as normalizeCapxulOperation, Ri as isCapxulError, Rr as askIdentity, Rt as toPublicError, Si as AuthClientPortTag, Sn as CLI_COMMAND_NAMES, T as TelemetryPortTag, Tn as stampTelemetryEnvelope, Tr as ChainIdSchema, Ut as observeFailedResult, V as makeOfframpMethods, Vi as isRecord, Vr as runPortEffect, W as makeDestinationsMethods, Wi as AuthCachePortTag, Wn as setups, Wr as runCapxulEffect, Wt as observeSdkClient, Xr as formatTraceparent, Yn as ORGANIZATION_SETUP_STATUSES, Yr as toCapxulResult, Zn as decodeChainCause, Zr as CAPXUL_ERROR_CODES, _i as toAllowedOrigin, ai as ClockPortTag, an as isSignerNotReadySignal, bn as redactTelemetryProps, br as EpochMsSchema, c as makePaymentDocumentsMethods, ci as BetterAuthBrowserLayer, cn as signerFailure, cr as AppIdSchema, d as SmartAccountPortTag, di as copyInvocationObservation, dn as makeClientRequestMethods, ei as carryInvocationParentSpan, fi as readInvocationObservation, fn as toCountryCode, fr as isBytes32, g as ConvexAccountLayer, gi as isCredentialField, gn as toHandle, h as makeAccountsMethods, hi as redactUrlSecrets, in as isEcdsaSignatureHex, jn as onboardings, jr as isEvmAddress, k as bindProductTelemetryInvocation, kn as OPERATIONS, kt as normalizeSafeSaltEmail, l as makeSmartAccountMethods, li as observationRequestHeaders, lr as images, m as PonderIndexerAdapter, mi as redactSecrets, mn as toTesterKind, n as bootIdentityFlow, nn as causeChain, on as openfortSignerNotReadyCode, p as makeHoldingsMethods, pi as sanitizeObservationContext, pn as toKycTier, r as makeMeMethods, ri as SystemClockLayer, s as makeActivityMethods, si as BetterAuthNodeLayer, sn as recoverRawDigestSigner, sr as diagnostics, t as executeIdentityProductObservation, tn as UNOBSERVABLE_SIGNER_STATUS, u as ConvexSmartAccountLayer, ui as attachInvocationObservation, un as makeCurrentUserMethods, vn as validateHandle, wi as globalLocalStorage, xi as AuthClientError, xn as CLI_CAPABILITIES, yi as toPublishableKey, yr as DurationMsSchema, z as logDroppedEvent, zi as FAILURE_MODES, zr as publicIdentityFailure, zt as failureDetail } from "./access-B-2qmxF_.mjs";
3
+ import { Cause, Clock, Context, Data, Deferred, Duration, Effect, Exit, FiberSet, Layer, Logger, Metric, Option, Predicate, References, Result, Schedule, Schema, SchemaIssue, SchemaParser, Scope, Stream, Tracer } from "effect";
4
+ import { getFunctionName } from "convex/server";
5
+ import { keccak256, stringToHex } from "viem";
6
+ import { privateKeyToAccount } from "viem/accounts";
7
+ import { AccountTypeEnum, ChainTypeEnum, EmbeddedState, Openfort, RecoveryMethod, ThirdPartyOAuthProvider } from "@openfort/openfort-js";
8
+ import { ConvexClient, ConvexHttpClient } from "convex/browser";
9
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/http";
10
+ import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/observability";
11
+ //#region ../observability/src/browser/network.ts
12
+ /** In unstructured bodies, preserve text around explicitly named credentials. */
13
+ const NETWORK_FIELD = /((?:"?)([A-Za-z_][A-Za-z0-9_.-]*)(?:"?)\s*[:=]\s*)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s,;&}]+)/gu;
14
+ function maskNetworkText(text, context) {
15
+ return redactSecrets(text).replace(NETWORK_FIELD, (whole, prefix, name, value) => {
16
+ if (!isCredentialField(name, context)) return whole;
17
+ const quote = value.startsWith("\"") ? "\"" : value.startsWith("'") ? "'" : "";
18
+ return `${prefix}${quote}[REDACTED]${quote}`;
19
+ });
20
+ }
21
+ function maskNetworkHeaders(headers) {
22
+ if (headers === void 0) return void 0;
23
+ const masked = {};
24
+ for (const [name, value] of Object.entries(headers)) masked[name] = isCredentialField(name, "header") ? "[REDACTED]" : /^(?:location|content-location|referer)$/iu.test(name) ? redactUrlSecrets(value) : maskNetworkText(value);
25
+ return masked;
26
+ }
27
+ function maskJsonValue(value, depth = 0, context) {
28
+ if (typeof value === "function" || typeof value === "symbol") return "[UNREADABLE]";
29
+ if (typeof value === "string") return maskNetworkText(value, context);
30
+ if (typeof value !== "object" || value === null) return value;
31
+ if (depth >= 16) return "[REDACTED]";
32
+ if (Array.isArray(value)) return value.map((item) => maskJsonValue(item, depth + 1, context));
33
+ const masked = Object.create(null);
34
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
35
+ if (!descriptor.enumerable) continue;
36
+ masked[key] = isCredentialField(key, context) ? "[REDACTED]" : !("value" in descriptor) ? "[UNREADABLE]" : typeof descriptor.value === "string" && isPostHogUrlProperty(key) ? redactUrlSecrets(descriptor.value) : maskJsonValue(descriptor.value, depth + 1, context);
37
+ }
38
+ return masked;
39
+ }
40
+ function maskNetworkBody(body, context) {
41
+ try {
42
+ if (typeof body !== "string") return maskJsonValue(body, 0, context);
43
+ let parsed;
44
+ try {
45
+ parsed = JSON.parse(body);
46
+ } catch {
47
+ if (/^[^=&\s]+=[\s\S]*$/u.test(body)) return new URLSearchParams(Array.from(new URLSearchParams(body), ([name, value]) => [name, isCredentialField(name, context) ? "[REDACTED]" : maskNetworkText(value)])).toString();
48
+ return maskNetworkText(body, context);
49
+ }
50
+ return JSON.stringify(maskJsonValue(parsed, 0, context));
51
+ } catch {
52
+ return "[UNREADABLE]";
53
+ }
54
+ }
55
+ /** Keep request evidence while masking credential values at the browser boundary. */
56
+ function maskCapturedNetworkRequest(request) {
57
+ const requestUrl = request.name ?? request.url ?? "";
58
+ let context;
59
+ try {
60
+ const path = new URL(requestUrl, "https://capxul.invalid").pathname;
61
+ if (/\/(?:auth|oauth|sign-in|signin|login|verify-otp|verify-email)(?:\/|$)/iu.test(path)) context = "authentication";
62
+ } catch {}
63
+ return {
64
+ ...request,
65
+ ...request.name === void 0 ? {} : { name: redactUrlSecrets(request.name) },
66
+ ...request.url === void 0 ? {} : { url: redactUrlSecrets(request.url) },
67
+ requestHeaders: maskNetworkHeaders(request.requestHeaders),
68
+ responseHeaders: maskNetworkHeaders(request.responseHeaders),
69
+ requestBody: maskNetworkBody(request.requestBody, context),
70
+ responseBody: maskNetworkBody(request.responseBody)
71
+ };
72
+ }
73
+ function isPostHogUrlProperty(key) {
74
+ const normalized = key.toLowerCase();
75
+ return normalized.includes("url") || normalized.includes("referrer") || normalized.includes("path") || normalized.includes("filename");
76
+ }
77
+ //#endregion
78
+ //#region src/signing/embedded-wallet.ts
79
+ function openfortEmbeddedWalletPort(input) {
80
+ return {
81
+ async getAddress() {
82
+ if (input.ensureReady !== void 0) await input.ensureReady();
83
+ return (await input.embeddedWallet.get()).address;
84
+ },
85
+ async signRawDigest(hash) {
86
+ if (input.ensureReady !== void 0) await input.ensureReady();
87
+ const signature = await input.embeddedWallet.signMessage(hash, {
88
+ hashMessage: false,
89
+ arrayifyMessage: false
90
+ });
91
+ if (!isEcdsaSignatureHex(signature)) throw new Error("embeddedSigner: embedded wallet returned invalid signature format");
92
+ return signature;
93
+ }
94
+ };
95
+ }
96
+ //#endregion
97
+ //#region src/signing/openfort-embedded.ts
98
+ /** Browser helper: wrap an initialized Openfort `embeddedWallet` API. */
99
+ function openfortEmbeddedSignerFromWallet(input) {
100
+ return embeddedSigner({ wallet: openfortEmbeddedWalletPort({
101
+ embeddedWallet: input.embeddedWallet,
102
+ ...input.ensureWalletReady === void 0 ? {} : { ensureReady: input.ensureWalletReady }
103
+ }) });
104
+ }
105
+ /**
106
+ * The embedded-wallet (passkey / Openfort) member of the named `CapxulSigner`
107
+ * constructor trio `localPrivateKeySigner` / `injectedWalletSigner` /
108
+ * `embeddedSigner` (backend-orchestrated-deploy.md). Takes the
109
+ * provider-agnostic `OpenfortEmbeddedWalletPort` (getAddress + signRawDigest);
110
+ * the Openfort-API convenience wrapper is `openfortEmbeddedSignerFromWallet`.
111
+ */
112
+ function embeddedSigner(input) {
113
+ let cachedAddress = null;
114
+ let addressInFlight = null;
115
+ let cacheEpoch = 0;
116
+ const resetAddressCache = () => {
117
+ cacheEpoch += 1;
118
+ cachedAddress = null;
119
+ addressInFlight = null;
120
+ };
121
+ const resolveAddress = async () => {
122
+ if (cachedAddress !== null) return cachedAddress;
123
+ if (addressInFlight !== null) return addressInFlight;
124
+ const epoch = cacheEpoch;
125
+ addressInFlight = (async () => {
126
+ try {
127
+ const raw = await input.wallet.getAddress();
128
+ if (!isEvmAddress(raw)) throw new Error("embeddedSigner: embedded wallet returned invalid address format");
129
+ const address = toAddress(raw);
130
+ if (epoch === cacheEpoch) cachedAddress = address;
131
+ return address;
132
+ } finally {
133
+ if (epoch === cacheEpoch) addressInFlight = null;
134
+ }
135
+ })();
136
+ return addressInFlight;
137
+ };
138
+ return {
139
+ source: "openfort-embedded",
140
+ getAddress: resolveAddress,
141
+ resetAddressCache,
142
+ async signUserOpHash(hash) {
143
+ if (!isBytes32(hash)) throw new Error("embeddedSigner: SafeOp digest must be a 0x-prefixed 32-byte hex");
144
+ const address = await resolveAddress();
145
+ let signature;
146
+ try {
147
+ signature = await input.wallet.signRawDigest(hash);
148
+ } catch (cause) {
149
+ const detail = cause instanceof Error ? cause.message : String(cause);
150
+ throw new Error(`embeddedSigner: raw digest signing failed; ensure the embedded wallet is configured (${detail})`, { cause });
151
+ }
152
+ if (!isEcdsaSignatureHex(signature)) throw new Error("embeddedSigner: embedded wallet returned invalid signature format");
153
+ if ((await recoverRawDigestSigner("embeddedSigner", {
154
+ hash,
155
+ signature
156
+ })).toLowerCase() !== address.toLowerCase()) throw new Error("embeddedSigner: signature did not recover the embedded wallet address for the raw SafeOp digest");
157
+ return signature;
158
+ }
159
+ };
160
+ }
161
+ //#endregion
162
+ //#region src/signing/openfort-failures.ts
163
+ function httpStatusOf(link) {
164
+ for (const status of [
165
+ readField(link.response, "status"),
166
+ link.statusCode,
167
+ link.status,
168
+ readField(link.details, "httpStatus")
169
+ ]) if (typeof status === "number") return status;
170
+ }
171
+ function isUnauthorizedCause(cause) {
172
+ for (const link of causeChain(cause)) if (httpStatusOf(link) === 401) return true;
173
+ return false;
174
+ }
175
+ function openfortProviderError(operation, cause) {
176
+ if (cause instanceof CapxulError) return cause;
177
+ const failure_mode = operation === "configure" && isUnauthorizedCause(cause) ? "unauthorized" : "unknown";
178
+ return Errors.providerError("openfort", operation, cause, { failure_mode });
179
+ }
180
+ function openfortFailureMode(error) {
181
+ return error.mode ?? "unknown";
182
+ }
183
+ /** Build a named PROVIDER_ERROR. The assembled Core SDK boundary reports it. */
184
+ function failOpenfort(operation, failure_mode, cause) {
185
+ return Errors.providerError("openfort", operation, cause, { failure_mode });
186
+ }
187
+ /**
188
+ * True when the browser cannot perform Web Crypto — sandboxed iframes, headless
189
+ * agent browsers, or non-HTTPS origins. OpenFort's embedded-wallet `configure`
190
+ * silently produces no address in this state, so we detect it up front and name
191
+ * it `no-secure-context` instead of letting it decay into `unknown`.
192
+ */
193
+ function isInsecureBrowserContext() {
194
+ return readField(globalThis, "isSecureContext") === false || globalThis.crypto?.subtle === void 0;
195
+ }
196
+ //#endregion
197
+ //#region src/signing/openfort-credentials.ts
198
+ const sinceCycleStart = (clock) => clock.cycleStartedAt === null ? void 0 : Date.now() - clock.cycleStartedAt;
199
+ /**
200
+ * The auth server's token reply: `token` from the OIDC endpoint, `session.token`
201
+ * from better-auth. A reply of any other shape fails the token read.
202
+ */
203
+ const AccessTokenBody = Schema.Struct({
204
+ token: Schema.optional(Schema.NullOr(Schema.String)),
205
+ session: Schema.optional(Schema.NullOr(Schema.Struct({ token: Schema.optional(Schema.NullOr(Schema.String)) })))
206
+ });
207
+ /**
208
+ * The two credentials Openfort needs from the signed-in session: the access
209
+ * token it verifies, and an encryption session for automatic recovery. A host
210
+ * that owns login passes `auth`; otherwise both come from the auth server.
211
+ */
212
+ function makeOpenfortCredentials(input) {
213
+ const { authProvider, authBaseUrl, auth, diagnostic, clock } = input;
214
+ function accessTokenUrl() {
215
+ return authProvider === "oidc" ? `${authBaseUrl}/convex/token` : `${authBaseUrl}/get-session`;
216
+ }
217
+ function encryptionSessionUrl() {
218
+ return `${authBaseUrl}/encryption-session`;
219
+ }
220
+ async function fetchBetterAuthAccessToken() {
221
+ clock.tokenCalls += 1;
222
+ const tokenStartedAt = Date.now();
223
+ try {
224
+ if (auth !== void 0) {
225
+ const token = (await auth.getAccessToken())?.trim() ?? "";
226
+ diagnostic?.trace("openfort.token", {
227
+ ok: token.length > 0,
228
+ tokenPresent: token.length > 0,
229
+ provider: authProvider,
230
+ call: clock.tokenCalls,
231
+ duration_ms: Date.now() - tokenStartedAt,
232
+ elapsed_ms: sinceCycleStart(clock)
233
+ });
234
+ return token.length === 0 ? null : token;
235
+ }
236
+ const response = await fetch(accessTokenUrl(), {
237
+ credentials: "include",
238
+ cache: "no-store"
239
+ });
240
+ if (!response.ok) {
241
+ diagnostic?.trace("openfort.token", {
242
+ ok: false,
243
+ tokenPresent: false,
244
+ httpStatus: response.status,
245
+ failure_mode: "unknown"
246
+ });
247
+ return null;
248
+ }
249
+ const body = Schema.decodeUnknownSync(AccessTokenBody)(await response.json());
250
+ const token = (authProvider === "oidc" ? body.token : body.session?.token)?.trim();
251
+ if (token === void 0 || token.length === 0) {
252
+ diagnostic?.trace("openfort.token", {
253
+ ok: false,
254
+ tokenPresent: false,
255
+ failure_mode: "unknown"
256
+ });
257
+ return null;
258
+ }
259
+ diagnostic?.trace("openfort.token", {
260
+ ok: true,
261
+ tokenPresent: true,
262
+ provider: authProvider,
263
+ call: clock.tokenCalls,
264
+ duration_ms: Date.now() - tokenStartedAt,
265
+ elapsed_ms: sinceCycleStart(clock)
266
+ });
267
+ return token;
268
+ } catch (cause) {
269
+ diagnostic?.trace("openfort.token", {
270
+ ok: false,
271
+ tokenPresent: false,
272
+ failure_mode: "unknown"
273
+ });
274
+ throw openfortProviderError("token", cause);
275
+ }
276
+ }
277
+ async function createEncryptionSession(accessToken) {
278
+ if (auth !== void 0) {
279
+ const sessionId = (await auth.createEncryptionSession(accessToken)).trim();
280
+ if (sessionId.length === 0) throw Errors.providerError("openfort", "encryptionSession", /* @__PURE__ */ new Error("Openfort encryption session response missing sessionId"), { failure_mode: "unknown" });
281
+ return { sessionId };
282
+ }
283
+ const response = await fetch(encryptionSessionUrl(), {
284
+ method: "POST",
285
+ credentials: "include",
286
+ headers: {
287
+ Authorization: `Bearer ${accessToken}`,
288
+ "Content-Type": "application/json"
289
+ },
290
+ body: JSON.stringify({})
291
+ });
292
+ if (!response.ok) throw Errors.providerError("openfort", "encryptionSession", /* @__PURE__ */ new Error(`Openfort encryption session failed (${response.status})`), {
293
+ httpStatus: response.status,
294
+ failure_mode: "unknown"
295
+ });
296
+ const sessionId = readField(await response.json(), "sessionId");
297
+ if (typeof sessionId !== "string" || sessionId.length === 0) throw Errors.providerError("openfort", "encryptionSession", /* @__PURE__ */ new Error("Openfort encryption session response missing sessionId"), {
298
+ httpStatus: response.status,
299
+ failure_mode: "unknown"
300
+ });
301
+ return {
302
+ sessionId,
303
+ httpStatus: response.status
304
+ };
305
+ }
306
+ return {
307
+ fetchBetterAuthAccessToken,
308
+ createEncryptionSession
309
+ };
310
+ }
311
+ //#endregion
312
+ //#region src/signing/openfort-storage.ts
313
+ /** Openfort SDK storage keys (`@openfort/openfort-js` StorageKeys). */
314
+ const OPENFORT_BROWSER_STORAGE_KEYS = [
315
+ "openfort.authentication",
316
+ "openfort.account",
317
+ "openfort.session",
318
+ "openfort.configuration"
319
+ ];
320
+ /**
321
+ * Matches `@openfort/openfort-js` ScopedStorage.createScope — chars 8–15 of the
322
+ * publishable key, prefixed onto each StorageKeys entry in localStorage.
323
+ */
324
+ function openfortBrowserStorageScope(publishableKey) {
325
+ const trimmed = publishableKey.trim();
326
+ if (trimmed.length < 16) return;
327
+ return trimmed.substring(8, 16);
328
+ }
329
+ /**
330
+ * Drop cached Openfort auth/account state so third-party login re-runs for the
331
+ * current Better Auth session. The SDK skips `authenticateThirdParty` when a
332
+ * stale `userId` is already in storage, which yields 401 on `v2/accounts`.
333
+ */
334
+ function clearStaleOpenfortBrowserStorage(publishableKey) {
335
+ if (typeof localStorage === "undefined") return;
336
+ const scope = openfortBrowserStorageScope(publishableKey);
337
+ if (scope === void 0) return;
338
+ for (const key of OPENFORT_BROWSER_STORAGE_KEYS) localStorage.removeItem(`${scope}.${key}`);
339
+ }
340
+ /**
341
+ * The Openfort error code that names the stale-user class (#1435).
342
+ * `getThirdPartyAuthToken` skips `authenticateThirdParty` while a `userId` sits
343
+ * in scoped storage. A purged `userId` therefore pins every later call to a 401
344
+ * that `extractApiError` reports as `USER_NOT_FOUND`.
345
+ *
346
+ * The set holds one member on purpose. Session-expiry codes (`SESSION_EXPIRED`,
347
+ * `NOT_LOGGED_IN`, `INVALID_TOKEN`, `REFRESH_TOKEN_ERROR`) are a different
348
+ * cause, and a bare 401 is a different cause again: `app-env-allowlist` is a
349
+ * 401 by definition (`packages/errors/src/errors.ts:80-81`). Healing those and
350
+ * tagging them `stale-openfort-cache` would delete the triage signal the tag
351
+ * exists to carry.
352
+ */
353
+ const STALE_USER_ERROR_CODES = /* @__PURE__ */ new Set(["USER_NOT_FOUND"]);
354
+ /**
355
+ * The one predicate that opens the heal. It reads the Openfort error code and
356
+ * never the message text (ADR-0023 R4). It walks the cause chain the way
357
+ * `isTransportError` walks it in the Convex transport adapter.
358
+ *
359
+ * Known ceiling (#1435): a 401 that carries no recognized code is NOT healed.
360
+ * `extractApiError` keeps the status only on `AuthenticationError`, so such a
361
+ * payload is reachable. Add the code a live payload shows; do not add a message
362
+ * match, because that trades one bug class for an ADR-0023 R4 violation.
363
+ */
364
+ function isStaleUserSignal(cause) {
365
+ for (const link of causeChain(cause)) if (typeof link.error === "string" && STALE_USER_ERROR_CODES.has(link.error) || typeof link.code === "string" && STALE_USER_ERROR_CODES.has(link.code)) return true;
366
+ return false;
367
+ }
368
+ //#endregion
369
+ //#region src/signing/openfort-readiness.ts
370
+ /**
371
+ * One wallet readiness cycle: initialize, read the access token, open an
372
+ * encryption session, configure the embedded wallet when it is not ready, and
373
+ * read it back, healing a stale cached Openfort user once.
374
+ */
375
+ function makeWalletReadyCycle(input) {
376
+ const { openfort, publishableKey, diagnostic, credentials, clock } = input;
377
+ const step = (name, extra = {}) => {
378
+ const now = Date.now();
379
+ diagnostic?.trace("openfort.step", {
380
+ step: name,
381
+ duration_ms: now - clock.stepStartedAt,
382
+ elapsed_ms: sinceCycleStart(clock),
383
+ ...extra
384
+ });
385
+ clock.stepStartedAt = now;
386
+ };
387
+ /**
388
+ * Closes the black hole: when the browser has no Web Crypto, OpenFort's
389
+ * `configure` would resolve to no address and the failure would be reported
390
+ * as `unknown`. Detect it before any network or wallet work. Tag it
391
+ * `no-secure-context` on a PROVIDER_ERROR scoped to `configure`. Add a
392
+ * DiagnosticPort breadcrumb. The assembled Core SDK boundary reports it.
393
+ */
394
+ function failNoSecureContext() {
395
+ diagnostic?.trace("openfort.configure", {
396
+ ok: false,
397
+ failure_mode: "no-secure-context"
398
+ });
399
+ throw failOpenfort("configure", "no-secure-context", /* @__PURE__ */ new Error("Web Crypto unavailable: browser is not a secure context"));
400
+ }
401
+ async function configureEmbeddedWallet(encryptionSession) {
402
+ await openfort.embeddedWallet.configure({
403
+ accountType: AccountTypeEnum.EOA,
404
+ chainType: ChainTypeEnum.EVM,
405
+ recoveryParams: {
406
+ recoveryMethod: RecoveryMethod.AUTOMATIC,
407
+ encryptionSession
408
+ }
409
+ });
410
+ }
411
+ /**
412
+ * ONE heal cycle on the stale-user rejection (#1435). Clear the scoped
413
+ * storage that pins the dead `userId`, run the existing configure path — it
414
+ * re-runs third-party auth against the live Better Auth session now that no
415
+ * `userId` is cached — and retry the read once. Exactly one cycle: a second
416
+ * rejection is terminal and carries `failure_mode: "stale-openfort-cache"`.
417
+ * The caller stays in `recovering` throughout; only the outcome moves it.
418
+ */
419
+ async function healStaleOpenfortCache(encryptionSession) {
420
+ try {
421
+ clearStaleOpenfortBrowserStorage(publishableKey);
422
+ diagnostic?.trace("openfort.storageCleared", { beforeConfigure: false });
423
+ await configureEmbeddedWallet(encryptionSession);
424
+ await openfort.embeddedWallet.get();
425
+ } catch (cause) {
426
+ diagnostic?.trace("openfort.staleCacheHeal", {
427
+ ok: false,
428
+ failure_mode: "stale-openfort-cache"
429
+ });
430
+ throw failOpenfort("get", "stale-openfort-cache", cause);
431
+ }
432
+ diagnostic?.trace("openfort.staleCacheHeal", { ok: true });
433
+ }
434
+ return function startWalletReady() {
435
+ return (async () => {
436
+ const startedAt = Date.now();
437
+ clock.cycleStartedAt = startedAt;
438
+ clock.stepStartedAt = startedAt;
439
+ clock.tokenCalls = 0;
440
+ if (isInsecureBrowserContext()) failNoSecureContext();
441
+ await openfort.waitForInitialization();
442
+ step("initialize");
443
+ const accessToken = await credentials.fetchBetterAuthAccessToken();
444
+ if (accessToken === null) throw openfortProviderError("token", /* @__PURE__ */ new Error("Better Auth access token unavailable for Openfort"));
445
+ step("token");
446
+ let encryption;
447
+ try {
448
+ encryption = await credentials.createEncryptionSession(accessToken);
449
+ } catch (cause) {
450
+ let httpStatus;
451
+ for (const link of causeChain(cause)) httpStatus ??= httpStatusOf(link);
452
+ diagnostic?.trace("openfort.encryptionSession", {
453
+ ok: false,
454
+ ...httpStatus === void 0 ? {} : { httpStatus },
455
+ failure_mode: "unknown"
456
+ });
457
+ throw openfortProviderError("encryptionSession", cause);
458
+ }
459
+ diagnostic?.trace("openfort.encryptionSession", {
460
+ ok: true,
461
+ ...encryption.httpStatus === void 0 ? {} : { httpStatus: encryption.httpStatus }
462
+ });
463
+ step("encryptionSession", encryption.httpStatus === void 0 ? {} : { httpStatus: encryption.httpStatus });
464
+ let embeddedState;
465
+ try {
466
+ embeddedState = await openfort.embeddedWallet.getEmbeddedState();
467
+ diagnostic?.trace("openfort.embeddedState", { state: embeddedState });
468
+ step("embeddedState", { state: embeddedState });
469
+ } catch (cause) {
470
+ diagnostic?.trace("openfort.embeddedState", {
471
+ ok: false,
472
+ failure_mode: "unknown"
473
+ });
474
+ throw openfortProviderError("embeddedState", cause);
475
+ }
476
+ if (embeddedState !== EmbeddedState.READY) {
477
+ clearStaleOpenfortBrowserStorage(publishableKey);
478
+ diagnostic?.trace("openfort.storageCleared", { beforeConfigure: true });
479
+ try {
480
+ await configureEmbeddedWallet(encryption.sessionId);
481
+ diagnostic?.trace("openfort.configure", { ok: true });
482
+ step("configure");
483
+ } catch (cause) {
484
+ const error = openfortProviderError("configure", cause);
485
+ diagnostic?.trace("openfort.configure", {
486
+ ok: false,
487
+ failure_mode: openfortFailureMode(error)
488
+ });
489
+ throw error;
490
+ }
491
+ }
492
+ try {
493
+ await openfort.embeddedWallet.get();
494
+ diagnostic?.trace("openfort.get", { ok: true });
495
+ step("get");
496
+ } catch (cause) {
497
+ if (!isStaleUserSignal(cause)) {
498
+ diagnostic?.trace("openfort.get", {
499
+ ok: false,
500
+ failure_mode: "unknown"
501
+ });
502
+ throw openfortProviderError("get", cause);
503
+ }
504
+ diagnostic?.trace("openfort.get", {
505
+ ok: false,
506
+ failure_mode: "stale-openfort-cache"
507
+ });
508
+ await healStaleOpenfortCache(encryption.sessionId);
509
+ step("healStaleCache");
510
+ }
511
+ step("walletReady", {
512
+ ok: true,
513
+ total_ms: Date.now() - startedAt,
514
+ token_calls: clock.tokenCalls
515
+ });
516
+ })();
517
+ };
518
+ }
519
+ //#endregion
520
+ //#region src/signing/openfort-recovery.ts
521
+ /**
522
+ * Sign once; when Openfort reports the signer not ready, reset readiness, run
523
+ * one fresh readiness cycle and sign again. A rejection by the person is never
524
+ * retried, and a second not-ready answer is a named `signer-not-ready` failure.
525
+ */
526
+ function recoveringSign(input) {
527
+ return async (hash) => {
528
+ try {
529
+ return await input.sign(hash);
530
+ } catch (cause) {
531
+ const providerErrorCode = openfortSignerNotReadyCode(cause);
532
+ const normalized = signerFailure("openfort-embedded", "signUserOpHash", cause);
533
+ if (normalized.code === "SIGNER_REJECTED" || providerErrorCode === void 0) throw normalized;
534
+ input.diagnostic?.trace("openfort.signerRecovery", {
535
+ outcome: "started",
536
+ attempt: 1,
537
+ failure_mode: "signer-not-ready"
538
+ });
539
+ input.resetReadiness();
540
+ try {
541
+ await input.ensureWalletReady();
542
+ const signature = await input.sign(hash);
543
+ input.diagnostic?.trace("openfort.signerRecovery", {
544
+ outcome: "succeeded",
545
+ attempt: 1,
546
+ failure_mode: "signer-not-ready"
547
+ });
548
+ return signature;
549
+ } catch (retryCause) {
550
+ const retryFailure = signerFailure("openfort-embedded", "signUserOpHash", retryCause);
551
+ if (retryFailure.code === "SIGNER_REJECTED") throw retryFailure;
552
+ if (isSignerNotReadySignal(retryCause)) input.markUnavailable();
553
+ input.diagnostic?.trace("openfort.signerRecovery", {
554
+ outcome: "failed",
555
+ attempt: 1,
556
+ failure_mode: isSignerNotReadySignal(retryCause) ? "signer-not-ready" : "unknown"
557
+ });
558
+ throw Errors.providerError("openfort", "signerRecovery", retryCause, {
559
+ failure_mode: "signer-not-ready",
560
+ details: {
561
+ provider_error_source: "openfort",
562
+ provider_error_code: providerErrorCode
563
+ }
564
+ });
565
+ }
566
+ }
567
+ };
568
+ }
569
+ //#endregion
570
+ //#region src/signing/openfort-browser.ts
571
+ function isolatedDiagnostic(diagnostic) {
572
+ if (diagnostic === void 0) return void 0;
573
+ return { trace: (scope, detail) => {
574
+ try {
575
+ Promise.resolve(diagnostic.trace(scope, detail)).catch(() => void 0);
576
+ } catch {}
577
+ } };
578
+ }
579
+ function createOpenfortBrowserSignerFromBootstrap(bootstrap, options = {}) {
580
+ const diagnostic = isolatedDiagnostic(options.diagnostic);
581
+ const authBaseUrl = normalizeBetterAuthBaseUrl(bootstrap.authBaseUrl);
582
+ let currentStatus = "unknown";
583
+ const statusListeners = /* @__PURE__ */ new Set();
584
+ function setStatus(next) {
585
+ if (currentStatus === next) return;
586
+ currentStatus = next;
587
+ for (const listener of statusListeners) try {
588
+ listener(next);
589
+ } catch {
590
+ statusListeners.delete(listener);
591
+ diagnostic?.trace("openfort.signerStatusListener", {
592
+ ok: false,
593
+ failure_mode: "unknown"
594
+ });
595
+ }
596
+ }
597
+ const statusStore = {
598
+ status: () => currentStatus,
599
+ subscribe: (listener) => {
600
+ statusListeners.add(listener);
601
+ return () => {
602
+ statusListeners.delete(listener);
603
+ };
604
+ }
605
+ };
606
+ const authProvider = bootstrap.openfortAuthProvider ?? "better-auth";
607
+ const clock = {
608
+ cycleStartedAt: null,
609
+ stepStartedAt: 0,
610
+ tokenCalls: 0
611
+ };
612
+ const credentials = makeOpenfortCredentials({
613
+ authProvider,
614
+ authBaseUrl,
615
+ auth: options.auth,
616
+ diagnostic,
617
+ clock
618
+ });
619
+ const openfort = new Openfort({
620
+ baseConfiguration: { publishableKey: bootstrap.openfortPublishableKey },
621
+ shieldConfiguration: { shieldPublishableKey: bootstrap.shieldPublishableKey },
622
+ thirdPartyAuth: {
623
+ provider: authProvider === "oidc" ? ThirdPartyOAuthProvider.OIDC : ThirdPartyOAuthProvider.BETTER_AUTH,
624
+ getAccessToken: credentials.fetchBetterAuthAccessToken
625
+ }
626
+ });
627
+ const startWalletReady = makeWalletReadyCycle({
628
+ openfort,
629
+ publishableKey: bootstrap.openfortPublishableKey,
630
+ diagnostic,
631
+ credentials,
632
+ clock
633
+ });
634
+ let walletReadyPromise = null;
635
+ async function ensureOpenfortWalletReady() {
636
+ let joined = walletReadyPromise;
637
+ if (joined === null) {
638
+ joined = startWalletReady();
639
+ walletReadyPromise = joined;
640
+ setStatus("recovering");
641
+ }
642
+ try {
643
+ await joined;
644
+ if (walletReadyPromise === joined) setStatus("ready");
645
+ } catch (cause) {
646
+ if (walletReadyPromise === joined) {
647
+ walletReadyPromise = null;
648
+ setStatus("unavailable");
649
+ }
650
+ throw cause;
651
+ }
652
+ }
653
+ const signer = openfortEmbeddedSignerFromWallet({
654
+ embeddedWallet: openfort.embeddedWallet,
655
+ ensureWalletReady: ensureOpenfortWalletReady
656
+ });
657
+ const controlledSign = options.signUserOpHash;
658
+ const signEmbeddedHash = controlledSign === void 0 ? (hash) => signer.signUserOpHash(hash) : (hash) => controlledSign(hash, (digest) => signer.signUserOpHash(digest));
659
+ const resetReadiness = () => {
660
+ walletReadyPromise = null;
661
+ clearStaleOpenfortBrowserStorage(bootstrap.openfortPublishableKey);
662
+ signer.resetAddressCache();
663
+ setStatus("unknown");
664
+ };
665
+ const signUserOpHash = recoveringSign({
666
+ sign: signEmbeddedHash,
667
+ resetReadiness,
668
+ ensureWalletReady: ensureOpenfortWalletReady,
669
+ markUnavailable: () => {
670
+ walletReadyPromise = null;
671
+ signer.resetAddressCache();
672
+ setStatus("unavailable");
673
+ },
674
+ diagnostic
675
+ });
676
+ return {
677
+ ...signer,
678
+ signUserOpHash,
679
+ statusStore,
680
+ ensureWalletReady: ensureOpenfortWalletReady,
681
+ getAddress: async () => {
682
+ const addressStartedAt = Date.now();
683
+ try {
684
+ const address = await signer.getAddress();
685
+ diagnostic?.trace("openfort.address", {
686
+ ok: true,
687
+ duration_ms: Date.now() - addressStartedAt
688
+ });
689
+ return address;
690
+ } catch (cause) {
691
+ diagnostic?.trace("openfort.address", {
692
+ ok: false,
693
+ failure_mode: "unknown"
694
+ });
695
+ throw openfortProviderError("getAddress", cause);
696
+ }
697
+ },
698
+ resetSession: resetReadiness
699
+ };
700
+ }
701
+ /** Build the existing browser signer around host-owned authenticated callbacks. */
702
+ function createOpenfortBrowserSigner(input) {
703
+ return createOpenfortBrowserSignerFromBootstrap(input.bootstrap, {
704
+ auth: input.auth,
705
+ ...input.diagnostic === void 0 ? {} : { diagnostic: input.diagnostic }
706
+ });
707
+ }
708
+ function normalizeBetterAuthBaseUrl(raw) {
709
+ const trimmed = raw.replace(/\/$/, "");
710
+ return trimmed.endsWith("/api/auth") ? trimmed : `${trimmed}/api/auth`;
711
+ }
712
+ /**
713
+ * Normalize a raw email for binding id + Safe salt derivation.
714
+ * Delegates to {@link normalizeSafeSaltEmail} so binding resolution and
715
+ * `deriveCapxulSafeAddress` never drift apart.
716
+ */
717
+ function normalizeBindingEmail(email) {
718
+ return normalizeSafeSaltEmail(email);
719
+ }
720
+ //#endregion
721
+ //#region src/signing/dev-signer.ts
722
+ const SESSION_KEY = "capxul.session";
723
+ /**
724
+ * Chains a `local-private-key` signer may sign on. Base Sepolia only, and this
725
+ * list does not grow without an ADR: the keys are deterministic throwaways
726
+ * derived from a shared seed (see `deriveDevPrivateKey`), so anyone holding
727
+ * the seed holds every account. A dev key on a value-bearing chain is a
728
+ * custody incident, not a config mistake.
729
+ */
730
+ const DEV_KEY_ALLOWED_CHAIN_IDS = [BASE_SEPOLIA_CHAIN_ID];
731
+ /**
732
+ * Testnet fence for the dev-key signing lane (#1149, folds #1065; ADR-0018 P9
733
+ * human/Openfort vs agent/dev-key split). Call it wherever a signer first
734
+ * meets a resolved chain; it refuses before the signer can be used.
735
+ *
736
+ * Only `local-private-key` signers are fenced — Openfort-embedded and injected
737
+ * wallets carry their own custody and are the sanctioned human paths.
738
+ */
739
+ function assertDevKeySignerIsTestnetOnly(signer, chainId) {
740
+ if (signer?.source !== "local-private-key") return {
741
+ ok: true,
742
+ value: void 0
743
+ };
744
+ if (DEV_KEY_ALLOWED_CHAIN_IDS.includes(chainId)) return {
745
+ ok: true,
746
+ value: void 0
747
+ };
748
+ return {
749
+ ok: false,
750
+ error: Errors.invalidInput("signer", `dev-key signer is testnet-only: chain ${chainId} is not allowed (expected ${DEV_KEY_ALLOWED_CHAIN_IDS.join(", ")})`)
751
+ };
752
+ }
753
+ /** Deterministic dev private key for an email under a seed. Exported for probes. */
754
+ function deriveDevPrivateKey(seed, email) {
755
+ return keccak256(stringToHex(seed + normalizeBindingEmail(email)));
756
+ }
757
+ function readSessionEmail(storage) {
758
+ const resolved = storage ?? globalLocalStorage();
759
+ if (resolved === void 0) throw new Error("devPrivateKeySigner: no browser localStorage available and no explicit email supplied");
760
+ const raw = resolved.getItem(SESSION_KEY);
761
+ if (raw === null) throw new Error("devPrivateKeySigner: no cached session yet — sign in before the account lane uses the signer");
762
+ let session;
763
+ try {
764
+ session = JSON.parse(raw);
765
+ } catch {
766
+ throw new Error("devPrivateKeySigner: cached session is not valid JSON");
767
+ }
768
+ if (session === null) throw new Error("devPrivateKeySigner: cached session is not valid JSON");
769
+ const email = readField(session, "email");
770
+ if (typeof email !== "string" || email.length === 0) throw new Error("devPrivateKeySigner: cached session has no email");
771
+ return email;
772
+ }
773
+ /**
774
+ * Browser-safe dev signer. Lazy: the email (and so the key) is resolved at
775
+ * each `getAddress()` / `signUserOpHash()` from the cached session, so the
776
+ * same signer instance follows whichever user is signed in.
777
+ */
778
+ function devPrivateKeySigner(input) {
779
+ if (input.seed.trim().length === 0) throw new Error("devPrivateKeySigner: seed must be non-empty");
780
+ const accounts = /* @__PURE__ */ new Map();
781
+ const resolveAccount = () => {
782
+ const email = normalizeBindingEmail(input.email ?? readSessionEmail(input.storage));
783
+ const cached = accounts.get(email);
784
+ if (cached !== void 0) return cached;
785
+ const account = privateKeyToAccount(deriveDevPrivateKey(input.seed, email));
786
+ accounts.set(email, account);
787
+ return account;
788
+ };
789
+ return {
790
+ source: "local-private-key",
791
+ async getAddress() {
792
+ return toAddress(resolveAccount().address);
793
+ },
794
+ async signUserOpHash(hash) {
795
+ return resolveAccount().sign({ hash });
796
+ }
797
+ };
798
+ }
799
+ //#endregion
800
+ //#region ../protocol/src/errors/convex-decoding.ts
801
+ const KNOWN_CODES = new Set(CAPXUL_ERROR_CODES);
802
+ function isCapxulCode(value) {
803
+ return typeof value === "string" && KNOWN_CODES.has(value);
804
+ }
805
+ /**
806
+ * A serialized failure, rebuilt whole. Nothing that crossed is dropped: a code
807
+ * this build does not know (a newer backend) becomes an explicit `UNKNOWN` that
808
+ * names the code it received, and an unrecognized mode or chain shape is kept
809
+ * raw in `details` instead of being guessed at or discarded.
810
+ */
811
+ function reconstruct(serialized) {
812
+ if (typeof serialized.code !== "string") return null;
813
+ const unrecognized = {
814
+ ...isCapxulCode(serialized.code) ? {} : { receivedCode: serialized.code },
815
+ ...serialized.mode === void 0 || isFailureMode(serialized.mode) ? {} : { unrecognizedMode: serialized.mode }
816
+ };
817
+ const chain = serialized.chain === void 0 ? null : decodeChainCause(serialized.chain);
818
+ if (serialized.chain !== void 0 && chain === null) unrecognized.unrecognizedChain = serialized.chain;
819
+ const hasDetails = isRecord(serialized.details) || Object.keys(unrecognized).length > 0;
820
+ const cause = causeLinks(serialized.cause);
821
+ const payload = {
822
+ code: isCapxulCode(serialized.code) ? serialized.code : "UNKNOWN",
823
+ message: typeof serialized.message === "string" ? serialized.message : serialized.code,
824
+ ...hasDetails ? { details: {
825
+ ...isRecord(serialized.details) ? serialized.details : {},
826
+ ...unrecognized
827
+ } } : {},
828
+ ...typeof serialized.correlationId === "string" ? { correlationId: serialized.correlationId } : {},
829
+ ...typeof serialized.layer === "string" ? { layer: serialized.layer } : {},
830
+ ...isFailureMode(serialized.mode) ? { mode: serialized.mode } : {},
831
+ ...chain === null ? {} : { chain },
832
+ ...cause === void 0 ? {} : { cause }
833
+ };
834
+ return deserializeCapxulError(payload);
835
+ }
836
+ function causeLinks(value) {
837
+ if (!Array.isArray(value)) return void 0;
838
+ const links = value.filter((link) => isRecord(link) && typeof link.name === "string" && typeof link.message === "string");
839
+ return links.length === 0 ? void 0 : links;
840
+ }
841
+ function decodeConvexError(err) {
842
+ if (err === null || err === void 0) return null;
843
+ if (err instanceof CapxulError) return err;
844
+ if (typeof err !== "object") return null;
845
+ if (!("data" in err)) return null;
846
+ const data = err.data;
847
+ if (isRecord(data)) return reconstruct(data);
848
+ if (typeof data === "string") try {
849
+ const parsed = JSON.parse(data);
850
+ if (isRecord(parsed)) return reconstruct(parsed);
851
+ } catch {
852
+ return null;
853
+ }
854
+ return null;
855
+ }
856
+ //#endregion
857
+ //#region src/applications/bootstrap-port.ts
858
+ var BootstrapError = class extends Data.TaggedError("BootstrapError") {};
859
+ function bootstrapErrorFromCapxul(kind, error) {
860
+ return new BootstrapError({
861
+ operation: "resolve",
862
+ kind,
863
+ publicCode: error.code,
864
+ cause: error,
865
+ ...kind === "notAuthenticated" || error.details === void 0 ? {} : { details: error.details }
866
+ });
867
+ }
868
+ var BootstrapPortTag = class extends Context.Service()("@capxul/sdk/ports/BootstrapPort") {};
869
+ //#endregion
870
+ //#region ../protocol/src/access/value-schema.ts
871
+ /**
872
+ * `SessionToken` schema. Mirrors `toSessionToken`: non-empty string.
873
+ * Issuance source distinguishes SDK-handshake tokens from auth-session
874
+ * tokens; the brand itself is opaque.
875
+ */
876
+ const SessionTokenSchema = Schema.String.pipe(Schema.refine((s) => s.length > 0, { message: "must be a non-empty string" }));
877
+ //#endregion
878
+ //#region ../protocol/src/access/bootstrap-schema.ts
879
+ const PUBLIC_POSTHOG_PROJECT_TOKEN = /^phc_[A-Za-z0-9_-]{1,191}$/u;
880
+ const PostHogIngestOrigin = Schema.String.pipe(Schema.refine((value) => {
881
+ try {
882
+ const url = new URL(value);
883
+ return url.protocol === "https:" && url.username.length === 0 && url.password.length === 0 && url.pathname === "/" && url.search.length === 0 && url.hash.length === 0 && (url.hostname === "posthog.com" || url.hostname.endsWith(".posthog.com"));
884
+ } catch {
885
+ return false;
886
+ }
887
+ }, { message: "must be a credential-free PostHog HTTPS ingest origin" }));
888
+ const EngineeringTelemetryBootstrapPolicy = Schema.Struct({
889
+ host: PostHogIngestOrigin,
890
+ projectToken: Schema.String.pipe(Schema.refine((value) => PUBLIC_POSTHOG_PROJECT_TOKEN.test(value), { message: "must be a public PostHog project token" })),
891
+ capxulEnv: Schema.Union([
892
+ Schema.Literal("development"),
893
+ Schema.Literal("staging"),
894
+ Schema.Literal("production"),
895
+ Schema.Literal("local")
896
+ ])
897
+ });
898
+ /**
899
+ * `BootstrapEnvelope` v1.
900
+ *
901
+ * - `protocol`: discriminator that lets future protocols coexist on the
902
+ * same endpoint without a wire-shape conflict.
903
+ * - `version`: numeric version inside the protocol. Unknown versions MUST
904
+ * fail decode with an INVALID_INPUT-class `CapxulError` at the SDK seam.
905
+ * - `state`: the resolved bootstrap payload. Field shape matches the
906
+ * `BootstrapResolution` port; the SDK consumer can pass `state`
907
+ * directly (after `normalizeRuntimeUrl` on the two URL fields) into
908
+ * the port without re-branding.
909
+ */
910
+ const BootstrapEnvelope = Schema.Struct({
911
+ protocol: Schema.Literal("capxul.bootstrap"),
912
+ version: Schema.Literal(1),
913
+ state: Schema.Struct({
914
+ applicationId: AppIdSchema,
915
+ chainId: ChainIdSchema,
916
+ sessionToken: SessionTokenSchema,
917
+ issuedAt: EpochMsSchema,
918
+ expiresIn: DurationMsSchema,
919
+ authBaseUrl: Schema.String,
920
+ convexUrl: Schema.String,
921
+ siteBaseUrl: Schema.String,
922
+ openfortPublishableKey: Schema.String,
923
+ shieldPublishableKey: Schema.String,
924
+ /** Which Openfort third-party auth provider the signer presents; absent means better-auth. */
925
+ openfortAuthProvider: Schema.optional(Schema.Literals(["better-auth", "oidc"])),
926
+ /** Origin of the Ponder indexer the SDK reads directly; absent means no indexer reads. */
927
+ indexerUrl: Schema.optional(Schema.String),
928
+ engineeringTelemetry: Schema.optional(EngineeringTelemetryBootstrapPolicy)
929
+ })
930
+ });
931
+ //#endregion
932
+ //#region src/applications/bootstrap-envelope.ts
933
+ /**
934
+ * Decode a bootstrap body. A body the envelope refuses throws INVALID_INPUT
935
+ * (#119 AC1): unknown `version` literals, missing fields and brand-rule
936
+ * failures all land there. The engineering telemetry policy decodes on its
937
+ * own, so a policy the SDK does not admit turns SDK telemetry off instead of
938
+ * failing the bootstrap.
939
+ */
940
+ function decodeBootstrapBody(body) {
941
+ const state = readField(body, "state");
942
+ const rawEngineeringTelemetry = readField(state, "engineeringTelemetry");
943
+ const decoded = SchemaParser.decodeUnknownResult(BootstrapEnvelope)(isRecord(body) && isRecord(state) ? {
944
+ ...body,
945
+ state: withoutPolicy(state)
946
+ } : body);
947
+ if (Result.isFailure(decoded)) throw Errors.invalidInput("bootstrapEnvelope", SchemaIssue.makeFormatterDefault()(decoded.failure));
948
+ const { state: decodedState } = decoded.success;
949
+ const decodedEngineeringTelemetry = rawEngineeringTelemetry === void 0 ? void 0 : SchemaParser.decodeUnknownResult(EngineeringTelemetryBootstrapPolicy)(rawEngineeringTelemetry, { onExcessProperty: "error" });
950
+ return {
951
+ applicationId: decodedState.applicationId,
952
+ chainId: decodedState.chainId,
953
+ sessionToken: decodedState.sessionToken,
954
+ issuedAt: decodedState.issuedAt,
955
+ expiresIn: decodedState.expiresIn,
956
+ authBaseUrl: normalizeRuntimeUrl("authBaseUrl", decodedState.authBaseUrl),
957
+ convexUrl: normalizeRuntimeUrl("convexUrl", decodedState.convexUrl),
958
+ siteBaseUrl: normalizeRuntimeUrl("siteBaseUrl", decodedState.siteBaseUrl),
959
+ openfortPublishableKey: decodedState.openfortPublishableKey,
960
+ shieldPublishableKey: decodedState.shieldPublishableKey,
961
+ ...decodedState.openfortAuthProvider === void 0 ? {} : { openfortAuthProvider: decodedState.openfortAuthProvider },
962
+ ...decodedState.indexerUrl === void 0 ? {} : { indexerUrl: decodedState.indexerUrl },
963
+ ...engineeringTelemetryOf(decodedEngineeringTelemetry)
964
+ };
965
+ }
966
+ /** The bootstrap state without the policy, which decodes separately. */
967
+ function withoutPolicy(state) {
968
+ const { engineeringTelemetry: _policy, ...rest } = state;
969
+ return rest;
970
+ }
971
+ /**
972
+ * The engineering telemetry policy the backend sent. One it does not admit
973
+ * leaves SDK telemetry off, and says so once with the decode issue.
974
+ */
975
+ function engineeringTelemetryOf(decoded) {
976
+ if (decoded === void 0) return {};
977
+ if (Result.isSuccess(decoded)) return { engineeringTelemetry: decoded.success };
978
+ console.warn("[capxul-sdk] engineering telemetry policy refused; SDK telemetry is off", { issue: SchemaIssue.makeFormatterDefault()(decoded.failure) });
979
+ return {};
980
+ }
981
+ function normalizeRuntimeUrl(field, raw) {
982
+ let parsed;
983
+ try {
984
+ parsed = new URL(raw);
985
+ } catch {
986
+ throw Errors.invalidInput(field, "must be an http or https URL");
987
+ }
988
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw Errors.invalidInput(field, "must be an http or https URL");
989
+ return parsed.toString().replace(/\/$/, "");
990
+ }
991
+ //#endregion
992
+ //#region src/applications/bootstrap-adapter.ts
993
+ /** A cached envelope this close to its expiry is fetched again, so it cannot lapse mid-command. */
994
+ const CACHE_EXPIRY_MARGIN_MS = 3e4;
995
+ /**
996
+ * The cached envelope, when it is still inside its own lifetime. It is decoded
997
+ * as a fresh body is; anything that does not decode, or a store that fails,
998
+ * is a miss.
999
+ */
1000
+ async function cachedEnvelope(cache, now) {
1001
+ try {
1002
+ const cached = decodeBootstrapBody(await cache.load());
1003
+ const expiresAt = cached.issuedAt + cached.expiresIn - CACHE_EXPIRY_MARGIN_MS;
1004
+ return cached.issuedAt <= now && now < expiresAt ? cached : null;
1005
+ } catch {
1006
+ return null;
1007
+ }
1008
+ }
1009
+ const DEFAULT_RETRY = {
1010
+ attempts: 2,
1011
+ baseDelayMs: 400
1012
+ };
1013
+ /** Request-side statuses a proxy or backend returns while momentarily unable to serve. */
1014
+ const TRANSIENT_HTTP_STATUSES = /* @__PURE__ */ new Set([
1015
+ 408,
1016
+ 425,
1017
+ 429
1018
+ ]);
1019
+ /** Every 5xx is a server-side condition worth one more try; the client sent nothing wrong. */
1020
+ function isTransientHttpStatus(status) {
1021
+ return status >= 500 || TRANSIENT_HTTP_STATUSES.has(status);
1022
+ }
1023
+ /**
1024
+ * A transient HTTP status is worth one more try. The bootstrap call crosses
1025
+ * the host's own proxy before it reaches Convex, and every observed failure
1026
+ * of that hop cleared on a retry seconds later. A network rejection (offline,
1027
+ * DNS, CORS) is not retried: it does not clear in a second, and the caller
1028
+ * surfaces it as `NETWORK_ERROR` at once. Auth and input rejections are
1029
+ * deterministic and never retried.
1030
+ */
1031
+ function isTransientBootstrapFailure(error) {
1032
+ if (error.kind !== "provider") return false;
1033
+ const status = readField(error.details, "httpStatus");
1034
+ return typeof status === "number" && isTransientHttpStatus(status);
1035
+ }
1036
+ async function safeText(res) {
1037
+ try {
1038
+ return await res.text();
1039
+ } catch {
1040
+ return "";
1041
+ }
1042
+ }
1043
+ var HttpBootstrapAdapter = class {
1044
+ bootstrapBaseUrl;
1045
+ bootstrapHost;
1046
+ fetchImpl;
1047
+ observation;
1048
+ retry;
1049
+ cache;
1050
+ constructor(deps) {
1051
+ this.bootstrapBaseUrl = deps.bootstrapBaseUrl.replace(/\/$/, "");
1052
+ this.bootstrapHost = hostOf(this.bootstrapBaseUrl);
1053
+ this.observation = deps.observation;
1054
+ this.retry = deps.retry ?? DEFAULT_RETRY;
1055
+ this.cache = deps.cache;
1056
+ this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
1057
+ }
1058
+ resolve(input) {
1059
+ const fetched = this.attempt(input).pipe(Effect.retry({
1060
+ times: this.retry.attempts,
1061
+ while: isTransientBootstrapFailure,
1062
+ schedule: Schedule.exponential(Duration.millis(this.retry.baseDelayMs))
1063
+ }));
1064
+ const cache = this.cache;
1065
+ if (cache === void 0) return fetched;
1066
+ return Effect.promise(() => cachedEnvelope(cache, Date.now())).pipe(Effect.filterOrElse((cached) => cached !== null, () => fetched));
1067
+ }
1068
+ attempt(input) {
1069
+ return Effect.tryPromise({
1070
+ try: () => {
1071
+ const headers = {
1072
+ "content-type": "application/json",
1073
+ ...input.origin === void 0 ? {} : { origin: input.origin },
1074
+ ...observationRequestHeaders(this.observation)
1075
+ };
1076
+ return this.fetchImpl(`${this.bootstrapBaseUrl}/v1/client/bootstrap`, {
1077
+ method: "POST",
1078
+ headers,
1079
+ body: JSON.stringify({ publishableKey: input.publishableKey })
1080
+ });
1081
+ },
1082
+ catch: (cause) => bootstrapErrorFromCapxul("network", Errors.networkError("bootstrap", cause, {
1083
+ provider: "bootstrap",
1084
+ failure_mode: "upstream-down",
1085
+ ...this.bootstrapHost === void 0 ? {} : { target_host: this.bootstrapHost }
1086
+ }))
1087
+ }).pipe(Effect.flatMap((res) => this.mapResponse(res, input)));
1088
+ }
1089
+ mapResponse(res, input) {
1090
+ if (res.ok) return Effect.tryPromise({
1091
+ try: async () => {
1092
+ const body = await res.json();
1093
+ const resolution = decodeBootstrapBody(body);
1094
+ const cache = this.cache;
1095
+ if (cache !== void 0) await Promise.resolve().then(() => cache.save(body)).catch(() => void 0);
1096
+ return resolution;
1097
+ },
1098
+ catch: (cause) => {
1099
+ if (cause instanceof CapxulError && cause.code === "INVALID_INPUT") return bootstrapErrorFromCapxul("invalidInput", cause);
1100
+ return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
1101
+ }
1102
+ });
1103
+ return Effect.promise(() => safeText(res)).pipe(Effect.flatMap((body) => {
1104
+ const refused = backendFailure$1(body);
1105
+ if (refused !== null) return Effect.fail(bootstrapErrorFromCapxul(bootstrapKind(refused.code), refused));
1106
+ const responseBody = body.slice(0, 300);
1107
+ const edgeError = res.headers?.get?.("x-vercel-error") ?? void 0;
1108
+ const edgeRequestId = res.headers?.get?.("x-vercel-id") ?? void 0;
1109
+ const evidence = {
1110
+ ...responseBody.length === 0 ? {} : { responseBody },
1111
+ ...edgeError === void 0 ? {} : { edgeError },
1112
+ ...edgeRequestId === void 0 ? {} : { edgeRequestId }
1113
+ };
1114
+ if (res.status === 401) return Effect.fail(bootstrapErrorFromCapxul("notAuthenticated", keyRefused(input, evidence)));
1115
+ return Effect.fail(bootstrapErrorFromCapxul("provider", Errors.providerError("convex", "bootstrap", /* @__PURE__ */ new Error(`HTTP ${res.status}${edgeError === void 0 ? "" : ` ${edgeError}`}`), {
1116
+ httpStatus: res.status,
1117
+ details: evidence
1118
+ })));
1119
+ }));
1120
+ }
1121
+ };
1122
+ /** A JSON `SerializedCapxulError` body, decoded whole; anything else is not the backend's. */
1123
+ function backendFailure$1(body) {
1124
+ try {
1125
+ return decodeConvexError({ data: JSON.parse(body) });
1126
+ } catch {
1127
+ return null;
1128
+ }
1129
+ }
1130
+ /**
1131
+ * A 401 the backend did not answer with its own refusal body (an older
1132
+ * backend answers `NOT_AUTHENTICATED: …` as text). The backend checks the
1133
+ * origin only when one is sent, so without one the key is what it refused.
1134
+ */
1135
+ function keyRefused(input, evidence) {
1136
+ return new CapxulError("NOT_AUTHENTICATED", input.origin === void 0 ? "The bootstrap refused the publishable key." : "The bootstrap refused the publishable key or its origin.", { details: {
1137
+ ...input.origin === void 0 ? {
1138
+ field: "publishableKey",
1139
+ reason: "unknown or revoked key"
1140
+ } : {},
1141
+ provider: "bootstrap",
1142
+ httpStatus: 401,
1143
+ ...evidence
1144
+ } });
1145
+ }
1146
+ function bootstrapKind(code) {
1147
+ if (code === "NOT_AUTHENTICATED") return "notAuthenticated";
1148
+ if (code === "INVALID_INPUT") return "invalidInput";
1149
+ if (code === "RATE_LIMITED") return "rateLimited";
1150
+ return "provider";
1151
+ }
1152
+ function HttpBootstrapLayer(deps) {
1153
+ return Layer.succeed(BootstrapPortTag, new HttpBootstrapAdapter(deps));
1154
+ }
1155
+ function hostOf(url) {
1156
+ try {
1157
+ return new URL(url).hostname;
1158
+ } catch {
1159
+ return;
1160
+ }
1161
+ }
1162
+ //#endregion
1163
+ //#region src/profiles/port.ts
1164
+ /** The chain families a payout address may name (#1955). */
1165
+ const PAYOUT_CHAINS = [
1166
+ "evm",
1167
+ "solana",
1168
+ "starknet"
1169
+ ];
1170
+ var IdentityError = class extends Data.TaggedError("IdentityError") {};
1171
+ function identityErrorFromCapxul(operation, error, cause = error) {
1172
+ return new IdentityError({
1173
+ operation,
1174
+ publicCode: error.code,
1175
+ publicError: error,
1176
+ cause,
1177
+ ...error.details === void 0 ? {} : { details: error.details }
1178
+ });
1179
+ }
1180
+ var IdentityPortTag = class extends Context.Service()("@capxul/sdk/ports/IdentityPort") {};
1181
+ //#endregion
1182
+ //#region src/profiles/adapter.ts
1183
+ var ConvexIdentityAdapter = class {
1184
+ #convex;
1185
+ constructor(deps) {
1186
+ this.#convex = deps.convex;
1187
+ }
1188
+ loadByAuthUserId = (authUserId) => this.#call(details.get, { authUserId }, (view) => view === null ? null : brandProfile(view));
1189
+ create = (input) => this.#call(details.create, input, brandProfile);
1190
+ update = (input) => this.#call(details.update, input, brandProfile);
1191
+ completeOnboarding = (input) => this.#call(onboardings.complete, input, brandProfile);
1192
+ /** One row call: its failure as an IdentityError named by the row, its reply branded. */
1193
+ #call(row, args, brand) {
1194
+ return callOperation(this.#convex, row, args).pipe(Effect.mapError((error) => identityErrorFromCapxul(row.name, error)), Effect.flatMap((output) => Effect.try({
1195
+ try: () => brand(output),
1196
+ catch: (cause) => identityErrorFromUnknown(row.name, cause)
1197
+ })), Effect.catchDefect((cause) => Effect.fail(identityErrorFromUnknown(row.name, cause))));
1198
+ }
1199
+ };
1200
+ const ConvexIdentityLayer = Layer.effect(IdentityPortTag, Effect.map(ConvexCallPortTag, (convex) => new ConvexIdentityAdapter({ convex })));
1201
+ function brandProfile(view) {
1202
+ return {
1203
+ authUserId: toAuthUserId(view.authUserId),
1204
+ ...view.testerKind === void 0 ? {} : { testerKind: toTesterKind(view.testerKind) },
1205
+ email: toEmail(view.email),
1206
+ displayName: view.displayName,
1207
+ country: view.country === null ? null : toCountryCode(view.country),
1208
+ onboarded: view.onboarded,
1209
+ withdrawalAddress: view.withdrawalAddress === null ? null : toAddress(view.withdrawalAddress),
1210
+ handle: view.handle === null ? null : toHandle(view.handle),
1211
+ imageUrl: view.imageUrl,
1212
+ kycTier: toKycTier(view.kycTier),
1213
+ createdAt: toEpochMs(view.createdAt),
1214
+ updatedAt: toEpochMs(view.updatedAt)
1215
+ };
1216
+ }
1217
+ /** A branding refusal keeps its code; anything else is a defect, never a provider failure. */
1218
+ function identityErrorFromUnknown(operation, cause) {
1219
+ if (cause instanceof CapxulError) return identityErrorFromCapxul(operation, cause);
1220
+ return identityErrorFromCapxul(operation, Errors.unknown(cause), cause);
1221
+ }
1222
+ //#endregion
1223
+ //#region src/profiles/programs.ts
1224
+ /** The single Context tag the profile atom + `identity.loadCurrent` resolve. */
1225
+ var IdentityDepsTag = class extends Context.Service()("@capxul/sdk/IdentityDeps") {};
1226
+ /** Wrap a bundle as the `Context<IdentityDepsTag>` the React provider consumes. */
1227
+ function identityDepsContext(deps) {
1228
+ return Context.make(IdentityDepsTag, deps);
1229
+ }
1230
+ /**
1231
+ * `identity.loadCurrent` as a single Effect requiring ONLY `IdentityDepsTag`.
1232
+ * Faithful transcription of the current public method body (`identity.ts`):
1233
+ * the leading `signal.aborted` pre-check stays in the thin method wrapper;
1234
+ * the program snapshots the actor session, then narrows the `IdentityPort`
1235
+ * failure to its `publicError`. Defects/sync-throws surface as `UNKNOWN`
1236
+ * through `toCapxulResult`'s `catchAllDefect` (and the atom's runtime), which
1237
+ * replaces the old outer `try/catch`.
1238
+ */
1239
+ const loadIdentityProgram = Effect.gen(function* () {
1240
+ const deps = yield* IdentityDepsTag;
1241
+ if (deps.actor === void 0) return yield* Effect.fail(Errors.notAuthenticated());
1242
+ const state = deps.actor.snapshot();
1243
+ if (state.phase === "faulted") return yield* Effect.fail(publicIdentityFailure(state.failure));
1244
+ const authUserId = sessionFromActor(deps.actor)?.authUserId;
1245
+ if (authUserId === void 0) return null;
1246
+ return yield* deps.identityPort.loadByAuthUserId(authUserId).pipe(Effect.catchDefect((cause) => Effect.fail(identityErrorFromCapxul("loadByAuthUserId", asCapxulError(cause), cause))), Effect.mapError((failure) => failure.publicError));
1247
+ });
1248
+ /**
1249
+ * THE ONE boundary grammar for those optional fields. Both `completeProfile`
1250
+ * entry points — the method bundle here and the identity runtime in
1251
+ * `create-capxul-client.ts` — run it, so a malformed value is refused the same
1252
+ * way at each and never reaches the port.
1253
+ */
1254
+ function checkProfileCarriage(profile) {
1255
+ const carriage = {};
1256
+ if (profile.imageStorageId !== void 0) {
1257
+ if (typeof profile.imageStorageId !== "string" || profile.imageStorageId.trim().length === 0) return {
1258
+ ok: false,
1259
+ field: "imageStorageId",
1260
+ message: "must be a non-empty storage id"
1261
+ };
1262
+ carriage.imageStorageId = profile.imageStorageId.trim();
1263
+ }
1264
+ if (profile.payoutAddresses !== void 0) {
1265
+ if (!Array.isArray(profile.payoutAddresses)) return {
1266
+ ok: false,
1267
+ field: "payoutAddresses",
1268
+ message: "must be an array"
1269
+ };
1270
+ if (profile.payoutAddresses.length > 10) return {
1271
+ ok: false,
1272
+ field: "payoutAddresses",
1273
+ message: `must carry at most 10 addresses`
1274
+ };
1275
+ const entries = [];
1276
+ for (const entry of profile.payoutAddresses) {
1277
+ if (typeof entry !== "object" || entry === null || !PAYOUT_CHAINS.some((chain) => chain === entry.chain) || typeof entry.address !== "string" || entry.address.trim().length === 0) return {
1278
+ ok: false,
1279
+ field: "payoutAddresses",
1280
+ message: `each entry needs a chain of ${PAYOUT_CHAINS.join(", ")} and an address`
1281
+ };
1282
+ entries.push({
1283
+ chain: entry.chain,
1284
+ address: entry.address.trim()
1285
+ });
1286
+ }
1287
+ carriage.payoutAddresses = entries;
1288
+ }
1289
+ return {
1290
+ ok: true,
1291
+ carriage
1292
+ };
1293
+ }
1294
+ function makeIdentityMethods(deps) {
1295
+ const convexCall = deps.convexCall;
1296
+ return {
1297
+ async loadCurrent(options) {
1298
+ if (options?.signal?.aborted) return {
1299
+ ok: false,
1300
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.identity.loadCurrent })
1301
+ };
1302
+ return toCapxulResult(loadIdentityProgram, identityDepsContext(deps), deps.runPromise);
1303
+ },
1304
+ async completeProfile(profile, options) {
1305
+ if (options?.signal?.aborted) return {
1306
+ ok: false,
1307
+ error: Errors.cancelled({ operation: onboardings.complete.name })
1308
+ };
1309
+ if (typeof profile !== "object" || profile === null || typeof profile.displayName !== "string" || typeof profile.country !== "string" || typeof profile.handle !== "string") return {
1310
+ ok: false,
1311
+ error: Errors.invalidInput("profile", "invalid profile")
1312
+ };
1313
+ const displayName = profile.displayName.trim();
1314
+ const handle = profile.handle.trim();
1315
+ if (displayName.length === 0) return {
1316
+ ok: false,
1317
+ error: Errors.invalidInput("displayName", "must not be empty")
1318
+ };
1319
+ if (handle.length === 0) return {
1320
+ ok: false,
1321
+ error: Errors.invalidInput("handle", "must not be empty")
1322
+ };
1323
+ let country;
1324
+ try {
1325
+ country = toCountryCode(profile.country);
1326
+ } catch {
1327
+ return {
1328
+ ok: false,
1329
+ error: Errors.invalidInput("country", "unsupported country")
1330
+ };
1331
+ }
1332
+ const carried = checkProfileCarriage(profile);
1333
+ if (!carried.ok) return {
1334
+ ok: false,
1335
+ error: Errors.invalidInput(carried.field, carried.message)
1336
+ };
1337
+ const session = deps.actor?.authSession();
1338
+ if (session === void 0 || session === null) return {
1339
+ ok: false,
1340
+ error: Errors.notAuthenticated()
1341
+ };
1342
+ return runPortEffect(deps.identityPort.completeOnboarding({
1343
+ authUserId: session.authUserId,
1344
+ email: session.email,
1345
+ displayName,
1346
+ country,
1347
+ handle,
1348
+ ...carried.carriage
1349
+ }), options, onboardings.complete.name, deps.runPromise);
1350
+ },
1351
+ async handleAvailable(handle, options) {
1352
+ const row = details.handleAvailable;
1353
+ if (options?.signal?.aborted) return {
1354
+ ok: false,
1355
+ error: Errors.cancelled({ operation: row.name })
1356
+ };
1357
+ if (convexCall === void 0) return {
1358
+ ok: false,
1359
+ error: Errors.notImplemented("identity", "handleAvailable")
1360
+ };
1361
+ return runCapxulEffect(callOperation(convexCall, row, { handle }), options, row.name, deps.runPromise);
1362
+ }
1363
+ };
1364
+ }
1365
+ //#endregion
1366
+ //#region src/profiles/person-onboarding.ts
1367
+ function normalizeHandleInput(input) {
1368
+ if (typeof input !== "string") return {
1369
+ ok: false,
1370
+ error: Errors.invalidInput("handle", "is required")
1371
+ };
1372
+ try {
1373
+ return {
1374
+ ok: true,
1375
+ value: validateHandle(input)
1376
+ };
1377
+ } catch (error) {
1378
+ return failed(error);
1379
+ }
1380
+ }
1381
+ function isClaimedIdentityState(state) {
1382
+ return state.phase === "authenticated" && state.account.at === "claimed";
1383
+ }
1384
+ function accountInFlight(state) {
1385
+ return state.phase === "authenticated" && (state.account.at === "deriving" || state.account.at === "claiming");
1386
+ }
1387
+ function settledAccount(deps, options) {
1388
+ return new Promise((resolve) => {
1389
+ let done = false;
1390
+ let unsubscribe = null;
1391
+ const onAbort = () => finish({
1392
+ ok: false,
1393
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completeOrganization })
1394
+ });
1395
+ const finish = (result) => {
1396
+ if (done) return;
1397
+ done = true;
1398
+ unsubscribe?.();
1399
+ options?.signal?.removeEventListener("abort", onAbort);
1400
+ resolve(result);
1401
+ };
1402
+ if (options?.signal?.aborted) {
1403
+ onAbort();
1404
+ return;
1405
+ }
1406
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
1407
+ const stop = deps.subscribeIdentity((state) => {
1408
+ if (!accountInFlight(state)) finish({
1409
+ ok: true,
1410
+ value: state
1411
+ });
1412
+ });
1413
+ if (done) {
1414
+ stop();
1415
+ return;
1416
+ }
1417
+ unsubscribe = stop;
1418
+ const current = deps.snapshotIdentity();
1419
+ if (!accountInFlight(current)) finish({
1420
+ ok: true,
1421
+ value: current
1422
+ });
1423
+ });
1424
+ }
1425
+ async function reachClaimedAccount(deps, options) {
1426
+ let state = deps.snapshotIdentity();
1427
+ if (accountInFlight(state)) {
1428
+ const settled = await settledAccount(deps, options);
1429
+ if (!settled.ok) return settled;
1430
+ state = settled.value;
1431
+ }
1432
+ if (state.phase !== "authenticated" || state.account.at === "unknown") {
1433
+ const ensured = await deps.sendIdentity({ _tag: "EnsureAccount" }, options);
1434
+ if (!ensured.ok) return ensured;
1435
+ state = ensured.value;
1436
+ }
1437
+ if (state.phase !== "authenticated") return organizationWrongState(state.phase, ["authenticated:claimed"]);
1438
+ if (accountInFlight(state)) {
1439
+ const settled = await settledAccount(deps, options);
1440
+ if (!settled.ok) return settled;
1441
+ state = settled.value;
1442
+ if (state.phase !== "authenticated") return organizationWrongState(state.phase, ["authenticated:claimed"]);
1443
+ }
1444
+ if (state.account.at !== "claimed") {
1445
+ const event = state.account.at === "failed" ? { _tag: "RetryAccount" } : state.account.at === "counterfactual" ? { _tag: "ClaimAccount" } : { _tag: "EnsureAccount" };
1446
+ const claimed = await deps.sendIdentity(event, options);
1447
+ if (!claimed.ok) return claimed;
1448
+ state = claimed.value;
1449
+ }
1450
+ return isClaimedIdentityState(state) ? {
1451
+ ok: true,
1452
+ value: state
1453
+ } : organizationWrongState(state.phase === "authenticated" ? state.account.at : state.phase, ["claimed"]);
1454
+ }
1455
+ const cancelledPerson = () => ({
1456
+ ok: false,
1457
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completePerson })
1458
+ });
1459
+ const joinPerson = (pending, signal) => {
1460
+ if (signal === void 0) return pending;
1461
+ if (signal.aborted) return Promise.resolve(cancelledPerson());
1462
+ return new Promise((resolve) => {
1463
+ let done = false;
1464
+ const onAbort = () => finish(cancelledPerson());
1465
+ const finish = (result) => {
1466
+ if (done) return;
1467
+ done = true;
1468
+ signal.removeEventListener("abort", onAbort);
1469
+ resolve(result);
1470
+ };
1471
+ signal.addEventListener("abort", onAbort, { once: true });
1472
+ if (signal.aborted) onAbort();
1473
+ pending.then(finish, (cause) => finish(failed(cause)));
1474
+ });
1475
+ };
1476
+ const waitForRestoreReady = (ready, signal) => {
1477
+ if (signal === void 0) return ready.then(() => true);
1478
+ if (signal.aborted) return Promise.resolve(false);
1479
+ return new Promise((resolve) => {
1480
+ let done = false;
1481
+ const onAbort = () => finish(false);
1482
+ const finish = (value) => {
1483
+ if (done) return;
1484
+ done = true;
1485
+ signal.removeEventListener("abort", onAbort);
1486
+ resolve(value);
1487
+ };
1488
+ signal.addEventListener("abort", onAbort, { once: true });
1489
+ if (signal.aborted) onAbort();
1490
+ ready.then(() => finish(true), () => finish(false));
1491
+ });
1492
+ };
1493
+ const samePersonInput = (left, right) => {
1494
+ if (left.displayName !== right.displayName || left.country !== right.country || left.imageStorageId !== right.imageStorageId) return false;
1495
+ const a = left.payoutAddresses;
1496
+ const b = right.payoutAddresses;
1497
+ if (a === void 0 || b === void 0) return a === b;
1498
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1499
+ return a.every((address, index) => address?.chain === b[index]?.chain && address?.address === b[index]?.address);
1500
+ };
1501
+ //#endregion
1502
+ //#region src/profiles/organization-onboarding.ts
1503
+ function normalizeOrganization(input) {
1504
+ if (typeof input !== "object" || input === null) return {
1505
+ ok: false,
1506
+ error: Errors.invalidInput("organization", "is required")
1507
+ };
1508
+ const name = typeof input.name === "string" ? input.name.trim() : "";
1509
+ if (name.length === 0) return {
1510
+ ok: false,
1511
+ error: Errors.invalidInput("name", "is required")
1512
+ };
1513
+ const handle = normalizeHandleInput(input.handle);
1514
+ if (!handle.ok) return handle;
1515
+ if (input.bio !== void 0 && typeof input.bio !== "string") return {
1516
+ ok: false,
1517
+ error: Errors.invalidInput("bio", "must be text")
1518
+ };
1519
+ if (input.size !== void 0 && typeof input.size !== "string") return {
1520
+ ok: false,
1521
+ error: Errors.invalidInput("size", "must be text")
1522
+ };
1523
+ if (input.logoStorageId !== void 0 && typeof input.logoStorageId !== "string") return {
1524
+ ok: false,
1525
+ error: Errors.invalidInput("logoStorageId", "must be a storage ID")
1526
+ };
1527
+ let country;
1528
+ try {
1529
+ country = toCountryCode(input.country);
1530
+ } catch {
1531
+ return {
1532
+ ok: false,
1533
+ error: Errors.invalidInput("country", "must be an ISO country code")
1534
+ };
1535
+ }
1536
+ const bio = input.bio?.trim() ?? "";
1537
+ const size = input.size?.trim() ?? "";
1538
+ return {
1539
+ ok: true,
1540
+ value: {
1541
+ name,
1542
+ handle: handle.value,
1543
+ country,
1544
+ ...bio.length === 0 ? {} : { bio },
1545
+ ...size.length === 0 ? {} : { size },
1546
+ ...input.logoStorageId === void 0 ? {} : { logoStorageId: input.logoStorageId }
1547
+ }
1548
+ };
1549
+ }
1550
+ function organizationWrongState(currentState, validStates, organizationId) {
1551
+ return {
1552
+ ok: false,
1553
+ error: Errors.wrongState({
1554
+ method: CAPXUL_OPERATIONS.onboarding.completeOrganization,
1555
+ currentState,
1556
+ validStates: [...validStates],
1557
+ ...organizationId === void 0 ? {} : { organizationId }
1558
+ })
1559
+ };
1560
+ }
1561
+ function requestCancelled(options) {
1562
+ return options?.signal?.aborted === true;
1563
+ }
1564
+ async function beginOrganization(deps, input, options) {
1565
+ const normalized = normalizeOrganization(input);
1566
+ if (!normalized.ok) return normalized;
1567
+ const setup = deps.organizationSetup;
1568
+ if (setup === void 0) return {
1569
+ ok: false,
1570
+ error: Errors.notImplemented("organizationSetup", "begin")
1571
+ };
1572
+ const captured = deps.authLifetime();
1573
+ try {
1574
+ const session = await deps.restoreSession(options);
1575
+ if (!session.ok) return session;
1576
+ if (session.value === null) return {
1577
+ ok: false,
1578
+ error: Errors.notAuthenticated()
1579
+ };
1580
+ const authUserId = String(session.value.authUserId);
1581
+ const current = () => deps.matchesAuthLifetime(captured, authUserId) && !requestCancelled(options);
1582
+ if (!current()) return cancelledOrganization();
1583
+ const profile = await deps.loadProfile(options);
1584
+ if (!current()) return cancelledOrganization();
1585
+ if (!profile.ok) return profile;
1586
+ if (profile.value?.displayName === null || profile.value?.handle === null || profile.value === null) return organizationWrongState("profile-incomplete", ["profile-complete"]);
1587
+ const refreshed = await deps.refreshIdentity(options);
1588
+ if (!current()) return cancelledOrganization();
1589
+ if (!refreshed.ok) return refreshed;
1590
+ const claimed = await reachClaimedAccount(deps, options);
1591
+ if (!current()) return cancelledOrganization();
1592
+ if (!claimed.ok) return claimed;
1593
+ const begun = await setup.begin(normalized.value);
1594
+ if (!current()) return cancelledOrganization();
1595
+ if (begun.ok) {
1596
+ await deps.sendIdentity({
1597
+ _tag: "AttachOrganization",
1598
+ orgId: String(begun.value.orgId)
1599
+ }, options);
1600
+ if (!current()) return cancelledOrganization();
1601
+ }
1602
+ return begun;
1603
+ } catch (cause) {
1604
+ return failed(cause);
1605
+ }
1606
+ }
1607
+ function setupFailure(setup) {
1608
+ const issue = setup.issue;
1609
+ const details = {
1610
+ ...issue?.error?.details,
1611
+ organizationId: String(setup.orgId),
1612
+ ...issue !== null && { setupReason: issue.reason }
1613
+ };
1614
+ if (issue?.error !== void 0) {
1615
+ const error = issue.error;
1616
+ return new CapxulError(error.code, error.message, {
1617
+ details,
1618
+ ...error.mode === void 0 ? {} : { mode: error.mode },
1619
+ ...error.chain === void 0 ? {} : { chain: error.chain },
1620
+ ...error.layer === void 0 ? {} : { layer: error.layer },
1621
+ ...error.correlationId === void 0 ? {} : { correlationId: error.correlationId }
1622
+ });
1623
+ }
1624
+ const fallback = Errors.wrongState({
1625
+ method: CAPXUL_OPERATIONS.onboarding.completeOrganization,
1626
+ currentState: setup.status,
1627
+ validStates: ["ready"],
1628
+ organizationId: String(setup.orgId)
1629
+ });
1630
+ return new CapxulError(fallback.code, fallback.message, { details: {
1631
+ ...fallback.details,
1632
+ ...details
1633
+ } });
1634
+ }
1635
+ const cancelledOrganization = () => ({
1636
+ ok: false,
1637
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completeOrganization })
1638
+ });
1639
+ function awaitReady(deps, orgId, options) {
1640
+ const setup = deps.organizationSetup;
1641
+ if (setup === void 0) return Promise.resolve({
1642
+ ok: false,
1643
+ error: Errors.notImplemented("organizationSetup", "subscribe")
1644
+ });
1645
+ const lifetime = deps.authLifetime();
1646
+ const isCurrent = () => deps.matchesAuthLifetime(lifetime, lifetime.authUserId);
1647
+ return new Promise((resolve) => {
1648
+ let done = false;
1649
+ let stopRemote = null;
1650
+ let stopIdentity = null;
1651
+ const finish = (result) => {
1652
+ if (done) return;
1653
+ done = true;
1654
+ stopRemote?.();
1655
+ stopIdentity?.();
1656
+ options?.signal?.removeEventListener("abort", onAbort);
1657
+ resolve(result);
1658
+ };
1659
+ const onAbort = () => finish(cancelledOrganization());
1660
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
1661
+ const releaseIdentity = deps.subscribeIdentity((state) => {
1662
+ if (state.phase !== "authenticated" || !deps.matchesAuthLifetime(lifetime, state.session.authUserId)) finish(cancelledOrganization());
1663
+ });
1664
+ if (done) releaseIdentity();
1665
+ else stopIdentity = releaseIdentity;
1666
+ if (options?.signal?.aborted || !isCurrent()) return onAbort();
1667
+ setup.subscribe(orgId, (snapshot) => {
1668
+ if (done) return;
1669
+ if (!isCurrent()) return finish(cancelledOrganization());
1670
+ if (snapshot.status === "loading") return;
1671
+ if (snapshot.status === "error") return finish({
1672
+ ok: false,
1673
+ error: snapshot.error
1674
+ });
1675
+ if (snapshot.value.status === "ready") return finish({
1676
+ ok: true,
1677
+ value: { organizationId: orgId }
1678
+ });
1679
+ if (snapshot.value.status === "needsAttention" || snapshot.value.status === "failed") finish({
1680
+ ok: false,
1681
+ error: setupFailure(snapshot.value)
1682
+ });
1683
+ }, options).then((result) => {
1684
+ if (!result.ok) return finish(result);
1685
+ if (done) result.value();
1686
+ else stopRemote = result.value;
1687
+ }).catch((cause) => finish(failed(cause)));
1688
+ });
1689
+ }
1690
+ //#endregion
1691
+ //#region src/profiles/onboardings.ts
1692
+ /** A thrown failure as a result: a CapxulError keeps its code; anything else is a defect. */
1693
+ function failed(cause) {
1694
+ return {
1695
+ ok: false,
1696
+ error: isCapxulError(cause) ? cause : Errors.unknown(cause)
1697
+ };
1698
+ }
1699
+ function makeOnboardingMethods(deps) {
1700
+ let personFlight = null;
1701
+ return {
1702
+ async listOrganizationSetups(input, options) {
1703
+ if (requestCancelled(options)) return cancelledOrganization();
1704
+ if (deps.organizationSetup === void 0) return {
1705
+ ok: false,
1706
+ error: Errors.notImplemented("organizationSetup", "listMine")
1707
+ };
1708
+ const lifetime = deps.authLifetime();
1709
+ const session = await deps.restoreSession(options);
1710
+ if (!session.ok) return session;
1711
+ if (session.value === null) return {
1712
+ ok: false,
1713
+ error: Errors.notAuthenticated()
1714
+ };
1715
+ if (!deps.matchesAuthLifetime(lifetime, String(session.value.authUserId)) || requestCancelled(options)) return cancelledOrganization();
1716
+ const listed = await deps.organizationSetup.listMine(input, options);
1717
+ return deps.matchesAuthLifetime(lifetime, String(session.value.authUserId)) && !requestCancelled(options) ? listed : cancelledOrganization();
1718
+ },
1719
+ async completePerson(input, options) {
1720
+ if (options?.signal?.aborted) return cancelledPerson();
1721
+ if (typeof input !== "object" || input === null || typeof input.displayName !== "string" || typeof input.country !== "string") return {
1722
+ ok: false,
1723
+ error: Errors.invalidInput("profile", "displayName and country are required")
1724
+ };
1725
+ const handle = normalizeHandleInput(input.handle);
1726
+ if (!handle.ok) return handle;
1727
+ const entryAuthority = deps.authLifetime();
1728
+ if (personFlight !== null && personFlight.generation === entryAuthority.generation) {
1729
+ if (personFlight.handle !== handle.value || !samePersonInput(personFlight.input, input)) return {
1730
+ ok: false,
1731
+ error: Errors.wrongState({
1732
+ method: CAPXUL_OPERATIONS.onboarding.completePerson,
1733
+ currentState: "settingUp",
1734
+ validStates: ["ready"]
1735
+ })
1736
+ };
1737
+ const held = personFlight;
1738
+ if (!await waitForRestoreReady(held.ready, options?.signal)) return cancelledPerson();
1739
+ const verified = await deps.restoreSession(options);
1740
+ if (!verified.ok) return verified;
1741
+ if (verified.value === null) return {
1742
+ ok: false,
1743
+ error: Errors.notAuthenticated()
1744
+ };
1745
+ if (held.authUserId() === String(verified.value.authUserId) && deps.matchesAuthLifetime({
1746
+ generation: held.generation,
1747
+ authUserId: held.authUserId()
1748
+ }, held.authUserId())) {
1749
+ const result = await joinPerson(held.pending, options?.signal);
1750
+ return deps.matchesAuthLifetime({
1751
+ generation: held.generation,
1752
+ authUserId: held.authUserId()
1753
+ }, held.authUserId()) ? result : cancelledPerson();
1754
+ }
1755
+ }
1756
+ let ownerAuthUserId = null;
1757
+ let readyReported = false;
1758
+ let markReady;
1759
+ const ready = new Promise((resolve) => {
1760
+ markReady = resolve;
1761
+ });
1762
+ const pending = (async () => {
1763
+ try {
1764
+ const isCancelled = () => options?.signal?.aborted === true;
1765
+ const session = await deps.restoreSession(options);
1766
+ if (!session.ok) return session;
1767
+ if (session.value === null) return {
1768
+ ok: false,
1769
+ error: Errors.notAuthenticated()
1770
+ };
1771
+ ownerAuthUserId = String(session.value.authUserId);
1772
+ readyReported = true;
1773
+ markReady();
1774
+ const isCurrent = () => {
1775
+ return deps.matchesAuthLifetime(entryAuthority, ownerAuthUserId);
1776
+ };
1777
+ if (!isCurrent()) return cancelledPerson();
1778
+ const available = await deps.handleAvailable(handle.value, options);
1779
+ if (!isCurrent()) return cancelledPerson();
1780
+ if (!available.ok) return available;
1781
+ if (!available.value.available) return {
1782
+ ok: false,
1783
+ error: Errors.invalidInput("handle", "handle is already taken")
1784
+ };
1785
+ if (!isCurrent()) return cancelledPerson();
1786
+ const completed = await deps.completeProfile({
1787
+ ...input,
1788
+ handle: available.value.normalized
1789
+ }, options);
1790
+ if (!isCurrent()) return cancelledPerson();
1791
+ if (!completed.ok) return completed;
1792
+ const refreshed = await deps.refreshIdentity(options);
1793
+ if (!isCurrent()) return cancelledPerson();
1794
+ if (!refreshed.ok) return refreshed;
1795
+ if (isCancelled()) return {
1796
+ ok: false,
1797
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completePerson })
1798
+ };
1799
+ if (!isCurrent()) return cancelledPerson();
1800
+ const ensured = await deps.ensureAccount(options);
1801
+ if (!isCurrent()) return cancelledPerson();
1802
+ if (!ensured.ok) return ensured;
1803
+ if (isCancelled()) return {
1804
+ ok: false,
1805
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completePerson })
1806
+ };
1807
+ const lifecycle = await deps.getLifecycle();
1808
+ if (!isCurrent()) return cancelledPerson();
1809
+ if (!lifecycle.ok) return lifecycle;
1810
+ if (isCancelled()) return {
1811
+ ok: false,
1812
+ error: Errors.cancelled({ operation: CAPXUL_OPERATIONS.onboarding.completePerson })
1813
+ };
1814
+ if (lifecycle.value.status !== "ready") return {
1815
+ ok: false,
1816
+ error: Errors.wrongState({
1817
+ method: CAPXUL_OPERATIONS.onboarding.completePerson,
1818
+ currentState: lifecycle.value.status,
1819
+ validStates: ["ready"]
1820
+ })
1821
+ };
1822
+ const profile = await deps.loadProfile(options);
1823
+ if (!isCurrent()) return cancelledPerson();
1824
+ if (!profile.ok) return profile;
1825
+ if (profile.value === null || profile.value.displayName === null || profile.value.handle !== available.value.normalized) return {
1826
+ ok: false,
1827
+ error: Errors.wrongState({
1828
+ method: CAPXUL_OPERATIONS.onboarding.completePerson,
1829
+ currentState: "profile-incomplete",
1830
+ validStates: ["profile-complete"]
1831
+ })
1832
+ };
1833
+ if (!isCurrent()) return cancelledPerson();
1834
+ return {
1835
+ ok: true,
1836
+ value: {
1837
+ profile: {
1838
+ ...profile.value,
1839
+ displayName: profile.value.displayName,
1840
+ handle: profile.value.handle
1841
+ },
1842
+ account: lifecycle.value
1843
+ }
1844
+ };
1845
+ } finally {
1846
+ if (!readyReported) markReady();
1847
+ }
1848
+ })();
1849
+ personFlight = {
1850
+ input,
1851
+ handle: handle.value,
1852
+ generation: entryAuthority.generation,
1853
+ ready,
1854
+ authUserId: () => ownerAuthUserId,
1855
+ pending
1856
+ };
1857
+ try {
1858
+ return await pending;
1859
+ } finally {
1860
+ if (personFlight?.pending === pending) personFlight = null;
1861
+ }
1862
+ },
1863
+ beginOrganization(input, options) {
1864
+ return beginOrganization(deps, input, options);
1865
+ },
1866
+ async completeOrganization(input, options) {
1867
+ try {
1868
+ const begun = await beginOrganization(deps, input, options);
1869
+ if (!begun.ok) return begun;
1870
+ const lifetime = deps.authLifetime();
1871
+ const isCurrent = () => deps.matchesAuthLifetime(lifetime, lifetime.authUserId);
1872
+ if (!isCurrent()) return cancelledOrganization();
1873
+ const { orgId } = begun.value;
1874
+ let setup = begun.value.setup;
1875
+ if (setup.status === "awaitingAuthorization") {
1876
+ const authorized = await deps.organizationSetup.authorize(orgId, options);
1877
+ if (!isCurrent()) return cancelledOrganization();
1878
+ if (!authorized.ok) return authorized;
1879
+ setup = authorized.value;
1880
+ }
1881
+ if (requestCancelled(options) || !isCurrent()) return cancelledOrganization();
1882
+ if (setup.status === "ready") return {
1883
+ ok: true,
1884
+ value: { organizationId: orgId }
1885
+ };
1886
+ if (setup.status === "needsAttention" || setup.status === "failed") return {
1887
+ ok: false,
1888
+ error: setupFailure(setup)
1889
+ };
1890
+ return awaitReady(deps, orgId, options);
1891
+ } catch (cause) {
1892
+ return failed(cause);
1893
+ }
1894
+ }
1895
+ };
1896
+ }
1897
+ //#endregion
1898
+ //#region src/platform/production-sessions.ts
1899
+ function resolveProductionObservability(input) {
1900
+ const observation = input.observability?.failures ?? input.observation;
1901
+ const telemetry = input.observability?.product ?? input.telemetry;
1902
+ const invocationObservability = input.invocationObservability;
1903
+ return {
1904
+ observation,
1905
+ telemetry,
1906
+ invocationObservation: invocationObservability?.failures ?? observation,
1907
+ invocationTelemetry: invocationObservability?.product ?? telemetry
1908
+ };
1909
+ }
1910
+ function hasConnectionObserver(port) {
1911
+ return "observeConnection" in port && typeof port.observeConnection === "function";
1912
+ }
1913
+ function observeProductionConvexConnection(convexCall, context) {
1914
+ if (!hasConnectionObserver(convexCall)) return () => {};
1915
+ const runPromise = Effect.runPromiseWith(context);
1916
+ try {
1917
+ return convexCall.observeConnection((diagnostic) => {
1918
+ const message = diagnostic.transition === "connected" ? Effect.logInfo("convex.connection.changed") : Effect.logWarning("convex.connection.changed");
1919
+ runPromise(message.pipe(Effect.annotateLogs(diagnostic), Effect.ignoreCause)).catch(() => void 0);
1920
+ });
1921
+ } catch {
1922
+ return () => {};
1923
+ }
1924
+ }
1925
+ function refreshConvexAuthOnSession(authClient, refresh) {
1926
+ return {
1927
+ ...authClient,
1928
+ verifyOtp: (input, options) => authClient.verifyOtp(input, options).pipe(Effect.tap(() => Effect.sync(refresh))),
1929
+ signOut: (options) => authClient.signOut(options).pipe(Effect.tap(() => Effect.sync(refresh)))
1930
+ };
1931
+ }
1932
+ /**
1933
+ * Signer-session reset, as a port wrapper INSIDE the Layer graph (blueprint §2:
1934
+ * "wrappers become Layers, not post-hoc spreads/Proxies").
1935
+ *
1936
+ * This was a spread over the assembled client — `{...client, auth: {...,
1937
+ * signOut}}` — applied after composition finished, so the reset lived on one
1938
+ * particular object rather than on the auth seam itself. Here it wraps
1939
+ * `AuthClientPort.signOut`, so it holds for every route to a sign-out.
1940
+ *
1941
+ * Capture the sign-out exit before reset. This preserves the old `try/finally`
1942
+ * order and keeps a reset failure in the typed error channel.
1943
+ */
1944
+ function resetSignerSessionOnSignOut(authClient, resetRef) {
1945
+ return {
1946
+ ...authClient,
1947
+ signOut: (options) => Effect.gen(function* () {
1948
+ const exit = yield* Effect.exit(authClient.signOut(options));
1949
+ yield* Effect.try({
1950
+ try: () => resetRef.current?.(),
1951
+ catch: (cause) => new AuthClientError({
1952
+ operation: "signOut",
1953
+ kind: "signer",
1954
+ cause
1955
+ })
1956
+ });
1957
+ return yield* exit;
1958
+ })
1959
+ };
1960
+ }
1961
+ //#endregion
1962
+ //#region src/platform/transport/browser-lifecycle.ts
1963
+ var BrowserLifecycleMonitor = class {
1964
+ #listeners = [];
1965
+ #last = {};
1966
+ constructor() {
1967
+ const windowTarget = eventTarget(globalThis.window);
1968
+ const documentTarget = eventTarget(globalThis.document);
1969
+ if (windowTarget === void 0 || documentTarget === void 0) return;
1970
+ this.#listen(documentTarget, "visibilitychange", () => {
1971
+ this.#record(globalThis.document?.visibilityState === "hidden" ? "visibility-hidden" : "visibility-visible");
1972
+ });
1973
+ this.#listen(windowTarget, "pagehide", () => this.#record("pagehide"));
1974
+ this.#listen(windowTarget, "pageshow", () => this.#record("pageshow"));
1975
+ this.#listen(windowTarget, "online", () => this.#record("online"));
1976
+ this.#listen(windowTarget, "offline", () => this.#record("offline"));
1977
+ }
1978
+ socketClose(event) {
1979
+ const now = Date.now();
1980
+ return {
1981
+ code: event.code,
1982
+ wasClean: event.wasClean,
1983
+ reasonPresent: event.reason.length > 0,
1984
+ ...this.#last.event === void 0 ? {} : { browserEvent: this.#last.event },
1985
+ ...this.#last.at === void 0 ? {} : { msSinceBrowserEvent: Math.max(0, now - this.#last.at) },
1986
+ ...globalThis.document?.visibilityState === void 0 ? {} : { documentVisibility: globalThis.document.visibilityState },
1987
+ ...globalThis.navigator?.onLine === void 0 ? {} : { navigatorOnline: globalThis.navigator.onLine }
1988
+ };
1989
+ }
1990
+ close() {
1991
+ for (const [target, name, listener] of this.#listeners) target.removeEventListener(name, listener);
1992
+ this.#listeners.length = 0;
1993
+ }
1994
+ #listen(target, name, listener) {
1995
+ target.addEventListener(name, listener);
1996
+ this.#listeners.push([
1997
+ target,
1998
+ name,
1999
+ listener
2000
+ ]);
2001
+ }
2002
+ #record(event) {
2003
+ this.#last = {
2004
+ event,
2005
+ at: Date.now()
2006
+ };
2007
+ }
2008
+ };
2009
+ function eventTarget(value) {
2010
+ return isEventTarget(value) ? value : void 0;
2011
+ }
2012
+ function isEventTarget(value) {
2013
+ return isRecord(value) && typeof value.addEventListener === "function" && typeof value.removeEventListener === "function" && typeof value.dispatchEvent === "function";
2014
+ }
2015
+ //#endregion
2016
+ //#region src/platform/transport/connection-monitor.ts
2017
+ var ConvexConnectionMonitor = class {
2018
+ #connectionId = makeConnectionId();
2019
+ #browser = new BrowserLifecycleMonitor();
2020
+ #lastClose;
2021
+ observedWebSocketConstructor() {
2022
+ const NativeWebSocket = globalThis.WebSocket;
2023
+ if (typeof globalThis.window === "undefined" || NativeWebSocket === void 0) return void 0;
2024
+ return new Proxy(NativeWebSocket, { construct: (Target, args) => {
2025
+ const socket = new Target(...args);
2026
+ socket.addEventListener("close", (event) => {
2027
+ this.#lastClose = this.#browser.socketClose(event);
2028
+ });
2029
+ return socket;
2030
+ } });
2031
+ }
2032
+ close() {
2033
+ this.#browser.close();
2034
+ }
2035
+ observe(client, observer) {
2036
+ const subscribe = client.subscribeToConnectionState?.bind(client);
2037
+ if (subscribe === void 0) return () => this.#browser.close();
2038
+ let previous;
2039
+ const unsubscribe = subscribe((next) => {
2040
+ const diagnostics = this.#diagnostics(previous, next);
2041
+ previous = next;
2042
+ for (const diagnostic of diagnostics) emitDiagnostic(observer, diagnostic);
2043
+ });
2044
+ if (previous === void 0) {
2045
+ const current = safeConnectionState(client);
2046
+ if (current !== void 0) {
2047
+ previous = current;
2048
+ for (const diagnostic of this.#diagnostics(void 0, current)) emitDiagnostic(observer, diagnostic);
2049
+ }
2050
+ }
2051
+ let closed = false;
2052
+ return () => {
2053
+ if (closed) return;
2054
+ closed = true;
2055
+ try {
2056
+ unsubscribe();
2057
+ } finally {
2058
+ this.#browser.close();
2059
+ }
2060
+ };
2061
+ }
2062
+ reconnectEvidence(start, end) {
2063
+ if (!connectionRestarted(start, end)) return void 0;
2064
+ return {
2065
+ kind: "connection-lost-in-flight",
2066
+ connection_id: this.#connectionId,
2067
+ start_connection_count: start.connectionCount,
2068
+ connection_count: end.connectionCount,
2069
+ ...end.connectionRetries === void 0 ? {} : { connection_retries: end.connectionRetries },
2070
+ ...closeFields(this.#lastClose)
2071
+ };
2072
+ }
2073
+ #diagnostics(previous, next) {
2074
+ const transitions = connectionTransitions(previous, next);
2075
+ if (transitions.length === 0) return [];
2076
+ const oldest = next.timeOfOldestInflightRequest;
2077
+ const oldestInflightMs = oldest instanceof Date && Number.isFinite(oldest.getTime()) ? Math.max(0, Date.now() - oldest.getTime()) : void 0;
2078
+ const shared = {
2079
+ previous_connected: previous?.isWebSocketConnected ?? false,
2080
+ connected: next.isWebSocketConnected,
2081
+ ...next.hasEverConnected === void 0 ? {} : { has_ever_connected: next.hasEverConnected },
2082
+ connection_count: next.connectionCount,
2083
+ ...next.connectionRetries === void 0 ? {} : { connection_retries: next.connectionRetries },
2084
+ ...next.hasInflightRequests === void 0 ? {} : { has_inflight_requests: next.hasInflightRequests },
2085
+ ...next.inflightActions === void 0 ? {} : { inflight_actions: next.inflightActions },
2086
+ ...next.inflightMutations === void 0 ? {} : { inflight_mutations: next.inflightMutations },
2087
+ ...oldestInflightMs === void 0 ? {} : { oldest_inflight_ms: oldestInflightMs },
2088
+ ...closeFields(this.#lastClose)
2089
+ };
2090
+ return transitions.map((transition) => ({
2091
+ connection_id: this.#connectionId,
2092
+ transition,
2093
+ ...shared
2094
+ }));
2095
+ }
2096
+ };
2097
+ function connectionRestarted(start, end) {
2098
+ if (start === void 0 || end === void 0) return false;
2099
+ const opensBeforeCancellation = start.isWebSocketConnected ? 1 : 2;
2100
+ return end.connectionCount >= start.connectionCount + opensBeforeCancellation;
2101
+ }
2102
+ function connectionTransitions(previous, next) {
2103
+ if (previous === void 0) {
2104
+ if (next.isWebSocketConnected) return (next.connectionRetries ?? 0) > 0 ? ["retrying", "connected"] : ["connected"];
2105
+ return (next.connectionRetries ?? 0) > 0 ? ["disconnected", "retrying"] : [];
2106
+ }
2107
+ const transitions = [];
2108
+ if (previous.isWebSocketConnected !== next.isWebSocketConnected) transitions.push(next.isWebSocketConnected ? "connected" : "disconnected");
2109
+ if ((next.connectionRetries ?? 0) > (previous.connectionRetries ?? 0)) transitions.push("retrying");
2110
+ return transitions;
2111
+ }
2112
+ function emitDiagnostic(observer, diagnostic) {
2113
+ try {
2114
+ observer(diagnostic);
2115
+ } catch {}
2116
+ }
2117
+ function safeConnectionState(client) {
2118
+ try {
2119
+ return client.connectionState?.();
2120
+ } catch {
2121
+ return;
2122
+ }
2123
+ }
2124
+ function closeFields(close) {
2125
+ if (close === void 0) return {};
2126
+ return {
2127
+ close_code: close.code,
2128
+ close_was_clean: close.wasClean,
2129
+ close_reason_present: close.reasonPresent,
2130
+ ...close.documentVisibility === void 0 ? {} : { document_visibility: close.documentVisibility },
2131
+ ...close.navigatorOnline === void 0 ? {} : { navigator_online: close.navigatorOnline },
2132
+ ...close.browserEvent === void 0 ? {} : { last_browser_event: close.browserEvent },
2133
+ ...close.msSinceBrowserEvent === void 0 ? {} : { ms_since_browser_event: close.msSinceBrowserEvent }
2134
+ };
2135
+ }
2136
+ let fallbackConnectionId = 0;
2137
+ function makeConnectionId() {
2138
+ try {
2139
+ return `convex_${globalThis.crypto.randomUUID()}`;
2140
+ } catch {
2141
+ fallbackConnectionId += 1;
2142
+ return `convex_${Date.now().toString(36)}_${fallbackConnectionId.toString(36)}`;
2143
+ }
2144
+ }
2145
+ //#endregion
2146
+ //#region src/platform/transport/convex-diagnostics.ts
2147
+ const writeConvexDiagnostic = (...args) => {
2148
+ try {
2149
+ console.error(...maskCapturedNetworkRequest({ requestBody: args }).requestBody);
2150
+ } catch {}
2151
+ };
2152
+ const stderrConvexLogger = {
2153
+ log: writeConvexDiagnostic,
2154
+ warn: writeConvexDiagnostic,
2155
+ error: writeConvexDiagnostic,
2156
+ logVerbose: () => void 0
2157
+ };
2158
+ const silentConvexLogger = {
2159
+ log: () => void 0,
2160
+ warn: () => void 0,
2161
+ error: () => void 0,
2162
+ logVerbose: () => void 0
2163
+ };
2164
+ /** Exact floor-first allowlist; every additional handler must migrate its validator first. */
2165
+ const OBSERVED_CONVEX_ACTIONS = new Set(OPERATIONS.filter((row) => row.kind === "action").map((row) => row.path));
2166
+ const OBSERVED_CONVEX_QUERIES = new Set(OPERATIONS.filter((row) => row.kind === "query").map((row) => row.path));
2167
+ const OBSERVED_CONVEX_MUTATIONS = new Set(OPERATIONS.filter((row) => row.kind === "mutation").map((row) => row.path));
2168
+ //#endregion
2169
+ //#region src/platform/transport/convex-errors.ts
2170
+ /**
2171
+ * All a redacting backend says about an error it raised itself: its request
2172
+ * id. The WebSocket client wraps it as `[CONVEX Q(path)] … Called by client`.
2173
+ */
2174
+ const REDACTED_ERROR = /^(?:\[CONVEX [QMA?]\([^)]*\)\] )?\[Request ID: ([^\]\s]+)\] Server Error(?:\s+Called by client)?$/u;
2175
+ function mapToCapxulError(operation, err, transport) {
2176
+ const decoded = decodeConvexError(err);
2177
+ if (decoded !== null) return decoded;
2178
+ if (err instanceof CapxulError) return err;
2179
+ if (transport !== void 0) {
2180
+ const { kind: _kind, start_connection_count: _start, ...connection } = transport;
2181
+ return Errors.networkError(operation, err, {
2182
+ provider: "convex",
2183
+ failure_mode: "upstream-down",
2184
+ reason: "connection-lost-in-flight",
2185
+ ...connection
2186
+ });
2187
+ }
2188
+ if (err instanceof Error && isTransportError(err)) return Errors.networkError(operation, err, { provider: "convex" });
2189
+ return backendFailure(operation, err);
2190
+ }
2191
+ /**
2192
+ * A failure the backend raised outside its own error boundary: Convex
2193
+ * refusing the arguments (`ArgumentValidationError`) is the caller's input; a
2194
+ * function this deployment does not have is `CAPABILITY_UNAVAILABLE` naming
2195
+ * its path, because the backend runs a different version from this client;
2196
+ * anything else (an uncaught "Server Error") is a backend defect. None is a
2197
+ * provider outage. The Convex request id rides in details, so the backend's
2198
+ * log line can be found.
2199
+ *
2200
+ * A deployed backend redacts these errors (`REDACT_LOGS_TO_CLIENT`, #2304):
2201
+ * the message is only the request id, and no structured cause exists, so the
2202
+ * message shape is the only discriminator. Neither case can be told apart, and
2203
+ * the reply says where to look instead. The text matches stay for a backend
2204
+ * that does not redact (development, local).
2205
+ */
2206
+ function backendFailure(operation, err) {
2207
+ const text = err instanceof Error ? err.message : String(err);
2208
+ const redacted = REDACTED_ERROR.exec(text.trim())?.[1];
2209
+ if (redacted !== void 0) return new CapxulError("UNKNOWN", `The backend refused ${operation} and gave no detail. Find Convex request ${redacted} in the backend logs. If the backend can be newer than this client, update the CLI or SDK.`, {
2210
+ cause: err,
2211
+ layer: "convex",
2212
+ details: {
2213
+ operation,
2214
+ provider: "convex",
2215
+ requestId: redacted,
2216
+ reason: "backend-error-redacted"
2217
+ }
2218
+ });
2219
+ const requestId = /\[Request ID: ([^\]\s]+)\]/u.exec(text)?.[1];
2220
+ const where = {
2221
+ operation,
2222
+ provider: "convex",
2223
+ ...requestId === void 0 ? {} : { requestId }
2224
+ };
2225
+ const argument = /ArgumentValidationError:?\s*([^\n]*)/u.exec(text);
2226
+ if (argument !== null) return new CapxulError("INVALID_INPUT", `Invalid arguments for ${operation}`, {
2227
+ cause: err,
2228
+ layer: "convex",
2229
+ details: {
2230
+ field: "args",
2231
+ reason: argument[1] ?? "",
2232
+ ...where
2233
+ }
2234
+ });
2235
+ const path = /Could not find public function for '([^']+)'/u.exec(text)?.[1];
2236
+ if (path !== void 0) return new CapxulError("CAPABILITY_UNAVAILABLE", `The backend has no function ${path}: it runs a different version from this client.`, {
2237
+ cause: err,
2238
+ layer: "convex",
2239
+ details: {
2240
+ reason: "backend-function-missing",
2241
+ path,
2242
+ ...where
2243
+ }
2244
+ });
2245
+ return new CapxulError("UNKNOWN", text, {
2246
+ cause: err,
2247
+ layer: "convex",
2248
+ details: where
2249
+ });
2250
+ }
2251
+ function mapToConvexCallError(operation, err, transport) {
2252
+ return convexCallErrorFromCapxul(operation, mapToCapxulError(operation, err, transport), transport);
2253
+ }
2254
+ function logActionTransportFailure(operation, error) {
2255
+ if (error.transport === void 0) return Effect.void;
2256
+ const { kind, ...connection } = error.transport;
2257
+ return Effect.logWarning("convex.action.connection_lost").pipe(Effect.annotateLogs({
2258
+ operation,
2259
+ failure_mode: "upstream-down",
2260
+ reason: kind,
2261
+ ...connection
2262
+ }), Effect.ignoreCause);
2263
+ }
2264
+ const TRANSPORT_ERROR_CODES = /* @__PURE__ */ new Set([
2265
+ "EAI_AGAIN",
2266
+ "ECONNREFUSED",
2267
+ "ECONNRESET",
2268
+ "ENOTFOUND",
2269
+ "EPIPE",
2270
+ "ETIMEDOUT",
2271
+ "UND_ERR_CONNECT_TIMEOUT",
2272
+ "UND_ERR_HEADERS_TIMEOUT",
2273
+ "UND_ERR_SOCKET"
2274
+ ]);
2275
+ function isTransportError(err) {
2276
+ const seen = /* @__PURE__ */ new Set();
2277
+ let current = err;
2278
+ while (current !== void 0 && !seen.has(current)) {
2279
+ seen.add(current);
2280
+ const code = current.code;
2281
+ if (current.name === "FetchError" || current.name === "NetworkError" || typeof code === "string" && TRANSPORT_ERROR_CODES.has(code)) return true;
2282
+ current = current.cause instanceof Error ? current.cause : void 0;
2283
+ }
2284
+ return false;
2285
+ }
2286
+ //#endregion
2287
+ //#region src/platform/transport/observed-args.ts
2288
+ /**
2289
+ * The args one allowlisted Convex call carries: the caller's own observation
2290
+ * context, the host's, the application and the trace parent, merged and
2291
+ * sanitized. A call off the allowlist, or with nothing to carry, is unchanged.
2292
+ */
2293
+ function observedConvexArgs(input) {
2294
+ const { allowlist, path, args, traceparent } = input;
2295
+ if (!allowlist.has(path)) return args;
2296
+ const invocationSnapshot = readInvocationObservation(args);
2297
+ if (path === diagnostics.health.path && invocationSnapshot?.active === false) {
2298
+ const unobservedArgs = { ...args };
2299
+ delete unobservedArgs.observationContext;
2300
+ return unobservedArgs;
2301
+ }
2302
+ const carriedContext = sanitizeObservationContext(args.observationContext);
2303
+ let hostContext;
2304
+ if (invocationSnapshot !== void 0) hostContext = invocationSnapshot.active ? invocationSnapshot.context : void 0;
2305
+ else {
2306
+ const resolveContext = input.resolveContext;
2307
+ if (resolveContext !== void 0) try {
2308
+ hostContext = resolveContext();
2309
+ } catch (cause) {
2310
+ hostContext = void 0;
2311
+ input.reportContextFailure(cause);
2312
+ }
2313
+ }
2314
+ if (hostContext === void 0 && carriedContext === void 0 && traceparent === void 0) return args;
2315
+ hostContext = sanitizeObservationContext({
2316
+ ...hostContext,
2317
+ ...carriedContext,
2318
+ ...input.applicationId === void 0 ? {} : { applicationId: input.applicationId },
2319
+ ...traceparent === void 0 ? {} : { traceparent }
2320
+ });
2321
+ if (hostContext === void 0) return args;
2322
+ return {
2323
+ ...args,
2324
+ observationContext: hostContext
2325
+ };
2326
+ }
2327
+ //#endregion
2328
+ //#region src/platform/transport/convex-subscription.ts
2329
+ /**
2330
+ * Subscribe one query, and the connection state when the caller asks for it.
2331
+ * The returned stop always finishes, and a callback never runs after it.
2332
+ */
2333
+ function subscribeConvexQuery(client, fn, args, callback, options) {
2334
+ const path = getFunctionName(fn);
2335
+ let active = true;
2336
+ let unsubscribeQuery;
2337
+ let unsubscribeConnection;
2338
+ const stop = () => {
2339
+ if (!active) return;
2340
+ active = false;
2341
+ try {
2342
+ unsubscribeQuery?.();
2343
+ } catch {}
2344
+ try {
2345
+ unsubscribeConnection?.();
2346
+ } catch {}
2347
+ };
2348
+ try {
2349
+ if (options?.onConnectionChange !== void 0) {
2350
+ if (client.connectionState === void 0 || client.subscribeToConnectionState === void 0) throw Errors.notImplemented("convex", "connectionState");
2351
+ let previous;
2352
+ const observe = (state) => {
2353
+ if (!active || previous === state.isWebSocketConnected) return;
2354
+ previous = state.isWebSocketConnected;
2355
+ try {
2356
+ options.onConnectionChange?.(previous);
2357
+ } catch {}
2358
+ };
2359
+ unsubscribeConnection = client.subscribeToConnectionState(observe);
2360
+ observe(client.connectionState());
2361
+ }
2362
+ unsubscribeQuery = client.onUpdate(fn, args, (value) => {
2363
+ if (active) callback({
2364
+ status: "ok",
2365
+ value
2366
+ });
2367
+ }, (err) => {
2368
+ if (active) callback({
2369
+ status: "error",
2370
+ error: mapToCapxulError(path, err)
2371
+ });
2372
+ });
2373
+ if (!active) unsubscribeQuery();
2374
+ return stop;
2375
+ } catch (cause) {
2376
+ stop();
2377
+ throw cause;
2378
+ }
2379
+ }
2380
+ //#endregion
2381
+ //#region src/platform/transport/convex-adapter.ts
2382
+ /**
2383
+ * A socket that has not emitted `close` within this window is not going to.
2384
+ * ponytail: one fixed bound; `closeTimeoutMs` is the knob if a slow runtime
2385
+ * ever needs a different one.
2386
+ */
2387
+ const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
2388
+ /**
2389
+ * The fetcher the Convex client's auth handshake gets. Convex first asks for
2390
+ * "a possibly cached token" (`forceRefreshToken: false`) and, as soon as the
2391
+ * server accepts it, for a fresh one (`true`): the token whose `exp` it
2392
+ * schedules the next refresh from. The Better Auth clients keep no token: each
2393
+ * ask mints one over the network, so the cached ask was a second token fetch
2394
+ * and a second server verification (a JWKS read) on every session restore. It
2395
+ * answers "none" instead, and Convex makes its one fetch forced, as a fresh
2396
+ * token. Every forced ask still reaches the auth client: that first fetch, the
2397
+ * refresh before `exp`, and the retry after the server rejects a token.
2398
+ * ponytail: the OAuth bearer client can hold a cached exchange; it now pays one
2399
+ * exchange for the first authentication instead of reusing it (never more
2400
+ * fetches than before). Pass the cache through if that start ever matters.
2401
+ */
2402
+ function freshTokenHandshake(fetchToken) {
2403
+ return async (args) => args.forceRefreshToken ? fetchToken(args) : null;
2404
+ }
2405
+ /** Run a Convex call under the current trace parent, when there is one. */
2406
+ const withTraceparent = (run) => Effect.flatMap(Effect.serviceOption(Tracer.ParentSpan), (parent) => run(parent._tag === "Some" ? formatTraceparent(parent.value) : void 0));
2407
+ var ConvexCallAdapter = class {
2408
+ #client;
2409
+ #convexUrl;
2410
+ #logger;
2411
+ #tokenProvider;
2412
+ #applicationId;
2413
+ #observation;
2414
+ #connectionMonitor;
2415
+ #closeTimeoutMs;
2416
+ constructor(deps) {
2417
+ this.#convexUrl = deps.convexUrl;
2418
+ this.#logger = deps.localDiagnostics === true ? stderrConvexLogger : silentConvexLogger;
2419
+ this.#closeTimeoutMs = deps.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
2420
+ this.#connectionMonitor = new ConvexConnectionMonitor();
2421
+ const webSocketConstructor = this.#connectionMonitor.observedWebSocketConstructor();
2422
+ this.#client = deps.client ?? new ConvexClient(deps.convexUrl, {
2423
+ ...webSocketConstructor === void 0 ? {} : { webSocketConstructor },
2424
+ logger: this.#logger
2425
+ });
2426
+ this.#tokenProvider = deps.tokenProvider;
2427
+ this.#applicationId = deps.applicationId;
2428
+ this.#observation = deps.observation;
2429
+ this.refreshAuth();
2430
+ }
2431
+ refreshAuth() {
2432
+ if (this.#tokenProvider) this.#client.setAuth(freshTokenHandshake(this.#tokenProvider));
2433
+ }
2434
+ observeConnection(observer) {
2435
+ return this.#connectionMonitor.observe(this.#client, observer);
2436
+ }
2437
+ query(fn, args, options) {
2438
+ const path = getFunctionName(fn);
2439
+ return withTraceparent((traceparent) => {
2440
+ return Effect.tryPromise({
2441
+ try: (signal) => {
2442
+ const observed = this.#observedArgs(OBSERVED_CONVEX_QUERIES, path, args, traceparent);
2443
+ return options?.fresh === true ? this.#freshQuery(fn, observed, signal, path) : this.#client.query(fn, observed);
2444
+ },
2445
+ catch: (cause) => mapToConvexCallError(path, cause)
2446
+ });
2447
+ });
2448
+ }
2449
+ async #freshQuery(fn, args, signal, path) {
2450
+ const token = await this.#tokenProvider?.({ forceRefreshToken: false });
2451
+ if (signal.aborted) throw Errors.cancelled({ operation: path });
2452
+ const client = new ConvexHttpClient(this.#convexUrl, {
2453
+ logger: this.#logger,
2454
+ fetch: (input, init) => globalThis.fetch(input, {
2455
+ ...init,
2456
+ signal
2457
+ })
2458
+ });
2459
+ if (token !== void 0 && token !== null) client.setAuth(token);
2460
+ const query = fn;
2461
+ return client.query(query, args);
2462
+ }
2463
+ mutation(fn, args) {
2464
+ const path = getFunctionName(fn);
2465
+ return withTraceparent((traceparent) => {
2466
+ return Effect.tryPromise({
2467
+ try: () => this.#client.mutation(fn, this.#observedArgs(OBSERVED_CONVEX_MUTATIONS, path, args, traceparent)),
2468
+ catch: (cause) => mapToConvexCallError(path, cause)
2469
+ });
2470
+ });
2471
+ }
2472
+ action(fn, args) {
2473
+ const path = getFunctionName(fn);
2474
+ return withTraceparent((traceparent) => {
2475
+ const connectionAtStart = connectionState(this.#client);
2476
+ return Effect.tryPromise({
2477
+ try: () => this.#client.action(fn, this.#observedArgs(OBSERVED_CONVEX_ACTIONS, path, args, traceparent)),
2478
+ catch: (cause) => {
2479
+ const transport = this.#connectionMonitor.reconnectEvidence(connectionAtStart, connectionState(this.#client));
2480
+ return mapToConvexCallError(path, cause, transport);
2481
+ }
2482
+ }).pipe(Effect.tapError((error) => logActionTransportFailure(path, error)));
2483
+ });
2484
+ }
2485
+ #observedArgs(allowlist, path, args, traceparent) {
2486
+ return observedConvexArgs({
2487
+ allowlist,
2488
+ path,
2489
+ args,
2490
+ traceparent,
2491
+ applicationId: this.#applicationId,
2492
+ resolveContext: this.#observation?.resolveContext,
2493
+ reportContextFailure: (cause) => this.#reportContextFailure(cause)
2494
+ });
2495
+ }
2496
+ #contextFailureReported = false;
2497
+ #reportContextFailure(cause) {
2498
+ if (this.#contextFailureReported) return;
2499
+ this.#contextFailureReported = true;
2500
+ console.warn("[capxul-sdk] observation resolveContext failed; calls are uncorrelated", { reason: cause instanceof Error ? cause.message : String(cause) });
2501
+ }
2502
+ subscribe(fn, args, callback, options) {
2503
+ return Effect.try({
2504
+ try: () => subscribeConvexQuery(this.#client, fn, args, callback, options),
2505
+ catch: (cause) => mapToConvexCallError(getFunctionName(fn), cause)
2506
+ }).pipe(Effect.tap(() => Effect.sync(() => {
2507
+ callback({ status: "loading" });
2508
+ })));
2509
+ }
2510
+ /**
2511
+ * Teardown always completes. `ConvexClient.close()` resolves only when the
2512
+ * socket emits `close`, and `WebSocketManager.close()` in the `connecting`
2513
+ * state arms `ws.onopen = () => ws.close()` without ever closing the socket
2514
+ * itself — so a handshake that stalls right after a server-initiated close
2515
+ * (code 1012, which is what a Convex deploy sends) leaves that promise
2516
+ * pending for as long as the page lives. Convex marks the client closed and
2517
+ * terminates the socket manager synchronously before returning it, so
2518
+ * abandoning it drops nothing. A rejection is dropped for the same reason —
2519
+ * the layer release turns it into a defect that fails the whole scope close,
2520
+ * and teardown has no caller who can act on it.
2521
+ */
2522
+ async close() {
2523
+ this.#connectionMonitor.close();
2524
+ await settledWithin(this.#client.close(), this.#closeTimeoutMs);
2525
+ }
2526
+ };
2527
+ function settledWithin(promise, timeoutMs) {
2528
+ return new Promise((resolve) => {
2529
+ const timer = setTimeout(resolve, timeoutMs);
2530
+ const finish = () => {
2531
+ clearTimeout(timer);
2532
+ resolve();
2533
+ };
2534
+ promise.then(finish, finish);
2535
+ });
2536
+ }
2537
+ function ConvexCallLayer(deps) {
2538
+ return Layer.effect(ConvexCallPortTag, Effect.acquireRelease(Effect.sync(() => new ConvexCallAdapter(deps)), (adapter) => Effect.promise(() => adapter.close()).pipe(Effect.orDie)));
2539
+ }
2540
+ function connectionState(client) {
2541
+ try {
2542
+ const state = client.connectionState?.();
2543
+ return typeof state?.connectionCount === "number" ? state : void 0;
2544
+ } catch {
2545
+ return;
2546
+ }
2547
+ }
2548
+ //#endregion
2549
+ //#region src/observability/telemetry/clone.ts
2550
+ function cloneTelemetryProps(props) {
2551
+ const cloned = {};
2552
+ for (const [key, value] of Object.entries(props)) cloned[key] = cloneTelemetryValue(value);
2553
+ return cloned;
2554
+ }
2555
+ function cloneTelemetryEvent(event) {
2556
+ return event.props === void 0 ? { name: event.name } : {
2557
+ name: event.name,
2558
+ props: cloneTelemetryProps(event.props)
2559
+ };
2560
+ }
2561
+ function cloneTelemetryValue(value) {
2562
+ if (Array.isArray(value)) return value.map(cloneTelemetryValue);
2563
+ if (value === null || typeof value !== "object") return value;
2564
+ if (Object.getPrototypeOf(value) !== Object.prototype) return value;
2565
+ const cloned = {};
2566
+ for (const [key, nested] of Object.entries(value)) cloned[key] = cloneTelemetryValue(nested);
2567
+ return cloned;
2568
+ }
2569
+ //#endregion
2570
+ //#region src/observability/telemetry/posthog-adapter.ts
2571
+ /** The PostHog transport refused one call; its reason rides the drop log. */
2572
+ var TelemetryTransportRejected = class extends Data.TaggedError("TelemetryTransportRejected") {};
2573
+ var PostHogTelemetryAdapter = class {
2574
+ #capture;
2575
+ #identify;
2576
+ #group;
2577
+ #reset;
2578
+ constructor(deps) {
2579
+ this.#capture = deps.capture;
2580
+ this.#identify = deps.identify ?? (() => void 0);
2581
+ this.#group = deps.group ?? (() => void 0);
2582
+ this.#reset = deps.reset ?? (() => void 0);
2583
+ }
2584
+ emit(event) {
2585
+ return this.#run(() => this.#capture(event.name, redactTelemetryProps(event.name, cloneTelemetryEvent(event).props), event), "emit", event.name);
2586
+ }
2587
+ identify(input) {
2588
+ return this.#run(() => this.#identify(cloneIdentifyInput(input)), "identify");
2589
+ }
2590
+ group(input) {
2591
+ return this.#run(() => this.#group(cloneGroupInput(input)), "group");
2592
+ }
2593
+ reset() {
2594
+ return this.#run(() => this.#reset(), "reset");
2595
+ }
2596
+ #run(operation, operationName, eventName) {
2597
+ const diagnose = (reason) => Effect.logWarning("product.telemetry.transport.dropped").pipe(Effect.annotateLogs({
2598
+ operation: operationName,
2599
+ reason: dropReason(reason),
2600
+ ...eventName === void 0 ? {} : { product_event: eventName }
2601
+ }), Effect.ignoreCause);
2602
+ return Effect.suspend(() => {
2603
+ let pending;
2604
+ try {
2605
+ pending = operation();
2606
+ } catch (reason) {
2607
+ return diagnose(reason);
2608
+ }
2609
+ if (pending === void 0) return Effect.void;
2610
+ const transport = Effect.tryPromise({
2611
+ try: () => pending,
2612
+ catch: (reason) => new TelemetryTransportRejected({ reason })
2613
+ }).pipe(Effect.catch((rejected) => diagnose(rejected.reason)));
2614
+ return Effect.forkDetach(transport, { startImmediately: true }).pipe(Effect.asVoid);
2615
+ });
2616
+ }
2617
+ };
2618
+ const DROP_REASON_MAX = 200;
2619
+ function dropReason(reason) {
2620
+ const text = reason instanceof Error ? `${reason.name}: ${reason.message}` : String(reason);
2621
+ return text.length <= DROP_REASON_MAX ? text : `${text.slice(0, DROP_REASON_MAX)}…`;
2622
+ }
2623
+ function PostHogTelemetryLayer(deps) {
2624
+ return Layer.succeed(TelemetryPortTag, new PostHogTelemetryAdapter(deps));
2625
+ }
2626
+ function cloneIdentifyInput(input) {
2627
+ const traits = input.traits === void 0 ? void 0 : cloneTelemetryProps(input.traits);
2628
+ const properties = input.properties === void 0 ? void 0 : cloneTelemetryProps(input.properties);
2629
+ return {
2630
+ distinctId: input.distinctId,
2631
+ ...input.anonDistinctId === void 0 ? {} : { anonDistinctId: input.anonDistinctId },
2632
+ ...traits === void 0 ? {} : { traits },
2633
+ ...properties === void 0 ? {} : { properties }
2634
+ };
2635
+ }
2636
+ function cloneGroupInput(input) {
2637
+ const properties = input.properties === void 0 ? void 0 : cloneTelemetryProps(input.properties);
2638
+ return properties === void 0 ? {
2639
+ groupType: input.groupType,
2640
+ groupKey: input.groupKey
2641
+ } : {
2642
+ groupType: input.groupType,
2643
+ groupKey: input.groupKey,
2644
+ properties
2645
+ };
2646
+ }
2647
+ //#endregion
2648
+ //#region src/observability/telemetry/from-posthog.ts
2649
+ /**
2650
+ * Drop props that must never cross to a host-owned external sink. Today that is
2651
+ * the `$exception` `details` blob: `captureException` serializes
2652
+ * `CapxulError.details` (e.g. `{ asset, available, required }`, `{ name }`,
2653
+ * `{ accountId }` — errors.ts) into it, and the shared redactor has no
2654
+ * `$exception` rule, so it is stripped here at the boundary (infra#1037). The
2655
+ * safe fields (error code, operation, failure_mode, the fixed leak-safe message,
2656
+ * stack frames) are preserved.
2657
+ */
2658
+ function stripHostUnsafeProps(props) {
2659
+ if (props === void 0) return void 0;
2660
+ const { details: _details, ...safe } = props;
2661
+ return safe;
2662
+ }
2663
+ /**
2664
+ * The subset of a posthog-js client the seam calls. `identify` / `group` /
2665
+ * `reset` are optional — a host that only wants event capture can omit them.
2666
+ */
2667
+ /**
2668
+ * Adapt the host's already-initialized posthog-like client into a
2669
+ * `TelemetryPort` for the `telemetry` prop / input. This port SUPPLANTS the
2670
+ * SDK's no-op default telemetry sink (`production.ts` binds it via
2671
+ * `Layer.succeed`, not `compose` — there is no client-side success relay to
2672
+ * compose with); it is additive to Capxul's backend first-party record and
2673
+ * never owns the client.
2674
+ */
2675
+ function postHogProductTelemetry(policy, fixedSnapshot) {
2676
+ const telemetry = new PostHogTelemetryAdapter({
2677
+ capture: (name, props, source) => {
2678
+ const snapshot = readInvocationObservation(source) ?? fixedSnapshot ?? policy.snapshot();
2679
+ if (!snapshot.active || policy.client === null || policy.client === void 0) return;
2680
+ const event = stampTelemetryEnvelope({
2681
+ name,
2682
+ props
2683
+ }, {
2684
+ capxul_env: policy.capxulEnv,
2685
+ producer: "sdk"
2686
+ });
2687
+ policy.deliver(() => policy.client.capture(name, {
2688
+ ...stripHostUnsafeProps(event.props),
2689
+ ...observationContextProps(snapshot.context)
2690
+ }));
2691
+ },
2692
+ identify: (input) => {
2693
+ if (!(fixedSnapshot ?? policy.snapshot()).active || policy.client?.identify === void 0) return;
2694
+ policy.deliver(() => policy.client.identify(input.distinctId, {
2695
+ ...input.traits,
2696
+ ...input.properties
2697
+ }));
2698
+ },
2699
+ group: (input) => {
2700
+ if (!(fixedSnapshot ?? policy.snapshot()).active || policy.client?.group === void 0) return;
2701
+ policy.deliver(() => policy.client.group(input.groupType, input.groupKey, input.properties === void 0 ? void 0 : { ...input.properties }));
2702
+ },
2703
+ reset: () => {
2704
+ if (!(fixedSnapshot ?? policy.snapshot()).active || policy.client?.reset === void 0) return;
2705
+ policy.deliver(() => policy.client.reset());
2706
+ }
2707
+ });
2708
+ registerProductTelemetryInvocation(telemetry, (source) => postHogProductTelemetry(policy, readInvocationObservation(source) ?? fixedSnapshot ?? policy.snapshot()));
2709
+ return telemetry;
2710
+ }
2711
+ //#endregion
2712
+ //#region src/observability/host.ts
2713
+ const HOST_INTERNALS = /* @__PURE__ */ new WeakMap();
2714
+ /** Build the SDK's one host module around an already-initialized PostHog client. */
2715
+ function postHogObservability(client, options = {}) {
2716
+ const inactive = Object.freeze({
2717
+ active: false,
2718
+ contextProps: Object.freeze({})
2719
+ });
2720
+ const snapshot = () => {
2721
+ if (client === null || client === void 0) return inactive;
2722
+ try {
2723
+ if (!(typeof options.enabled === "function" ? options.enabled() : options.enabled ?? true)) return inactive;
2724
+ const rawContext = typeof options.context === "function" ? options.context() : options.context;
2725
+ const sanitized = sanitizeObservationContext(rawContext);
2726
+ const context = sanitized === void 0 ? void 0 : Object.freeze({ ...sanitized });
2727
+ return Object.freeze({
2728
+ active: true,
2729
+ ...context === void 0 ? {} : { context },
2730
+ contextProps: Object.freeze(observationContextProps(context))
2731
+ });
2732
+ } catch {
2733
+ return inactive;
2734
+ }
2735
+ };
2736
+ const policy = {
2737
+ client,
2738
+ capxulEnv: options.capxulEnv ?? "unknown",
2739
+ snapshot,
2740
+ deliver: (capture) => {
2741
+ try {
2742
+ const delivery = capture();
2743
+ if (Predicate.isPromiseLike(delivery)) Promise.resolve(delivery).catch(() => void 0);
2744
+ } catch {}
2745
+ }
2746
+ };
2747
+ const module = {
2748
+ failures: postHogFailureObservation(policy),
2749
+ product: postHogProductTelemetry(policy)
2750
+ };
2751
+ HOST_INTERNALS.set(module, {
2752
+ engineeringPolicy: Object.freeze({
2753
+ ...options.engineeringDeliveryBudget === void 0 ? {} : { engineeringDeliveryBudget: options.engineeringDeliveryBudget },
2754
+ ...options.engineeringEnabled === void 0 ? {} : { engineeringEnabled: options.engineeringEnabled },
2755
+ ...options.engineeringPrivacy === void 0 ? {} : { engineeringPrivacy: options.engineeringPrivacy }
2756
+ }),
2757
+ bind: () => {
2758
+ const invocation = snapshot();
2759
+ return {
2760
+ failures: postHogFailureObservation(policy, invocation),
2761
+ product: postHogProductTelemetry(policy, invocation)
2762
+ };
2763
+ },
2764
+ snapshot
2765
+ });
2766
+ return module;
2767
+ }
2768
+ /** @internal Bind both projections to one call-start decision/context snapshot. */
2769
+ function bindHostObservabilityInvocation(observability) {
2770
+ if (observability === void 0) return void 0;
2771
+ return HOST_INTERNALS.get(observability)?.bind() ?? observability;
2772
+ }
2773
+ /** @internal Read one call-start snapshot for actor/transition carriage. */
2774
+ function snapshotHostObservability(observability) {
2775
+ const snapshot = observability === void 0 ? void 0 : HOST_INTERNALS.get(observability)?.snapshot();
2776
+ return snapshot === void 0 ? void 0 : {
2777
+ active: snapshot.active,
2778
+ ...snapshot.context === void 0 ? {} : { context: snapshot.context }
2779
+ };
2780
+ }
2781
+ /** @internal Carry the explicit host engineering policy into production composition. */
2782
+ function hostEngineeringPolicy(observability) {
2783
+ const policy = observability === void 0 ? void 0 : HOST_INTERNALS.get(observability)?.engineeringPolicy;
2784
+ return {
2785
+ ...policy?.engineeringDeliveryBudget === void 0 ? {} : { deliveryBudget: policy.engineeringDeliveryBudget },
2786
+ ...policy?.engineeringEnabled === void 0 ? {} : { enabled: policy.engineeringEnabled },
2787
+ ...policy?.engineeringPrivacy === void 0 ? {} : { privacy: policy.engineeringPrivacy }
2788
+ };
2789
+ }
2790
+ //#endregion
2791
+ //#region ../observability/src/delivery/admission.ts
2792
+ function newDeliveryState(budget, ownsMetrics) {
2793
+ return {
2794
+ budget,
2795
+ ownsMetrics,
2796
+ closed: Deferred.makeUnsafe(),
2797
+ isClosed: false,
2798
+ metricsRunning: false,
2799
+ logs: [],
2800
+ spans: /* @__PURE__ */ new Map(),
2801
+ terminalBodies: /* @__PURE__ */ new WeakSet(),
2802
+ bodyLeases: /* @__PURE__ */ new WeakMap(),
2803
+ pendingBodies: /* @__PURE__ */ new Set()
2804
+ };
2805
+ }
2806
+ /** Hold the records a body carries until its request finishes or the body is discarded. */
2807
+ function holdBody(state, body, releases) {
2808
+ if (releases.length === 0) return;
2809
+ const lease = {
2810
+ started: false,
2811
+ release: () => {
2812
+ if (!state.pendingBodies.delete(lease)) return;
2813
+ state.bodyLeases.delete(body);
2814
+ for (const release of releases) release();
2815
+ }
2816
+ };
2817
+ state.bodyLeases.set(body, lease);
2818
+ state.pendingBodies.add(lease);
2819
+ }
2820
+ const discardBody = (state, body) => state.bodyLeases.get(body)?.release();
2821
+ const drainLogs = (state) => {
2822
+ for (const release of state.logs.splice(0)) release();
2823
+ };
2824
+ function closeDelivery(state) {
2825
+ state.isClosed = true;
2826
+ Deferred.doneUnsafe(state.closed, Effect.void);
2827
+ for (const lease of state.pendingBodies) if (!lease.started) lease.release();
2828
+ drainLogs(state);
2829
+ for (const release of state.spans.values()) release();
2830
+ state.spans.clear();
2831
+ if (state.ownsMetrics) state.budget.metricRegistry.clear();
2832
+ }
2833
+ const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
2834
+ const attributes = (input, limit = 32) => {
2835
+ const out = {};
2836
+ let count = 0;
2837
+ let bytes = 0;
2838
+ try {
2839
+ for (const key in input) {
2840
+ if (count++ >= limit) break;
2841
+ if (key.length > 64) continue;
2842
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
2843
+ const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
2844
+ if (scalar === void 0) continue;
2845
+ bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
2846
+ if (bytes > (limit >= 24 ? 2e3 : 300)) break;
2847
+ out[key] = scalar;
2848
+ }
2849
+ } catch {}
2850
+ return out;
2851
+ };
2852
+ const terminalName = (name) => name === "cli.command.completed" || name === "cli.wallet.step" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
2853
+ //#endregion
2854
+ //#region ../observability/src/delivery/records.ts
2855
+ function admittedLogger(state, delegate) {
2856
+ return Logger.make((options) => {
2857
+ if (state.isClosed) return;
2858
+ let release;
2859
+ try {
2860
+ const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
2861
+ release = state.budget.admitRecord(terminalName(message));
2862
+ if (release === void 0) return;
2863
+ const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
2864
+ const fiber = Object.create(options.fiber);
2865
+ Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
2866
+ state.logs.push(release);
2867
+ delegate.log({
2868
+ ...options,
2869
+ message,
2870
+ cause: Cause.empty,
2871
+ fiber
2872
+ });
2873
+ } catch {
2874
+ if (release !== void 0) {
2875
+ const index = state.logs.indexOf(release);
2876
+ if (index !== -1) state.logs.splice(index, 1);
2877
+ release();
2878
+ }
2879
+ state.budget.drop();
2880
+ }
2881
+ });
2882
+ }
2883
+ function admittedTracer(state, delegate) {
2884
+ return Tracer.make({
2885
+ span(options) {
2886
+ const name = text(options.name);
2887
+ const release = state.isClosed ? void 0 : state.budget.admitRecord(terminalName(name));
2888
+ const spanOptions = {
2889
+ ...options,
2890
+ name,
2891
+ annotations: Context.empty(),
2892
+ links: []
2893
+ };
2894
+ const span = release === void 0 ? new Tracer.NativeSpan({
2895
+ ...spanOptions,
2896
+ sampled: false
2897
+ }) : delegate.span(spanOptions);
2898
+ if (release !== void 0) state.spans.set(span.spanId, release);
2899
+ const wrapped = boundedSpan(state, span);
2900
+ wrapped.addLinks(options.links ?? []);
2901
+ return wrapped;
2902
+ },
2903
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
2904
+ });
2905
+ }
2906
+ /** The span as a producer sees it: at most 32 fields and 2000 attribute bytes, 2 events, 2 links. */
2907
+ function boundedSpan(state, span) {
2908
+ const { budget } = state;
2909
+ const wrapped = Object.create(span);
2910
+ let fields = 0, events = 0, links = 0, attributeBytes = 0;
2911
+ let ended = false;
2912
+ Object.defineProperties(wrapped, {
2913
+ attribute: { value: (key, value) => {
2914
+ if (fields >= 32 && !span.attributes.has(key)) {
2915
+ budget.drop();
2916
+ return;
2917
+ }
2918
+ const safe = attributes({ [key]: value }, 1);
2919
+ if (!Object.hasOwn(safe, key)) return;
2920
+ const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
2921
+ const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
2922
+ if (attributeBytes - previous + next > 2e3) {
2923
+ budget.drop();
2924
+ return;
2925
+ }
2926
+ attributeBytes += next - previous;
2927
+ if (!span.attributes.has(key)) fields += 1;
2928
+ span.attribute(key, safe[key]);
2929
+ } },
2930
+ event: { value: (eventName, time, attrs) => {
2931
+ if (events++ >= 2) {
2932
+ budget.drop();
2933
+ return;
2934
+ }
2935
+ span.event(text(eventName), time, attributes(attrs ?? {}, 4));
2936
+ } },
2937
+ addLinks: { value: (items) => {
2938
+ for (const item of items.slice(0, Math.max(0, 2 - links))) {
2939
+ if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
2940
+ budget.drop();
2941
+ continue;
2942
+ }
2943
+ span.addLinks([{
2944
+ span: Tracer.externalSpan({
2945
+ traceId: item.span.traceId,
2946
+ spanId: item.span.spanId,
2947
+ sampled: item.span.sampled
2948
+ }),
2949
+ attributes: attributes(item.attributes, 4)
2950
+ }]);
2951
+ links += 1;
2952
+ }
2953
+ } },
2954
+ end: { value: (time, exit) => {
2955
+ if (ended) return;
2956
+ ended = true;
2957
+ span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
2958
+ } }
2959
+ });
2960
+ return wrapped;
2961
+ }
2962
+ //#endregion
2963
+ //#region ../observability/src/delivery/budget.ts
2964
+ /** Fixed admission limits for one CLI invocation and its SDK client. */
2965
+ const CLI_ENGINEERING_LIMITS = Object.freeze({
2966
+ records: 80,
2967
+ ordinaryRecords: 64,
2968
+ recordBytes: 16384,
2969
+ requests: 5,
2970
+ ordinaryRequests: 2,
2971
+ requestBytes: 1572864,
2972
+ metricSeries: 16,
2973
+ requestTimeoutMs: 1e3
2974
+ });
2975
+ //#endregion
2976
+ //#region ../observability/src/delivery/requests.ts
2977
+ function admittedSerialization(state, delegate) {
2978
+ return {
2979
+ logs(data) {
2980
+ const releases = state.logs.splice(0);
2981
+ try {
2982
+ const body = delegate.logs(data);
2983
+ if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => terminalName(l.body?.stringValue ?? ""))))) state.terminalBodies.add(body);
2984
+ holdBody(state, body, releases);
2985
+ return body;
2986
+ } catch (error) {
2987
+ for (const release of releases) release();
2988
+ throw error;
2989
+ }
2990
+ },
2991
+ traces(data) {
2992
+ const releases = [];
2993
+ for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
2994
+ const release = state.spans.get(span.spanId);
2995
+ if (release !== void 0) releases.push(release);
2996
+ state.spans.delete(span.spanId);
2997
+ }
2998
+ try {
2999
+ const body = delegate.traces(data);
3000
+ if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) state.terminalBodies.add(body);
3001
+ holdBody(state, body, releases);
3002
+ return body;
3003
+ } catch (error) {
3004
+ for (const release of releases) release();
3005
+ throw error;
3006
+ }
3007
+ },
3008
+ metrics(data) {
3009
+ const body = delegate.metrics(data);
3010
+ state.terminalBodies.add(body);
3011
+ return body;
3012
+ }
3013
+ };
3014
+ }
3015
+ /** A 429's Retry-After, bounded to two seconds; any other failure retries at once. */
3016
+ const retryAfter = Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
3017
+ if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
3018
+ const header = input.reason.response.headers["retry-after"];
3019
+ const numeric = Number(header);
3020
+ const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
3021
+ return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
3022
+ })));
3023
+ function admittedTransport(state, client, enabled = () => true) {
3024
+ const { budget } = state;
3025
+ const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
3026
+ if (enabled()) return response;
3027
+ budget.drop();
3028
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
3029
+ })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
3030
+ let received = 0;
3031
+ return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
3032
+ received += chunk.byteLength;
3033
+ if (received > 65536) throw new Error("Engineering response limit exceeded");
3034
+ })).pipe(Effect.catchReason("HttpClientError", "EmptyBodyError", () => Effect.void));
3035
+ }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
3036
+ times: 1,
3037
+ schedule: retryAfter
3038
+ }));
3039
+ return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
3040
+ if (state.isClosed) {
3041
+ discardBody(state, request.body);
3042
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
3043
+ }
3044
+ const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
3045
+ if (request.body._tag === "Empty") {
3046
+ discardBody(state, request.body);
3047
+ return Effect.succeed(droppedResponse());
3048
+ }
3049
+ const bytes = request.body.contentLength;
3050
+ const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, state.terminalBodies.has(request.body));
3051
+ if (release === void 0) {
3052
+ discardBody(state, request.body);
3053
+ return Effect.succeed(droppedResponse());
3054
+ }
3055
+ const lease = state.bodyLeases.get(request.body);
3056
+ if (lease !== void 0) lease.started = true;
3057
+ return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(state.closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
3058
+ if (Option.isNone(result)) budget.drop();
3059
+ })), Effect.catchCause(() => Effect.sync(() => {
3060
+ budget.drop();
3061
+ })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
3062
+ release();
3063
+ discardBody(state, request.body);
3064
+ })));
3065
+ }));
3066
+ }
3067
+ //#endregion
3068
+ //#region ../observability/src/engineering-delivery.ts
3069
+ /** Native exporters retain their serialization and scheduling; this owns admission only. */
3070
+ function cliEngineeringDelivery(budget, ownsMetrics) {
3071
+ const state = newDeliveryState(budget, ownsMetrics);
3072
+ return {
3073
+ close: () => closeDelivery(state),
3074
+ discardBody: (body) => discardBody(state, body),
3075
+ metrics(run) {
3076
+ return Effect.suspend(() => {
3077
+ if (state.isClosed || state.metricsRunning) return Effect.void;
3078
+ state.metricsRunning = true;
3079
+ return run.pipe(Effect.ensuring(Effect.sync(() => {
3080
+ state.metricsRunning = false;
3081
+ })));
3082
+ });
3083
+ },
3084
+ logger: (delegate) => admittedLogger(state, delegate),
3085
+ tracer: (delegate) => admittedTracer(state, delegate),
3086
+ serialization: (delegate) => admittedSerialization(state, delegate),
3087
+ transport: (client, enabled) => admittedTransport(state, client, enabled)
3088
+ };
3089
+ }
3090
+ //#endregion
3091
+ //#region ../observability/src/engineering/config.ts
3092
+ const ENGINEERING_CAPXUL_ENVS = [
3093
+ "development",
3094
+ "staging",
3095
+ "production",
3096
+ "local"
3097
+ ];
3098
+ const traceHeaderFilter = (_name) => true;
3099
+ const credentialHeaderPattern = Object.assign(/./u, { exec: (name) => isCredentialField(name, "header") ? Object.assign([name], {
3100
+ index: 0,
3101
+ input: name
3102
+ }) : null });
3103
+ const postHogOtlpEndpoints = (host) => {
3104
+ const base = host.replace(/\/+$/, "");
3105
+ return {
3106
+ logs: `${base}/i/v1/logs`,
3107
+ traces: `${base}/i/v1/traces`,
3108
+ metrics: `${base}/i/v1/metrics`
3109
+ };
3110
+ };
3111
+ const ENGINEERING_CAPXUL_ENV_SET = new Set(ENGINEERING_CAPXUL_ENVS);
3112
+ const ENGINEERING_PRODUCERS = /* @__PURE__ */ new Set(["browser", "server"]);
3113
+ const PUBLIC_POSTHOG_AUTHORIZATION = /^Bearer phc_[A-Za-z0-9_-]{1,191}$/u;
3114
+ const SAFE_RESOURCE_VALUE = /^[A-Za-z0-9][A-Za-z0-9._+@/-]{0,127}$/u;
3115
+ function validateEngineeringPrivacy(config) {
3116
+ if (config.privacy !== void 0 && config.privacy !== "minimal") throw new TypeError("engineering telemetry privacy must be minimal when specified");
3117
+ if (config.privacy === "minimal" && (!/^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(config.sdkVersion) || config.serviceName !== void 0 && !/^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(config.serviceName))) throw new TypeError("minimal engineering resource values must not contain paths");
3118
+ }
3119
+ const validateEngineeringTelemetryConfig = (config) => {
3120
+ let url;
3121
+ try {
3122
+ url = new URL(config.host);
3123
+ } catch {
3124
+ throw new TypeError("engineering telemetry host must be an absolute HTTPS URL");
3125
+ }
3126
+ if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0 || url.pathname !== "/" || url.search.length > 0 || url.hash.length > 0 || !(url.hostname === "posthog.com" || url.hostname.endsWith(".posthog.com"))) throw new TypeError("engineering telemetry host must be a credential-free HTTPS origin");
3127
+ if (!ENGINEERING_CAPXUL_ENV_SET.has(config.capxulEnv)) throw new TypeError("engineering telemetry capxulEnv is not canonical");
3128
+ if (!ENGINEERING_PRODUCERS.has(config.producer)) throw new TypeError("engineering telemetry producer is not canonical");
3129
+ if (config.enabled !== void 0 && typeof config.enabled !== "boolean" && typeof config.enabled !== "function") throw new TypeError("engineering telemetry enabled must be a boolean or callback");
3130
+ validateEngineeringPrivacy(config);
3131
+ if (!SAFE_RESOURCE_VALUE.test(config.sdkVersion)) throw new TypeError("engineering telemetry sdkVersion must be a bounded safe value");
3132
+ if (config.serviceName !== void 0 && !SAFE_RESOURCE_VALUE.test(config.serviceName)) throw new TypeError("engineering telemetry serviceName must be a bounded safe value");
3133
+ const headerEntries = Object.entries(config.headers);
3134
+ if (headerEntries.length !== 1 || headerEntries[0]?.[0].toLowerCase() !== "authorization" || !PUBLIC_POSTHOG_AUTHORIZATION.test(headerEntries[0]?.[1] ?? "")) throw new TypeError("engineering telemetry headers must contain exactly one public PostHog authorization token");
3135
+ return {
3136
+ ...config,
3137
+ host: url.origin,
3138
+ headers: Object.freeze({ authorization: headerEntries[0][1] })
3139
+ };
3140
+ };
3141
+ //#endregion
3142
+ //#region ../observability/src/engineering/tracer.ts
3143
+ const failureText = (failure, key, limit, source) => {
3144
+ if (failure === null || typeof failure !== "object") return void 0;
3145
+ try {
3146
+ let owner = failure;
3147
+ let descriptor;
3148
+ for (let depth = 0; owner !== null && depth < 4; depth += 1) {
3149
+ descriptor = Object.getOwnPropertyDescriptor(owner, key);
3150
+ if (descriptor !== void 0 || key !== "name") break;
3151
+ owner = Object.getPrototypeOf(owner);
3152
+ }
3153
+ const value = descriptor?.value;
3154
+ return typeof value === "string" || key === "code" && typeof value === "number" ? redactSecrets(String(value), source).slice(0, limit) : void 0;
3155
+ } catch {
3156
+ return;
3157
+ }
3158
+ };
3159
+ const exportFailure = (failure, source) => {
3160
+ const message = failureText(failure, "message", 2048, source) ?? (typeof failure === "string" ? redactSecrets(failure, source).slice(0, 2048) : "Engineering operation failed");
3161
+ const code = failureText(failure, "code", 128);
3162
+ const error = new Error((code === void 0 ? message : `${message} [code=${code}]`).slice(0, 2048));
3163
+ error.name = failureText(failure, "_tag", 128) ?? failureText(failure, "name", 128) ?? "Error";
3164
+ error.stack = failureText(failure, "stack", 8192, source) ?? `${error.name}: ${error.message}`;
3165
+ return error;
3166
+ };
3167
+ const exportFailureExit = (cause, source) => Exit.failCause(Cause.fromReasons(cause.reasons.filter((reason) => !Cause.isInterruptReason(reason)).slice(0, 8).map((reason) => Cause.isFailReason(reason) ? Cause.makeFailReason(exportFailure(reason.error, source)) : Cause.makeDieReason(exportFailure(reason.defect, source)))));
3168
+ const BROWSER_KEEPALIVE_BYTES = 65536;
3169
+ let unfinishedBrowserExportBytes = 0;
3170
+ const hasBrowserDocument = () => typeof window !== "undefined" && typeof document !== "undefined";
3171
+ const browserExportClient = (client) => HttpClient.transform(client, (response, request) => Effect.acquireUseRelease(Effect.sync(() => {
3172
+ const bytes = "contentLength" in request.body ? request.body.contentLength : void 0;
3173
+ if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
3174
+ unfinishedBrowserExportBytes += bytes;
3175
+ return bytes;
3176
+ }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catchIf((error) => error.reason._tag === "EmptyBodyError", () => Effect.void)))), (bytes) => Effect.sync(() => {
3177
+ if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
3178
+ })));
3179
+ const spanUpstream = (span) => {
3180
+ let current = span;
3181
+ while (current?._tag === "Span") {
3182
+ const upstream = current.attributes.get("chain.upstream");
3183
+ if (upstream === "alchemy" || upstream === "infura" || upstream === "gateway") return upstream;
3184
+ current = current.parent._tag === "Some" ? current.parent.value : void 0;
3185
+ }
3186
+ };
3187
+ function redactHttpPath(path, fullUrl, source) {
3188
+ let origin = "https://redaction.invalid";
3189
+ try {
3190
+ const url = new URL(String(fullUrl));
3191
+ if (url.protocol === "https:" || url.protocol === "http:") origin = url.origin;
3192
+ } catch {}
3193
+ const prefix = path.startsWith("/") ? origin : "";
3194
+ return redactUrlSecrets(`${prefix}${path}`, source).slice(prefix.length, prefix.length + 2048);
3195
+ }
3196
+ /** A span whose prototype is `span`: it answers every Span member until an own property overrides it. */
3197
+ function inheritSpan(span) {
3198
+ return Object.create(span);
3199
+ }
3200
+ /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
3201
+ const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer.make({
3202
+ span(options) {
3203
+ const span = enabled() ? delegate.span(options) : new Tracer.NativeSpan(options);
3204
+ const wrapped = inheritSpan(span);
3205
+ Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
3206
+ const source = spanUpstream(span);
3207
+ const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
3208
+ if (header !== void 0 && isCredentialField(header, "header")) span.attribute(name, "[REDACTED]");
3209
+ else if (typeof value === "string" && name === "url.query") span.attribute(name, redactUrlSecrets(`?${value}`).slice(1, 2049));
3210
+ else if (typeof value === "string" && (name === "url.path" || name === "http.target")) {
3211
+ const fullUrl = span.attributes.get("url.full") ?? span.attributes.get("http.url");
3212
+ const path = name === "url.path" && typeof fullUrl === "string" ? /^https?:\/\/[^/]+([^?#]*)/u.exec(fullUrl)?.[1] ?? value : value;
3213
+ span.attribute(name, redactHttpPath(path, fullUrl, source));
3214
+ } else if (typeof value === "string" && (name === "url.full" || name === "http.url" || header === "location" || header === "referer" || header === "referrer")) span.attribute(name, redactUrlSecrets(value, source).slice(0, 2048));
3215
+ else if (header !== void 0 && typeof value === "string") span.attribute(name, redactSecrets(value, source).slice(0, 2048));
3216
+ else span.attribute(name, value);
3217
+ } });
3218
+ Object.defineProperty(wrapped, "end", {
3219
+ configurable: false,
3220
+ enumerable: false,
3221
+ value: (endTime, exit) => {
3222
+ if (!enabled()) {
3223
+ Object.defineProperty(wrapped, "status", { value: {
3224
+ _tag: "Ended",
3225
+ startTime: span.status.startTime,
3226
+ endTime,
3227
+ exit
3228
+ } });
3229
+ return;
3230
+ }
3231
+ if (Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
3232
+ const first = exit.cause.reasons.find((reason) => !Cause.isInterruptReason(reason));
3233
+ const code = failureText(first === void 0 ? void 0 : Cause.isFailReason(first) ? first.error : first.defect, "code", 128);
3234
+ if (code !== void 0) span.attribute("error.code", code);
3235
+ span.end(endTime, exportFailureExit(exit.cause, spanUpstream(span)));
3236
+ } else span.end(endTime, exit);
3237
+ },
3238
+ writable: false
3239
+ });
3240
+ return wrapped;
3241
+ },
3242
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
3243
+ });
3244
+ //#endregion
3245
+ //#region ../observability/src/engineering/minimal.ts
3246
+ const MINIMAL_LOG_NAMES = /* @__PURE__ */ new Set([
3247
+ "capxul_sdk_operation",
3248
+ "identity.transition",
3249
+ "convex.connection.changed",
3250
+ "convex.action.connection_lost",
3251
+ "cli.command.completed",
3252
+ "cli.sdk.operation.failed",
3253
+ "cli.signer.wait",
3254
+ "cli.wallet.step"
3255
+ ]);
3256
+ const MINIMAL_ERROR_CODES = /* @__PURE__ */ new Set([
3257
+ ...CAPXUL_ERROR_CODES,
3258
+ "CLI_USAGE",
3259
+ "CLI_CONFIG",
3260
+ "CLI_STORAGE",
3261
+ "CLI_DEFECT",
3262
+ "CLI_DEADLINE"
3263
+ ]);
3264
+ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
3265
+ "schema_version",
3266
+ "duration_ms",
3267
+ "wait_duration_ms",
3268
+ "retry_count",
3269
+ "http.status_code",
3270
+ "http.response.status_code",
3271
+ "http_status",
3272
+ "rpc.code",
3273
+ "connection_count",
3274
+ "connection_retries",
3275
+ "elapsed_ms",
3276
+ "total_ms",
3277
+ "token_calls",
3278
+ "call",
3279
+ "attempt",
3280
+ "bridge_protocol_version",
3281
+ "httpStatus"
3282
+ ]);
3283
+ const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
3284
+ "correlation_id",
3285
+ "journey_id",
3286
+ "invocation_id",
3287
+ "posthogDistinctId"
3288
+ ]);
3289
+ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
3290
+ "command",
3291
+ "capability",
3292
+ "invocation_kind",
3293
+ "outcome",
3294
+ "capxul_env",
3295
+ "unit"
3296
+ ]);
3297
+ const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
3298
+ const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
3299
+ const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
3300
+ const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value) || CLI_COMMAND_NAMES.some((command) => value === `cli.command.${command}`)) ? value : "capxul.engineering";
3301
+ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
3302
+ ["outcome", [
3303
+ "succeeded",
3304
+ "refused",
3305
+ "failed",
3306
+ "cancelled",
3307
+ "applied"
3308
+ ]],
3309
+ ["mode", FAILURE_MODES],
3310
+ ["failure_mode", FAILURE_MODES],
3311
+ ["capxul_env", ENGINEERING_CAPXUL_ENVS],
3312
+ ["producer", ["browser", "server"]],
3313
+ ["command", CLI_COMMAND_NAMES],
3314
+ ["capability", CLI_CAPABILITIES],
3315
+ ["invocation_kind", CLI_INVOCATION_KINDS],
3316
+ ["diagnostic_scope", [
3317
+ "openfort.step",
3318
+ "openfort.token",
3319
+ "openfort.encryptionSession",
3320
+ "openfort.embeddedState",
3321
+ "openfort.storageCleared",
3322
+ "openfort.configure",
3323
+ "openfort.get",
3324
+ "openfort.address",
3325
+ "openfort.staleCacheHeal",
3326
+ "openfort.signerRecovery",
3327
+ "openfort.signerStatusListener"
3328
+ ]],
3329
+ ["step", [
3330
+ "initialize",
3331
+ "token",
3332
+ "encryptionSession",
3333
+ "embeddedState",
3334
+ "configure",
3335
+ "get",
3336
+ "healStaleCache",
3337
+ "walletReady"
3338
+ ]],
3339
+ ["provider", ["better-auth", "oidc"]],
3340
+ ["cli_identity_kind", ["anonymous", "identified"]],
3341
+ ["output_mode", ["human", "json"]],
3342
+ ["setup_status", ORGANIZATION_SETUP_STATUSES],
3343
+ ["setup_reason", ORGANIZATION_SETUP_REASONS],
3344
+ ["signer_stage", [
3345
+ "address",
3346
+ "readiness",
3347
+ "sign"
3348
+ ]],
3349
+ ["unit", [
3350
+ "1",
3351
+ "s",
3352
+ "ms"
3353
+ ]]
3354
+ ]);
3355
+ const MINIMAL_CODE_KEYS = /* @__PURE__ */ new Set([
3356
+ "error.code",
3357
+ "error_code",
3358
+ "failure_code",
3359
+ "refusal_code",
3360
+ "capxul_error_code"
3361
+ ]);
3362
+ const MINIMAL_VERSION_KEYS = /* @__PURE__ */ new Set([
3363
+ "sdk_version",
3364
+ "cli_version",
3365
+ "release"
3366
+ ]);
3367
+ function minimalStringValue(key, text) {
3368
+ if (key === "operation") return isCapxulOperation(text);
3369
+ if (key === "organization_id") return /^org_[0-9A-Za-z]+$/u.test(text);
3370
+ if (MINIMAL_CODE_KEYS.has(key)) return MINIMAL_ERROR_CODES.has(text);
3371
+ if (MINIMAL_VERSION_KEYS.has(key)) return /^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(text);
3372
+ if (key === "trace_id") return traceId(text);
3373
+ if (key === "span_id" || key === "parent_span_id") return spanId(text);
3374
+ const values = MINIMAL_ENUM_VALUES.get(key);
3375
+ if (values !== void 0) return values.includes(text);
3376
+ return MINIMAL_CORRELATIONS.has(key) && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(text) && !/^0x[0-9a-f]+$/iu.test(text) && redactSecrets(text) === text;
3377
+ }
3378
+ /** A named privacy projection, separate from the default credential-only redaction policy. */
3379
+ function minimalAttributes(attributes, metric = false) {
3380
+ return attributes.flatMap(({ key, value }) => {
3381
+ if (metric && !MINIMAL_METRIC_LABELS.has(key)) return [];
3382
+ const text = value.stringValue;
3383
+ const number = value.doubleValue ?? value.intValue;
3384
+ if (MINIMAL_NUMBERS.has(key) && typeof number === "number" && Number.isFinite(number)) return [{
3385
+ key,
3386
+ value: { doubleValue: number }
3387
+ }];
3388
+ if (typeof text !== "string") return [];
3389
+ return minimalStringValue(key, text) ? [{
3390
+ key,
3391
+ value: { stringValue: text }
3392
+ }] : [];
3393
+ });
3394
+ }
3395
+ //#endregion
3396
+ //#region ../observability/src/engineering/metric-points.ts
3397
+ const finiteProtocolNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
3398
+ const protocolCount = (value, maximum = Number.MAX_SAFE_INTEGER) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= maximum ? value : void 0;
3399
+ const protocolFlags = (value) => protocolCount(Object.getOwnPropertyDescriptor(value, "flags")?.value, 4294967295);
3400
+ function protocolTime(value) {
3401
+ if (typeof value === "string" && /^\d{1,20}$/u.test(value) && BigInt(value) <= 18446744073709551615n) return value;
3402
+ const number = protocolCount(value);
3403
+ if (number !== void 0) return String(number);
3404
+ if (value === null || typeof value !== "object") return void 0;
3405
+ const low = protocolCount(Object.getOwnPropertyDescriptor(value, "low")?.value, 4294967295);
3406
+ const high = protocolCount(Object.getOwnPropertyDescriptor(value, "high")?.value, 4294967295);
3407
+ return low === void 0 || high === void 0 ? void 0 : (BigInt(high) << 32n | BigInt(low)).toString();
3408
+ }
3409
+ function minimalNumberPoints(points) {
3410
+ return points.flatMap((point) => {
3411
+ const attributes = minimalAttributes(point.attributes, true);
3412
+ const asDouble = finiteProtocolNumber(point.asDouble);
3413
+ const asInt = typeof point.asInt === "number" && Number.isSafeInteger(point.asInt) ? point.asInt : void 0;
3414
+ if (attributes.length !== point.attributes.length || asDouble === void 0 && asInt === void 0) return [];
3415
+ return [{
3416
+ attributes,
3417
+ timeUnixNano: protocolTime(point.timeUnixNano),
3418
+ startTimeUnixNano: protocolTime(point.startTimeUnixNano),
3419
+ asDouble,
3420
+ asInt: asDouble === void 0 ? asInt : void 0,
3421
+ flags: protocolFlags(point)
3422
+ }];
3423
+ });
3424
+ }
3425
+ function minimalHistogramPoints(points) {
3426
+ return points.flatMap((point) => {
3427
+ const attributes = minimalAttributes(point.attributes ?? [], true);
3428
+ const count = protocolCount(point.count);
3429
+ const bucketCounts = point.bucketCounts;
3430
+ const explicitBounds = point.explicitBounds;
3431
+ if (attributes.length !== (point.attributes?.length ?? 0) || count === void 0 || !Array.isArray(bucketCounts) || !bucketCounts.every((value) => protocolCount(value) !== void 0) || !Array.isArray(explicitBounds) || !explicitBounds.every((value) => finiteProtocolNumber(value) !== void 0) || bucketCounts.length !== explicitBounds.length + 1) return [];
3432
+ return [{
3433
+ attributes,
3434
+ timeUnixNano: protocolTime(point.timeUnixNano),
3435
+ startTimeUnixNano: protocolTime(point.startTimeUnixNano),
3436
+ count,
3437
+ sum: finiteProtocolNumber(point.sum),
3438
+ min: finiteProtocolNumber(point.min),
3439
+ max: finiteProtocolNumber(point.max),
3440
+ bucketCounts: bucketCounts.map((value) => value),
3441
+ explicitBounds: explicitBounds.map((value) => value),
3442
+ flags: protocolFlags(point)
3443
+ }];
3444
+ });
3445
+ }
3446
+ //#endregion
3447
+ //#region ../observability/src/engineering/serialization.ts
3448
+ function minimalSerialization(config) {
3449
+ const service = config.serviceName ?? "capxul-sdk";
3450
+ const resource = { attributes: Object.entries({
3451
+ "service.name": service,
3452
+ "service.version": config.sdkVersion,
3453
+ sdk_version: config.sdkVersion,
3454
+ capxul_env: config.capxulEnv,
3455
+ producer: config.producer
3456
+ }).map(([key, value]) => ({
3457
+ key,
3458
+ value: { stringValue: value }
3459
+ })) };
3460
+ return {
3461
+ logs: (data) => HttpBody.jsonUnsafe({ resourceLogs: data.resourceLogs.map((entry) => ({
3462
+ resource,
3463
+ scopeLogs: entry.scopeLogs.map((scope) => ({
3464
+ scope: { name: service },
3465
+ logRecords: (scope.logRecords ?? []).map((record) => ({
3466
+ timeUnixNano: protocolTime(record.timeUnixNano),
3467
+ observedTimeUnixNano: protocolTime(record.observedTimeUnixNano),
3468
+ severityNumber: protocolCount(record.severityNumber, 24),
3469
+ severityText: [
3470
+ "All",
3471
+ "Trace",
3472
+ "Debug",
3473
+ "Info",
3474
+ "Warn",
3475
+ "Error",
3476
+ "Fatal",
3477
+ "None"
3478
+ ].includes(record.severityText ?? "") ? record.severityText : void 0,
3479
+ body: { stringValue: minimalName(record.body?.stringValue) },
3480
+ attributes: minimalAttributes(record.attributes),
3481
+ droppedAttributesCount: protocolCount(record.droppedAttributesCount) ?? 0,
3482
+ traceId: traceId(record.traceId) ? record.traceId : void 0,
3483
+ spanId: spanId(record.spanId) ? record.spanId : void 0,
3484
+ flags: protocolFlags(record)
3485
+ }))
3486
+ }))
3487
+ })) }),
3488
+ traces: (data) => HttpBody.jsonUnsafe({ resourceSpans: data.resourceSpans.map((entry) => ({
3489
+ resource,
3490
+ scopeSpans: entry.scopeSpans.map((scope) => ({
3491
+ scope: { name: service },
3492
+ spans: scope.spans.filter((span) => traceId(span.traceId) && spanId(span.spanId)).map((span) => {
3493
+ const attributes = minimalAttributes(span.attributes);
3494
+ const code = attributes.find((attribute) => attribute.key === "error.code");
3495
+ return {
3496
+ traceId: span.traceId,
3497
+ spanId: span.spanId,
3498
+ name: minimalName(span.name),
3499
+ kind: protocolCount(span.kind, 5) ?? 0,
3500
+ startTimeUnixNano: protocolTime(span.startTimeUnixNano),
3501
+ endTimeUnixNano: protocolTime(span.endTimeUnixNano),
3502
+ attributes,
3503
+ droppedAttributesCount: protocolCount(span.droppedAttributesCount) ?? 0,
3504
+ parentSpanId: spanId(span.parentSpanId) ? span.parentSpanId : void 0,
3505
+ flags: protocolFlags(span),
3506
+ status: { code: protocolCount(span.status.code, 2) ?? 0 },
3507
+ events: span.events.slice(0, 8).map((event) => ({
3508
+ timeUnixNano: protocolTime(event.timeUnixNano),
3509
+ droppedAttributesCount: protocolCount(event.droppedAttributesCount) ?? 0,
3510
+ name: event.name === "exception" ? "exception" : minimalName(event.name),
3511
+ attributes: event.name === "exception" ? [{
3512
+ key: "exception.type",
3513
+ value: code?.value ?? { stringValue: "Error" }
3514
+ }] : minimalAttributes(event.attributes)
3515
+ })),
3516
+ droppedEventsCount: protocolCount(span.droppedEventsCount) ?? 0,
3517
+ links: span.links.filter((link) => traceId(link.traceId) && spanId(link.spanId)).slice(0, 8).map((link) => ({
3518
+ traceId: link.traceId,
3519
+ spanId: link.spanId,
3520
+ flags: protocolFlags(link),
3521
+ attributes: minimalAttributes(link.attributes),
3522
+ droppedAttributesCount: protocolCount(link.droppedAttributesCount) ?? 0
3523
+ })),
3524
+ droppedLinksCount: protocolCount(span.droppedLinksCount) ?? 0
3525
+ };
3526
+ })
3527
+ }))
3528
+ })) }),
3529
+ metrics: (data) => {
3530
+ const resourceMetrics = data.resourceMetrics.map((entry) => ({
3531
+ resource,
3532
+ scopeMetrics: entry.scopeMetrics.map((scope) => ({
3533
+ scope: { name: service },
3534
+ metrics: scope.metrics.filter((metric) => MINIMAL_METRIC_NAMES.has(metric.name)).flatMap((metric) => {
3535
+ const gauge = metric.gauge === void 0 ? void 0 : { dataPoints: minimalNumberPoints(metric.gauge.dataPoints) };
3536
+ const sum = metric.sum === void 0 ? void 0 : {
3537
+ aggregationTemporality: protocolCount(metric.sum.aggregationTemporality, 2) ?? 0,
3538
+ isMonotonic: metric.sum.isMonotonic,
3539
+ dataPoints: minimalNumberPoints(metric.sum.dataPoints)
3540
+ };
3541
+ const histogram = metric.histogram === void 0 ? void 0 : {
3542
+ aggregationTemporality: protocolCount(metric.histogram.aggregationTemporality, 2) ?? 0,
3543
+ dataPoints: minimalHistogramPoints(metric.histogram.dataPoints)
3544
+ };
3545
+ if (![
3546
+ gauge,
3547
+ sum,
3548
+ histogram
3549
+ ].some((series) => (series?.dataPoints.length ?? 0) > 0)) return [];
3550
+ return [{
3551
+ name: metric.name,
3552
+ unit: [
3553
+ "1",
3554
+ "s",
3555
+ "ms"
3556
+ ].includes(metric.unit ?? "") ? metric.unit : "1",
3557
+ ...gauge === void 0 ? {} : { gauge },
3558
+ ...sum === void 0 ? {} : { sum },
3559
+ ...histogram === void 0 ? {} : { histogram }
3560
+ }];
3561
+ })
3562
+ }))
3563
+ }));
3564
+ return resourceMetrics.some((entry) => entry.scopeMetrics.some((scope) => scope.metrics.length > 0)) ? HttpBody.jsonUnsafe({ resourceMetrics }) : HttpBody.empty;
3565
+ }
3566
+ };
3567
+ }
3568
+ Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
3569
+ var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
3570
+ //#endregion
3571
+ //#region ../observability/src/engineering.ts
3572
+ /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
3573
+ const makeEngineeringTelemetryLayer = (config) => {
3574
+ const validated = validateEngineeringTelemetryConfig(config);
3575
+ return Layer.suspend(() => {
3576
+ const bounded = validated.deliveryBudget === void 0 ? void 0 : cliEngineeringDelivery(validated.deliveryBudget, validated.serviceName === "capxul-cli");
3577
+ const endpoints = postHogOtlpEndpoints(validated.host);
3578
+ let disabled = false;
3579
+ const enabled = () => {
3580
+ if (disabled) return false;
3581
+ try {
3582
+ disabled = (typeof validated.enabled === "function" ? validated.enabled() : validated.enabled ?? true) !== true;
3583
+ } catch {
3584
+ disabled = true;
3585
+ }
3586
+ return !disabled;
3587
+ };
3588
+ const delivery = {
3589
+ ...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
3590
+ ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval },
3591
+ ...bounded === void 0 ? {} : {
3592
+ maxBatchSize: Number.MAX_SAFE_INTEGER,
3593
+ exportInterval: 1e3,
3594
+ shutdownTimeout: validated.serviceName === "capxul-cli" ? 0 : 500
3595
+ }
3596
+ };
3597
+ const resource = {
3598
+ serviceName: validated.serviceName ?? "capxul-sdk",
3599
+ serviceVersion: validated.sdkVersion,
3600
+ attributes: {
3601
+ capxul_env: validated.capxulEnv,
3602
+ producer: validated.producer,
3603
+ sdk_version: validated.sdkVersion
3604
+ }
3605
+ };
3606
+ const tracing = Layer.effect(Tracer.Tracer, OtlpTracer.make({
3607
+ url: endpoints.traces,
3608
+ headers: validated.headers,
3609
+ resource,
3610
+ ...delivery
3611
+ }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(bounded?.tracer(tracer) ?? tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
3612
+ const ownedLogging = Layer.effect(EngineeringLog, OtlpLogger.make({
3613
+ url: endpoints.logs,
3614
+ headers: validated.headers,
3615
+ resource,
3616
+ ...delivery,
3617
+ ...bounded === void 0 ? {} : { excludeLogSpans: true }
3618
+ }).pipe(Effect.map((nativeLogger) => {
3619
+ const logger = bounded?.logger(nativeLogger) ?? nativeLogger;
3620
+ return Logger.make((options) => {
3621
+ if (enabled()) logger.log(options);
3622
+ });
3623
+ }))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
3624
+ const logging = Logger.layer([EngineeringLog], { mergeWithExisting: true }).pipe(Layer.provideMerge(ownedLogging));
3625
+ const metricConfig = {
3626
+ url: endpoints.metrics,
3627
+ headers: validated.headers,
3628
+ resource,
3629
+ temporality: "delta",
3630
+ ...delivery
3631
+ };
3632
+ const metrics = bounded === void 0 ? OtlpMetrics.layer(metricConfig) : validated.serviceName !== "capxul-cli" ? Layer.empty : Layer.effectDiscard(Effect.gen(function* () {
3633
+ const flusher = yield* OtlpExporter.Flusher;
3634
+ yield* OtlpMetrics.make({
3635
+ ...metricConfig,
3636
+ exportInterval: Number.POSITIVE_INFINITY,
3637
+ shutdownTimeout: 0
3638
+ }).pipe(Effect.provideService(OtlpExporter.Flusher, {
3639
+ ...flusher,
3640
+ register: (run) => flusher.register(bounded.metrics(run))
3641
+ }));
3642
+ })).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
3643
+ const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
3644
+ const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
3645
+ if (validated.producer !== "browser" || !hasBrowserDocument()) return;
3646
+ const browserWindow = window;
3647
+ const browserDocument = document;
3648
+ const flusher = yield* OtlpExporter.Flusher;
3649
+ const run = yield* FiberSet.makeRuntime();
3650
+ const flush = () => {
3651
+ run(flusher.flush);
3652
+ };
3653
+ const visibilityChanged = () => {
3654
+ if (browserDocument.visibilityState === "hidden") flush();
3655
+ };
3656
+ yield* Effect.acquireRelease(Effect.sync(() => {
3657
+ browserDocument.addEventListener("visibilitychange", visibilityChanged);
3658
+ browserWindow.addEventListener("pagehide", flush);
3659
+ }), () => Effect.sync(() => {
3660
+ browserDocument.removeEventListener("visibilitychange", visibilityChanged);
3661
+ browserWindow.removeEventListener("pagehide", flush);
3662
+ }));
3663
+ }));
3664
+ const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
3665
+ const originalTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
3666
+ const selectedTransport = bounded?.transport(originalTransport, enabled) ?? originalTransport;
3667
+ return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => {
3668
+ if (enabled() && request.body._tag !== "Empty") return response;
3669
+ bounded?.discardBody(request.body);
3670
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })));
3671
+ }));
3672
+ })).pipe(Layer.provide(FetchHttpClient.layer));
3673
+ const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
3674
+ const projected = minimalSerialization(validated);
3675
+ const selected = validated.privacy === "minimal" ? projected : {
3676
+ ...json,
3677
+ metrics: projected.metrics
3678
+ };
3679
+ return bounded?.serialization(selected) ?? selected;
3680
+ })).pipe(Layer.provide(OtlpSerialization.layerJson));
3681
+ const combined = browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
3682
+ const scoped = bounded === void 0 ? combined : combined.pipe(Layer.provideMerge(Layer.effectDiscard(Effect.addFinalizer(() => Effect.sync(bounded.close)))));
3683
+ return validated.deliveryBudget === void 0 ? scoped : scoped.pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, validated.serviceName === "capxul-cli" ? validated.deliveryBudget.metricRegistry : validated.deliveryBudget.emptyMetricRegistry())));
3684
+ });
3685
+ };
3686
+ function derivedBrowserOrigin(runtime) {
3687
+ if (runtime !== "browser") throw Errors.invalidInput("origin", "required outside browser runtime");
3688
+ const origin = readField(readField(globalThis, "location"), "origin");
3689
+ if (typeof origin === "string" && origin.length > 0) return origin;
3690
+ throw Errors.invalidInput("origin", "required when browser location is unavailable");
3691
+ }
3692
+ /**
3693
+ * Browser local dev serves `/api/auth` via the Vite proxy on `window.location.origin`
3694
+ * while bootstrap returns the remote Convex site host. Openfort wallet setup reads
3695
+ * Better Auth cookies from `get-session` — those only attach on the same origin the
3696
+ * OTP flow used, so rewrite when hosts differ.
3697
+ */
3698
+ function resolveBrowserAuthBaseUrl(input) {
3699
+ if (input.override !== void 0) return normalizeHttpUrl("authBaseUrl", input.override);
3700
+ const bootstrapUrl = normalizeHttpUrl("authBaseUrl", input.bootstrapAuthBaseUrl);
3701
+ if (input.runtime !== "browser") return bootstrapUrl;
3702
+ try {
3703
+ const localAuthBase = normalizeHttpUrl("authBaseUrl", `${derivedBrowserOrigin(input.runtime)}/api/auth`);
3704
+ const remoteAuthBase = bootstrapUrl.endsWith("/api/auth") ? bootstrapUrl : `${bootstrapUrl}/api/auth`;
3705
+ if (new URL(remoteAuthBase).host !== new URL(localAuthBase).host) return localAuthBase;
3706
+ return bootstrapUrl;
3707
+ } catch {
3708
+ return bootstrapUrl;
3709
+ }
3710
+ }
3711
+ function resolveRuntimeUrls(bootstrap, runtime, authBaseUrlOverride) {
3712
+ try {
3713
+ return {
3714
+ ok: true,
3715
+ value: {
3716
+ authBaseUrl: resolveBrowserAuthBaseUrl({
3717
+ bootstrapAuthBaseUrl: bootstrap.authBaseUrl,
3718
+ runtime,
3719
+ ...authBaseUrlOverride === void 0 ? {} : { override: authBaseUrlOverride }
3720
+ }),
3721
+ convexUrl: normalizeHttpUrl("convexUrl", bootstrap.convexUrl)
3722
+ }
3723
+ };
3724
+ } catch (cause) {
3725
+ return {
3726
+ ok: false,
3727
+ error: toPublicError(cause)
3728
+ };
3729
+ }
3730
+ }
3731
+ function normalizeHttpUrl(field, raw) {
3732
+ let parsed;
3733
+ try {
3734
+ parsed = new URL(raw);
3735
+ } catch {
3736
+ throw Errors.invalidInput(field, "must be an http or https URL");
3737
+ }
3738
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw Errors.invalidInput(field, "must be an http or https URL");
3739
+ return parsed.toString().replace(/\/$/, "");
3740
+ }
3741
+ //#endregion
3742
+ //#region src/platform/production-ports.ts
3743
+ function productionBootstrapPortLayer(bootstrap) {
3744
+ return Layer.succeed(BootstrapPortTag, bootstrap);
3745
+ }
3746
+ function productionAuthClientLayer(input, refreshConvexAuthRef, resetSignerSessionRef) {
3747
+ return (input.authClient !== void 0 ? Layer.succeed(AuthClientPortTag, input.authClient) : input.runtime === "browser" ? BetterAuthBrowserLayer({
3748
+ authBaseUrl: input.runtimeUrls.authBaseUrl,
3749
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3750
+ ...input.observation === void 0 ? {} : { observation: input.observation }
3751
+ }) : BetterAuthNodeLayer({
3752
+ ...input.nodeSessionStorage === void 0 ? {} : { nodeSessionStorage: input.nodeSessionStorage },
3753
+ authBaseUrl: input.runtimeUrls.authBaseUrl,
3754
+ ...input.origin === void 0 ? {} : { origin: input.origin },
3755
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3756
+ ...input.observation === void 0 ? {} : { observation: input.observation }
3757
+ })).pipe(Layer.flatMap((context) => {
3758
+ const refreshed = refreshConvexAuthOnSession(Context.get(context, AuthClientPortTag), () => {
3759
+ const refresh = refreshConvexAuthRef.current;
3760
+ if (refresh === null) {
3761
+ refreshConvexAuthRef.pending = true;
3762
+ return;
3763
+ }
3764
+ refresh();
3765
+ });
3766
+ return Layer.succeedContext(Context.make(AuthClientPortTag, resetSignerSessionRef === void 0 ? refreshed : resetSignerSessionOnSignOut(refreshed, resetSignerSessionRef)));
3767
+ }));
3768
+ }
3769
+ function productionAuthCacheLayer(input) {
3770
+ return Layer.succeed(AuthCachePortTag, input.authCache ?? detectAuthCacheAdapter());
3771
+ }
3772
+ function productionConvexCallLayer(input, refreshConvexAuthRef) {
3773
+ return Layer.unwrap(Effect.map(AuthClientPortTag, (authClient) => ConvexCallLayer({
3774
+ convexUrl: input.runtimeUrls.convexUrl,
3775
+ localDiagnostics: input.localDiagnostics,
3776
+ ...input.applicationId === void 0 ? {} : { applicationId: input.applicationId },
3777
+ ...input.observation === void 0 ? {} : { observation: input.observation },
3778
+ ...input.convexClient === void 0 ? {} : { client: input.convexClient },
3779
+ tokenProvider: async ({ forceRefreshToken }) => {
3780
+ const tokenResult = await Effect.runPromise(Effect.result(authClient.getConvexJwt({
3781
+ forceRefresh: forceRefreshToken,
3782
+ ...input.signal === void 0 ? {} : { signal: input.signal }
3783
+ })));
3784
+ if (Result.isFailure(tokenResult)) return null;
3785
+ return String(tokenResult.success.token);
3786
+ }
3787
+ }).pipe(Layer.flatMap((context) => {
3788
+ const convexCall = Context.get(context, ConvexCallPortTag);
3789
+ if (isRefreshableConvexCallPort(convexCall)) {
3790
+ refreshConvexAuthRef.current = () => convexCall.refreshAuth();
3791
+ if (refreshConvexAuthRef.pending) {
3792
+ refreshConvexAuthRef.pending = false;
3793
+ refreshConvexAuthRef.current();
3794
+ }
3795
+ }
3796
+ return Layer.succeedContext(context);
3797
+ }))));
3798
+ }
3799
+ function isRefreshableConvexCallPort(convexCall) {
3800
+ return "refreshAuth" in convexCall && typeof convexCall.refreshAuth === "function";
3801
+ }
3802
+ function optionalEngineeringTelemetryLayer(policy, runtime, hostPolicy) {
3803
+ if (policy === void 0) return Layer.empty;
3804
+ try {
3805
+ return makeEngineeringTelemetryLayer({
3806
+ host: policy.host,
3807
+ headers: { authorization: `Bearer ${policy.projectToken}` },
3808
+ capxulEnv: policy.capxulEnv,
3809
+ producer: runtime === "browser" ? "browser" : "server",
3810
+ sdkVersion: SDK_VERSION,
3811
+ ...hostPolicy
3812
+ });
3813
+ } catch {
3814
+ return Layer.empty;
3815
+ }
3816
+ }
3817
+ function localDiagnosticsLayer(enabled, runtime) {
3818
+ if (!enabled) return Logger.layer([]);
3819
+ return runtime === "node" ? Layer.succeed(Logger.LogToStderr, true) : Layer.empty;
3820
+ }
3821
+ function productionLocalDiagnostics(input) {
3822
+ return input.localDiagnostics !== false;
3823
+ }
3824
+ /**
3825
+ * Build the Ponder read port for this client, or `undefined` when no indexer
3826
+ * origin was supplied. The token provider is the same
3827
+ * `AuthClientPort.getConvexJwt` closure `ConvexCallPort` uses, so the indexer
3828
+ * and Convex see one session with one scope.
3829
+ */
3830
+ function productionIndexerReadPort(input, ports, observation) {
3831
+ if (input.activityReadSource === "convex") return void 0;
3832
+ if (input.indexerUrl === void 0 || input.indexerUrl === "") return void 0;
3833
+ const baseUrl = normalizeHttpUrl("indexerUrl", input.indexerUrl);
3834
+ return new PonderIndexerAdapter({
3835
+ baseUrl,
3836
+ token: async ({ forceRefreshToken, signal }) => {
3837
+ const tokenSignal = input.signal === void 0 ? signal : signal === void 0 ? input.signal : AbortSignal.any([input.signal, signal]);
3838
+ const token = await Effect.runPromise(Effect.result(ports.authClient.getConvexJwt({
3839
+ forceRefresh: forceRefreshToken,
3840
+ ...tokenSignal === void 0 ? {} : { signal: tokenSignal }
3841
+ })));
3842
+ if (Result.isFailure(token)) throw toPublicError(token.failure);
3843
+ return String(token.success.token);
3844
+ },
3845
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3846
+ ...observation === void 0 ? {} : { observation }
3847
+ });
3848
+ }
3849
+ //#endregion
3850
+ //#region src/platform/production-layers.ts
3851
+ /**
3852
+ * Project the built graph into the flat `FlowPorts` record the method bundles
3853
+ * still take. It is a VIEW of `ProductionAdapters.context`, taken from the
3854
+ * graph and never instead of it — the graph stays alive in the scope and is
3855
+ * handed to every caller.
3856
+ */
3857
+ const collectProductionFlowPorts = Effect.gen(function* () {
3858
+ const authClient = yield* AuthClientPortTag;
3859
+ const authCache = yield* AuthCachePortTag;
3860
+ const bootstrap = yield* BootstrapPortTag;
3861
+ const clock = yield* ClockPortTag;
3862
+ const convexCall = yield* ConvexCallPortTag;
3863
+ return {
3864
+ authClient,
3865
+ authCache,
3866
+ identity: yield* IdentityPortTag,
3867
+ smartAccount: yield* SmartAccountPortTag,
3868
+ accountRead: yield* AccountReadPortTag,
3869
+ bootstrap,
3870
+ clock,
3871
+ telemetry: yield* TelemetryPortTag,
3872
+ convexCall
3873
+ };
3874
+ });
3875
+ /**
3876
+ * The production port graph. Every port is named here once, so a port left
3877
+ * out does not compile: the return type requires all of them.
3878
+ *
3879
+ * Phase 1: authClient -> convexCall. The auth wrapper reads
3880
+ * RefreshConvexAuthRef at call time, after the convex layer registers the
3881
+ * concrete refreshAuth callback during construction. Phase 2: the ready
3882
+ * convexCall (and telemetry, for the reads that observe) -> the Convex-backed
3883
+ * domain ports. A layer used twice is built once.
3884
+ */
3885
+ function makeProductionAdapterLayer(input) {
3886
+ const refreshConvexAuthRef = {
3887
+ current: null,
3888
+ pending: false
3889
+ };
3890
+ const authClient = productionAuthClientLayer(input, refreshConvexAuthRef, input.resetSignerSession);
3891
+ const convexCall = productionConvexCallLayer(input, refreshConvexAuthRef).pipe(Layer.provide(authClient));
3892
+ const telemetry = input.telemetry === void 0 ? PostHogTelemetryLayer({ capture: () => void 0 }) : Layer.succeed(TelemetryPortTag, input.telemetry);
3893
+ const observedConvexCall = Layer.merge(convexCall, telemetry);
3894
+ const org = Layer.effect(OrgPortTag, Effect.all([ConvexCallPortTag, TelemetryPortTag]).pipe(Effect.map(([convex, telemetryPort]) => new ConvexOrganizationAdapter({
3895
+ convex,
3896
+ telemetry: telemetryPort
3897
+ }))));
3898
+ return Layer.mergeAll(productionBootstrapPortLayer(input.bootstrap), authClient, productionAuthCacheLayer(input), ConvexIdentityLayer.pipe(Layer.provide(convexCall)), ConvexSmartAccountLayer.pipe(Layer.provide(convexCall)), ConvexAccountLayer().pipe(Layer.provide(observedConvexCall)), SystemClockLayer, telemetry, convexCall, org.pipe(Layer.provide(observedConvexCall)));
3899
+ }
3900
+ //#endregion
3901
+ //#region src/platform/production-input.ts
3902
+ function validateCreateCapxulClientInput(input) {
3903
+ const runtime = input.runtime ?? detectRuntime();
3904
+ if (input.signer !== void 0 && input.openfortSignerFactory !== void 0) return {
3905
+ ok: false,
3906
+ error: Errors.invalidInput("signer", "choose signer or openfortSignerFactory")
3907
+ };
3908
+ if (input.nodeSessionStorage !== void 0 && runtime === "browser") return {
3909
+ ok: false,
3910
+ error: Errors.invalidInput("nodeSessionStorage", "is only supported in Node")
3911
+ };
3912
+ if ((input.requirement ?? "none") === "deployed" && input.signer === void 0 && input.openfortSignerFactory === void 0 && runtime !== "browser") return {
3913
+ ok: false,
3914
+ error: Errors.invalidInput("signer", "required when requirement is \"deployed\"")
3915
+ };
3916
+ return {
3917
+ ok: true,
3918
+ value: void 0
3919
+ };
3920
+ }
3921
+ function detectRuntime() {
3922
+ return readField(globalThis, "window") !== void 0 || readField(globalThis, "document") !== void 0 ? "browser" : "node";
3923
+ }
3924
+ //#endregion
3925
+ //#region src/platform/production-bootstrap.ts
3926
+ function resolveInput(input) {
3927
+ try {
3928
+ const runtime = input.runtime ?? detectRuntime();
3929
+ const publishableKey = toPublishableKey(input.publishableKey);
3930
+ const origin = input.origin === void 0 ? runtime === "browser" ? toAllowedOrigin(derivedBrowserOrigin(runtime)) : void 0 : toAllowedOrigin(input.origin);
3931
+ return {
3932
+ ok: true,
3933
+ value: {
3934
+ publishableKey,
3935
+ ...origin === void 0 ? {} : { origin },
3936
+ bootstrapBaseUrl: normalizeHttpUrl("bootstrapBaseUrl", input.bootstrapBaseUrl ?? (runtime === "browser" ? derivedBrowserOrigin(runtime) : "https://api.staging.capxul.com")),
3937
+ runtime
3938
+ }
3939
+ };
3940
+ } catch (cause) {
3941
+ return {
3942
+ ok: false,
3943
+ error: toPublicError(cause)
3944
+ };
3945
+ }
3946
+ }
3947
+ async function runBootstrap(effect) {
3948
+ const result = await Effect.runPromise(Effect.result(effect));
3949
+ if (Result.isSuccess(result)) return {
3950
+ ok: true,
3951
+ value: result.success
3952
+ };
3953
+ return {
3954
+ ok: false,
3955
+ error: toPublicError(result.failure)
3956
+ };
3957
+ }
3958
+ /**
3959
+ * Bootstrap-event props. `runtime` used to be called `capxulEnv`, which was a
3960
+ * name collision, not a value: it carried "browser"/"node", never an
3961
+ * environment. ADR-0020 A1 makes `capxul_env` the environment discriminator
3962
+ * every synced artifact filters on, so this field was renamed to what it
3963
+ * actually is. `sdk_version` follows the canon envelope spelling.
3964
+ */
3965
+ function bootstrapTelemetryEnvelope(input, resolvedInput) {
3966
+ return {
3967
+ runtime: resolvedInput.runtime,
3968
+ env: input.requirement ?? "none",
3969
+ ...resolvedInput.origin === void 0 ? {} : { origin: resolvedInput.origin },
3970
+ sdk_version: SDK_VERSION
3971
+ };
3972
+ }
3973
+ async function emitBootstrapTelemetry(telemetry, event) {
3974
+ if (telemetry === void 0) return;
3975
+ await Effect.runPromise(telemetry.emit(event).pipe(Effect.catchDefect(() => Effect.void)));
3976
+ }
3977
+ //#endregion
3978
+ //#region src/platform/production-adapters.ts
3979
+ async function createProductionAdapters(input) {
3980
+ const localDiagnostics = productionLocalDiagnostics(input);
3981
+ const { observation, telemetry, invocationObservation, invocationTelemetry } = resolveProductionObservability(input);
3982
+ const resolvedInput = resolveInput(input);
3983
+ if (!resolvedInput.ok) return resolvedInput;
3984
+ const scope = await Effect.runPromise(Scope.make());
3985
+ const closeScope = idempotentClose(() => Effect.runPromise(Scope.close(scope, Exit.void)));
3986
+ const bootstrapLayer = HttpBootstrapLayer({
3987
+ bootstrapBaseUrl: resolvedInput.value.bootstrapBaseUrl,
3988
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
3989
+ ...invocationObservation === void 0 ? {} : { observation: invocationObservation },
3990
+ ...input.bootstrapCache === void 0 ? {} : { cache: input.bootstrapCache }
3991
+ });
3992
+ try {
3993
+ const bootstrapContext = await Effect.runPromise(Layer.buildWithScope(bootstrapLayer, scope));
3994
+ const bootstrap = Context.get(bootstrapContext, BootstrapPortTag);
3995
+ const bootstrapResult = await runBootstrap(bootstrap.resolve({
3996
+ publishableKey: resolvedInput.value.publishableKey,
3997
+ ...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin }
3998
+ }));
3999
+ if (!bootstrapResult.ok) {
4000
+ await emitBootstrapTelemetry(invocationTelemetry, {
4001
+ name: "bootstrap_failed",
4002
+ props: {
4003
+ ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
4004
+ reason: bootstrapResult.error.code,
4005
+ ...failureDetail(bootstrapResult.error),
4006
+ ...failureEvidenceProps(bootstrapResult.error)
4007
+ }
4008
+ });
4009
+ await closeScope().catch(() => void 0);
4010
+ return bootstrapResult;
4011
+ }
4012
+ const chainFence = assertDevKeySignerIsTestnetOnly(input.signer, bootstrapResult.value.chainId);
4013
+ if (!chainFence.ok) {
4014
+ await closeScope().catch(() => void 0);
4015
+ return chainFence;
4016
+ }
4017
+ const runtimeUrlsResult = resolveRuntimeUrls(bootstrapResult.value, resolvedInput.value.runtime, input.authBaseUrl);
4018
+ if (!runtimeUrlsResult.ok) {
4019
+ await emitBootstrapTelemetry(invocationTelemetry, {
4020
+ name: "bootstrap_failed",
4021
+ props: {
4022
+ ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
4023
+ applicationId: bootstrapResult.value.applicationId,
4024
+ reason: runtimeUrlsResult.error.code
4025
+ }
4026
+ });
4027
+ await closeScope().catch(() => void 0);
4028
+ return runtimeUrlsResult;
4029
+ }
4030
+ const runtimeUrls = runtimeUrlsResult;
4031
+ await emitBootstrapTelemetry(invocationTelemetry, {
4032
+ name: "bootstrap_resolved",
4033
+ props: {
4034
+ ...bootstrapTelemetryEnvelope(input, resolvedInput.value),
4035
+ applicationId: bootstrapResult.value.applicationId
4036
+ }
4037
+ });
4038
+ let injectedClient;
4039
+ const convexClientFactory = input.convexClientFactory;
4040
+ if (convexClientFactory !== void 0) try {
4041
+ injectedClient = convexClientFactory(runtimeUrls.value.convexUrl);
4042
+ } catch (cause) {
4043
+ await closeScope().catch(() => void 0);
4044
+ return {
4045
+ ok: false,
4046
+ error: toPublicError(cause)
4047
+ };
4048
+ }
4049
+ const portsLayer = makeProductionAdapterLayer({
4050
+ bootstrap,
4051
+ runtime: resolvedInput.value.runtime,
4052
+ localDiagnostics,
4053
+ ...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin },
4054
+ runtimeUrls: runtimeUrls.value,
4055
+ chainId: bootstrapResult.value.chainId,
4056
+ applicationId: bootstrapResult.value.applicationId,
4057
+ ...observation === void 0 ? {} : { observation },
4058
+ ...input.nodeSessionStorage === void 0 ? {} : { nodeSessionStorage: input.nodeSessionStorage },
4059
+ ...input.authCache === void 0 ? {} : { authCache: input.authCache },
4060
+ ...input.authClient === void 0 ? {} : { authClient: input.authClient },
4061
+ ...telemetry === void 0 ? {} : { telemetry },
4062
+ ...injectedClient === void 0 ? {} : { convexClient: injectedClient },
4063
+ ...input.fetch === void 0 ? {} : { fetch: input.fetch },
4064
+ ...input.signal === void 0 ? {} : { signal: input.signal },
4065
+ ...input.resetSignerSession === void 0 ? {} : { resetSignerSession: input.resetSignerSession }
4066
+ });
4067
+ const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime, hostEngineeringPolicy(input.observability))).pipe(Layer.provideMerge(localDiagnosticsLayer(localDiagnostics, resolvedInput.value.runtime)));
4068
+ const context = await Effect.runPromise(Layer.buildWithScope(applicationLayer, scope));
4069
+ const collected = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
4070
+ const indexerUrl = input.indexerUrl ?? bootstrapResult.value.indexerUrl;
4071
+ const indexerRead = productionIndexerReadPort(indexerUrl === void 0 ? input : {
4072
+ ...input,
4073
+ indexerUrl
4074
+ }, collected, observation);
4075
+ const ports = indexerRead === void 0 ? collected : {
4076
+ ...collected,
4077
+ indexerRead
4078
+ };
4079
+ const stopConnectionObservation = observeProductionConvexConnection(ports.convexCall, context);
4080
+ const close = idempotentClose(async () => {
4081
+ try {
4082
+ stopConnectionObservation();
4083
+ } finally {
4084
+ await closeScope();
4085
+ }
4086
+ });
4087
+ return {
4088
+ ok: true,
4089
+ value: {
4090
+ context,
4091
+ ports,
4092
+ bootstrap: bootstrapResult.value,
4093
+ close
4094
+ }
4095
+ };
4096
+ } catch (cause) {
4097
+ await closeScope().catch(() => void 0);
4098
+ return {
4099
+ ok: false,
4100
+ error: toPublicError(cause)
4101
+ };
4102
+ }
4103
+ }
4104
+ function idempotentClose(close) {
4105
+ let closed = false;
4106
+ return async () => {
4107
+ if (closed) return;
4108
+ closed = true;
4109
+ await close();
4110
+ };
4111
+ }
4112
+ //#endregion
4113
+ //#region src/platform/client/financial-ops.ts
4114
+ function makeFinancialOpsMethods(deps) {
4115
+ return {
4116
+ me: makeMeMethods(deps),
4117
+ targets: makeTargetsMethods(deps),
4118
+ destinations: makeDestinationsMethods(deps),
4119
+ payments: makePaymentsMethods(deps),
4120
+ approvals: makeApprovalsMethods(deps),
4121
+ activity: makeActivityMethods(deps),
4122
+ offramp: makeOfframpMethods(),
4123
+ paymentDocuments: makePaymentDocumentsMethods(deps)
4124
+ };
4125
+ }
4126
+ //#endregion
4127
+ //#region src/media/images.ts
4128
+ /** The upload POST failed: the storage provider, not the row, refused. */
4129
+ function uploadFailed(cause) {
4130
+ return {
4131
+ ok: false,
4132
+ error: Errors.providerError("convex-storage", "uploadImage", cause)
4133
+ };
4134
+ }
4135
+ /** The storage id the upload response names, if it names one. */
4136
+ function storageIdOf(body) {
4137
+ const storageId = readField(body, "storageId");
4138
+ return typeof storageId === "string" && storageId.length > 0 ? storageId : void 0;
4139
+ }
4140
+ function makeMediaMethods(deps) {
4141
+ const fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
4142
+ /** One observed call of one row, recorded under the row's name. */
4143
+ const call = (row, args, options) => {
4144
+ const controls = deps.invocationControls?.(options) ?? options;
4145
+ return runCapxulEffect(callOperation(deps.convexCall, row, copyInvocationObservation(controls, args)), controls, row.name, deps.runPromise);
4146
+ };
4147
+ return {
4148
+ async uploadImage(blob, options) {
4149
+ const url = await call(images.generateUploadUrl, {}, options);
4150
+ if (!url.ok) return url;
4151
+ try {
4152
+ const response = await fetchImpl(url.value.uploadUrl, {
4153
+ method: "POST",
4154
+ headers: { "Content-Type": blob.type },
4155
+ body: blob,
4156
+ ...options?.signal === void 0 ? {} : { signal: options.signal }
4157
+ });
4158
+ if (!response.ok) return uploadFailed(`upload failed with status ${response.status}`);
4159
+ const storageId = storageIdOf(await response.json());
4160
+ if (storageId === void 0) return uploadFailed("upload response carried no storageId");
4161
+ return {
4162
+ ok: true,
4163
+ value: { storageId }
4164
+ };
4165
+ } catch (cause) {
4166
+ return uploadFailed(cause);
4167
+ }
4168
+ },
4169
+ setProfileImage: (input, options) => call(images.setProfileImage, { storageId: input.storageId }, options),
4170
+ setOrgLogo: (input, options) => call(images.setOrgLogo, {
4171
+ orgId: input.orgId,
4172
+ storageId: input.storageId
4173
+ }, options)
4174
+ };
4175
+ }
4176
+ //#endregion
4177
+ //#region src/operator/diagnostics.ts
4178
+ /** A W3C version-00 `traceparent` as the caller's span; anything else carries no parent. */
4179
+ const traceparentSpan = (traceparent) => {
4180
+ const parts = traceparent.split("-");
4181
+ const [version, traceId = "", spanId = "", flags = ""] = parts;
4182
+ if (parts.length !== 4 || version !== "00" || !/^[0-9a-f]{32}$/iu.test(traceId) || !/^[0-9a-f]{16}$/iu.test(spanId)) return void 0;
4183
+ return Tracer.externalSpan({
4184
+ traceId,
4185
+ spanId,
4186
+ sampled: (Number.parseInt(flags, 16) & 1) === 1
4187
+ });
4188
+ };
4189
+ function makeSystemMethods(deps) {
4190
+ return { health: (nonce, options) => {
4191
+ let controls = deps.invocationControls?.(options) ?? options;
4192
+ const invocation = readInvocationObservation(controls);
4193
+ const traceparent = invocation?.active === false ? void 0 : sanitizeObservationContext(invocation?.context)?.traceparent;
4194
+ const parent = traceparent === void 0 ? void 0 : traceparentSpan(traceparent);
4195
+ if (parent !== void 0) controls = carryInvocationParentSpan(controls, parent);
4196
+ return runCapxulEffect(callOperation(deps.convexCall, diagnostics.health, copyInvocationObservation(controls, { nonce })), controls, diagnostics.health.name, deps.runPromise);
4197
+ } };
4198
+ }
4199
+ //#endregion
4200
+ //#region src/platform/client/host-observation.ts
4201
+ function transitionError(record) {
4202
+ const failure = record.failure;
4203
+ if (failure?.error !== void 0) {
4204
+ const error = failure.error;
4205
+ if (record.correlation_id === void 0 || error.correlationId !== void 0) return error;
4206
+ return new CapxulError(error.code, error.message, {
4207
+ cause: error,
4208
+ correlationId: record.correlation_id,
4209
+ ...error.details === void 0 ? {} : { details: error.details },
4210
+ ...error.layer === void 0 ? {} : { layer: error.layer }
4211
+ });
4212
+ }
4213
+ return new CapxulError(record.error_code, failure?.message ?? "Identity work failed", {
4214
+ ...failure === void 0 ? {} : { cause: failure },
4215
+ details: {
4216
+ machine: record.machine,
4217
+ state: record.state,
4218
+ event: record.event,
4219
+ slot: record.slot,
4220
+ ...failure?.mode === void 0 ? {} : { failure_mode: failure.mode }
4221
+ },
4222
+ ...record.correlation_id === void 0 ? {} : { correlationId: record.correlation_id }
4223
+ });
4224
+ }
4225
+ function withHostObservation(actor, snapshot) {
4226
+ if (snapshot === void 0) return actor;
4227
+ const controls = (input) => withHostObservationControls(snapshot, input, actor.authSession()?.authUserId);
4228
+ return {
4229
+ ...actor,
4230
+ ask: (event, input) => actor.ask(event, controls(input)),
4231
+ tell: (event, input) => actor.tell(event, controls(input)),
4232
+ restoreAuthSession: (session, input) => actor.restoreAuthSession(session, controls(input))
4233
+ };
4234
+ }
4235
+ function withHostObservationControls(snapshot, input, actorId) {
4236
+ const previous = readInvocationObservation(input);
4237
+ if (previous !== void 0) return attachInvocationObservation({ ...input }, previous);
4238
+ const captured = snapshot?.() ?? { active: true };
4239
+ const context = captured.active ? withCapturedAttemptContext(captured.context, input) : captured.context;
4240
+ return attachInvocationObservation({ ...input }, {
4241
+ ...captured,
4242
+ ...context === void 0 ? {} : { context },
4243
+ ...actorId === void 0 ? {} : { actorId }
4244
+ });
4245
+ }
4246
+ //#endregion
4247
+ //#region src/platform/client/signer-readiness.ts
4248
+ /**
4249
+ * Start the signer readiness cycle when the identity becomes claimed (#1667).
4250
+ *
4251
+ * The cycle used to run only from the embedded-wallet port's `getAddress` and
4252
+ * `signRawDigest`. Nothing calls those after a sign-in or a session restore,
4253
+ * and `resetSession`, which every OTP verify performs, sets the status back to
4254
+ * `unknown`. Thus `client.signer.status()` never left `unknown` on a fresh
4255
+ * session, and each `.Actions` gate that requires `ready` stayed closed. A
4256
+ * claimed identity is the first point where the browser signer has a session to
4257
+ * recover. A signed-out client starts no cycle and reports `unknown`.
4258
+ *
4259
+ * The assembly does not await the call, on purpose: readiness must converge
4260
+ * without a signature. The cycle reports its own failure. It writes
4261
+ * `unavailable` to the same store the gate reads, and adds a DiagnosticPort
4262
+ * breadcrumb, and the next `getAddress` retries it, as before. The rejection is
4263
+ * thus observed and not discarded. A throw here would only cause an unhandled
4264
+ * rejection, and the actor shell removes an observer that throws.
4265
+ *
4266
+ * The `unknown` test holds this to ONE cycle per session. The call sets the
4267
+ * status to `recovering` synchronously, so the later claimed transitions of the
4268
+ * same session, such as the org lane, start no second cycle, and a
4269
+ * `resetSession` that sets the status back to `unknown` permits one more.
4270
+ *
4271
+ * ponytail: one cycle per claim. A cycle that fails stays `unavailable` until
4272
+ * a `getAddress` or a new claim, so a gate that refuses to sign while
4273
+ * `unavailable` needs a reload. Give the action part an explicit retry if that
4274
+ * becomes the common failure.
4275
+ */
4276
+ function startSignerReadinessOnClaim(actor, signer) {
4277
+ if (signer?.statusStore === void 0 || signer.ensureWalletReady === void 0) return () => {};
4278
+ const browserSigner = signer;
4279
+ return actor.subscribe((state) => {
4280
+ if (state.phase !== "authenticated" || state.account.at !== "claimed") return;
4281
+ if (browserSigner.statusStore?.status() !== "unknown") return;
4282
+ browserSigner.ensureWalletReady?.().catch(() => {});
4283
+ });
4284
+ }
4285
+ /**
4286
+ * Report the result of each signer readiness cycle (#1962 R04).
4287
+ *
4288
+ * The readiness store already carries the cycle boundary this needs, so no
4289
+ * public signer hook, second identity subscriber, or render counter is added.
4290
+ * `ensureOpenfortWalletReady` writes `recovering` synchronously when it starts
4291
+ * a cycle, and only the cycle that is still the current one writes `ready` or
4292
+ * `unavailable` (`create-openfort-browser-signer.ts:501-521`). Callers that
4293
+ * join a live cycle share that single write, and a `resetSession` writes
4294
+ * `unknown` first, so a cycle that a reset or an account replacement outran
4295
+ * reports nothing.
4296
+ *
4297
+ * The host context is bound when the cycle starts, so a person who signs out
4298
+ * and back in while a cycle runs cannot relabel the result that began under
4299
+ * the previous person. A signer with no readiness cycle — a node or dev key,
4300
+ * and a headless client whose requirement never opens the account lane —
4301
+ * writes no status and reports nothing.
4302
+ */
4303
+ function observeSignerReadinessCycles(signer, telemetry, hostObservationSnapshot, runPromise) {
4304
+ const statusStore = signer?.statusStore;
4305
+ if (statusStore === void 0 || telemetry === void 0) return () => {};
4306
+ let running = false;
4307
+ let startContext;
4308
+ return statusStore.subscribe((status) => {
4309
+ if (status === "recovering") {
4310
+ running = true;
4311
+ startContext = hostObservationSnapshot?.();
4312
+ return;
4313
+ }
4314
+ const cycleContext = startContext;
4315
+ const completesCycle = running;
4316
+ running = false;
4317
+ startContext = void 0;
4318
+ if (!completesCycle || status !== "ready" && status !== "unavailable") return;
4319
+ const event = {
4320
+ name: "access_recovery_completed",
4321
+ props: {
4322
+ recovery: "signer_readiness",
4323
+ outcome: status
4324
+ }
4325
+ };
4326
+ if (cycleContext !== void 0) attachInvocationObservation(event, cycleContext);
4327
+ runPromise(Effect.suspend(() => bindProductTelemetryInvocation(telemetry, event).emit(event)).pipe(logDroppedEvent(event.name))).catch(() => {});
4328
+ });
4329
+ }
4330
+ /**
4331
+ * A fresh OTP in the same browser tab reuses Openfort's localStorage and an
4332
+ * in-memory cached EOA unless the signer resets before the account lane runs.
4333
+ * Wallet readiness then starts on the fresh session, so Openfort's cycle (tens
4334
+ * of seconds on a new account) overlaps the profile and Organization forms
4335
+ * instead of running only when the claim finally asks for the signer address.
4336
+ */
4337
+ function resetSignerForFreshSession(signer) {
4338
+ if (signer === void 0 || !("resetSession" in signer) || typeof signer.resetSession !== "function") return;
4339
+ try {
4340
+ signer.resetSession();
4341
+ } catch (cause) {
4342
+ throw signerFailure(signer.source, "resetSession", cause);
4343
+ }
4344
+ if (signer.statusStore?.status() === "unknown" && typeof signer.ensureWalletReady === "function") signer.ensureWalletReady().catch(() => {});
4345
+ }
4346
+ //#endregion
4347
+ //#region src/platform/client/identity-runtime.ts
4348
+ /**
4349
+ * The identity runtime `_internal.identity` hands the React bindings: the
4350
+ * actor's state, its facade runner, raw sends that answer a refusal as a
4351
+ * value, and the onboarding profile write behind the same boundary grammar.
4352
+ */
4353
+ function makeIdentityRuntime(actor, identityPort, runPromise) {
4354
+ return {
4355
+ snapshot: actor.snapshot,
4356
+ subscribe: actor.subscribe,
4357
+ subscribeTransitions: actor.subscribeTransitions,
4358
+ runFacade: (verb, controls, run) => runIdentityFacade(verb, controls, run, runPromise),
4359
+ async send(event, controls) {
4360
+ try {
4361
+ return {
4362
+ ok: true,
4363
+ state: await runPromise(actor.ask(event, controls))
4364
+ };
4365
+ } catch (cause) {
4366
+ if (cause instanceof ActorFailure) return {
4367
+ ok: false,
4368
+ refused: cause.reason,
4369
+ state: actor.snapshot()
4370
+ };
4371
+ throw cause;
4372
+ }
4373
+ },
4374
+ async completeProfile(profile, controls) {
4375
+ if (typeof profile !== "object" || profile === null || typeof profile.displayName !== "string" || typeof profile.country !== "string" || typeof profile.handle !== "string") return {
4376
+ ok: false,
4377
+ reason: "INVALID_INPUT"
4378
+ };
4379
+ const displayName = profile.displayName.trim();
4380
+ if (displayName.length === 0) return {
4381
+ ok: false,
4382
+ reason: "INVALID_INPUT"
4383
+ };
4384
+ let country;
4385
+ try {
4386
+ country = toCountryCode(profile.country);
4387
+ } catch {
4388
+ return {
4389
+ ok: false,
4390
+ reason: "INVALID_INPUT"
4391
+ };
4392
+ }
4393
+ const handle = profile.handle.trim();
4394
+ if (handle.length === 0) return {
4395
+ ok: false,
4396
+ reason: "INVALID_INPUT"
4397
+ };
4398
+ const carried = checkProfileCarriage(profile);
4399
+ if (!carried.ok) return {
4400
+ ok: false,
4401
+ reason: "INVALID_INPUT"
4402
+ };
4403
+ const session = actor.authSession();
4404
+ if (session === null) return {
4405
+ ok: false,
4406
+ reason: "NOT_AUTHENTICATED"
4407
+ };
4408
+ const result = await runPortEffect(identityPort.completeOnboarding({
4409
+ authUserId: session.authUserId,
4410
+ email: session.email,
4411
+ displayName,
4412
+ country,
4413
+ handle,
4414
+ ...carried.carriage
4415
+ }), controls, onboardings.complete.name, runPromise);
4416
+ return result.ok ? { ok: true } : {
4417
+ ok: false,
4418
+ reason: result.error.code,
4419
+ error: result.error
4420
+ };
4421
+ }
4422
+ };
4423
+ }
4424
+ //#endregion
4425
+ //#region src/platform/client/actor-lifetime.ts
4426
+ /**
4427
+ * Boot the identity actor in its own scope and attach every observer the
4428
+ * client keeps on it. `stopActor` detaches them and closes the scope once.
4429
+ */
4430
+ function bootClientActor(input, effectRunner, observation) {
4431
+ const scope = effectRunner.runSync(Scope.make());
4432
+ const actor = withHostObservation(effectRunner.runSync(Scope.provide(bootIdentityFlow({
4433
+ ports: input.ports,
4434
+ chainId: input.bootstrap.chainId,
4435
+ requirement: input.requirement,
4436
+ ...input.signer === void 0 ? {} : { signer: input.signer },
4437
+ ...input.organizationSetup === void 0 ? {} : { organizationSetup: input.organizationSetup },
4438
+ ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
4439
+ runPromise: effectRunner.runPromise
4440
+ }), scope)), observation.hostObservationSnapshot);
4441
+ const unsubscribeProductTelemetry = actor.subscribeTransitions((record) => {
4442
+ effectRunner.runPromise(executeIdentityProductObservation(input.ports.telemetry, record, actor.snapshot())).catch(() => {});
4443
+ });
4444
+ const failureObservation = observation.failureObservation;
4445
+ const unsubscribeFailureObservation = failureObservation === void 0 ? () => {} : actor.subscribeTransitions((record) => {
4446
+ if (record.outcome !== "failed") return;
4447
+ observeFailedResult({
4448
+ ok: false,
4449
+ error: transitionError(record)
4450
+ }, failureObservation, normalizeCapxulOperation(`identity.${record.event}`), "machine");
4451
+ });
4452
+ const unsubscribeSignerReadiness = startSignerReadinessOnClaim(actor, input.signer);
4453
+ const unsubscribeReadinessObservation = observeSignerReadinessCycles(input.signer, input.ports.telemetry, observation.hostObservationSnapshot, effectRunner.runPromise);
4454
+ let stopPromise = null;
4455
+ const stopActor = () => {
4456
+ if (stopPromise === null) {
4457
+ actor.invalidateAuthLifetime();
4458
+ unsubscribeProductTelemetry();
4459
+ unsubscribeFailureObservation();
4460
+ unsubscribeSignerReadiness();
4461
+ unsubscribeReadinessObservation();
4462
+ stopPromise = effectRunner.runPromise(Scope.close(scope, Exit.void));
4463
+ }
4464
+ return stopPromise;
4465
+ };
4466
+ if (input.signal !== void 0) input.signal.addEventListener("abort", () => void stopActor(), { once: true });
4467
+ return {
4468
+ actor,
4469
+ stopActor
4470
+ };
4471
+ }
4472
+ //#endregion
4473
+ //#region src/platform/client/close.ts
4474
+ /** Join the assembled actor lifetime to the adapter lifetime for internal client factories. */
4475
+ function withAdapterClose(client, closeAdapter, runPromise) {
4476
+ const closeActor = client._internal.close;
4477
+ let closePromise;
4478
+ const close = () => {
4479
+ closePromise ??= Promise.allSettled([Promise.resolve().then(() => closeActor?.()), Promise.resolve().then(closeAdapter)]).then((results) => {
4480
+ const failure = results.find((result) => result.status === "rejected");
4481
+ if (failure?.status === "rejected") throw failure.reason;
4482
+ });
4483
+ return closePromise;
4484
+ };
4485
+ return {
4486
+ ...client,
4487
+ close: () => closeClientResources(close, runPromise),
4488
+ _internal: {
4489
+ ...client._internal,
4490
+ close
4491
+ }
4492
+ };
4493
+ }
4494
+ /** Keep lifecycle failures on the same public result boundary as domain work. */
4495
+ const closeClientResources = (close, runPromise) => toCapxulResult(Effect.promise(close), Context.empty(), runPromise);
4496
+ //#endregion
4497
+ //#region src/platform/client/internal-surface.ts
4498
+ /**
4499
+ * The client's `_internal` surface: the identity runtime, the dev faucet, the
4500
+ * indexer seam, the setup receipt read and the telemetry hooks first-party
4501
+ * bindings use. None of it is public API.
4502
+ */
4503
+ function makeClientInternals(input, shared) {
4504
+ return {
4505
+ identity: shared.identity,
4506
+ bootstrap: input.bootstrap,
4507
+ accounts: { fund: (...args) => shared.accounts.fund(...args) },
4508
+ ...input.ports.indexerRead === void 0 ? {} : { indexer: input.ports.indexerRead },
4509
+ organizationSetup: { getProofReceipt: (orgId) => runPortEffect(callOperation(input.ports.convexCall, setups.getProofReceipt, { orgId }).pipe(Effect.mapError((publicError) => ({ publicError }))), void 0, CAPXUL_OPERATIONS.runtime.port, shared.runPromise) },
4510
+ telemetry: input.ports.telemetry,
4511
+ accountSetup: shared.accountSetup,
4512
+ captureExternalAddressAcknowledged: (address, organizationId) => {
4513
+ const telemetry = input.ports.telemetry;
4514
+ if (telemetry === void 0) return;
4515
+ shared.runPromise(Effect.suspend(() => telemetry.emit({
4516
+ name: "quick_pay_external_address_acknowledged",
4517
+ props: {
4518
+ recipient_locator_kind: "address",
4519
+ recipient_resolution_status: "external_address",
4520
+ recipient_address: address,
4521
+ external_address_acknowledged: true,
4522
+ ...organizationId === void 0 ? {} : { organization_id: organizationId }
4523
+ }
4524
+ })).pipe(logDroppedEvent("quick_pay_external_address_acknowledged")));
4525
+ },
4526
+ close: shared.close
4527
+ };
4528
+ }
4529
+ //#endregion
4530
+ //#region src/platform/client/assemble.ts
4531
+ function assembleCapxulClient(input) {
4532
+ const observation = input;
4533
+ const authCache = input.authCache ?? detectAuthCacheAdapter();
4534
+ const accountSetup = input.accountSetup ?? "automatic";
4535
+ const effectRunner = input.effectRunner ?? {
4536
+ runSync: Effect.runSync,
4537
+ runPromise: Effect.runPromise
4538
+ };
4539
+ const { actor, stopActor } = bootClientActor(input, effectRunner, observation);
4540
+ const invocationControls = (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls, actor.authSession()?.authUserId);
4541
+ const selectedOrgPort = input.orgPort;
4542
+ let kickProvisioning;
4543
+ const financialOps = makeFinancialOpsMethods({
4544
+ runPromise: effectRunner.runPromise,
4545
+ invocationControls,
4546
+ actor,
4547
+ chainId: input.bootstrap.chainId,
4548
+ convexCall: input.ports.convexCall,
4549
+ ...input.signer === void 0 ? {} : { signer: input.signer },
4550
+ telemetry: input.ports.telemetry,
4551
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
4552
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
4553
+ });
4554
+ const accountInput = {
4555
+ actor,
4556
+ authCache,
4557
+ identityPort: input.ports.identity,
4558
+ smartAccountPort: input.ports.smartAccount,
4559
+ telemetry: input.ports.telemetry,
4560
+ chainId: input.bootstrap.chainId,
4561
+ requirement: input.requirement,
4562
+ accountReadPort: input.ports.accountRead,
4563
+ profileMethods: financialOps.me,
4564
+ convexCall: input.ports.convexCall,
4565
+ runPromise: effectRunner.runPromise,
4566
+ invocationControls,
4567
+ ...input.signer === void 0 ? {} : { signer: input.signer }
4568
+ };
4569
+ const accountBundle = makeAccountMethods(accountInput);
4570
+ kickProvisioning = accountBundle.kickProvisioning;
4571
+ const account = accountBundle.methods;
4572
+ const authMethodsInput = {
4573
+ actor,
4574
+ authClient: input.ports.authClient,
4575
+ authCache,
4576
+ clock: input.ports.clock,
4577
+ telemetry: input.ports.telemetry,
4578
+ runPromise: effectRunner.runPromise,
4579
+ ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
4580
+ ...input.invokeTimeoutMs === void 0 ? {} : { invokeTimeoutMs: input.invokeTimeoutMs },
4581
+ afterVerifyOtp: async () => {
4582
+ resetSignerForFreshSession(input.signer);
4583
+ if (accountSetup === "explicit") return;
4584
+ kickProvisioning?.();
4585
+ }
4586
+ };
4587
+ const authCore = makeAuthMethods(authMethodsInput);
4588
+ const auth = {
4589
+ ...authCore,
4590
+ clientRequests: makeClientRequestMethods({
4591
+ authBaseUrl: input.bootstrap.authBaseUrl,
4592
+ signIn: authCore.signIn,
4593
+ verifyOtp: authCore.verifyOtp
4594
+ })
4595
+ };
4596
+ const smartAccountInput = {
4597
+ actor,
4598
+ smartAccountPort: input.ports.smartAccount,
4599
+ chainId: input.bootstrap.chainId,
4600
+ sessionAuthUserIdFallback: null,
4601
+ telemetry: input.ports.telemetry,
4602
+ runPromise: effectRunner.runPromise
4603
+ };
4604
+ const smartAccount = makeSmartAccountMethods(smartAccountInput);
4605
+ const identity = makeIdentityMethods({
4606
+ identityPort: input.ports.identity,
4607
+ actor,
4608
+ convexCall: input.ports.convexCall,
4609
+ runPromise: effectRunner.runPromise
4610
+ });
4611
+ const onboarding = makeOnboardingMethods({
4612
+ authLifetime: actor.authLifetime,
4613
+ matchesAuthLifetime: actor.matchesAuthLifetime,
4614
+ restoreSession: (...args) => auth.getSession(...args),
4615
+ refreshIdentity: (options) => runCapxulEffect(askIdentity(actor, { _tag: "ReadSession" }, options).pipe(Effect.asVoid), options, CAPXUL_OPERATIONS.identity.readSession, effectRunner.runPromise),
4616
+ handleAvailable: (...args) => identity.handleAvailable(...args),
4617
+ completeProfile: (...args) => identity.completeProfile(...args),
4618
+ loadProfile: (...args) => identity.loadCurrent(...args),
4619
+ ensureAccount: (...args) => account._internal.ensureReady(...args),
4620
+ getLifecycle: (...args) => account.getLifecycle(...args),
4621
+ snapshotIdentity: actor.snapshot,
4622
+ subscribeIdentity: actor.subscribe,
4623
+ sendIdentity: (event, options) => runCapxulEffect(askIdentity(actor, event, options), options, CAPXUL_OPERATIONS._internal.identity.send, effectRunner.runPromise),
4624
+ ...input.organizationSetup === void 0 ? {} : { organizationSetup: input.organizationSetup }
4625
+ });
4626
+ const system = makeSystemMethods({
4627
+ convexCall: input.ports.convexCall,
4628
+ runPromise: effectRunner.runPromise,
4629
+ invocationControls: (controls) => withHostObservationControls(observation.hostObservationSnapshot, controls)
4630
+ });
4631
+ const media = makeMediaMethods({
4632
+ convexCall: input.ports.convexCall,
4633
+ runPromise: effectRunner.runPromise,
4634
+ invocationControls
4635
+ });
4636
+ const holdings = makeHoldingsMethods({
4637
+ convexCall: input.ports.convexCall,
4638
+ runPromise: effectRunner.runPromise,
4639
+ invocationControls,
4640
+ ...input.ports.indexerRead === void 0 ? {} : { indexerRead: input.ports.indexerRead }
4641
+ });
4642
+ const accountsInput = {
4643
+ accountReadPort: input.ports.accountRead,
4644
+ chainId: input.bootstrap.chainId,
4645
+ telemetry: input.ports.telemetry,
4646
+ runPromise: effectRunner.runPromise,
4647
+ invocationControls
4648
+ };
4649
+ const accounts = makeAccountsMethods(accountsInput);
4650
+ const orgMethods = makeOrgMethods({
4651
+ actor,
4652
+ chainId: input.bootstrap.chainId,
4653
+ telemetry: input.ports.telemetry,
4654
+ runPromise: effectRunner.runPromise,
4655
+ invocationControls,
4656
+ requestKeyScope: `${input.bootstrap.convexUrl}:${String(input.bootstrap.chainId)}`,
4657
+ ...selectedOrgPort === void 0 ? {} : { orgPort: selectedOrgPort },
4658
+ convexCall: input.ports.convexCall,
4659
+ ...input.signer === void 0 ? {} : { signer: input.signer },
4660
+ ...input.organizationSetup === void 0 ? {} : { organizationSetup: input.organizationSetup }
4661
+ });
4662
+ const currentUser = makeCurrentUserMethods({
4663
+ me: financialOps.me,
4664
+ accounts,
4665
+ smartAccount,
4666
+ orgs: orgMethods.orgs
4667
+ });
4668
+ const identityRuntime = makeIdentityRuntime(actor, input.ports.identity, effectRunner.runPromise);
4669
+ const client = {
4670
+ checkouts: makeCheckoutMethods({
4671
+ convexCall: input.ports.convexCall,
4672
+ runPromise: effectRunner.runPromise,
4673
+ ...input.signer === void 0 || input.ports.convexCall === void 0 ? {} : { execution: {
4674
+ actor,
4675
+ signer: input.signer,
4676
+ chainId: input.bootstrap.chainId,
4677
+ convexCall: input.ports.convexCall,
4678
+ runPromise: effectRunner.runPromise,
4679
+ telemetry: input.ports.telemetry
4680
+ } },
4681
+ invocationControls
4682
+ }),
4683
+ auth,
4684
+ smartAccount,
4685
+ identity,
4686
+ onboarding,
4687
+ system,
4688
+ account,
4689
+ accounts,
4690
+ currentUser,
4691
+ ...financialOps,
4692
+ holdings,
4693
+ media,
4694
+ orgs: orgMethods.orgs,
4695
+ org: (...args) => orgMethods.org(...args),
4696
+ signer: input.signer?.statusStore ?? UNOBSERVABLE_SIGNER_STATUS,
4697
+ _internal: makeClientInternals(input, {
4698
+ identity: identityRuntime,
4699
+ accounts,
4700
+ accountSetup,
4701
+ runPromise: effectRunner.runPromise,
4702
+ close: stopActor
4703
+ }),
4704
+ close: () => closeClientResources(stopActor, effectRunner.runPromise)
4705
+ };
4706
+ return observeSdkClient(client, observation.failureObservation, observation.hostObservationSnapshot);
4707
+ }
4708
+ //#endregion
4709
+ //#region src/observability/diagnostics/effect-log.ts
4710
+ /**
4711
+ * Diagnostic breadcrumbs as SDK log records. The signer's readiness cycle
4712
+ * traces (`openfort.*`) used to reach only the browser console; through this
4713
+ * adapter they run in the client's own Effect context, so the engineering
4714
+ * telemetry layer ships them to PostHog Logs beside `identity.transition`.
4715
+ * Logging never throws back into the cycle that emitted it.
4716
+ */
4717
+ var EffectLogDiagnosticAdapter = class {
4718
+ #runPromise;
4719
+ #console;
4720
+ constructor(context, consoleFallback) {
4721
+ this.#runPromise = Effect.runPromiseWith(context);
4722
+ this.#console = consoleFallback;
4723
+ }
4724
+ trace(scope, detail) {
4725
+ this.#console?.trace(scope, detail);
4726
+ try {
4727
+ this.#runPromise(Effect.logInfo(scope).pipe(Effect.annotateLogs({
4728
+ ...detail,
4729
+ diagnostic_scope: scope
4730
+ }), Effect.ignoreCause)).catch(() => void 0);
4731
+ } catch {}
4732
+ }
4733
+ };
4734
+ //#endregion
4735
+ //#region src/observability/diagnostics/console.ts
4736
+ const DEFAULT_ACCOUNT_SETUP_LOG_PREFIX = "[capxul:account-setup]";
4737
+ var ConsoleDiagnosticAdapter = class {
4738
+ prefix;
4739
+ constructor(prefix = DEFAULT_ACCOUNT_SETUP_LOG_PREFIX) {
4740
+ this.prefix = prefix;
4741
+ }
4742
+ trace(scope, detail) {
4743
+ globalThis.console?.debug?.(`${this.prefix} ${scope}`, detail);
4744
+ }
4745
+ };
4746
+ //#endregion
4747
+ //#region src/signing/openfort-production.ts
4748
+ /** @internal Resolve the signer after production bootstrap without exposing its other fields. */
4749
+ function resolveOpenfortProductionSigner(input) {
4750
+ const bootstrap = input.bootstrap;
4751
+ let signer = input.signer ?? input.factory?.({
4752
+ authBaseUrl: input.authBaseUrl,
4753
+ openfortPublishableKey: bootstrap.openfortPublishableKey,
4754
+ shieldPublishableKey: bootstrap.shieldPublishableKey,
4755
+ ...bootstrap.openfortAuthProvider === void 0 ? {} : { openfortAuthProvider: bootstrap.openfortAuthProvider }
4756
+ });
4757
+ if (signer === void 0 && input.runtime === "browser") signer = input.createBrowserSigner({
4758
+ ...bootstrap,
4759
+ authBaseUrl: input.authBaseUrl
4760
+ }, {
4761
+ diagnostic: input.diagnostic,
4762
+ signUserOpHash: input.embeddedSign
4763
+ });
4764
+ if (signer === void 0 || input.finalSign === void 0) return signer;
4765
+ return {
4766
+ source: signer.source,
4767
+ getAddress: () => signer.getAddress(),
4768
+ signUserOpHash: (hash) => input.finalSign(hash, signer),
4769
+ ...signer.statusStore === void 0 ? {} : { statusStore: signer.statusStore },
4770
+ ...signer.ensureWalletReady === void 0 ? {} : { ensureWalletReady: signer.ensureWalletReady.bind(signer) },
4771
+ ..."resetSession" in signer ? { resetSession: () => signer.resetSession() } : {}
4772
+ };
4773
+ }
4774
+ //#endregion
4775
+ //#region src/platform/production-client.ts
4776
+ /** Shared production composition. The normal factory passes no controls. */
4777
+ async function createCapxulClientWithSignerControls(input, controls) {
4778
+ const invocationObservability = bindHostObservabilityInvocation(input.observability);
4779
+ const observation = invocationObservability?.failures ?? input.observation;
4780
+ const validation = validateCreateCapxulClientInput(input);
4781
+ if (!validation.ok) return observeFailedResult(validation, observation, "createCapxulClient");
4782
+ const resetSignerSession = { current: null };
4783
+ const adapters = await createProductionAdapters({
4784
+ ...input,
4785
+ resetSignerSession,
4786
+ ...invocationObservability === void 0 ? {} : { invocationObservability }
4787
+ });
4788
+ if (!adapters.ok) return observeFailedResult(adapters, observation, "createCapxulClient");
4789
+ try {
4790
+ const runtime = input.runtime ?? detectRuntime();
4791
+ const resolvedAuthBaseUrl = resolveBrowserAuthBaseUrl({
4792
+ bootstrapAuthBaseUrl: adapters.value.bootstrap.authBaseUrl,
4793
+ runtime,
4794
+ ...input.authBaseUrl === void 0 ? {} : { override: input.authBaseUrl }
4795
+ });
4796
+ const signer = resolveOpenfortProductionSigner({
4797
+ bootstrap: adapters.value.bootstrap,
4798
+ runtime,
4799
+ authBaseUrl: resolvedAuthBaseUrl,
4800
+ createBrowserSigner: createOpenfortBrowserSignerFromBootstrap,
4801
+ diagnostic: new EffectLogDiagnosticAdapter(adapters.value.context, productionLocalDiagnostics(input) ? new ConsoleDiagnosticAdapter() : void 0),
4802
+ signer: input.signer,
4803
+ factory: input.openfortSignerFactory,
4804
+ finalSign: controls.signUserOpHash,
4805
+ embeddedSign: controls.signEmbeddedUserOpHash
4806
+ });
4807
+ const factoryChainFence = assertDevKeySignerIsTestnetOnly(signer, adapters.value.bootstrap.chainId);
4808
+ if (!factoryChainFence.ok) {
4809
+ await adapters.value.close().catch(() => void 0);
4810
+ return observeFailedResult(factoryChainFence, observation, "createCapxulClient");
4811
+ }
4812
+ if (signer !== void 0 && "resetSession" in signer) {
4813
+ const { resetSession, source } = signer;
4814
+ resetSignerSession.current = () => {
4815
+ try {
4816
+ resetSession();
4817
+ } catch (cause) {
4818
+ throw signerFailure(source, "resetSession", cause);
4819
+ }
4820
+ };
4821
+ }
4822
+ const effectRunner = {
4823
+ runSync: Effect.runSyncWith(adapters.value.context),
4824
+ runPromise: Effect.runPromiseWith(adapters.value.context)
4825
+ };
4826
+ const ownedClient = withAdapterClose(assembleCapxulClient({
4827
+ ports: adapters.value.ports,
4828
+ bootstrap: adapters.value.bootstrap,
4829
+ authCache: adapters.value.ports.authCache,
4830
+ requirement: input.requirement ?? "none",
4831
+ accountSetup: input.accountSetup,
4832
+ ...signer === void 0 ? {} : { signer },
4833
+ orgPort: Context.get(adapters.value.context, OrgPortTag),
4834
+ organizationSetup: new ConvexOrganizationSetupAdapter({
4835
+ convex: adapters.value.ports.convexCall,
4836
+ ...signer === void 0 ? {} : { signer },
4837
+ chainId: adapters.value.bootstrap.chainId,
4838
+ runPromise: effectRunner.runPromise
4839
+ }),
4840
+ ...input.signal === void 0 ? {} : { signal: input.signal },
4841
+ ...input.otpTtlMs === void 0 ? {} : { otpTtlMs: input.otpTtlMs },
4842
+ invokeTimeoutMs: input.invokeTimeoutMs ?? 3e4,
4843
+ ...input.observability?.failures === void 0 && input.observation === void 0 ? {} : { failureObservation: input.observability?.failures ?? input.observation },
4844
+ ...input.observability === void 0 ? {} : { hostObservationSnapshot: () => snapshotHostObservability(input.observability) },
4845
+ effectRunner
4846
+ }), adapters.value.close, effectRunner.runPromise);
4847
+ const close = idempotentClose(async () => {
4848
+ try {
4849
+ if (signer !== void 0 && "resetSession" in signer) try {
4850
+ signer.resetSession();
4851
+ } catch (cause) {
4852
+ throw signerFailure(signer.source, "resetSession", cause);
4853
+ }
4854
+ } finally {
4855
+ await ownedClient._internal.close?.();
4856
+ }
4857
+ });
4858
+ return {
4859
+ ok: true,
4860
+ value: {
4861
+ ...ownedClient,
4862
+ close: () => closeClientResources(close, effectRunner.runPromise),
4863
+ _internal: {
4864
+ ...ownedClient._internal,
4865
+ close
4866
+ }
4867
+ }
4868
+ };
4869
+ } catch (cause) {
4870
+ await adapters.value.close().catch(() => void 0);
4871
+ return observeFailedResult({
4872
+ ok: false,
4873
+ error: toPublicError(cause)
4874
+ }, observation, "createCapxulClient");
4875
+ }
4876
+ }
4877
+ const SDK_VERSION = version;
4878
+ async function createCapxulClient(input) {
4879
+ return createCapxulClientWithSignerControls(input, {});
4880
+ }
4881
+ //#endregion
4882
+ export { cloneTelemetryEvent as a, identityErrorFromCapxul as c, devPrivateKeySigner as d, createOpenfortBrowserSigner as f, openfortEmbeddedWalletPort as h, postHogObservability as i, bootstrapErrorFromCapxul as l, openfortEmbeddedSignerFromWallet as m, createCapxulClientWithSignerControls as n, cloneTelemetryProps as o, embeddedSigner as p, assembleCapxulClient as r, IdentityPortTag as s, createCapxulClient as t, deriveDevPrivateKey as u };