@keboola/api-client-react 31.0.0 → 33.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -69,6 +69,12 @@ A keboola client is `{ api, session }`. This package provides the two halves sep
69
69
  in-band rotation, so the host can mirror it (e.g. into a token store). Exposes the session via
70
70
  `useAuth()`.
71
71
 
72
+ `onRestore` fires once with the boot `restore()`'s `{ status }`. The slot that renders cannot
73
+ stand in for it — `expired` and `empty` both land in `anonymousFallback`, and so does a `restored`
74
+ session that carried no project — so an app that wants to say "your session expired" rather than
75
+ show a bare sign-in screen needs this callback. A `restore()` that throws reports `empty`, the
76
+ same value the provider acts on, so a transport failure is never reported as an expiry.
77
+
72
78
  - `useApiClient()` — returns the `api`; **throws** outside `<ApiClientProvider />`. Use it from
73
79
  query/mutation hooks.
74
80
  - `useProject()` / `useStack()` — shortcut hooks for `api.context.project` / `api.context.stack`.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @keboola/api-client-react
2
2
 
3
+ ## 33.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @keboola/api-client@33.0.0
9
+
10
+ ## 32.0.0
11
+
12
+ ### Minor Changes
13
+
14
+ - Add `onRestore` to `AuthProvider`, so a consumer can act on the outcome of the boot `restore()`.
15
+
16
+ The provider owns that call and kept the result to itself, but `{ status: 'empty' | 'restored' |
17
+ 'expired' }` are three different things to say to a user and the rendered slot cannot tell them
18
+ apart: `expired` and `empty` both land in `anonymousFallback`, and so does a `restored` session that
19
+ carried no project. An app wanting to say "your session expired" rather than a bare sign-in screen
20
+ had to reconstruct the outcome from `hasPersistedValue()` sampled before mount — which reads a
21
+ cleared-because-corrupt entry, a stack mismatch and a transport failure all as an expiry.
22
+
23
+ A `restore()` that throws reports `empty`, matching what the provider itself acts on, so being
24
+ offline at boot is never dressed up as a session expiry. The callback fires once, including under
25
+ StrictMode's double-invoked effects.
26
+
27
+ ### Patch Changes
28
+
29
+ - Add a `storage.bootstrap` fixture slice — the three responses a client's ready gate needs before
30
+ it is usable (`GET /v2/storage`, `/v2/storage/tokens/verify`, `/v2/storage/dev-branches`).
31
+
32
+ `getStackInfo()`, `verifyStorageToken()` and `getDevBranches()` are typed by
33
+ `@keboola/api-client/storage/types`, so a field the backend adds arrives with `pnpm gen:types`
34
+ rather than being transcribed into every MSW test that bootstraps a client. `getStackInfo()`
35
+ announces every service by default, which is what keeps a test's queue / data-science / vault
36
+ client from being a dummy that throws `ServiceUnavailableError`; pass `services` to narrow it.
37
+
38
+ `@keboola/api-client` gains a runtime `ServiceId` const in `@keboola/api-client/constants`, with
39
+ the `ServiceId` type now derived from it. Announcing every service is exhaustive by construction:
40
+ a service added to the client flows into the fixture without an edit.
41
+
42
+ `@keboola/api-client-react`'s test fixtures now come from the slice; its published output is unchanged.
43
+
44
+ - Updated dependencies:
45
+ - @keboola/api-client@32.0.0
46
+
3
47
  ## 31.0.0
4
48
 
5
49
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -53,11 +53,15 @@ const ApiClientProvider = ({ api, children }) => /* @__PURE__ */ (0, react_jsx_r
53
53
  });
54
54
  //#endregion
55
55
  //#region src/AuthProvider.tsx
56
- const AuthProvider = ({ session, baseUrl, credential, projectId, onAccessTokenChange, loadingFallback = null, errorFallback = null, anonymousFallback = null, children }) => {
56
+ const AuthProvider = ({ session, baseUrl, credential, projectId, onAccessTokenChange, onRestore, loadingFallback = null, errorFallback = null, anonymousFallback = null, children }) => {
57
57
  const [snapshot, setSnapshot] = (0, react.useState)(() => session.debug());
58
58
  const [isRestoring, setIsRestoring] = (0, react.useState)(() => session.hasPersistedValue());
59
59
  const [restoreStatus, setRestoreStatus] = (0, react.useState)(null);
60
60
  const restoreStartedRef = (0, react.useRef)(false);
61
+ const onRestoreRef = (0, react.useRef)(onRestore);
62
+ (0, react.useEffect)(() => {
63
+ onRestoreRef.current = onRestore;
64
+ }, [onRestore]);
61
65
  (0, react.useEffect)(() => session.subscribe(setSnapshot), [session]);
62
66
  (0, react.useEffect)(() => {
63
67
  if (baseUrl === void 0) return;
@@ -71,9 +75,18 @@ const AuthProvider = ({ session, baseUrl, credential, projectId, onAccessTokenCh
71
75
  (0, react.useEffect)(() => {
72
76
  if (restoreStartedRef.current) return;
73
77
  restoreStartedRef.current = true;
74
- session.restore().then((result) => setRestoreStatus(result.status)).catch((error) => {
78
+ const reportCallbackFailure = (error) => console.error("Keboola bearer session onRestore callback failed", error);
79
+ const settle = (result) => {
80
+ setRestoreStatus(result.status);
81
+ try {
82
+ Promise.resolve(onRestoreRef.current?.(result)).catch(reportCallbackFailure);
83
+ } catch (error) {
84
+ reportCallbackFailure(error);
85
+ }
86
+ };
87
+ session.restore().then(settle).catch((error) => {
75
88
  console.error("Keboola bearer session restore failed", error);
76
- setRestoreStatus("empty");
89
+ settle({ status: "empty" });
77
90
  }).finally(() => setIsRestoring(false));
78
91
  }, [session]);
79
92
  (0, react.useEffect)(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["createContext","use","useState","useRef","useState","useRef"],"sources":["../src/context.ts","../src/ApiClientProvider.tsx","../src/AuthProvider.tsx","../src/LegacyAuthProvider.tsx"],"sourcesContent":["import { createContext, use } from 'react';\n\nimport type { BearerSession, KeboolaApi, LegacySession } from '@keboola/api-client';\n\nconst MISSING_API_CLIENT_ERROR = 'useApiClient must be used within <ApiClientProvider />';\nconst MISSING_PROJECT_ERROR = 'useProject must be used within <ApiClientProvider />';\nconst MISSING_STACK_ERROR = 'useStack must be used within <ApiClientProvider />';\nconst MISSING_AUTH_ERROR = 'useAuth must be used within <AuthProvider />';\nconst MISSING_LEGACY_AUTH_ERROR = 'useLegacyAuth must be used within <LegacyAuthProvider />';\n\n// The product API (`auth` + `management` + every product namespace), flow-agnostic. Both the\n// bearer and legacy flows produce the same `KeboolaApi` surface, so consumers never branch on flow.\nconst ApiContext = createContext<KeboolaApi | null>(null);\nexport const ApiProvider = ApiContext;\n\n// The lifecycle sessions live in their own, flow-specific contexts so the auth surface a component\n// reaches for is explicit. Only the matching provider is mounted per flow.\nconst AuthContext = createContext<BearerSession | null>(null);\nexport const AuthContextProvider = AuthContext;\n\nconst LegacyAuthContext = createContext<LegacySession | null>(null);\nexport const LegacyAuthContextProvider = LegacyAuthContext;\n\nexport const useApiClient = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_API_CLIENT_ERROR);\n return api;\n};\n\nexport const useProject = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_PROJECT_ERROR);\n return api.context.project;\n};\n\nexport const useStack = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_STACK_ERROR);\n return api.context.stack;\n};\n\n/** The bearer (OAuth) lifecycle session. Throws outside `<AuthProvider />`. */\nexport const useAuth = () => {\n const session = use(AuthContext);\n if (session === null) throw new Error(MISSING_AUTH_ERROR);\n return session;\n};\n\n/** The legacy (SAPI) lifecycle session. Throws outside `<LegacyAuthProvider />`. */\nexport const useLegacyAuth = () => {\n const session = use(LegacyAuthContext);\n if (session === null) throw new Error(MISSING_LEGACY_AUTH_ERROR);\n return session;\n};\n","import type { ReactNode } from 'react';\n\nimport type { KeboolaApi } from '@keboola/api-client';\n\nimport { ApiProvider } from './context';\n\ntype Props = {\n /** The flow-agnostic product API — the `api` half of a keboola client. */\n api: KeboolaApi;\n children: ReactNode;\n};\n\n/**\n * Provides the product API to `useApiClient()`. A pure pass-through: the auth lifecycle (driving\n * to ready, gating on a fallback) belongs to `AuthProvider` / `LegacyAuthProvider`, which mount\n * this provider once their session is ready.\n */\nexport const ApiClientProvider = ({ api, children }: Props) => (\n <ApiProvider value={api}>{children}</ApiProvider>\n);\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { BearerSession } from '@keboola/api-client';\n\nimport { AuthContextProvider } from './context';\n\ntype BearerCredential = Parameters<BearerSession['authenticate']>[0];\ntype Snapshot = ReturnType<BearerSession['debug']>;\ntype RestoreStatus = Awaited<ReturnType<BearerSession['restore']>>['status'];\n\n/** The session state guaranteed to exist once the storage build is ready. */\ntype ScopedState = { projectId: number; baseUrl: string };\n\ntype BaseProps = {\n session: BearerSession;\n onAccessTokenChange?: (accessToken: string) => void;\n /** Rendered while a persisted session is being restored, or the bootstrap is in flight. */\n loadingFallback?: ReactNode;\n /** Rendered when the storage build fails. */\n errorFallback?: ReactNode;\n /**\n * Rendered only once the session is scoped and the storage build is ready. Pass a function to\n * receive the scoped state (the project the session settled on) without re-narrowing it yourself.\n */\n children: ReactNode | ((scoped: ScopedState) => ReactNode);\n};\n\n/**\n * Two mutually exclusive bootstrap modes, discriminated by whether a credential is supplied:\n * - **programmatic** — `credential` + `projectId` given, so `baseUrl` is **required**: a credential\n * is bound to the stack that issued it and `authenticate` refuses an unconfigured session.\n * - **interactive** — none of the three given; the stack comes from the session instead — `restore()`\n * adopts the persisted one, otherwise the login UI sets it. `anonymousFallback` is **required**\n * here (a missing login UI would leave the app on a permanently blank screen).\n */\ntype Props = BaseProps &\n (\n | {\n credential: BearerCredential;\n projectId: number;\n baseUrl: string;\n anonymousFallback?: never;\n }\n | {\n credential?: undefined;\n projectId?: undefined;\n baseUrl?: never;\n anonymousFallback: ReactNode;\n }\n );\n\nexport const AuthProvider = ({\n session,\n baseUrl,\n credential,\n projectId,\n onAccessTokenChange,\n loadingFallback = null,\n errorFallback = null,\n anonymousFallback = null,\n children,\n}: Props) => {\n const [snapshot, setSnapshot] = useState<Snapshot>(() => session.debug());\n const [isRestoring, setIsRestoring] = useState(() => session.hasPersistedValue());\n const [restoreStatus, setRestoreStatus] = useState<RestoreStatus | null>(null);\n const restoreStartedRef = useRef(false);\n\n useEffect(() => session.subscribe(setSnapshot), [session]);\n\n // Programmatic only. Interactive leaves the stack unset: `restore()` adopts the persisted one, and\n // failing that the login UI picks it — so there is nothing to configure up front.\n useEffect(() => {\n if (baseUrl === undefined) return;\n if (snapshot.config.baseUrl !== null) return;\n session.setBaseUrl(baseUrl);\n }, [session, baseUrl, snapshot.config.baseUrl]);\n\n // Runs regardless of whether a stack is configured — with none, `restore()` adopts the stack the\n // persisted session belongs to. (Declared after the effect above, so programmatic mode has already\n // configured its stack by the time this runs.) The ref guards StrictMode's double-invoked effect:\n // `restoreStatus` only lands asynchronously, so it cannot prevent a second synchronous call.\n useEffect(() => {\n if (restoreStartedRef.current) return;\n restoreStartedRef.current = true;\n session\n .restore()\n .then((result) => setRestoreStatus(result.status))\n .catch((error) => {\n console.error('Keboola bearer session restore failed', error);\n setRestoreStatus('empty');\n })\n .finally(() => setIsRestoring(false));\n }, [session]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (credential === undefined) return;\n if (snapshot.config.status !== 'anonymous') return;\n session.authenticate(credential);\n }, [session, restoreStatus, credential, snapshot.config.status]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (projectId === undefined) return;\n if (snapshot.config.status !== 'authenticated') return;\n session.setProject(projectId);\n }, [session, restoreStatus, projectId, snapshot.config.status]);\n\n const accessToken =\n snapshot.config.status === 'anonymous' ? undefined : snapshot.config.credential.accessToken;\n\n useEffect(() => {\n if (accessToken !== undefined) onAccessTokenChange?.(accessToken);\n }, [accessToken, onAccessTokenChange]);\n\n // Programmatic mode drives itself to ready, so it must never fall through to `anonymousFallback`.\n const isProgrammatic = credential !== undefined && projectId !== undefined;\n\n const content = (() => {\n if (snapshot.storage === 'ready') {\n if (typeof children !== 'function') return children;\n // A ready storage build implies a scoped session; narrowing here is for the type checker.\n if (snapshot.config.status !== 'scoped') return null;\n const { projectId: scopedProjectId, baseUrl: scopedBaseUrl } = snapshot.config;\n return children({ projectId: scopedProjectId, baseUrl: scopedBaseUrl });\n }\n if (snapshot.storage === 'errored') return errorFallback;\n if (isRestoring || snapshot.storage === 'pending' || isProgrammatic) return loadingFallback;\n return anonymousFallback;\n })();\n\n return <AuthContextProvider value={session}>{content}</AuthContextProvider>;\n};\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { LegacySession } from '@keboola/api-client';\n\nimport { LegacyAuthContextProvider } from './context';\n\ntype Props = {\n session: LegacySession;\n /** Stack base URL. */\n baseUrl: string;\n /** SAPI token — drives the storage build to ready. */\n storageToken: string;\n /** Optional manage-api token — unlocks `api.management`. */\n managementToken?: string;\n /** Rendered until the storage build reaches ready. */\n fallback?: ReactNode;\n /** Rendered when the storage build fails — e.g. the SAPI token is expired or revoked. */\n errorFallback?: ReactNode;\n children: ReactNode;\n};\n\n/**\n * Owns the legacy (SAPI) lifecycle: on mount it configures the stack and drives the session to\n * ready, rendering `fallback` until the storage build resolves. The backend-injected SAPI token is\n * known up front, so this drives itself (unlike the interactive bearer `AuthProvider`). Exposes the\n * session via `useLegacyAuth()` and gates `children` behind readiness.\n */\nexport const LegacyAuthProvider = ({\n session,\n baseUrl,\n storageToken,\n managementToken,\n fallback = null,\n errorFallback = fallback,\n children,\n}: Props) => {\n // The build state, not just readiness: `errored` and `pending` render differently.\n const [storageState, setStorageState] = useState(() => session.debug().storage);\n // Guards the once-only drive against React StrictMode's double-mounted effect.\n const startedRef = useRef<boolean>(false);\n\n useEffect(() => {\n const unsubscribe = session.subscribe((snapshot) => setStorageState(snapshot.storage));\n\n if (!startedRef.current) {\n startedRef.current = true;\n session.setBaseUrl(baseUrl);\n if (managementToken) session.authenticateManagement(managementToken);\n // Fire-and-forget: `subscribe` moves `storageState` once the build resolves. A failed build\n // resolves rather than rejects, so this catch only covers a synchronous throw.\n void session\n .authenticateStorageAsync(storageToken)\n .catch((error) => console.error('Keboola session bootstrap failed', error));\n }\n\n return unsubscribe;\n // drive to ready only on initial mount\n // oxlint-disable-next-line react/exhaustive-deps\n }, []);\n\n // The only place a failed build is observable — nothing rejects, so without this it is silent.\n useEffect(() => {\n if (storageState === 'errored') {\n console.error('Keboola session bootstrap failed: the storage build errored.');\n }\n }, [storageState]);\n\n const content = (() => {\n if (storageState === 'ready') return children;\n if (storageState === 'errored') return errorFallback;\n return fallback;\n })();\n\n return <LegacyAuthContextProvider value={session}>{content}</LegacyAuthContextProvider>;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["createContext","use","useState","useRef","useState","useRef"],"sources":["../src/context.ts","../src/ApiClientProvider.tsx","../src/AuthProvider.tsx","../src/LegacyAuthProvider.tsx"],"sourcesContent":["import { createContext, use } from 'react';\n\nimport type { BearerSession, KeboolaApi, LegacySession } from '@keboola/api-client';\n\nconst MISSING_API_CLIENT_ERROR = 'useApiClient must be used within <ApiClientProvider />';\nconst MISSING_PROJECT_ERROR = 'useProject must be used within <ApiClientProvider />';\nconst MISSING_STACK_ERROR = 'useStack must be used within <ApiClientProvider />';\nconst MISSING_AUTH_ERROR = 'useAuth must be used within <AuthProvider />';\nconst MISSING_LEGACY_AUTH_ERROR = 'useLegacyAuth must be used within <LegacyAuthProvider />';\n\n// The product API (`auth` + `management` + every product namespace), flow-agnostic. Both the\n// bearer and legacy flows produce the same `KeboolaApi` surface, so consumers never branch on flow.\nconst ApiContext = createContext<KeboolaApi | null>(null);\nexport const ApiProvider = ApiContext;\n\n// The lifecycle sessions live in their own, flow-specific contexts so the auth surface a component\n// reaches for is explicit. Only the matching provider is mounted per flow.\nconst AuthContext = createContext<BearerSession | null>(null);\nexport const AuthContextProvider = AuthContext;\n\nconst LegacyAuthContext = createContext<LegacySession | null>(null);\nexport const LegacyAuthContextProvider = LegacyAuthContext;\n\nexport const useApiClient = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_API_CLIENT_ERROR);\n return api;\n};\n\nexport const useProject = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_PROJECT_ERROR);\n return api.context.project;\n};\n\nexport const useStack = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_STACK_ERROR);\n return api.context.stack;\n};\n\n/** The bearer (OAuth) lifecycle session. Throws outside `<AuthProvider />`. */\nexport const useAuth = () => {\n const session = use(AuthContext);\n if (session === null) throw new Error(MISSING_AUTH_ERROR);\n return session;\n};\n\n/** The legacy (SAPI) lifecycle session. Throws outside `<LegacyAuthProvider />`. */\nexport const useLegacyAuth = () => {\n const session = use(LegacyAuthContext);\n if (session === null) throw new Error(MISSING_LEGACY_AUTH_ERROR);\n return session;\n};\n","import type { ReactNode } from 'react';\n\nimport type { KeboolaApi } from '@keboola/api-client';\n\nimport { ApiProvider } from './context';\n\ntype Props = {\n /** The flow-agnostic product API — the `api` half of a keboola client. */\n api: KeboolaApi;\n children: ReactNode;\n};\n\n/**\n * Provides the product API to `useApiClient()`. A pure pass-through: the auth lifecycle (driving\n * to ready, gating on a fallback) belongs to `AuthProvider` / `LegacyAuthProvider`, which mount\n * this provider once their session is ready.\n */\nexport const ApiClientProvider = ({ api, children }: Props) => (\n <ApiProvider value={api}>{children}</ApiProvider>\n);\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { BearerSession } from '@keboola/api-client';\n\nimport { AuthContextProvider } from './context';\n\ntype BearerCredential = Parameters<BearerSession['authenticate']>[0];\ntype Snapshot = ReturnType<BearerSession['debug']>;\ntype RestoreResult = Awaited<ReturnType<BearerSession['restore']>>;\ntype RestoreStatus = RestoreResult['status'];\n\n/** The session state guaranteed to exist once the storage build is ready. */\ntype ScopedState = { projectId: number; baseUrl: string };\n\ntype BaseProps = {\n session: BearerSession;\n onAccessTokenChange?: (accessToken: string) => void;\n /**\n * Fires once with the outcome of the boot `restore()`.\n *\n * - `empty`, `restored` and `expired` are three different things to say to a user, and the slot\n * that renders cannot tell them apart: `expired` and `empty` both land in `anonymousFallback`,\n * and so does a `restored` session that carried no project.\n * - A `restore()` that throws (offline, a 5xx) reports `empty` — the same value the provider acts\n * on — so a caller never reads a transport failure as an expiry.\n */\n onRestore?: (result: RestoreResult) => void;\n /** Rendered while a persisted session is being restored, or the bootstrap is in flight. */\n loadingFallback?: ReactNode;\n /** Rendered when the storage build fails. */\n errorFallback?: ReactNode;\n /**\n * Rendered only once the session is scoped and the storage build is ready. Pass a function to\n * receive the scoped state (the project the session settled on) without re-narrowing it yourself.\n */\n children: ReactNode | ((scoped: ScopedState) => ReactNode);\n};\n\n/**\n * Two mutually exclusive bootstrap modes, discriminated by whether a credential is supplied:\n * - **programmatic** — `credential` + `projectId` given, so `baseUrl` is **required**: a credential\n * is bound to the stack that issued it and `authenticate` refuses an unconfigured session.\n * - **interactive** — none of the three given; the stack comes from the session instead — `restore()`\n * adopts the persisted one, otherwise the login UI sets it. `anonymousFallback` is **required**\n * here (a missing login UI would leave the app on a permanently blank screen).\n */\ntype Props = BaseProps &\n (\n | {\n credential: BearerCredential;\n projectId: number;\n baseUrl: string;\n anonymousFallback?: never;\n }\n | {\n credential?: undefined;\n projectId?: undefined;\n baseUrl?: never;\n anonymousFallback: ReactNode;\n }\n );\n\nexport const AuthProvider = ({\n session,\n baseUrl,\n credential,\n projectId,\n onAccessTokenChange,\n onRestore,\n loadingFallback = null,\n errorFallback = null,\n anonymousFallback = null,\n children,\n}: Props) => {\n const [snapshot, setSnapshot] = useState<Snapshot>(() => session.debug());\n const [isRestoring, setIsRestoring] = useState(() => session.hasPersistedValue());\n const [restoreStatus, setRestoreStatus] = useState<RestoreStatus | null>(null);\n const restoreStartedRef = useRef(false);\n // Held in a ref so the restore effect keeps `[session]` as its only dependency — a caller passing\n // an inline closure must not re-arm the restore. A passive effect is enough: the ref starts at the\n // mount-time callback, and `restore()` settles in a later microtask than any commit's effects, so\n // there is no window in which a stale closure could be the one called.\n const onRestoreRef = useRef(onRestore);\n useEffect(() => {\n onRestoreRef.current = onRestore;\n }, [onRestore]);\n\n useEffect(() => session.subscribe(setSnapshot), [session]);\n\n // Programmatic only. Interactive leaves the stack unset: `restore()` adopts the persisted one, and\n // failing that the login UI picks it — so there is nothing to configure up front.\n useEffect(() => {\n if (baseUrl === undefined) return;\n if (snapshot.config.baseUrl !== null) return;\n session.setBaseUrl(baseUrl);\n }, [session, baseUrl, snapshot.config.baseUrl]);\n\n // Runs regardless of whether a stack is configured — with none, `restore()` adopts the stack the\n // persisted session belongs to. (Declared after the effect above, so programmatic mode has already\n // configured its stack by the time this runs.) The ref guards StrictMode's double-invoked effect:\n // `restoreStatus` only lands asynchronously, so it cannot prevent a second synchronous call.\n useEffect(() => {\n if (restoreStartedRef.current) return;\n restoreStartedRef.current = true;\n const reportCallbackFailure = (error: unknown) =>\n console.error('Keboola bearer session onRestore callback failed', error);\n\n // The consumer's callback is isolated: it runs inside the promise chain, so a throw would\n // otherwise land in the `.catch` below — reporting a successful restore as a failure, logging a\n // lie, and calling `settle` a second time. `Promise.resolve` covers an `async` callback too:\n // TypeScript accepts one against this `void` return type, and its rejection would escape the\n // `try` as an unhandled rejection.\n const settle = (result: RestoreResult) => {\n setRestoreStatus(result.status);\n try {\n void Promise.resolve(onRestoreRef.current?.(result)).catch(reportCallbackFailure);\n } catch (error) {\n reportCallbackFailure(error);\n }\n };\n session\n .restore()\n .then(settle)\n .catch((error) => {\n console.error('Keboola bearer session restore failed', error);\n settle({ status: 'empty' });\n })\n .finally(() => setIsRestoring(false));\n }, [session]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (credential === undefined) return;\n if (snapshot.config.status !== 'anonymous') return;\n session.authenticate(credential);\n }, [session, restoreStatus, credential, snapshot.config.status]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (projectId === undefined) return;\n if (snapshot.config.status !== 'authenticated') return;\n session.setProject(projectId);\n }, [session, restoreStatus, projectId, snapshot.config.status]);\n\n const accessToken =\n snapshot.config.status === 'anonymous' ? undefined : snapshot.config.credential.accessToken;\n\n useEffect(() => {\n if (accessToken !== undefined) onAccessTokenChange?.(accessToken);\n }, [accessToken, onAccessTokenChange]);\n\n // Programmatic mode drives itself to ready, so it must never fall through to `anonymousFallback`.\n const isProgrammatic = credential !== undefined && projectId !== undefined;\n\n const content = (() => {\n if (snapshot.storage === 'ready') {\n if (typeof children !== 'function') return children;\n // A ready storage build implies a scoped session; narrowing here is for the type checker.\n if (snapshot.config.status !== 'scoped') return null;\n const { projectId: scopedProjectId, baseUrl: scopedBaseUrl } = snapshot.config;\n return children({ projectId: scopedProjectId, baseUrl: scopedBaseUrl });\n }\n if (snapshot.storage === 'errored') return errorFallback;\n if (isRestoring || snapshot.storage === 'pending' || isProgrammatic) return loadingFallback;\n return anonymousFallback;\n })();\n\n return <AuthContextProvider value={session}>{content}</AuthContextProvider>;\n};\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { LegacySession } from '@keboola/api-client';\n\nimport { LegacyAuthContextProvider } from './context';\n\ntype Props = {\n session: LegacySession;\n /** Stack base URL. */\n baseUrl: string;\n /** SAPI token — drives the storage build to ready. */\n storageToken: string;\n /** Optional manage-api token — unlocks `api.management`. */\n managementToken?: string;\n /** Rendered until the storage build reaches ready. */\n fallback?: ReactNode;\n /** Rendered when the storage build fails — e.g. the SAPI token is expired or revoked. */\n errorFallback?: ReactNode;\n children: ReactNode;\n};\n\n/**\n * Owns the legacy (SAPI) lifecycle: on mount it configures the stack and drives the session to\n * ready, rendering `fallback` until the storage build resolves. The backend-injected SAPI token is\n * known up front, so this drives itself (unlike the interactive bearer `AuthProvider`). Exposes the\n * session via `useLegacyAuth()` and gates `children` behind readiness.\n */\nexport const LegacyAuthProvider = ({\n session,\n baseUrl,\n storageToken,\n managementToken,\n fallback = null,\n errorFallback = fallback,\n children,\n}: Props) => {\n // The build state, not just readiness: `errored` and `pending` render differently.\n const [storageState, setStorageState] = useState(() => session.debug().storage);\n // Guards the once-only drive against React StrictMode's double-mounted effect.\n const startedRef = useRef<boolean>(false);\n\n useEffect(() => {\n const unsubscribe = session.subscribe((snapshot) => setStorageState(snapshot.storage));\n\n if (!startedRef.current) {\n startedRef.current = true;\n session.setBaseUrl(baseUrl);\n if (managementToken) session.authenticateManagement(managementToken);\n // Fire-and-forget: `subscribe` moves `storageState` once the build resolves. A failed build\n // resolves rather than rejects, so this catch only covers a synchronous throw.\n void session\n .authenticateStorageAsync(storageToken)\n .catch((error) => console.error('Keboola session bootstrap failed', error));\n }\n\n return unsubscribe;\n // drive to ready only on initial mount\n // oxlint-disable-next-line react/exhaustive-deps\n }, []);\n\n // The only place a failed build is observable — nothing rejects, so without this it is silent.\n useEffect(() => {\n if (storageState === 'errored') {\n console.error('Keboola session bootstrap failed: the storage build errored.');\n }\n }, [storageState]);\n\n const content = (() => {\n if (storageState === 'ready') return children;\n if (storageState === 'errored') return errorFallback;\n return fallback;\n })();\n\n return <LegacyAuthContextProvider value={session}>{content}</LegacyAuthContextProvider>;\n};\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@ declare const useApiClient: () => KeboolaApi;
5
5
  declare const useProject: () => {
6
6
  hasFeature: (...feature: string[]) => boolean;
7
7
  hasAdminFeature: (...feature: string[]) => boolean;
8
- hasBackend: (backend: import("@keboola/api-client/storage/types").Backend) => boolean;
8
+ hasBackend: (backend: import("@keboola/api-client/constants").Backend) => boolean;
9
9
  sapiToken: import("@keboola/api-client/storage/types").StorageToken;
10
10
  };
11
11
  declare const useStack: () => {
@@ -22,8 +22,8 @@ declare const useStack: () => {
22
22
  features: string[];
23
23
  components: import("@keboola/api-client/storage/types").StackComponent[];
24
24
  hasFeature: (...feature: string[]) => boolean;
25
- getServiceUrl: (serviceId: import("@keboola/api-client/storage/types").ServiceId) => string | null;
26
- hasService: (serviceId: import("@keboola/api-client/storage/types").ServiceId) => boolean;
25
+ getServiceUrl: (serviceId: import("@keboola/api-client/constants").ServiceId) => string | null;
26
+ hasService: (serviceId: import("@keboola/api-client/constants").ServiceId) => boolean;
27
27
  getComponent: (componentId: string) => import("@keboola/api-client/storage/types").StackComponent | null;
28
28
  hasComponent: (...componentId: string[]) => boolean;
29
29
  };
@@ -40,8 +40,10 @@ declare const useAuth: () => {
40
40
  setProject: (projectId: number) => void;
41
41
  setProjectAsync: (projectId: number) => Promise<void>;
42
42
  clearProject: () => void;
43
+ forProject: (projectId: number) => Promise<import("@keboola/api-client").ProjectClients>;
43
44
  reset: () => void;
44
45
  debugForceRefresh: () => void;
46
+ getAuthHeaders: () => Promise<Record<string, string>>;
45
47
  restore: () => Promise<{
46
48
  status: 'empty' | 'restored' | 'expired';
47
49
  }>;
@@ -154,6 +156,7 @@ declare const ApiClientProvider: ({ api, children }: Props$2) => import("react/j
154
156
  //#endregion
155
157
  //#region src/AuthProvider.d.ts
156
158
  type BearerCredential = Parameters<BearerSession['authenticate']>[0];
159
+ type RestoreResult = Awaited<ReturnType<BearerSession['restore']>>;
157
160
  /** The session state guaranteed to exist once the storage build is ready. */
158
161
  type ScopedState = {
159
162
  projectId: number;
@@ -162,6 +165,16 @@ type ScopedState = {
162
165
  type BaseProps = {
163
166
  session: BearerSession;
164
167
  onAccessTokenChange?: (accessToken: string) => void;
168
+ /**
169
+ * Fires once with the outcome of the boot `restore()`.
170
+ *
171
+ * - `empty`, `restored` and `expired` are three different things to say to a user, and the slot
172
+ * that renders cannot tell them apart: `expired` and `empty` both land in `anonymousFallback`,
173
+ * and so does a `restored` session that carried no project.
174
+ * - A `restore()` that throws (offline, a 5xx) reports `empty` — the same value the provider acts
175
+ * on — so a caller never reads a transport failure as an expiry.
176
+ */
177
+ onRestore?: (result: RestoreResult) => void;
165
178
  /** Rendered while a persisted session is being restored, or the bootstrap is in flight. */
166
179
  loadingFallback?: ReactNode;
167
180
  /** Rendered when the storage build fails. */
@@ -191,7 +204,7 @@ type Props$1 = BaseProps & ({
191
204
  baseUrl?: never;
192
205
  anonymousFallback: ReactNode;
193
206
  });
194
- declare const AuthProvider: ({ session, baseUrl, credential, projectId, onAccessTokenChange, loadingFallback, errorFallback, anonymousFallback, children }: Props$1) => import("react/jsx-runtime").JSX.Element;
207
+ declare const AuthProvider: ({ session, baseUrl, credential, projectId, onAccessTokenChange, onRestore, loadingFallback, errorFallback, anonymousFallback, children }: Props$1) => import("react/jsx-runtime").JSX.Element;
195
208
  //#endregion
196
209
  //#region src/LegacyAuthProvider.d.ts
197
210
  type Props = {
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ declare const useApiClient: () => KeboolaApi;
5
5
  declare const useProject: () => {
6
6
  hasFeature: (...feature: string[]) => boolean;
7
7
  hasAdminFeature: (...feature: string[]) => boolean;
8
- hasBackend: (backend: import("@keboola/api-client/storage/types").Backend) => boolean;
8
+ hasBackend: (backend: import("@keboola/api-client/constants").Backend) => boolean;
9
9
  sapiToken: import("@keboola/api-client/storage/types").StorageToken;
10
10
  };
11
11
  declare const useStack: () => {
@@ -22,8 +22,8 @@ declare const useStack: () => {
22
22
  features: string[];
23
23
  components: import("@keboola/api-client/storage/types").StackComponent[];
24
24
  hasFeature: (...feature: string[]) => boolean;
25
- getServiceUrl: (serviceId: import("@keboola/api-client/storage/types").ServiceId) => string | null;
26
- hasService: (serviceId: import("@keboola/api-client/storage/types").ServiceId) => boolean;
25
+ getServiceUrl: (serviceId: import("@keboola/api-client/constants").ServiceId) => string | null;
26
+ hasService: (serviceId: import("@keboola/api-client/constants").ServiceId) => boolean;
27
27
  getComponent: (componentId: string) => import("@keboola/api-client/storage/types").StackComponent | null;
28
28
  hasComponent: (...componentId: string[]) => boolean;
29
29
  };
@@ -40,8 +40,10 @@ declare const useAuth: () => {
40
40
  setProject: (projectId: number) => void;
41
41
  setProjectAsync: (projectId: number) => Promise<void>;
42
42
  clearProject: () => void;
43
+ forProject: (projectId: number) => Promise<import("@keboola/api-client").ProjectClients>;
43
44
  reset: () => void;
44
45
  debugForceRefresh: () => void;
46
+ getAuthHeaders: () => Promise<Record<string, string>>;
45
47
  restore: () => Promise<{
46
48
  status: 'empty' | 'restored' | 'expired';
47
49
  }>;
@@ -154,6 +156,7 @@ declare const ApiClientProvider: ({ api, children }: Props$2) => import("react/j
154
156
  //#endregion
155
157
  //#region src/AuthProvider.d.ts
156
158
  type BearerCredential = Parameters<BearerSession['authenticate']>[0];
159
+ type RestoreResult = Awaited<ReturnType<BearerSession['restore']>>;
157
160
  /** The session state guaranteed to exist once the storage build is ready. */
158
161
  type ScopedState = {
159
162
  projectId: number;
@@ -162,6 +165,16 @@ type ScopedState = {
162
165
  type BaseProps = {
163
166
  session: BearerSession;
164
167
  onAccessTokenChange?: (accessToken: string) => void;
168
+ /**
169
+ * Fires once with the outcome of the boot `restore()`.
170
+ *
171
+ * - `empty`, `restored` and `expired` are three different things to say to a user, and the slot
172
+ * that renders cannot tell them apart: `expired` and `empty` both land in `anonymousFallback`,
173
+ * and so does a `restored` session that carried no project.
174
+ * - A `restore()` that throws (offline, a 5xx) reports `empty` — the same value the provider acts
175
+ * on — so a caller never reads a transport failure as an expiry.
176
+ */
177
+ onRestore?: (result: RestoreResult) => void;
165
178
  /** Rendered while a persisted session is being restored, or the bootstrap is in flight. */
166
179
  loadingFallback?: ReactNode;
167
180
  /** Rendered when the storage build fails. */
@@ -191,7 +204,7 @@ type Props$1 = BaseProps & ({
191
204
  baseUrl?: never;
192
205
  anonymousFallback: ReactNode;
193
206
  });
194
- declare const AuthProvider: ({ session, baseUrl, credential, projectId, onAccessTokenChange, loadingFallback, errorFallback, anonymousFallback, children }: Props$1) => import("react/jsx-runtime").JSX.Element;
207
+ declare const AuthProvider: ({ session, baseUrl, credential, projectId, onAccessTokenChange, onRestore, loadingFallback, errorFallback, anonymousFallback, children }: Props$1) => import("react/jsx-runtime").JSX.Element;
195
208
  //#endregion
196
209
  //#region src/LegacyAuthProvider.d.ts
197
210
  type Props = {
package/dist/index.js CHANGED
@@ -52,11 +52,15 @@ const ApiClientProvider = ({ api, children }) => /* @__PURE__ */ jsx(ApiProvider
52
52
  });
53
53
  //#endregion
54
54
  //#region src/AuthProvider.tsx
55
- const AuthProvider = ({ session, baseUrl, credential, projectId, onAccessTokenChange, loadingFallback = null, errorFallback = null, anonymousFallback = null, children }) => {
55
+ const AuthProvider = ({ session, baseUrl, credential, projectId, onAccessTokenChange, onRestore, loadingFallback = null, errorFallback = null, anonymousFallback = null, children }) => {
56
56
  const [snapshot, setSnapshot] = useState(() => session.debug());
57
57
  const [isRestoring, setIsRestoring] = useState(() => session.hasPersistedValue());
58
58
  const [restoreStatus, setRestoreStatus] = useState(null);
59
59
  const restoreStartedRef = useRef(false);
60
+ const onRestoreRef = useRef(onRestore);
61
+ useEffect(() => {
62
+ onRestoreRef.current = onRestore;
63
+ }, [onRestore]);
60
64
  useEffect(() => session.subscribe(setSnapshot), [session]);
61
65
  useEffect(() => {
62
66
  if (baseUrl === void 0) return;
@@ -70,9 +74,18 @@ const AuthProvider = ({ session, baseUrl, credential, projectId, onAccessTokenCh
70
74
  useEffect(() => {
71
75
  if (restoreStartedRef.current) return;
72
76
  restoreStartedRef.current = true;
73
- session.restore().then((result) => setRestoreStatus(result.status)).catch((error) => {
77
+ const reportCallbackFailure = (error) => console.error("Keboola bearer session onRestore callback failed", error);
78
+ const settle = (result) => {
79
+ setRestoreStatus(result.status);
80
+ try {
81
+ Promise.resolve(onRestoreRef.current?.(result)).catch(reportCallbackFailure);
82
+ } catch (error) {
83
+ reportCallbackFailure(error);
84
+ }
85
+ };
86
+ session.restore().then(settle).catch((error) => {
74
87
  console.error("Keboola bearer session restore failed", error);
75
- setRestoreStatus("empty");
88
+ settle({ status: "empty" });
76
89
  }).finally(() => setIsRestoring(false));
77
90
  }, [session]);
78
91
  useEffect(() => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/ApiClientProvider.tsx","../src/AuthProvider.tsx","../src/LegacyAuthProvider.tsx"],"sourcesContent":["import { createContext, use } from 'react';\n\nimport type { BearerSession, KeboolaApi, LegacySession } from '@keboola/api-client';\n\nconst MISSING_API_CLIENT_ERROR = 'useApiClient must be used within <ApiClientProvider />';\nconst MISSING_PROJECT_ERROR = 'useProject must be used within <ApiClientProvider />';\nconst MISSING_STACK_ERROR = 'useStack must be used within <ApiClientProvider />';\nconst MISSING_AUTH_ERROR = 'useAuth must be used within <AuthProvider />';\nconst MISSING_LEGACY_AUTH_ERROR = 'useLegacyAuth must be used within <LegacyAuthProvider />';\n\n// The product API (`auth` + `management` + every product namespace), flow-agnostic. Both the\n// bearer and legacy flows produce the same `KeboolaApi` surface, so consumers never branch on flow.\nconst ApiContext = createContext<KeboolaApi | null>(null);\nexport const ApiProvider = ApiContext;\n\n// The lifecycle sessions live in their own, flow-specific contexts so the auth surface a component\n// reaches for is explicit. Only the matching provider is mounted per flow.\nconst AuthContext = createContext<BearerSession | null>(null);\nexport const AuthContextProvider = AuthContext;\n\nconst LegacyAuthContext = createContext<LegacySession | null>(null);\nexport const LegacyAuthContextProvider = LegacyAuthContext;\n\nexport const useApiClient = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_API_CLIENT_ERROR);\n return api;\n};\n\nexport const useProject = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_PROJECT_ERROR);\n return api.context.project;\n};\n\nexport const useStack = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_STACK_ERROR);\n return api.context.stack;\n};\n\n/** The bearer (OAuth) lifecycle session. Throws outside `<AuthProvider />`. */\nexport const useAuth = () => {\n const session = use(AuthContext);\n if (session === null) throw new Error(MISSING_AUTH_ERROR);\n return session;\n};\n\n/** The legacy (SAPI) lifecycle session. Throws outside `<LegacyAuthProvider />`. */\nexport const useLegacyAuth = () => {\n const session = use(LegacyAuthContext);\n if (session === null) throw new Error(MISSING_LEGACY_AUTH_ERROR);\n return session;\n};\n","import type { ReactNode } from 'react';\n\nimport type { KeboolaApi } from '@keboola/api-client';\n\nimport { ApiProvider } from './context';\n\ntype Props = {\n /** The flow-agnostic product API — the `api` half of a keboola client. */\n api: KeboolaApi;\n children: ReactNode;\n};\n\n/**\n * Provides the product API to `useApiClient()`. A pure pass-through: the auth lifecycle (driving\n * to ready, gating on a fallback) belongs to `AuthProvider` / `LegacyAuthProvider`, which mount\n * this provider once their session is ready.\n */\nexport const ApiClientProvider = ({ api, children }: Props) => (\n <ApiProvider value={api}>{children}</ApiProvider>\n);\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { BearerSession } from '@keboola/api-client';\n\nimport { AuthContextProvider } from './context';\n\ntype BearerCredential = Parameters<BearerSession['authenticate']>[0];\ntype Snapshot = ReturnType<BearerSession['debug']>;\ntype RestoreStatus = Awaited<ReturnType<BearerSession['restore']>>['status'];\n\n/** The session state guaranteed to exist once the storage build is ready. */\ntype ScopedState = { projectId: number; baseUrl: string };\n\ntype BaseProps = {\n session: BearerSession;\n onAccessTokenChange?: (accessToken: string) => void;\n /** Rendered while a persisted session is being restored, or the bootstrap is in flight. */\n loadingFallback?: ReactNode;\n /** Rendered when the storage build fails. */\n errorFallback?: ReactNode;\n /**\n * Rendered only once the session is scoped and the storage build is ready. Pass a function to\n * receive the scoped state (the project the session settled on) without re-narrowing it yourself.\n */\n children: ReactNode | ((scoped: ScopedState) => ReactNode);\n};\n\n/**\n * Two mutually exclusive bootstrap modes, discriminated by whether a credential is supplied:\n * - **programmatic** — `credential` + `projectId` given, so `baseUrl` is **required**: a credential\n * is bound to the stack that issued it and `authenticate` refuses an unconfigured session.\n * - **interactive** — none of the three given; the stack comes from the session instead — `restore()`\n * adopts the persisted one, otherwise the login UI sets it. `anonymousFallback` is **required**\n * here (a missing login UI would leave the app on a permanently blank screen).\n */\ntype Props = BaseProps &\n (\n | {\n credential: BearerCredential;\n projectId: number;\n baseUrl: string;\n anonymousFallback?: never;\n }\n | {\n credential?: undefined;\n projectId?: undefined;\n baseUrl?: never;\n anonymousFallback: ReactNode;\n }\n );\n\nexport const AuthProvider = ({\n session,\n baseUrl,\n credential,\n projectId,\n onAccessTokenChange,\n loadingFallback = null,\n errorFallback = null,\n anonymousFallback = null,\n children,\n}: Props) => {\n const [snapshot, setSnapshot] = useState<Snapshot>(() => session.debug());\n const [isRestoring, setIsRestoring] = useState(() => session.hasPersistedValue());\n const [restoreStatus, setRestoreStatus] = useState<RestoreStatus | null>(null);\n const restoreStartedRef = useRef(false);\n\n useEffect(() => session.subscribe(setSnapshot), [session]);\n\n // Programmatic only. Interactive leaves the stack unset: `restore()` adopts the persisted one, and\n // failing that the login UI picks it — so there is nothing to configure up front.\n useEffect(() => {\n if (baseUrl === undefined) return;\n if (snapshot.config.baseUrl !== null) return;\n session.setBaseUrl(baseUrl);\n }, [session, baseUrl, snapshot.config.baseUrl]);\n\n // Runs regardless of whether a stack is configured — with none, `restore()` adopts the stack the\n // persisted session belongs to. (Declared after the effect above, so programmatic mode has already\n // configured its stack by the time this runs.) The ref guards StrictMode's double-invoked effect:\n // `restoreStatus` only lands asynchronously, so it cannot prevent a second synchronous call.\n useEffect(() => {\n if (restoreStartedRef.current) return;\n restoreStartedRef.current = true;\n session\n .restore()\n .then((result) => setRestoreStatus(result.status))\n .catch((error) => {\n console.error('Keboola bearer session restore failed', error);\n setRestoreStatus('empty');\n })\n .finally(() => setIsRestoring(false));\n }, [session]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (credential === undefined) return;\n if (snapshot.config.status !== 'anonymous') return;\n session.authenticate(credential);\n }, [session, restoreStatus, credential, snapshot.config.status]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (projectId === undefined) return;\n if (snapshot.config.status !== 'authenticated') return;\n session.setProject(projectId);\n }, [session, restoreStatus, projectId, snapshot.config.status]);\n\n const accessToken =\n snapshot.config.status === 'anonymous' ? undefined : snapshot.config.credential.accessToken;\n\n useEffect(() => {\n if (accessToken !== undefined) onAccessTokenChange?.(accessToken);\n }, [accessToken, onAccessTokenChange]);\n\n // Programmatic mode drives itself to ready, so it must never fall through to `anonymousFallback`.\n const isProgrammatic = credential !== undefined && projectId !== undefined;\n\n const content = (() => {\n if (snapshot.storage === 'ready') {\n if (typeof children !== 'function') return children;\n // A ready storage build implies a scoped session; narrowing here is for the type checker.\n if (snapshot.config.status !== 'scoped') return null;\n const { projectId: scopedProjectId, baseUrl: scopedBaseUrl } = snapshot.config;\n return children({ projectId: scopedProjectId, baseUrl: scopedBaseUrl });\n }\n if (snapshot.storage === 'errored') return errorFallback;\n if (isRestoring || snapshot.storage === 'pending' || isProgrammatic) return loadingFallback;\n return anonymousFallback;\n })();\n\n return <AuthContextProvider value={session}>{content}</AuthContextProvider>;\n};\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { LegacySession } from '@keboola/api-client';\n\nimport { LegacyAuthContextProvider } from './context';\n\ntype Props = {\n session: LegacySession;\n /** Stack base URL. */\n baseUrl: string;\n /** SAPI token — drives the storage build to ready. */\n storageToken: string;\n /** Optional manage-api token — unlocks `api.management`. */\n managementToken?: string;\n /** Rendered until the storage build reaches ready. */\n fallback?: ReactNode;\n /** Rendered when the storage build fails — e.g. the SAPI token is expired or revoked. */\n errorFallback?: ReactNode;\n children: ReactNode;\n};\n\n/**\n * Owns the legacy (SAPI) lifecycle: on mount it configures the stack and drives the session to\n * ready, rendering `fallback` until the storage build resolves. The backend-injected SAPI token is\n * known up front, so this drives itself (unlike the interactive bearer `AuthProvider`). Exposes the\n * session via `useLegacyAuth()` and gates `children` behind readiness.\n */\nexport const LegacyAuthProvider = ({\n session,\n baseUrl,\n storageToken,\n managementToken,\n fallback = null,\n errorFallback = fallback,\n children,\n}: Props) => {\n // The build state, not just readiness: `errored` and `pending` render differently.\n const [storageState, setStorageState] = useState(() => session.debug().storage);\n // Guards the once-only drive against React StrictMode's double-mounted effect.\n const startedRef = useRef<boolean>(false);\n\n useEffect(() => {\n const unsubscribe = session.subscribe((snapshot) => setStorageState(snapshot.storage));\n\n if (!startedRef.current) {\n startedRef.current = true;\n session.setBaseUrl(baseUrl);\n if (managementToken) session.authenticateManagement(managementToken);\n // Fire-and-forget: `subscribe` moves `storageState` once the build resolves. A failed build\n // resolves rather than rejects, so this catch only covers a synchronous throw.\n void session\n .authenticateStorageAsync(storageToken)\n .catch((error) => console.error('Keboola session bootstrap failed', error));\n }\n\n return unsubscribe;\n // drive to ready only on initial mount\n // oxlint-disable-next-line react/exhaustive-deps\n }, []);\n\n // The only place a failed build is observable — nothing rejects, so without this it is silent.\n useEffect(() => {\n if (storageState === 'errored') {\n console.error('Keboola session bootstrap failed: the storage build errored.');\n }\n }, [storageState]);\n\n const content = (() => {\n if (storageState === 'ready') return children;\n if (storageState === 'errored') return errorFallback;\n return fallback;\n })();\n\n return <LegacyAuthContextProvider value={session}>{content}</LegacyAuthContextProvider>;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/ApiClientProvider.tsx","../src/AuthProvider.tsx","../src/LegacyAuthProvider.tsx"],"sourcesContent":["import { createContext, use } from 'react';\n\nimport type { BearerSession, KeboolaApi, LegacySession } from '@keboola/api-client';\n\nconst MISSING_API_CLIENT_ERROR = 'useApiClient must be used within <ApiClientProvider />';\nconst MISSING_PROJECT_ERROR = 'useProject must be used within <ApiClientProvider />';\nconst MISSING_STACK_ERROR = 'useStack must be used within <ApiClientProvider />';\nconst MISSING_AUTH_ERROR = 'useAuth must be used within <AuthProvider />';\nconst MISSING_LEGACY_AUTH_ERROR = 'useLegacyAuth must be used within <LegacyAuthProvider />';\n\n// The product API (`auth` + `management` + every product namespace), flow-agnostic. Both the\n// bearer and legacy flows produce the same `KeboolaApi` surface, so consumers never branch on flow.\nconst ApiContext = createContext<KeboolaApi | null>(null);\nexport const ApiProvider = ApiContext;\n\n// The lifecycle sessions live in their own, flow-specific contexts so the auth surface a component\n// reaches for is explicit. Only the matching provider is mounted per flow.\nconst AuthContext = createContext<BearerSession | null>(null);\nexport const AuthContextProvider = AuthContext;\n\nconst LegacyAuthContext = createContext<LegacySession | null>(null);\nexport const LegacyAuthContextProvider = LegacyAuthContext;\n\nexport const useApiClient = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_API_CLIENT_ERROR);\n return api;\n};\n\nexport const useProject = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_PROJECT_ERROR);\n return api.context.project;\n};\n\nexport const useStack = () => {\n const api = use(ApiContext);\n if (api === null) throw new Error(MISSING_STACK_ERROR);\n return api.context.stack;\n};\n\n/** The bearer (OAuth) lifecycle session. Throws outside `<AuthProvider />`. */\nexport const useAuth = () => {\n const session = use(AuthContext);\n if (session === null) throw new Error(MISSING_AUTH_ERROR);\n return session;\n};\n\n/** The legacy (SAPI) lifecycle session. Throws outside `<LegacyAuthProvider />`. */\nexport const useLegacyAuth = () => {\n const session = use(LegacyAuthContext);\n if (session === null) throw new Error(MISSING_LEGACY_AUTH_ERROR);\n return session;\n};\n","import type { ReactNode } from 'react';\n\nimport type { KeboolaApi } from '@keboola/api-client';\n\nimport { ApiProvider } from './context';\n\ntype Props = {\n /** The flow-agnostic product API — the `api` half of a keboola client. */\n api: KeboolaApi;\n children: ReactNode;\n};\n\n/**\n * Provides the product API to `useApiClient()`. A pure pass-through: the auth lifecycle (driving\n * to ready, gating on a fallback) belongs to `AuthProvider` / `LegacyAuthProvider`, which mount\n * this provider once their session is ready.\n */\nexport const ApiClientProvider = ({ api, children }: Props) => (\n <ApiProvider value={api}>{children}</ApiProvider>\n);\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { BearerSession } from '@keboola/api-client';\n\nimport { AuthContextProvider } from './context';\n\ntype BearerCredential = Parameters<BearerSession['authenticate']>[0];\ntype Snapshot = ReturnType<BearerSession['debug']>;\ntype RestoreResult = Awaited<ReturnType<BearerSession['restore']>>;\ntype RestoreStatus = RestoreResult['status'];\n\n/** The session state guaranteed to exist once the storage build is ready. */\ntype ScopedState = { projectId: number; baseUrl: string };\n\ntype BaseProps = {\n session: BearerSession;\n onAccessTokenChange?: (accessToken: string) => void;\n /**\n * Fires once with the outcome of the boot `restore()`.\n *\n * - `empty`, `restored` and `expired` are three different things to say to a user, and the slot\n * that renders cannot tell them apart: `expired` and `empty` both land in `anonymousFallback`,\n * and so does a `restored` session that carried no project.\n * - A `restore()` that throws (offline, a 5xx) reports `empty` — the same value the provider acts\n * on — so a caller never reads a transport failure as an expiry.\n */\n onRestore?: (result: RestoreResult) => void;\n /** Rendered while a persisted session is being restored, or the bootstrap is in flight. */\n loadingFallback?: ReactNode;\n /** Rendered when the storage build fails. */\n errorFallback?: ReactNode;\n /**\n * Rendered only once the session is scoped and the storage build is ready. Pass a function to\n * receive the scoped state (the project the session settled on) without re-narrowing it yourself.\n */\n children: ReactNode | ((scoped: ScopedState) => ReactNode);\n};\n\n/**\n * Two mutually exclusive bootstrap modes, discriminated by whether a credential is supplied:\n * - **programmatic** — `credential` + `projectId` given, so `baseUrl` is **required**: a credential\n * is bound to the stack that issued it and `authenticate` refuses an unconfigured session.\n * - **interactive** — none of the three given; the stack comes from the session instead — `restore()`\n * adopts the persisted one, otherwise the login UI sets it. `anonymousFallback` is **required**\n * here (a missing login UI would leave the app on a permanently blank screen).\n */\ntype Props = BaseProps &\n (\n | {\n credential: BearerCredential;\n projectId: number;\n baseUrl: string;\n anonymousFallback?: never;\n }\n | {\n credential?: undefined;\n projectId?: undefined;\n baseUrl?: never;\n anonymousFallback: ReactNode;\n }\n );\n\nexport const AuthProvider = ({\n session,\n baseUrl,\n credential,\n projectId,\n onAccessTokenChange,\n onRestore,\n loadingFallback = null,\n errorFallback = null,\n anonymousFallback = null,\n children,\n}: Props) => {\n const [snapshot, setSnapshot] = useState<Snapshot>(() => session.debug());\n const [isRestoring, setIsRestoring] = useState(() => session.hasPersistedValue());\n const [restoreStatus, setRestoreStatus] = useState<RestoreStatus | null>(null);\n const restoreStartedRef = useRef(false);\n // Held in a ref so the restore effect keeps `[session]` as its only dependency — a caller passing\n // an inline closure must not re-arm the restore. A passive effect is enough: the ref starts at the\n // mount-time callback, and `restore()` settles in a later microtask than any commit's effects, so\n // there is no window in which a stale closure could be the one called.\n const onRestoreRef = useRef(onRestore);\n useEffect(() => {\n onRestoreRef.current = onRestore;\n }, [onRestore]);\n\n useEffect(() => session.subscribe(setSnapshot), [session]);\n\n // Programmatic only. Interactive leaves the stack unset: `restore()` adopts the persisted one, and\n // failing that the login UI picks it — so there is nothing to configure up front.\n useEffect(() => {\n if (baseUrl === undefined) return;\n if (snapshot.config.baseUrl !== null) return;\n session.setBaseUrl(baseUrl);\n }, [session, baseUrl, snapshot.config.baseUrl]);\n\n // Runs regardless of whether a stack is configured — with none, `restore()` adopts the stack the\n // persisted session belongs to. (Declared after the effect above, so programmatic mode has already\n // configured its stack by the time this runs.) The ref guards StrictMode's double-invoked effect:\n // `restoreStatus` only lands asynchronously, so it cannot prevent a second synchronous call.\n useEffect(() => {\n if (restoreStartedRef.current) return;\n restoreStartedRef.current = true;\n const reportCallbackFailure = (error: unknown) =>\n console.error('Keboola bearer session onRestore callback failed', error);\n\n // The consumer's callback is isolated: it runs inside the promise chain, so a throw would\n // otherwise land in the `.catch` below — reporting a successful restore as a failure, logging a\n // lie, and calling `settle` a second time. `Promise.resolve` covers an `async` callback too:\n // TypeScript accepts one against this `void` return type, and its rejection would escape the\n // `try` as an unhandled rejection.\n const settle = (result: RestoreResult) => {\n setRestoreStatus(result.status);\n try {\n void Promise.resolve(onRestoreRef.current?.(result)).catch(reportCallbackFailure);\n } catch (error) {\n reportCallbackFailure(error);\n }\n };\n session\n .restore()\n .then(settle)\n .catch((error) => {\n console.error('Keboola bearer session restore failed', error);\n settle({ status: 'empty' });\n })\n .finally(() => setIsRestoring(false));\n }, [session]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (credential === undefined) return;\n if (snapshot.config.status !== 'anonymous') return;\n session.authenticate(credential);\n }, [session, restoreStatus, credential, snapshot.config.status]);\n\n useEffect(() => {\n if (restoreStatus === null || restoreStatus === 'restored') return;\n if (projectId === undefined) return;\n if (snapshot.config.status !== 'authenticated') return;\n session.setProject(projectId);\n }, [session, restoreStatus, projectId, snapshot.config.status]);\n\n const accessToken =\n snapshot.config.status === 'anonymous' ? undefined : snapshot.config.credential.accessToken;\n\n useEffect(() => {\n if (accessToken !== undefined) onAccessTokenChange?.(accessToken);\n }, [accessToken, onAccessTokenChange]);\n\n // Programmatic mode drives itself to ready, so it must never fall through to `anonymousFallback`.\n const isProgrammatic = credential !== undefined && projectId !== undefined;\n\n const content = (() => {\n if (snapshot.storage === 'ready') {\n if (typeof children !== 'function') return children;\n // A ready storage build implies a scoped session; narrowing here is for the type checker.\n if (snapshot.config.status !== 'scoped') return null;\n const { projectId: scopedProjectId, baseUrl: scopedBaseUrl } = snapshot.config;\n return children({ projectId: scopedProjectId, baseUrl: scopedBaseUrl });\n }\n if (snapshot.storage === 'errored') return errorFallback;\n if (isRestoring || snapshot.storage === 'pending' || isProgrammatic) return loadingFallback;\n return anonymousFallback;\n })();\n\n return <AuthContextProvider value={session}>{content}</AuthContextProvider>;\n};\n","import { useEffect, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { LegacySession } from '@keboola/api-client';\n\nimport { LegacyAuthContextProvider } from './context';\n\ntype Props = {\n session: LegacySession;\n /** Stack base URL. */\n baseUrl: string;\n /** SAPI token — drives the storage build to ready. */\n storageToken: string;\n /** Optional manage-api token — unlocks `api.management`. */\n managementToken?: string;\n /** Rendered until the storage build reaches ready. */\n fallback?: ReactNode;\n /** Rendered when the storage build fails — e.g. the SAPI token is expired or revoked. */\n errorFallback?: ReactNode;\n children: ReactNode;\n};\n\n/**\n * Owns the legacy (SAPI) lifecycle: on mount it configures the stack and drives the session to\n * ready, rendering `fallback` until the storage build resolves. The backend-injected SAPI token is\n * known up front, so this drives itself (unlike the interactive bearer `AuthProvider`). Exposes the\n * session via `useLegacyAuth()` and gates `children` behind readiness.\n */\nexport const LegacyAuthProvider = ({\n session,\n baseUrl,\n storageToken,\n managementToken,\n fallback = null,\n errorFallback = fallback,\n children,\n}: Props) => {\n // The build state, not just readiness: `errored` and `pending` render differently.\n const [storageState, setStorageState] = useState(() => session.debug().storage);\n // Guards the once-only drive against React StrictMode's double-mounted effect.\n const startedRef = useRef<boolean>(false);\n\n useEffect(() => {\n const unsubscribe = session.subscribe((snapshot) => setStorageState(snapshot.storage));\n\n if (!startedRef.current) {\n startedRef.current = true;\n session.setBaseUrl(baseUrl);\n if (managementToken) session.authenticateManagement(managementToken);\n // Fire-and-forget: `subscribe` moves `storageState` once the build resolves. A failed build\n // resolves rather than rejects, so this catch only covers a synchronous throw.\n void session\n .authenticateStorageAsync(storageToken)\n .catch((error) => console.error('Keboola session bootstrap failed', error));\n }\n\n return unsubscribe;\n // drive to ready only on initial mount\n // oxlint-disable-next-line react/exhaustive-deps\n }, []);\n\n // The only place a failed build is observable — nothing rejects, so without this it is silent.\n useEffect(() => {\n if (storageState === 'errored') {\n console.error('Keboola session bootstrap failed: the storage build errored.');\n }\n }, [storageState]);\n\n const content = (() => {\n if (storageState === 'ready') return children;\n if (storageState === 'errored') return errorFallback;\n return fallback;\n })();\n\n return <LegacyAuthContextProvider value={session}>{content}</LegacyAuthContextProvider>;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keboola/api-client-react",
3
- "version": "31.0.0",
3
+ "version": "33.0.0",
4
4
  "description": "React bindings for @keboola/api-client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "peerDependencies": {
53
53
  "react": "^19.0.0",
54
- "@keboola/api-client": "^31.0.0"
54
+ "@keboola/api-client": "^33.0.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@arethetypeswrong/cli": "^0.18.2",
@@ -72,7 +72,8 @@
72
72
  "typescript": "npm:@typescript/typescript6@^6.0.2",
73
73
  "vite": "^8.1.5",
74
74
  "vitest": "^4.1.10",
75
- "@keboola/api-client": "^31.0.0",
75
+ "@keboola/api-fixtures": "^28.0.0",
76
+ "@keboola/api-client": "^33.0.0",
76
77
  "@keboola/oxlint-config": "^10.0.2",
77
78
  "@keboola/tsconfig": "^0.2.1"
78
79
  },