@capxul/sdk-react 4.20.0-beta.23 → 4.20.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +603 -591
- package/dist/index.mjs +3007 -2915
- package/dist/{controllers-BMAU2kKH.d.mts → onboarding-controller-DrJiYWfY.d.mts} +9 -11
- package/dist/{controllers-BV3e8WD3.mjs → onboarding-controller-ZlvveARR.mjs} +229 -115
- package/dist/testing/index.d.mts +5 -5
- package/dist/testing/index.mjs +1 -1
- package/package.json +40 -10
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
import { CapxulClient, CapxulError, CapxulErrorCode, IdentityDestination, IdentityEvent, IdentityProfileDetails, IdentityState, IdentityTransition, OrgLane, Readiness
|
|
3
|
-
//#region src/identity.d.ts
|
|
2
|
+
import { CapxulClient, CapxulError, CapxulErrorCode, IdentityDestination, IdentityEvent, IdentityProfileDetails, IdentityState, IdentityTransition, OrgLane, Readiness } from "@capxul/sdk";
|
|
3
|
+
//#region src/platform/identity-contract.d.ts
|
|
4
4
|
type Destination = IdentityDestination;
|
|
5
5
|
interface InvocationOptions {
|
|
6
6
|
readonly correlationId?: string;
|
|
@@ -36,9 +36,11 @@ interface CreateOrganizationSubmission {
|
|
|
36
36
|
readonly profileDetails: ProfileDetails;
|
|
37
37
|
readonly organization: OrganizationDetails;
|
|
38
38
|
}
|
|
39
|
+
/** A refused facade call: its code, and the whole error when one was raised. */
|
|
39
40
|
type FacadeFailure = {
|
|
40
41
|
readonly ok: false;
|
|
41
42
|
readonly reason: CapxulErrorCode;
|
|
43
|
+
readonly error?: CapxulError;
|
|
42
44
|
};
|
|
43
45
|
type CreateOrganizationFailure = FacadeFailure & {
|
|
44
46
|
readonly error: CapxulError;
|
|
@@ -65,14 +67,8 @@ interface CapxulAuth {
|
|
|
65
67
|
readonly retry: (options?: InvocationOptions) => Promise<EmptyResult>;
|
|
66
68
|
}
|
|
67
69
|
type TransitionListener = (record: IdentityTransition, state: IdentityState) => void;
|
|
68
|
-
declare function useCapxulIdentity(): IdentityState;
|
|
69
|
-
declare function useCapxulSend(): CapxulSend;
|
|
70
|
-
declare function useCapxulAuth(): CapxulAuth;
|
|
71
|
-
declare function useCapxulDestination(): Destination | null;
|
|
72
|
-
declare function useCapxulTransitions(listener: TransitionListener): void;
|
|
73
|
-
declare const entered: (record: IdentityTransition, target: StateLabel) => boolean;
|
|
74
70
|
//#endregion
|
|
75
|
-
//#region src/controllers.d.ts
|
|
71
|
+
//#region src/access/controllers.d.ts
|
|
76
72
|
type Slot<P> = (props: P) => ReactNode;
|
|
77
73
|
type ActionResult = {
|
|
78
74
|
readonly ok: true;
|
|
@@ -148,6 +144,8 @@ type ProfileSlotProps = {
|
|
|
148
144
|
readonly continueOrganization: (profile: ProfileDetails) => void;
|
|
149
145
|
readonly cancel: NavigationAction;
|
|
150
146
|
};
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/access/onboarding-controller.d.ts
|
|
151
149
|
interface OnboardingControllerProps {
|
|
152
150
|
readonly intent: "personal" | "organization" | null;
|
|
153
151
|
readonly organizationProfile: ProfileDetails | null;
|
|
@@ -202,6 +200,6 @@ interface OnboardingControllerProps {
|
|
|
202
200
|
}>;
|
|
203
201
|
};
|
|
204
202
|
}
|
|
205
|
-
declare function CapxulOnboardingController(props: OnboardingControllerProps): ReactNode;
|
|
203
|
+
declare function CapxulOnboardingController(props: OnboardingControllerProps): import("react").ReactNode;
|
|
206
204
|
//#endregion
|
|
207
|
-
export {
|
|
205
|
+
export { TransitionListener as A, CreateOrganizationFailure as C, OrganizationDetails as D, InvocationOptions as E, ProfileDetails as O, CapxulSend as S, Destination as T, ReadyDestination as _, ActionResult as a, Slot as b, CapxulAuthenticationController as c, NavigationAction as d, OrgFailure as f, ProfileSlotProps as g, PendingAuthState as h, AccountProgress as i, SendResult as k, ControllerAction as l, OtpPendingState as m, OnboardingControllerProps as n, AuthenticatedState as o, OrgProgress as p, AccountFailure as r, AuthenticationSlots as s, CapxulOnboardingController as t, FaultedState as u, RetryAction as v, CreateOrganizationSubmission as w, CapxulAuth as x, SignedOutState as y };
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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
|
|
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/
|
|
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,71 +216,23 @@ function toInvocationControls(options) {
|
|
|
209
216
|
};
|
|
210
217
|
}
|
|
211
218
|
//#endregion
|
|
212
|
-
//#region src/identity.
|
|
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?.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
|
-
return state.account.org?.orgId ?? null;
|
|
270
|
-
};
|
|
271
224
|
/** A superseded session read waits at most this long for the winning read. */
|
|
272
225
|
const SESSION_SETTLE_LIMIT_MS = 3e4;
|
|
273
226
|
const accountInFlight = (state) => state.phase === "authenticated" && (state.account.at === "deriving" || state.account.at === "claiming");
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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) => {
|
|
277
236
|
let done = false;
|
|
278
237
|
let stop = null;
|
|
279
238
|
let timer = null;
|
|
@@ -309,20 +268,29 @@ function createAuth(client, clearAuthenticatedQueries) {
|
|
|
309
268
|
stop = unsubscribe;
|
|
310
269
|
timer = setTimeout(() => finish({
|
|
311
270
|
ok: false,
|
|
312
|
-
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
|
+
})
|
|
313
277
|
}), SESSION_SETTLE_LIMIT_MS);
|
|
314
278
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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) => {
|
|
326
294
|
let done = false;
|
|
327
295
|
let stop = null;
|
|
328
296
|
const onAbort = () => finish({
|
|
@@ -351,44 +319,110 @@ function createAuth(client, clearAuthenticatedQueries) {
|
|
|
351
319
|
stop = unsubscribe;
|
|
352
320
|
if (!accountInFlight(runtime.snapshot())) finish({ ok: true });
|
|
353
321
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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();
|
|
367
346
|
if (state.phase !== "authenticated") return {
|
|
368
347
|
ok: false,
|
|
369
348
|
reason: "WRONG_STATE"
|
|
370
349
|
};
|
|
371
350
|
if (state.account.at === "claimed") return { ok: true };
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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 {
|
|
388
417
|
ok: false,
|
|
389
|
-
reason:
|
|
418
|
+
reason: error.code,
|
|
419
|
+
error
|
|
390
420
|
};
|
|
391
|
-
}
|
|
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;
|
|
392
426
|
const completeProfile = async (profile, invocation) => {
|
|
393
427
|
const result = await runtime.completeProfile(profile, invocation);
|
|
394
428
|
return result.ok ? { ok: true } : result;
|
|
@@ -399,14 +433,14 @@ function createAuth(client, clearAuthenticatedQueries) {
|
|
|
399
433
|
ok: false,
|
|
400
434
|
reason: "INVALID_INPUT"
|
|
401
435
|
};
|
|
402
|
-
const refreshed = await
|
|
436
|
+
const refreshed = await readSession(runtime, invocation);
|
|
403
437
|
if (!refreshed.ok) return refreshed;
|
|
404
438
|
const current = runtime.snapshot();
|
|
405
439
|
if (current.phase !== "authenticated" || !current.profileComplete) {
|
|
406
440
|
const completed = await runtime.completeProfile(submission.profileDetails, invocation);
|
|
407
441
|
if (!completed.ok) return completed;
|
|
408
442
|
}
|
|
409
|
-
const claimed = await reachClaimed(invocation);
|
|
443
|
+
const claimed = await reachClaimed(runtime, invocation);
|
|
410
444
|
return claimed.ok ? {
|
|
411
445
|
ok: true,
|
|
412
446
|
organization
|
|
@@ -476,9 +510,9 @@ function createAuth(client, clearAuthenticatedQueries) {
|
|
|
476
510
|
ok: false,
|
|
477
511
|
reason: completed.reason
|
|
478
512
|
};
|
|
479
|
-
const refreshed = await
|
|
513
|
+
const refreshed = await readSession(runtime, invocation);
|
|
480
514
|
if (!refreshed.ok) return refreshed;
|
|
481
|
-
return
|
|
515
|
+
return failure(await runtime.send({ _tag: "EnsureAccount" }, invocation)) ?? { ok: true };
|
|
482
516
|
}),
|
|
483
517
|
createOrganization: (submission, options) => guarded(runtime, "createOrganization", options, async (invocation) => {
|
|
484
518
|
const current = runtime.snapshot();
|
|
@@ -526,7 +560,7 @@ function createAuth(client, clearAuthenticatedQueries) {
|
|
|
526
560
|
const state = runtime.snapshot();
|
|
527
561
|
const orgId = runningOrganization(state);
|
|
528
562
|
if (orgId !== null) {
|
|
529
|
-
const scoped = client.org(orgId);
|
|
563
|
+
const scoped = client.org(toOrgId(orgId));
|
|
530
564
|
const current = await scoped.setup.get(invocation);
|
|
531
565
|
if (!current.ok) return {
|
|
532
566
|
ok: false,
|
|
@@ -550,6 +584,10 @@ function createAuth(client, clearAuthenticatedQueries) {
|
|
|
550
584
|
})
|
|
551
585
|
};
|
|
552
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);
|
|
553
591
|
function CapxulIdentityProvider({ client, children }) {
|
|
554
592
|
const queryClient = useQueryClient();
|
|
555
593
|
const runtime = client?._internal.identity ?? null;
|
|
@@ -667,7 +705,7 @@ function useCapxulTransitions(listener) {
|
|
|
667
705
|
}
|
|
668
706
|
const entered = (record, target) => record.outcome === "applied" && record.from !== target && record.to === target;
|
|
669
707
|
//#endregion
|
|
670
|
-
//#region src/provider.tsx
|
|
708
|
+
//#region src/platform/provider.tsx
|
|
671
709
|
/**
|
|
672
710
|
* Transient failure codes worth a retry — network blips, rate limits, and
|
|
673
711
|
* upstream provider/unknown hiccups that a later attempt may clear. Everything
|
|
@@ -819,7 +857,80 @@ function CapxulProvider(props) {
|
|
|
819
857
|
});
|
|
820
858
|
}
|
|
821
859
|
//#endregion
|
|
822
|
-
//#region src/
|
|
860
|
+
//#region ../protocol/src/errors/failure-modes.ts
|
|
861
|
+
/**
|
|
862
|
+
* Why an operation failed — the one flat vocabulary. The first four are the
|
|
863
|
+
* auth/provider diagnoses that predate this file. The rest name the chain
|
|
864
|
+
* path. `FailureMode` is derived from this list so a runtime membership check
|
|
865
|
+
* (the SDK's `$exception` projection, the observability schema) and the type
|
|
866
|
+
* can never drift apart.
|
|
867
|
+
*/
|
|
868
|
+
const FAILURE_MODES = [
|
|
869
|
+
"auth-origin-mismatch",
|
|
870
|
+
"stale-openfort-cache",
|
|
871
|
+
"app-env-allowlist",
|
|
872
|
+
"no-secure-context",
|
|
873
|
+
"signer-not-ready",
|
|
874
|
+
"rate-limited",
|
|
875
|
+
"timeout",
|
|
876
|
+
"upstream-down",
|
|
877
|
+
"revert",
|
|
878
|
+
"insufficient-balance",
|
|
879
|
+
"insufficient-allowance",
|
|
880
|
+
"rejected",
|
|
881
|
+
"unauthorized",
|
|
882
|
+
"forbidden",
|
|
883
|
+
"bad-request",
|
|
884
|
+
"method-unsupported",
|
|
885
|
+
"unknown"
|
|
886
|
+
];
|
|
887
|
+
function isFailureMode(value) {
|
|
888
|
+
return typeof value === "string" && FAILURE_MODES.includes(value);
|
|
889
|
+
}
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region ../protocol/src/errors/model.ts
|
|
892
|
+
/**
|
|
893
|
+
* `Code` narrows to the literal a factory passes, so `Errors.invalidInput(...)`
|
|
894
|
+
* is a `CapxulError<"INVALID_INPUT">`. An operation's error channel is then the
|
|
895
|
+
* union of the codes it can actually produce.
|
|
896
|
+
*/
|
|
897
|
+
var CapxulError$1 = class extends Error {
|
|
898
|
+
code;
|
|
899
|
+
details;
|
|
900
|
+
correlationId;
|
|
901
|
+
layer;
|
|
902
|
+
/** The one-word cause (`FailureMode`). `chain.mode` when a chain cause is set. */
|
|
903
|
+
mode;
|
|
904
|
+
/** The structured chain-path cause. Never rewritten by a later layer. */
|
|
905
|
+
chain;
|
|
906
|
+
constructor(code, message, options = {}) {
|
|
907
|
+
super(message, "cause" in options ? { cause: options.cause } : void 0);
|
|
908
|
+
this.name = "CapxulError";
|
|
909
|
+
this.code = code;
|
|
910
|
+
if (options.details !== void 0) this.details = options.details;
|
|
911
|
+
if (options.correlationId !== void 0) this.correlationId = options.correlationId;
|
|
912
|
+
if (options.layer !== void 0) this.layer = options.layer;
|
|
913
|
+
const legacy = options.details?.failure_mode;
|
|
914
|
+
const mode = options.chain?.mode ?? (isFailureMode(options.mode) ? options.mode : void 0) ?? (isFailureMode(legacy) ? legacy : void 0);
|
|
915
|
+
if (mode !== void 0) this.mode = mode;
|
|
916
|
+
if (options.chain !== void 0) this.chain = options.chain;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
//#endregion
|
|
920
|
+
//#region ../protocol/src/primitives/absurd.ts
|
|
921
|
+
/**
|
|
922
|
+
* The default branch of an exhaustive switch: `default: return absurd(value)`.
|
|
923
|
+
* The compiler proves it unreachable; if a value the types rule out arrives at
|
|
924
|
+
* run time anyway, it fails as a named defect instead of falling through.
|
|
925
|
+
*/
|
|
926
|
+
function absurd(value) {
|
|
927
|
+
throw new CapxulError$1("UNKNOWN", "An exhaustive switch received a value its type rules out.", { details: {
|
|
928
|
+
defect: "unreachable_case",
|
|
929
|
+
value: String(value)
|
|
930
|
+
} });
|
|
931
|
+
}
|
|
932
|
+
//#endregion
|
|
933
|
+
//#region src/access/controllers.tsx
|
|
823
934
|
const action = async (send, event) => {
|
|
824
935
|
const result = await send(event);
|
|
825
936
|
return result.ok ? { ok: true } : {
|
|
@@ -858,11 +969,14 @@ function CapxulAuthenticationController({ slots }) {
|
|
|
858
969
|
state,
|
|
859
970
|
destination
|
|
860
971
|
});
|
|
972
|
+
default: return absurd(state);
|
|
861
973
|
}
|
|
862
974
|
}
|
|
863
975
|
function ready(destination) {
|
|
864
976
|
return destination?.to === "dashboardPersonal" || destination?.to === "dashboardOrganization";
|
|
865
977
|
}
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/access/onboarding-controller.tsx
|
|
866
980
|
function CapxulOnboardingController(props) {
|
|
867
981
|
const state = useCapxulIdentity();
|
|
868
982
|
const destination = useCapxulDestination();
|
|
@@ -981,4 +1095,4 @@ function organizationForm(props, state, auth, submitted) {
|
|
|
981
1095
|
});
|
|
982
1096
|
}
|
|
983
1097
|
//#endregion
|
|
984
|
-
export {
|
|
1098
|
+
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 };
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as OnboardingControllerProps, s as AuthenticationSlots } from "../onboarding-controller-DrJiYWfY.mjs";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
import { CapxulTestClient, CapxulTestClock, CapxulTestObservation, CreateCapxulTestClientOptions, SeedTestIdentityInput, createCapxulTestClient } from "@capxul/sdk/testing";
|
|
5
5
|
//#region src/testing/index.d.ts
|
|
6
|
-
interface CreateCapxulReactTestHarnessOptions extends CreateCapxulTestClientOptions {
|
|
6
|
+
export interface CreateCapxulReactTestHarnessOptions extends CreateCapxulTestClientOptions {
|
|
7
7
|
readonly testing?: CapxulTestClient;
|
|
8
8
|
}
|
|
9
|
-
interface CapxulReactTestHarness {
|
|
9
|
+
export interface CapxulReactTestHarness {
|
|
10
10
|
readonly testing: CapxulTestClient;
|
|
11
11
|
readonly client: CapxulTestClient["client"];
|
|
12
12
|
readonly provider: (children: ReactNode) => React.JSX.Element;
|
|
@@ -14,6 +14,6 @@ interface CapxulReactTestHarness {
|
|
|
14
14
|
readonly onboarding: (props: OnboardingControllerProps) => React.JSX.Element;
|
|
15
15
|
}
|
|
16
16
|
/** Build renderer-neutral React test elements over the real SDK testing client. */
|
|
17
|
-
declare function createCapxulReactTestHarness(options?: CreateCapxulReactTestHarnessOptions): CapxulReactTestHarness;
|
|
17
|
+
export declare function createCapxulReactTestHarness(options?: CreateCapxulReactTestHarnessOptions): CapxulReactTestHarness;
|
|
18
18
|
//#endregion
|
|
19
|
-
export {
|
|
19
|
+
export { type CapxulTestClient, type CapxulTestClock, type CapxulTestObservation, type CreateCapxulTestClientOptions, type SeedTestIdentityInput, createCapxulTestClient };
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as CapxulProvider, n as CapxulAuthenticationController, t as CapxulOnboardingController } from "../onboarding-controller-ZlvveARR.mjs";
|
|
2
2
|
import "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
import { createCapxulTestClient } from "@capxul/sdk/testing";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capxul/sdk-react",
|
|
3
|
-
"version": "4.20.0-beta.
|
|
3
|
+
"version": "4.20.0-beta.26",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Xelmar-tech/infrastructure.git",
|
|
@@ -12,6 +12,36 @@
|
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
14
|
"type": "module",
|
|
15
|
+
"imports": {
|
|
16
|
+
"#access": "./src/access/index.ts",
|
|
17
|
+
"#access/client-connection": "./src/access/client-connection.tsx",
|
|
18
|
+
"#access/controllers": "./src/access/controllers.tsx",
|
|
19
|
+
"#access/dashboard-access": "./src/access/dashboard-access.tsx",
|
|
20
|
+
"#access/onboarding-controller": "./src/access/onboarding-controller.tsx",
|
|
21
|
+
"#accounts": "./src/accounts/index.ts",
|
|
22
|
+
"#activity": "./src/activity/index.ts",
|
|
23
|
+
"#activity/activity": "./src/activity/activity.tsx",
|
|
24
|
+
"#media": "./src/media/index.ts",
|
|
25
|
+
"#organizations": "./src/organizations/index.ts",
|
|
26
|
+
"#organizations/org-member": "./src/organizations/org-member.tsx",
|
|
27
|
+
"#package.json": "./package.json",
|
|
28
|
+
"#parties": "./src/parties/index.ts",
|
|
29
|
+
"#parties/contacts": "./src/parties/contacts.tsx",
|
|
30
|
+
"#payments": "./src/payments/index.ts",
|
|
31
|
+
"#payments/send-money": "./src/payments/send-money.tsx",
|
|
32
|
+
"#payroll": "./src/payroll/index.ts",
|
|
33
|
+
"#payroll/payroll": "./src/payroll/payroll.tsx",
|
|
34
|
+
"#payroll/payroll-run": "./src/payroll/payroll-run.tsx",
|
|
35
|
+
"#permissions": "./src/permissions/index.ts",
|
|
36
|
+
"#platform/bootstrap-context": "./src/platform/bootstrap-context.tsx",
|
|
37
|
+
"#platform/client-context": "./src/platform/client-context.tsx",
|
|
38
|
+
"#platform/identity": "./src/platform/identity.tsx",
|
|
39
|
+
"#platform/provider": "./src/platform/provider.tsx",
|
|
40
|
+
"#platform/tests/render-hook-with-capxul": "./src/platform/tests/render-hook-with-capxul.tsx",
|
|
41
|
+
"#profiles": "./src/profiles/index.ts",
|
|
42
|
+
"#testing": "./src/testing/index.tsx",
|
|
43
|
+
"#*": "./src/*.ts"
|
|
44
|
+
},
|
|
15
45
|
"exports": {
|
|
16
46
|
".": {
|
|
17
47
|
"types": "./dist/index.d.mts",
|
|
@@ -26,26 +56,26 @@
|
|
|
26
56
|
"access": "public"
|
|
27
57
|
},
|
|
28
58
|
"dependencies": {
|
|
29
|
-
"@capxul/sdk": "4.20.0-beta.
|
|
59
|
+
"@capxul/sdk": "4.20.0-beta.26"
|
|
30
60
|
},
|
|
31
61
|
"devDependencies": {
|
|
32
62
|
"@tanstack/react-query": "^5.66.9",
|
|
33
63
|
"@testing-library/react": "^16.3.2",
|
|
64
|
+
"@total-typescript/shoehorn": "0.1.2",
|
|
34
65
|
"@types/jsdom": "^28.0.3",
|
|
35
66
|
"@types/react": "^19.2.15",
|
|
36
67
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
37
|
-
"@vitest/coverage-v8": "
|
|
68
|
+
"@vitest/coverage-v8": "5.0.1",
|
|
38
69
|
"ink": "^7.0.3",
|
|
39
70
|
"ink-testing-library": "^4.0.0",
|
|
40
71
|
"jsdom": "^29.1.1",
|
|
41
72
|
"react": "^19.2.6",
|
|
42
73
|
"react-dom": "^19.2.6",
|
|
43
|
-
"
|
|
44
|
-
"vite
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"@capxul/
|
|
48
|
-
"@capxul/types": "0.3.0",
|
|
74
|
+
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
75
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
|
|
76
|
+
"vite-plus": "1.0.0",
|
|
77
|
+
"vitest": "5.0.1",
|
|
78
|
+
"@capxul/protocol": "0.2.0-beta.0",
|
|
49
79
|
"@capxul/typescript-config": "0.0.0"
|
|
50
80
|
},
|
|
51
81
|
"peerDependencies": {
|
|
@@ -57,7 +87,7 @@
|
|
|
57
87
|
},
|
|
58
88
|
"scripts": {
|
|
59
89
|
"check-types": "vp exec ../../node_modules/@typescript/native/bin/tsc -p tsconfig.json --noEmit",
|
|
60
|
-
"lint": "vp lint -c ../../.oxlintrc.json .
|
|
90
|
+
"lint": "vp lint -c ../../.oxlintrc.json .",
|
|
61
91
|
"build": "vp pack",
|
|
62
92
|
"_vp-tasks-allowed": "vp-allowed: `test` + `test:coverage` are vp tasks in vite.config.ts so vitest self-writes don't bust cache (#205)."
|
|
63
93
|
}
|