@capxul/sdk-react 4.20.0-beta.4 → 4.20.0-beta.40

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.
@@ -1,8 +1,8 @@
1
1
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
2
  import { QueryClient, QueryClientProvider, useQueryClient } from "@tanstack/react-query";
3
- import { CapxulError, createCapxulClient, isCapxulError, resolveIdentityDestination, toCountryCode, toHandle } from "@capxul/sdk";
3
+ import { CapxulError, createCapxulClient, isCapxulError, resolveIdentityDestination, toCountryCode, toHandle, toOrgId } from "@capxul/sdk";
4
4
  import { jsx } from "react/jsx-runtime";
5
- //#region src/internal/capxul-bootstrap-context.tsx
5
+ //#region src/platform/bootstrap-context.tsx
6
6
  const CapxulBootstrapContext = createContext(null);
7
7
  function CapxulBootstrapProvider({ value, children }) {
8
8
  return /* @__PURE__ */ jsx(CapxulBootstrapContext.Provider, {
@@ -16,7 +16,7 @@ function useCapxul() {
16
16
  return state;
17
17
  }
18
18
  //#endregion
19
- //#region src/internal/capxul-client-context.tsx
19
+ //#region src/platform/client-context.tsx
20
20
  const MISSING_CAPXUL_CLIENT_PROVIDER = Symbol("MISSING_CAPXUL_CLIENT_PROVIDER");
21
21
  const CapxulClientContext = createContext(MISSING_CAPXUL_CLIENT_PROVIDER);
22
22
  function CapxulClientProvider({ client, children }) {
@@ -36,7 +36,14 @@ function useCapxulClientOrNull() {
36
36
  return client;
37
37
  }
38
38
  //#endregion
39
- //#region src/internal/reactivity-keys.ts
39
+ //#region src/platform/query-keys.ts
40
+ /**
41
+ * An Organization id a consumer passed as a plain string (often a URL
42
+ * parameter), as a key segment. An absent or empty id names no Organization.
43
+ */
44
+ function orgKeySegment(orgId) {
45
+ return orgId === void 0 || orgId === "" ? void 0 : toOrgId(orgId);
46
+ }
40
47
  /**
41
48
  * The account a read is answered FOR, as a key segment (#2007 R19).
42
49
  *
@@ -144,7 +151,7 @@ const capxulKeys = {
144
151
  "terms"
145
152
  ],
146
153
  activity: ["capxul", "activity"],
147
- activityDetail: (reference, actor = void 0) => [
154
+ activityDetail: (reference, actor) => [
148
155
  "capxul",
149
156
  "activity",
150
157
  actor ?? "self",
@@ -170,7 +177,7 @@ const capxulKeys = {
170
177
  ]
171
178
  };
172
179
  //#endregion
173
- //#region src/internal/invocation-controls.ts
180
+ //#region src/platform/invocation-controls.ts
174
181
  /**
175
182
  * Keep only the five public option fields, and only when they hold the right
176
183
  * kind of value. A named action verb takes its attempt as an optional FIRST
@@ -209,72 +216,23 @@ function toInvocationControls(options) {
209
216
  };
210
217
  }
211
218
  //#endregion
212
- //#region src/identity.tsx
213
- const MISSING_IDENTITY_PROVIDER = Symbol("MISSING_IDENTITY_PROVIDER");
214
- const IdentityContext = createContext(MISSING_IDENTITY_PROVIDER);
219
+ //#region src/platform/identity-lanes.ts
215
220
  const failure = (result) => result.ok ? null : {
216
221
  ok: false,
217
222
  reason: result.refused
218
223
  };
219
- async function guarded(runtime, verb, options, run) {
220
- const invocation = toInvocationControls(options);
221
- try {
222
- return await (runtime.runFacade?.(verb, invocation, run) ?? run(invocation));
223
- } catch {
224
- return {
225
- ok: false,
226
- reason: "UNKNOWN"
227
- };
228
- }
229
- }
230
- function organizationFailure(reason, state) {
231
- const stateFailure = state.phase === "faulted" ? state.failure : state.phase === "authenticated" && state.account.at === "failed" ? state.account.failure : state.phase === "authenticated" && state.account.at === "claimed" && state.account.org?.at === "failed" ? state.account.org.failure : void 0;
232
- if (stateFailure?.code === reason) return {
233
- ok: false,
234
- reason,
235
- error: stateFailure.error ?? new CapxulError(stateFailure.code === "WORK_DIED" ? "UNKNOWN" : stateFailure.code, stateFailure.message, {
236
- ...stateFailure.mode === void 0 ? {} : { details: { failure_mode: stateFailure.mode } },
237
- layer: "identity"
238
- })
239
- };
240
- return {
241
- ok: false,
242
- reason,
243
- error: new CapxulError(reason === "WORK_DIED" ? "UNKNOWN" : reason, reason === "INVALID_INPUT" ? "Organization details are invalid." : "Organization creation failed.", { layer: "identity" })
244
- };
245
- }
246
- function normalizeOrganization(organization) {
247
- const name = typeof organization.name === "string" ? organization.name.trim() : "";
248
- const handle = typeof organization.handle === "string" ? organization.handle.trim().toLowerCase() : "";
249
- if (name.length === 0) return null;
250
- if (organization.bio !== void 0 && typeof organization.bio !== "string") return null;
251
- if (organization.size !== void 0 && typeof organization.size !== "string") return null;
252
- if (organization.logoStorageId !== void 0 && (typeof organization.logoStorageId !== "string" || organization.logoStorageId.length === 0)) return null;
253
- try {
254
- return {
255
- name,
256
- handle: toHandle(handle),
257
- country: toCountryCode(organization.country),
258
- ...organization.bio === void 0 ? {} : { bio: organization.bio },
259
- ...organization.size === void 0 ? {} : { size: organization.size },
260
- ...organization.logoStorageId === void 0 ? {} : { logoStorageId: organization.logoStorageId }
261
- };
262
- } catch {
263
- return null;
264
- }
265
- }
266
- /** The Organization id when its lane is already running or finished, else null. */
267
- const runningOrganization = (state) => {
268
- if (state.phase !== "authenticated" || state.account.at !== "claimed") return null;
269
- const org = state.account.org;
270
- return org !== null && (org.at === "loading" || org.at === "settingUp" || org.at === "ready") ? org.orgId : null;
271
- };
272
224
  /** A superseded session read waits at most this long for the winning read. */
273
225
  const SESSION_SETTLE_LIMIT_MS = 3e4;
274
226
  const accountInFlight = (state) => state.phase === "authenticated" && (state.account.at === "deriving" || state.account.at === "claiming");
275
- function createAuth(client, clearAuthenticatedQueries) {
276
- const runtime = client._internal.identity;
277
- const settledSession = (invocation) => new Promise((resolve) => {
227
+ /**
228
+ * The session slot keeps the latest ReadSession only: a second reader
229
+ * interrupts the first and the machine refuses the first with SUPERSEDED.
230
+ * Two onboarding callers read at once after a reload (the controller's
231
+ * resume and the recovery submit), so the loser must wait for the winner's
232
+ * read to land and then continue on the shared state, not fail.
233
+ */
234
+ function settledSession(runtime, invocation) {
235
+ return new Promise((resolve) => {
278
236
  let done = false;
279
237
  let stop = null;
280
238
  let timer = null;
@@ -310,20 +268,29 @@ function createAuth(client, clearAuthenticatedQueries) {
310
268
  stop = unsubscribe;
311
269
  timer = setTimeout(() => finish({
312
270
  ok: false,
313
- reason: "UNKNOWN"
271
+ reason: "UNKNOWN",
272
+ error: new CapxulError("UNKNOWN", "The session did not settle in time.", {
273
+ mode: "timeout",
274
+ layer: "identity",
275
+ details: { limitMs: SESSION_SETTLE_LIMIT_MS }
276
+ })
314
277
  }), SESSION_SETTLE_LIMIT_MS);
315
278
  });
316
- const read = async (invocation) => {
317
- const result = await runtime.send({ _tag: "ReadSession" }, invocation);
318
- const refused = failure(result);
319
- if (refused === null) return { ok: true };
320
- return refused.reason === "SUPERSEDED" ? settledSession(invocation) : refused;
321
- };
322
- const ensureAccount = async (invocation) => {
323
- const result = await runtime.send({ _tag: "EnsureAccount" }, invocation);
324
- return failure(result) ?? { ok: true };
325
- };
326
- const settledAccount = (invocation) => new Promise((resolve) => {
279
+ }
280
+ /** Read the session; a superseded read waits for the winning one. */
281
+ async function readSession(runtime, invocation) {
282
+ const result = await runtime.send({ _tag: "ReadSession" }, invocation);
283
+ const refused = failure(result);
284
+ if (refused === null) return { ok: true };
285
+ return refused.reason === "SUPERSEDED" ? settledSession(runtime, invocation) : refused;
286
+ }
287
+ /**
288
+ * The account lane accepts no account event while a derivation or claim is
289
+ * landing. A person who submits the Organization before their own Safe claim
290
+ * settles must wait for it, not be refused with WRONG_STATE.
291
+ */
292
+ function settledAccount(runtime, invocation) {
293
+ return new Promise((resolve) => {
327
294
  let done = false;
328
295
  let stop = null;
329
296
  const onAbort = () => finish({
@@ -352,44 +319,110 @@ function createAuth(client, clearAuthenticatedQueries) {
352
319
  stop = unsubscribe;
353
320
  if (!accountInFlight(runtime.snapshot())) finish({ ok: true });
354
321
  });
355
- const reachClaimed = async (invocation) => {
356
- let state = runtime.snapshot();
357
- if (accountInFlight(state)) {
358
- const settled = await settledAccount(invocation);
359
- if (!settled.ok) return settled;
360
- state = runtime.snapshot();
361
- }
362
- if (state.phase !== "authenticated" || state.account.at === "unknown") {
363
- const result = await runtime.send({ _tag: "EnsureAccount" }, invocation);
364
- const refused = failure(result);
365
- if (refused !== null) return refused;
366
- state = result.state;
367
- }
322
+ }
323
+ /** Drive the person's own Account to claimed, waiting out a landing derivation or claim. */
324
+ async function reachClaimed(runtime, invocation) {
325
+ let state = runtime.snapshot();
326
+ if (accountInFlight(state)) {
327
+ const settled = await settledAccount(runtime, invocation);
328
+ if (!settled.ok) return settled;
329
+ state = runtime.snapshot();
330
+ }
331
+ if (state.phase !== "authenticated" || state.account.at === "unknown") {
332
+ const result = await runtime.send({ _tag: "EnsureAccount" }, invocation);
333
+ const refused = failure(result);
334
+ if (refused !== null) return refused;
335
+ state = result.state;
336
+ }
337
+ if (state.phase !== "authenticated") return {
338
+ ok: false,
339
+ reason: "WRONG_STATE"
340
+ };
341
+ if (state.account.at === "claimed") return { ok: true };
342
+ if (accountInFlight(state)) {
343
+ const settled = await settledAccount(runtime, invocation);
344
+ if (!settled.ok) return settled;
345
+ state = runtime.snapshot();
368
346
  if (state.phase !== "authenticated") return {
369
347
  ok: false,
370
348
  reason: "WRONG_STATE"
371
349
  };
372
350
  if (state.account.at === "claimed") return { ok: true };
373
- if (accountInFlight(state)) {
374
- const settled = await settledAccount(invocation);
375
- if (!settled.ok) return settled;
376
- state = runtime.snapshot();
377
- if (state.phase !== "authenticated") return {
378
- ok: false,
379
- reason: "WRONG_STATE"
380
- };
381
- if (state.account.at === "claimed") return { ok: true };
382
- }
383
- const event = state.account.at === "failed" ? { _tag: "RetryAccount" } : state.account.at === "counterfactual" ? { _tag: "ClaimAccount" } : { _tag: "EnsureAccount" };
384
- const result = await runtime.send(event, invocation);
385
- const refused = failure(result);
386
- if (refused !== null) return refused;
387
- const next = result.state;
388
- return next.phase === "authenticated" && next.account.at === "claimed" ? { ok: true } : {
351
+ }
352
+ const event = state.account.at === "failed" ? { _tag: "RetryAccount" } : state.account.at === "counterfactual" ? { _tag: "ClaimAccount" } : { _tag: "EnsureAccount" };
353
+ const result = await runtime.send(event, invocation);
354
+ const refused = failure(result);
355
+ if (refused !== null) return refused;
356
+ const next = result.state;
357
+ return next.phase === "authenticated" && next.account.at === "claimed" ? { ok: true } : {
358
+ ok: false,
359
+ reason: "WRONG_STATE"
360
+ };
361
+ }
362
+ //#endregion
363
+ //#region src/platform/organization-submission.ts
364
+ function organizationFailure(reason, state) {
365
+ const stateFailure = state.phase === "faulted" ? state.failure : state.phase === "authenticated" && state.account.at === "failed" ? state.account.failure : state.phase === "authenticated" && state.account.at === "claimed" ? state.account.org?.failure : void 0;
366
+ if (stateFailure?.code === reason) return {
367
+ ok: false,
368
+ reason,
369
+ error: stateFailure.error ?? new CapxulError(stateFailure.code === "WORK_DIED" ? "UNKNOWN" : stateFailure.code, stateFailure.message, {
370
+ ...stateFailure.mode === void 0 ? {} : { details: { failure_mode: stateFailure.mode } },
371
+ layer: "identity"
372
+ })
373
+ };
374
+ return {
375
+ ok: false,
376
+ reason,
377
+ error: new CapxulError(reason === "WORK_DIED" ? "UNKNOWN" : reason, reason === "INVALID_INPUT" ? "Organization details are invalid." : "Organization creation failed.", { layer: "identity" })
378
+ };
379
+ }
380
+ function normalizeOrganization(organization) {
381
+ const name = typeof organization.name === "string" ? organization.name.trim() : "";
382
+ const handle = typeof organization.handle === "string" ? organization.handle.trim().toLowerCase() : "";
383
+ if (name.length === 0) return null;
384
+ if (organization.bio !== void 0 && typeof organization.bio !== "string") return null;
385
+ if (organization.size !== void 0 && typeof organization.size !== "string") return null;
386
+ if (organization.logoStorageId !== void 0 && (typeof organization.logoStorageId !== "string" || organization.logoStorageId.length === 0)) return null;
387
+ try {
388
+ return {
389
+ name,
390
+ handle: toHandle(handle),
391
+ country: toCountryCode(organization.country),
392
+ ...organization.bio === void 0 ? {} : { bio: organization.bio },
393
+ ...organization.size === void 0 ? {} : { size: organization.size },
394
+ ...organization.logoStorageId === void 0 ? {} : { logoStorageId: organization.logoStorageId }
395
+ };
396
+ } catch {
397
+ return null;
398
+ }
399
+ }
400
+ /** The Organization id when its lane is already running or finished, else null. */
401
+ const runningOrganization = (state) => {
402
+ if (state.phase !== "authenticated" || state.account.at !== "claimed") return null;
403
+ return state.account.org?.orgId ?? null;
404
+ };
405
+ //#endregion
406
+ //#region src/platform/identity-auth.ts
407
+ async function guarded(runtime, verb, options, run) {
408
+ const invocation = toInvocationControls(options);
409
+ try {
410
+ return await (runtime.runFacade?.(verb, invocation, run) ?? run(invocation));
411
+ } catch (cause) {
412
+ const error = isCapxulError(cause) ? cause : new CapxulError("UNKNOWN", "The identity request failed unexpectedly.", {
413
+ cause,
414
+ layer: "identity"
415
+ });
416
+ return {
389
417
  ok: false,
390
- reason: "WRONG_STATE"
418
+ reason: error.code,
419
+ error
391
420
  };
392
- };
421
+ }
422
+ }
423
+ /** The auth verbs behind `useCapxulAuth`, each run as one guarded facade call. */
424
+ function createAuth(client, clearAuthenticatedQueries) {
425
+ const runtime = client._internal.identity;
393
426
  const completeProfile = async (profile, invocation) => {
394
427
  const result = await runtime.completeProfile(profile, invocation);
395
428
  return result.ok ? { ok: true } : result;
@@ -400,14 +433,14 @@ function createAuth(client, clearAuthenticatedQueries) {
400
433
  ok: false,
401
434
  reason: "INVALID_INPUT"
402
435
  };
403
- const refreshed = await read(invocation);
436
+ const refreshed = await readSession(runtime, invocation);
404
437
  if (!refreshed.ok) return refreshed;
405
438
  const current = runtime.snapshot();
406
439
  if (current.phase !== "authenticated" || !current.profileComplete) {
407
440
  const completed = await runtime.completeProfile(submission.profileDetails, invocation);
408
441
  if (!completed.ok) return completed;
409
442
  }
410
- const claimed = await reachClaimed(invocation);
443
+ const claimed = await reachClaimed(runtime, invocation);
411
444
  return claimed.ok ? {
412
445
  ok: true,
413
446
  organization
@@ -477,9 +510,9 @@ function createAuth(client, clearAuthenticatedQueries) {
477
510
  ok: false,
478
511
  reason: completed.reason
479
512
  };
480
- const refreshed = await read(invocation);
513
+ const refreshed = await readSession(runtime, invocation);
481
514
  if (!refreshed.ok) return refreshed;
482
- return ensureAccount(invocation);
515
+ return failure(await runtime.send({ _tag: "EnsureAccount" }, invocation)) ?? { ok: true };
483
516
  }),
484
517
  createOrganization: (submission, options) => guarded(runtime, "createOrganization", options, async (invocation) => {
485
518
  const current = runtime.snapshot();
@@ -488,20 +521,6 @@ function createAuth(client, clearAuthenticatedQueries) {
488
521
  ok: true,
489
522
  orgId: running
490
523
  };
491
- if (current.phase === "authenticated" && current.account.at === "claimed") {
492
- const org = current.account.org;
493
- if (org?.at === "failed" && org.orgId !== null) {
494
- if (!org.retryable) return {
495
- ok: false,
496
- reason: org.failure.code
497
- };
498
- const retried = await runtime.send({ _tag: "RetryOrganization" }, invocation);
499
- return failure(retried) ?? {
500
- ok: true,
501
- orgId: org.orgId
502
- };
503
- }
504
- }
505
524
  const prepared = await prepareOrganization(submission, invocation);
506
525
  if (!prepared.ok) return prepared;
507
526
  const opened = runningOrganization(runtime.snapshot());
@@ -509,20 +528,24 @@ function createAuth(client, clearAuthenticatedQueries) {
509
528
  ok: true,
510
529
  orgId: opened
511
530
  };
512
- const created = await runtime.send({
513
- _tag: "CreateOrganization",
514
- draft: prepared.organization
515
- }, invocation);
516
- const refused = failure(created);
517
- if (refused !== null) return refused;
518
- const state = created.state;
519
- const org = state.phase === "authenticated" && state.account.at === "claimed" ? state.account.org : null;
520
- return org?.at === "ready" && org.orgId !== null ? {
521
- ok: true,
522
- orgId: org.orgId
523
- } : {
531
+ const begun = await client.onboarding.beginOrganization(prepared.organization, invocation);
532
+ if (!begun.ok) return {
524
533
  ok: false,
525
- reason: "UNKNOWN"
534
+ reason: begun.error.code,
535
+ error: begun.error
536
+ };
537
+ const { orgId, setup } = begun.value;
538
+ if (setup.status === "awaitingAuthorization") {
539
+ const authorized = await client.org(orgId).setup.authorize(invocation);
540
+ if (!authorized.ok) return {
541
+ ok: false,
542
+ reason: authorized.error.code,
543
+ error: authorized.error
544
+ };
545
+ }
546
+ return {
547
+ ok: true,
548
+ orgId: String(orgId)
526
549
  };
527
550
  }).then((result) => {
528
551
  if (result.ok) return result;
@@ -535,8 +558,21 @@ function createAuth(client, clearAuthenticatedQueries) {
535
558
  }),
536
559
  retry: (options) => guarded(runtime, "retry", options, async (invocation) => {
537
560
  const state = runtime.snapshot();
538
- const event = state.phase === "authenticated" && state.account.at === "claimed" ? { _tag: "RetryOrganization" } : { _tag: "RetryAccount" };
539
- const result = await runtime.send(event, invocation);
561
+ const orgId = runningOrganization(state);
562
+ if (orgId !== null) {
563
+ const scoped = client.org(toOrgId(orgId));
564
+ const current = await scoped.setup.get(invocation);
565
+ if (!current.ok) return {
566
+ ok: false,
567
+ reason: current.error.code
568
+ };
569
+ const result = current.value.status === "awaitingAuthorization" ? await scoped.setup.authorize(invocation) : await scoped.setup.resume(invocation);
570
+ return result.ok ? { ok: true } : {
571
+ ok: false,
572
+ reason: result.error.code
573
+ };
574
+ }
575
+ const result = await runtime.send({ _tag: "RetryAccount" }, invocation);
540
576
  return failure(result) ?? { ok: true };
541
577
  }),
542
578
  resumeSubmittedOrganization: (submission, options) => guarded(runtime, "createOrganization", options, async (invocation) => {
@@ -548,6 +584,10 @@ function createAuth(client, clearAuthenticatedQueries) {
548
584
  })
549
585
  };
550
586
  }
587
+ //#endregion
588
+ //#region src/platform/identity.tsx
589
+ const MISSING_IDENTITY_PROVIDER = Symbol("MISSING_IDENTITY_PROVIDER");
590
+ const IdentityContext = createContext(MISSING_IDENTITY_PROVIDER);
551
591
  function CapxulIdentityProvider({ client, children }) {
552
592
  const queryClient = useQueryClient();
553
593
  const runtime = client?._internal.identity ?? null;
@@ -565,7 +605,7 @@ function CapxulIdentityProvider({ client, children }) {
565
605
  if (!active) return;
566
606
  state = runtime.snapshot();
567
607
  }
568
- if (state.phase === "authenticated" && state.profileComplete && state.account.at === "unknown") await runtime.send({ _tag: "EnsureAccount" }, { signal: controller.signal });
608
+ if (client._internal.accountSetup !== "explicit" && state.phase === "authenticated" && state.profileComplete && state.account.at === "unknown") await runtime.send({ _tag: "EnsureAccount" }, { signal: controller.signal });
569
609
  }).catch(() => void 0);
570
610
  return () => {
571
611
  active = false;
@@ -580,7 +620,7 @@ function CapxulIdentityProvider({ client, children }) {
580
620
  const account = actingAccount(state);
581
621
  const accountChanged = previousAccount !== void 0 && account !== void 0 && account !== previousAccount;
582
622
  previousAccount = account;
583
- if (record.outcome === "applied" && (record.from !== record.to || accountChanged) && (record.to === "authenticated:claimed" || record.to === "authenticated:claimed:ready")) queryClient.invalidateQueries({
623
+ if (record.outcome === "applied" && (record.from !== record.to || accountChanged) && (record.to === "authenticated:claimed" || record.to === "authenticated:claimed:organization")) queryClient.invalidateQueries({
584
624
  queryKey: capxulKeys.root,
585
625
  ...accountChanged ? { refetchType: "none" } : {}
586
626
  });
@@ -665,7 +705,7 @@ function useCapxulTransitions(listener) {
665
705
  }
666
706
  const entered = (record, target) => record.outcome === "applied" && record.from !== target && record.to === target;
667
707
  //#endregion
668
- //#region src/provider.tsx
708
+ //#region src/platform/provider.tsx
669
709
  /**
670
710
  * Transient failure codes worth a retry — network blips, rate limits, and
671
711
  * upstream provider/unknown hiccups that a later attempt may clear. Everything
@@ -720,6 +760,7 @@ function clearClientScopedQueries(queryClient, ownsQueryClient) {
720
760
  }
721
761
  function CapxulProvider(props) {
722
762
  const { publishableKey, client: injectedClient, requirement, signer, observability, bootstrapBaseUrl, queryClient, children } = props;
763
+ const accountSetup = "accountSetup" in props ? props.accountSetup : void 0;
723
764
  const [resolvedQueryClient] = useState(() => queryClient ?? makeDefaultQueryClient());
724
765
  const [ownsQueryClient] = useState(() => queryClient === void 0);
725
766
  const [attempt, setAttempt] = useState(0);
@@ -731,13 +772,15 @@ function CapxulProvider(props) {
731
772
  ...requirement === void 0 ? {} : { requirement },
732
773
  ...signer === void 0 ? {} : { signer },
733
774
  ...observability === void 0 ? {} : { observability },
734
- ...bootstrapBaseUrl === void 0 ? {} : { bootstrapBaseUrl }
775
+ ...bootstrapBaseUrl === void 0 ? {} : { bootstrapBaseUrl },
776
+ ...accountSetup === void 0 ? {} : { accountSetup }
735
777
  }, [
736
778
  publishableKey,
737
779
  requirement,
738
780
  signer,
739
781
  observability,
740
782
  bootstrapBaseUrl,
783
+ accountSetup,
741
784
  attempt
742
785
  ]);
743
786
  const [ownedBootstrap, setOwnedBootstrap] = useState(null);
@@ -814,7 +857,84 @@ function CapxulProvider(props) {
814
857
  });
815
858
  }
816
859
  //#endregion
817
- //#region src/controllers.tsx
860
+ //#region ../protocol/src/errors/failure-modes.ts
861
+ /**
862
+ * Why an operation failed — the one flat vocabulary. The first five are the
863
+ * auth and signer diagnoses that predate this file. The next eleven name the
864
+ * chain path. `refused` and `defect` name a provider refusal and a Capxul
865
+ * fault on any path (ADR-0039 D2). `FailureMode` is derived from this list so
866
+ * a runtime membership check (the SDK's `$exception` projection, the
867
+ * observability schema) and the type can never drift apart. Every word has a
868
+ * failure class in `classes.ts`.
869
+ */
870
+ const FAILURE_MODES = [
871
+ "auth-origin-mismatch",
872
+ "stale-openfort-cache",
873
+ "app-env-allowlist",
874
+ "no-secure-context",
875
+ "signer-not-ready",
876
+ "rate-limited",
877
+ "timeout",
878
+ "upstream-down",
879
+ "revert",
880
+ "insufficient-balance",
881
+ "insufficient-allowance",
882
+ "rejected",
883
+ "unauthorized",
884
+ "forbidden",
885
+ "bad-request",
886
+ "method-unsupported",
887
+ "refused",
888
+ "defect",
889
+ "unknown"
890
+ ];
891
+ function isFailureMode(value) {
892
+ return typeof value === "string" && FAILURE_MODES.includes(value);
893
+ }
894
+ //#endregion
895
+ //#region ../protocol/src/errors/model.ts
896
+ /**
897
+ * `Code` narrows to the literal a factory passes, so `Errors.invalidInput(...)`
898
+ * is a `CapxulError<"INVALID_INPUT">`. An operation's error channel is then the
899
+ * union of the codes it can actually produce.
900
+ */
901
+ var CapxulError$1 = class extends Error {
902
+ code;
903
+ details;
904
+ correlationId;
905
+ layer;
906
+ /** The one-word cause (`FailureMode`). `chain.mode` when a chain cause is set. */
907
+ mode;
908
+ /** The structured chain-path cause. Never rewritten by a later layer. */
909
+ chain;
910
+ constructor(code, message, options = {}) {
911
+ super(message, "cause" in options ? { cause: options.cause } : void 0);
912
+ this.name = "CapxulError";
913
+ this.code = code;
914
+ if (options.details !== void 0) this.details = options.details;
915
+ if (options.correlationId !== void 0) this.correlationId = options.correlationId;
916
+ if (options.layer !== void 0) this.layer = options.layer;
917
+ const legacy = options.details?.failure_mode;
918
+ const mode = options.chain?.mode ?? (isFailureMode(options.mode) ? options.mode : void 0) ?? (isFailureMode(legacy) ? legacy : void 0);
919
+ if (mode !== void 0) this.mode = mode;
920
+ if (options.chain !== void 0) this.chain = options.chain;
921
+ }
922
+ };
923
+ //#endregion
924
+ //#region ../protocol/src/primitives/absurd.ts
925
+ /**
926
+ * The default branch of an exhaustive switch: `default: return absurd(value)`.
927
+ * The compiler proves it unreachable; if a value the types rule out arrives at
928
+ * run time anyway, it fails as a named defect instead of falling through.
929
+ */
930
+ function absurd(value) {
931
+ throw new CapxulError$1("UNKNOWN", "An exhaustive switch received a value its type rules out.", { details: {
932
+ defect: "unreachable_case",
933
+ value: String(value)
934
+ } });
935
+ }
936
+ //#endregion
937
+ //#region src/access/controllers.tsx
818
938
  const action = async (send, event) => {
819
939
  const result = await send(event);
820
940
  return result.ok ? { ok: true } : {
@@ -853,11 +973,14 @@ function CapxulAuthenticationController({ slots }) {
853
973
  state,
854
974
  destination
855
975
  });
976
+ default: return absurd(state);
856
977
  }
857
978
  }
858
979
  function ready(destination) {
859
980
  return destination?.to === "dashboardPersonal" || destination?.to === "dashboardOrganization";
860
981
  }
982
+ //#endregion
983
+ //#region src/access/onboarding-controller.tsx
861
984
  function CapxulOnboardingController(props) {
862
985
  const state = useCapxulIdentity();
863
986
  const destination = useCapxulDestination();
@@ -865,6 +988,16 @@ function CapxulOnboardingController(props) {
865
988
  const resumeSubmittedOrganization = useResumeSubmittedOrganization();
866
989
  const resumedSubmission = useRef(null);
867
990
  const [failedRecovery, setFailedRecovery] = useState(null);
991
+ const [organizationActions, setOrganizationActions] = useState(0);
992
+ async function withOrganizationProgress(action) {
993
+ setOrganizationActions((count) => count + 1);
994
+ try {
995
+ return await action();
996
+ } finally {
997
+ setOrganizationActions((count) => count - 1);
998
+ }
999
+ }
1000
+ const createOrganization = (submission, options) => withOrganizationProgress(() => auth.createOrganization(submission, options));
868
1001
  const submission = props.submittedOrganization;
869
1002
  const accountAt = state.phase === "authenticated" ? state.account.at : null;
870
1003
  useEffect(() => {
@@ -900,11 +1033,11 @@ function CapxulOnboardingController(props) {
900
1033
  continueOrganization: props.onOrganizationProfile,
901
1034
  cancel: navigation.profileCancel
902
1035
  });
903
- if (props.intent === "organization" && props.organizationProfile !== null && submission === null) return organizationForm(props, state, auth, null);
904
- if (submission !== null && failedRecovery === submission && state.account.at !== "claimed") return organizationForm(props, state, auth, submission);
1036
+ if (props.intent === "organization" && props.organizationProfile !== null && submission === null) return organizationForm(props, state, createOrganization, null);
1037
+ if (submission !== null && failedRecovery === submission && state.account.at !== "claimed") return organizationForm(props, state, createOrganization, submission);
905
1038
  if (submission !== null && state.account.at !== "claimed") {
906
1039
  if (state.account.at === "failed") {
907
- const retry = state.account.retryable ? (options) => auth.createOrganization(submission, options) : void 0;
1040
+ const retry = state.account.retryable ? (options) => createOrganization(submission, options) : void 0;
908
1041
  return slots.accountFailure({
909
1042
  state,
910
1043
  account: state.account,
@@ -918,16 +1051,16 @@ function CapxulOnboardingController(props) {
918
1051
  }
919
1052
  if (submission !== null && state.account.at === "claimed") {
920
1053
  const org = state.account.org;
921
- if (org === null) return organizationForm(props, state, auth, submission);
922
- if (org.at === "failed") {
923
- const retry = org.retryable ? org.orgId === null ? (options) => auth.createOrganization(submission, options) : auth.retry : void 0;
1054
+ if (org === null) return organizationForm(props, state, createOrganization, submission);
1055
+ if (org.observation === "error" || org.setup?.status === "failed" || org.setup?.status === "needsAttention" || org.setup?.status === "awaitingAuthorization" && organizationActions === 0) {
1056
+ const retry = org.orgId === null ? (options) => createOrganization(submission, options) : (options) => withOrganizationProgress(() => auth.retry(options));
924
1057
  return slots.organizationFailure({
925
1058
  state,
926
1059
  org,
927
- ...retry === void 0 ? {} : { retry }
1060
+ retry
928
1061
  });
929
1062
  }
930
- if (org.at !== "ready") return slots.organizationProgress({
1063
+ if (org.setup?.status !== "ready" || org.observation !== "connected") return slots.organizationProgress({
931
1064
  state,
932
1065
  org
933
1066
  });
@@ -946,7 +1079,7 @@ function CapxulOnboardingController(props) {
946
1079
  destination
947
1080
  }) : null;
948
1081
  }
949
- function organizationForm(props, state, auth, submitted) {
1082
+ function organizationForm(props, state, createOrganization, submitted) {
950
1083
  const profileDetails = submitted?.profileDetails ?? props.organizationProfile;
951
1084
  if (profileDetails === null) return null;
952
1085
  return props.slots.organization({
@@ -955,9 +1088,9 @@ function organizationForm(props, state, auth, submitted) {
955
1088
  submitted,
956
1089
  pinnedHandle: submitted?.organization.handle ?? null,
957
1090
  submit: (organization, options) => {
958
- if (submitted !== null) return auth.createOrganization(submitted, options);
1091
+ if (submitted !== null) return createOrganization(submitted, options);
959
1092
  const normalized = normalizeOrganization(organization);
960
- if (normalized === null) return auth.createOrganization({
1093
+ if (normalized === null) return createOrganization({
961
1094
  profileDetails,
962
1095
  organization
963
1096
  }, options);
@@ -966,7 +1099,7 @@ function organizationForm(props, state, auth, submitted) {
966
1099
  organization: normalized
967
1100
  };
968
1101
  props.onSubmittedOrganization(next);
969
- return auth.createOrganization(next, options);
1102
+ return createOrganization(next, options);
970
1103
  },
971
1104
  back: () => {
972
1105
  props.onOrganizationProfile(null);
@@ -976,4 +1109,4 @@ function organizationForm(props, state, auth, submitted) {
976
1109
  });
977
1110
  }
978
1111
  //#endregion
979
- export { useCapxul as _, useCapxulAuth as a, useCapxulIdentityOrNull as c, toInvocationControls as d, toInvocationOptions as f, useCapxulClientOrNull as g, capxulKeys as h, entered as i, useCapxulSend as l, actingAccount as m, CapxulOnboardingController as n, useCapxulDestination as o, accountKey as p, CapxulProvider as r, useCapxulIdentity as s, CapxulAuthenticationController as t, useCapxulTransitions as u };
1112
+ export { orgKeySegment as _, entered as a, useCapxulIdentity as c, useCapxulTransitions as d, toInvocationControls as f, capxulKeys as g, actingAccount as h, CapxulProvider as i, useCapxulIdentityOrNull as l, accountKey as m, CapxulAuthenticationController as n, useCapxulAuth as o, toInvocationOptions as p, absurd as r, useCapxulDestination as s, CapxulOnboardingController as t, useCapxulSend as u, useCapxulClientOrNull as v, useCapxul as y };