@cognite/dune 0.3.7 → 0.4.1

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.
@@ -17,10 +17,50 @@ type App = {
17
17
  name: string;
18
18
  description: string;
19
19
  versionTag: string;
20
+ /** When set to "appsApi", deploy uses the App Hosting API instead of Files API */
21
+ infra?: "appsApi";
20
22
  };
21
23
 
22
24
  declare const deploy: (deployment: Deployment, app: App, folder: string) => Promise<void>;
23
25
 
26
+ /**
27
+ * App Hosting API Deployment
28
+ *
29
+ * Handles deployment of packaged applications via the new App Hosting API
30
+ * instead of CDF Files API + dataset.
31
+ */
32
+
33
+ declare class AppHostingDeployer {
34
+ private client;
35
+ constructor(client: CogniteClient);
36
+ private get appsBasePath();
37
+ /**
38
+ * Ensure the app exists in the App Hosting service. Creates it if missing.
39
+ */
40
+ ensureApp(externalId: string, name: string, description: string): Promise<void>;
41
+ /**
42
+ * Upload a new version of the app as a zip file.
43
+ *
44
+ * `entryPath` must be a relative path (no leading slash). The App
45
+ * Hosting backend rejects absolute paths as a security measure against
46
+ * archive traversal, see `ZipCentralDirectory.normalizeEntryPath` in
47
+ * infrastructure/services/app-hosting.
48
+ */
49
+ uploadVersion(appExternalId: string, version: string, zipPath: string, entryPath?: string): Promise<void>;
50
+ /**
51
+ * Publish the version and set it as the ACTIVE alias.
52
+ */
53
+ publishAndActivate(appExternalId: string, version: string): Promise<void>;
54
+ /**
55
+ * Full deployment: ensure app exists, upload version, and optionally publish and activate.
56
+ *
57
+ * When `published` is false the version is uploaded in DRAFT state with no alias —
58
+ * it is stored but not served to end users. Pass `published: true` to transition the
59
+ * version to PUBLISHED and set it as the ACTIVE alias so it starts receiving traffic.
60
+ */
61
+ deploy(appExternalId: string, name: string, description: string, versionTag: string, zipPath: string, published?: boolean): Promise<void>;
62
+ }
63
+
24
64
  /**
25
65
  * CDF Application Deployment
26
66
  *
@@ -95,4 +135,4 @@ declare const getSdk: (deployment: Deployment, folder: string) => Promise<Cognit
95
135
  */
96
136
  declare const getToken: (deployment: Deployment) => Promise<string>;
97
137
 
98
- export { type App, ApplicationPackager, CdfApplicationDeployer, type Deployment, deploy, getSdk, getToken };
138
+ export { type App, AppHostingDeployer, ApplicationPackager, CdfApplicationDeployer, type Deployment, deploy, getSdk, getToken };
@@ -1,8 +1,131 @@
1
1
  // src/deploy/deploy.ts
2
- import fs3 from "fs";
2
+ import fs4 from "fs";
3
3
 
4
- // src/deploy/application-deployer.ts
4
+ // src/deploy/apphosting-deployer.ts
5
5
  import fs from "fs";
6
+ import path from "path";
7
+ var AppHostingDeployer = class {
8
+ constructor(client) {
9
+ this.client = client;
10
+ }
11
+ get appsBasePath() {
12
+ return `/api/v1/projects/${encodeURIComponent(this.client.project)}/apphosting/apps`;
13
+ }
14
+ /**
15
+ * Ensure the app exists in the App Hosting service. Creates it if missing.
16
+ */
17
+ async ensureApp(externalId, name, description) {
18
+ console.log("\u{1F50D} Ensuring app exists...");
19
+ try {
20
+ await this.client.post(this.appsBasePath, {
21
+ data: { items: [{ externalId, name, description }] }
22
+ });
23
+ console.log(`\u2705 App '${externalId}' created`);
24
+ } catch (error) {
25
+ if (error instanceof Error && "status" in error && error.status === 409) {
26
+ console.log(`\u2705 App '${externalId}' already exists`);
27
+ return;
28
+ }
29
+ throw error;
30
+ }
31
+ }
32
+ /**
33
+ * Upload a new version of the app as a zip file.
34
+ *
35
+ * `entryPath` must be a relative path (no leading slash). The App
36
+ * Hosting backend rejects absolute paths as a security measure against
37
+ * archive traversal, see `ZipCentralDirectory.normalizeEntryPath` in
38
+ * infrastructure/services/app-hosting.
39
+ */
40
+ async uploadVersion(appExternalId, version, zipPath, entryPath = "index.html") {
41
+ console.log(`\u{1F4E4} Uploading version ${version}...`);
42
+ const fileBuffer = fs.readFileSync(zipPath);
43
+ const fileName = path.basename(zipPath);
44
+ const formData = new FormData();
45
+ formData.append("file", new Blob([fileBuffer]), fileName);
46
+ formData.append("version", version);
47
+ formData.append("entryPath", entryPath);
48
+ const encodedAppExternalId = encodeURIComponent(appExternalId);
49
+ const uploadPath = `${this.appsBasePath}/${encodedAppExternalId}/versions`;
50
+ const token = await this.client.authenticate();
51
+ const url = `${this.client.getBaseUrl()}${uploadPath}`;
52
+ const controller = new AbortController();
53
+ const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1e3);
54
+ let response;
55
+ try {
56
+ response = await fetch(url, {
57
+ method: "POST",
58
+ headers: { Authorization: `Bearer ${token}` },
59
+ body: formData,
60
+ signal: controller.signal
61
+ });
62
+ } catch (error) {
63
+ if (error instanceof Error && error.name === "AbortError") {
64
+ throw new Error("Upload timed out after 5 minutes");
65
+ }
66
+ throw error;
67
+ } finally {
68
+ clearTimeout(timeout);
69
+ }
70
+ if (!response.ok) {
71
+ const body = await response.text();
72
+ let message = body;
73
+ try {
74
+ const json = JSON.parse(body);
75
+ message = json.message ?? json.error ?? body;
76
+ } catch {
77
+ }
78
+ throw new Error(`Upload failed: ${response.status} \u2014 ${message}`);
79
+ }
80
+ console.log(`\u2705 Version ${version} uploaded`);
81
+ }
82
+ /**
83
+ * Publish the version and set it as the ACTIVE alias.
84
+ */
85
+ async publishAndActivate(appExternalId, version) {
86
+ console.log(`\u{1F680} Publishing and activating version ${version}...`);
87
+ const encodedAppExternalId = encodeURIComponent(appExternalId);
88
+ const url = `${this.appsBasePath}/${encodedAppExternalId}/versions/update`;
89
+ await this.client.post(url, {
90
+ data: {
91
+ items: [
92
+ {
93
+ version,
94
+ update: {
95
+ lifecycleState: { set: "PUBLISHED" },
96
+ alias: { set: "ACTIVE" }
97
+ }
98
+ }
99
+ ]
100
+ }
101
+ });
102
+ console.log(`\u2705 Version ${version} is now PUBLISHED and ACTIVE`);
103
+ }
104
+ /**
105
+ * Full deployment: ensure app exists, upload version, and optionally publish and activate.
106
+ *
107
+ * When `published` is false the version is uploaded in DRAFT state with no alias —
108
+ * it is stored but not served to end users. Pass `published: true` to transition the
109
+ * version to PUBLISHED and set it as the ACTIVE alias so it starts receiving traffic.
110
+ */
111
+ async deploy(appExternalId, name, description, versionTag, zipPath, published = false) {
112
+ console.log("\n\u{1F680} Deploying application via App Hosting API...\n");
113
+ try {
114
+ await this.ensureApp(appExternalId, name, description);
115
+ await this.uploadVersion(appExternalId, versionTag, zipPath);
116
+ if (published) {
117
+ await this.publishAndActivate(appExternalId, versionTag);
118
+ }
119
+ console.log("\n\u2705 Deployment successful!");
120
+ } catch (error) {
121
+ const message = error instanceof Error ? error.message : String(error);
122
+ throw Object.assign(new Error(`Deployment failed: ${message}`), { cause: error });
123
+ }
124
+ }
125
+ };
126
+
127
+ // src/deploy/application-deployer.ts
128
+ import fs2 from "fs";
6
129
  var CdfApplicationDeployer = class {
7
130
  /**
8
131
  * @param {CogniteClient} client - Cognite SDK client
@@ -56,7 +179,7 @@ var CdfApplicationDeployer = class {
56
179
  console.log(`\u2705 Data set '${this.DATA_SET_EXTERNAL_ID}' validated (ID: ${dataSetId})
57
180
  `);
58
181
  console.log("\u{1F4C1} Creating file record...");
59
- const fileContent = fs.readFileSync(zipFilename);
182
+ const fileContent = fs2.readFileSync(zipFilename);
60
183
  const metadata = {
61
184
  published: String(published),
62
185
  name,
@@ -109,22 +232,22 @@ var CdfApplicationDeployer = class {
109
232
  };
110
233
 
111
234
  // src/deploy/application-packager.ts
112
- import fs2 from "fs";
113
- import path from "path";
235
+ import fs3 from "fs";
236
+ import path2 from "path";
114
237
  import archiver from "archiver";
115
238
  var ApplicationPackager = class {
116
239
  /**
117
240
  * @param {string} distDirectory - Build directory to package (can be relative or absolute)
118
241
  */
119
242
  constructor(distDirectory = "dist") {
120
- this.distPath = path.isAbsolute(distDirectory) ? distDirectory : path.join(process.cwd(), distDirectory);
243
+ this.distPath = path2.isAbsolute(distDirectory) ? distDirectory : path2.join(process.cwd(), distDirectory);
121
244
  }
122
245
  /**
123
246
  * Validate that build directory exists
124
247
  * @throws {Error} If build directory not found
125
248
  */
126
249
  validateBuildDirectory() {
127
- if (!fs2.existsSync(this.distPath)) {
250
+ if (!fs3.existsSync(this.distPath)) {
128
251
  throw new Error(`Build directory "${this.distPath}" not found. Run build first.`);
129
252
  }
130
253
  }
@@ -138,7 +261,7 @@ var ApplicationPackager = class {
138
261
  this.validateBuildDirectory();
139
262
  console.log("\u{1F4E6} Packaging application...");
140
263
  return new Promise((resolve, reject) => {
141
- const output = fs2.createWriteStream(outputFilename);
264
+ const output = fs3.createWriteStream(outputFilename);
142
265
  const archive = archiver("zip", {
143
266
  zlib: { level: 9 }
144
267
  // Maximum compression
@@ -316,26 +439,35 @@ var getSdk = async (deployment, folder) => {
316
439
  };
317
440
 
318
441
  // src/deploy/deploy.ts
319
- var deploy = async (deployment, app, folder) => {
442
+ async function deployViaAppHosting(deployment, app, folder, zipFilename) {
443
+ const { externalId, name, description, versionTag } = app;
444
+ const sdk = await getSdk(deployment, folder);
445
+ const deployer = new AppHostingDeployer(sdk);
446
+ await deployer.deploy(externalId, name, description, versionTag, zipFilename, deployment.published);
447
+ }
448
+ async function deployViaCdf(deployment, app, folder, zipFilename) {
449
+ const { externalId, name, description, versionTag } = app;
320
450
  const sdk = await getSdk(deployment, folder);
321
- const distPath = `${folder}/dist`;
322
- const packager = new ApplicationPackager(distPath);
323
- const zipFilename = await packager.createZip("app.zip", true);
324
451
  const deployer = new CdfApplicationDeployer(sdk);
325
- await deployer.deploy(
326
- app.externalId,
327
- app.name,
328
- app.description,
329
- app.versionTag,
330
- zipFilename,
331
- deployment.published
332
- );
452
+ await deployer.deploy(externalId, name, description, versionTag, zipFilename, deployment.published);
453
+ }
454
+ var deploy = async (deployment, app, folder) => {
455
+ const zipFilename = await new ApplicationPackager(`${folder}/dist`).createZip("app.zip", true);
333
456
  try {
334
- fs3.unlinkSync(zipFilename);
335
- } catch {
457
+ if (app.infra === "appsApi") {
458
+ await deployViaAppHosting(deployment, app, folder, zipFilename);
459
+ } else {
460
+ await deployViaCdf(deployment, app, folder, zipFilename);
461
+ }
462
+ } finally {
463
+ try {
464
+ fs4.unlinkSync(zipFilename);
465
+ } catch {
466
+ }
336
467
  }
337
468
  };
338
469
  export {
470
+ AppHostingDeployer,
339
471
  ApplicationPackager,
340
472
  CdfApplicationDeployer,
341
473
  deploy,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { CDFConfig, DuneAuthProvider, DuneAuthProviderContext, DuneAuthProviderProps, DuneContextValue, EMPTY_SDK, createCDFSDK, getToken, useDune } from './auth/index.js';
1
+ export { AppSdkAuthProvider, CDFConfig, DuneAuthProvider, DuneAuthProviderContext, DuneAuthProviderProps, DuneContextValue, EMPTY_SDK, createCDFSDK, getToken, useDune } from './auth/index.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
4
4
  import '@cognite/sdk';
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import {
2
+ AppSdkAuthProvider,
2
3
  DuneAuthProvider,
3
4
  DuneAuthProviderContext,
4
5
  EMPTY_SDK,
5
6
  createCDFSDK,
6
7
  getToken,
7
8
  useDune
8
- } from "./chunk-53VTKDSC.js";
9
+ } from "./chunk-XWZCFZUH.js";
9
10
  export {
11
+ AppSdkAuthProvider,
10
12
  DuneAuthProvider,
11
13
  DuneAuthProviderContext,
12
14
  EMPTY_SDK,
@@ -25,7 +25,16 @@ var fusionOpenPlugin = () => {
25
25
  const { org, project, baseUrl } = firstDeployment || {};
26
26
  const parsedBaseUrl = baseUrl?.split("//")[1];
27
27
  if (org && project && baseUrl) {
28
- const fusionUrl = `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/development/${port}?cluster=${parsedBaseUrl}&workspace=industrial-tools`;
28
+ let fusionUrl;
29
+ if (appJson.infra === "appsApi") {
30
+ if (!appJson.externalId) {
31
+ console.warn(" \u279C app.json is missing externalId \u2014 cannot determine Fusion URL");
32
+ return;
33
+ }
34
+ fusionUrl = `https://${org}.fusion.cognite.com/${project}/dune-app-host/development/${appJson.externalId}/${port}?cluster=${parsedBaseUrl}`;
35
+ } else {
36
+ fusionUrl = `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/development/${port}?cluster=${parsedBaseUrl}&workspace=industrial-tools`;
37
+ }
29
38
  console.log(` \u279C Fusion: ${fusionUrl}`);
30
39
  openUrl(fusionUrl);
31
40
  return;
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "@cognite/dune",
3
- "version": "0.3.7",
3
+ "version": "0.4.1",
4
4
  "description": "Build and deploy React apps to Cognite Data Fusion",
5
- "keywords": ["cognite", "dune", "cdf", "fusion", "react", "scaffold", "deploy"],
5
+ "keywords": [
6
+ "cognite",
7
+ "dune",
8
+ "cdf",
9
+ "fusion",
10
+ "react",
11
+ "scaffold",
12
+ "deploy"
13
+ ],
6
14
  "license": "Apache-2.0",
7
15
  "author": "Cognite",
8
16
  "repository": {
@@ -41,7 +49,17 @@
41
49
  "bin": {
42
50
  "dune": "./bin/cli.js"
43
51
  },
44
- "files": ["bin/cli.js", "bin/deploy-command.js", "bin/deploy-interactive-command.js", "bin/skills-command.js", "bin/auth", "bin/utils", "dist", "src", "_templates"],
52
+ "files": [
53
+ "bin/cli.js",
54
+ "bin/deploy-command.js",
55
+ "bin/deploy-interactive-command.js",
56
+ "bin/skills-command.js",
57
+ "bin/auth",
58
+ "bin/utils",
59
+ "dist",
60
+ "src",
61
+ "_templates"
62
+ ],
45
63
  "scripts": {
46
64
  "build": "tsup",
47
65
  "prepare": "tsup",
@@ -51,7 +69,9 @@
51
69
  "test:watch": "vitest"
52
70
  },
53
71
  "dependencies": {
72
+ "@cognite/app-sdk": "^0.3.0",
54
73
  "archiver": "^7.0.1",
74
+ "commander": "^14.0.3",
55
75
  "enquirer": "^2.4.1",
56
76
  "execa": "^5.1.1",
57
77
  "hygen": "^6.2.11",
@@ -0,0 +1,126 @@
1
+ // @vitest-environment happy-dom
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import { useContext } from 'react';
4
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ import { AppSdkAuthProvider } from './app-sdk-auth-provider';
7
+ import { DuneAuthProviderContext, type DuneContextValue } from './dune-auth-provider';
8
+
9
+ const mockConnectToHostApp = vi.hoisted(() => vi.fn());
10
+ const MockCogniteClient = vi.hoisted(() => vi.fn());
11
+
12
+ vi.mock(import('@cognite/app-sdk'), () => ({
13
+ connectToHostApp: mockConnectToHostApp,
14
+ }));
15
+
16
+ vi.mock(import('@cognite/sdk'), async (importOriginal) => {
17
+ const actual = await importOriginal();
18
+ return {
19
+ ...actual,
20
+ CogniteClient: MockCogniteClient as Partial<typeof actual.CogniteClient> as typeof actual.CogniteClient,
21
+ };
22
+ });
23
+
24
+ function ContextCapture({ onValue }: { onValue: (v: DuneContextValue) => void }) {
25
+ const value = useContext(DuneAuthProviderContext);
26
+ onValue(value);
27
+ return null;
28
+ }
29
+
30
+ type ApiMock = {
31
+ getProject: () => Promise<string>;
32
+ getBaseUrl: () => Promise<string>;
33
+ getAccessToken: () => Promise<string>;
34
+ };
35
+
36
+ function makeApi(overrides?: Partial<ApiMock>): ApiMock {
37
+ return {
38
+ getProject: vi.fn(async () => 'my-project'),
39
+ getBaseUrl: vi.fn(async () => 'https://cognite.test'),
40
+ getAccessToken: vi.fn(async () => 'token-abc'),
41
+ ...overrides,
42
+ };
43
+ }
44
+
45
+ describe(AppSdkAuthProvider.name, () => {
46
+ const mockSdkInstance = { authenticate: vi.fn(async () => undefined) };
47
+
48
+ beforeEach(() => {
49
+ vi.clearAllMocks();
50
+ vi.mocked(MockCogniteClient).mockReturnValue(mockSdkInstance);
51
+ });
52
+
53
+ it('renders the loadingComponent while the handshake is in progress', () => {
54
+ // Arrange
55
+ vi.mocked(mockConnectToHostApp).mockReturnValue(new Promise(() => {})); // never resolves
56
+
57
+ // Act
58
+ render(
59
+ <AppSdkAuthProvider loadingComponent={<div>Connecting…</div>}>
60
+ <div>content</div>
61
+ </AppSdkAuthProvider>
62
+ );
63
+
64
+ // Assert
65
+ expect(screen.getByText('Connecting…')).toBeDefined();
66
+ expect(screen.queryByText('content')).toBeNull();
67
+ });
68
+
69
+ it('exposes the configured sdk and clears loading on successful connection', async () => {
70
+ // Arrange
71
+ const api = makeApi();
72
+ vi.mocked(mockConnectToHostApp).mockResolvedValue({ api });
73
+ const captured: DuneContextValue[] = [];
74
+
75
+ // Act — loadingComponent=null so children render even during loading
76
+ render(
77
+ <AppSdkAuthProvider loadingComponent={null}>
78
+ <ContextCapture onValue={(v) => captured.push({ ...v })} />
79
+ </AppSdkAuthProvider>
80
+ );
81
+ await waitFor(() => expect(captured[captured.length - 1]?.isLoading).toBe(false));
82
+
83
+ // Assert
84
+ expect(captured[captured.length - 1]).toMatchObject({ isLoading: false, error: undefined, sdk: mockSdkInstance });
85
+ expect(MockCogniteClient).toHaveBeenCalledWith(
86
+ expect.objectContaining({
87
+ appId: 'dune-app',
88
+ project: 'my-project',
89
+ baseUrl: 'https://cognite.test',
90
+ oidcTokenProvider: expect.any(Function),
91
+ })
92
+ );
93
+ expect(mockSdkInstance.authenticate).toHaveBeenCalled();
94
+ });
95
+
96
+ it('renders the errorComponent when connectToHostApp rejects with an Error', async () => {
97
+ // Arrange
98
+ vi.mocked(mockConnectToHostApp).mockRejectedValue(new Error('Handshake timed out'));
99
+
100
+ // Act
101
+ render(
102
+ <AppSdkAuthProvider errorComponent={(msg) => <div>Error: {msg}</div>}>
103
+ <div>content</div>
104
+ </AppSdkAuthProvider>
105
+ );
106
+
107
+ // Assert
108
+ await waitFor(() => expect(screen.getByText('Error: Handshake timed out')).toBeDefined());
109
+ expect(screen.queryByText('content')).toBeNull();
110
+ });
111
+
112
+ it('coerces non-Error rejections to a string in the errorComponent', async () => {
113
+ // Arrange
114
+ vi.mocked(mockConnectToHostApp).mockRejectedValue('network timeout');
115
+
116
+ // Act
117
+ render(
118
+ <AppSdkAuthProvider errorComponent={(msg) => <div>Error: {msg}</div>}>
119
+ <div>content</div>
120
+ </AppSdkAuthProvider>
121
+ );
122
+
123
+ // Assert
124
+ await waitFor(() => expect(screen.getByText('Error: network timeout')).toBeDefined());
125
+ });
126
+ });
@@ -0,0 +1,69 @@
1
+ import { connectToHostApp } from '@cognite/app-sdk';
2
+ import { CogniteClient } from '@cognite/sdk';
3
+ import { useEffect, useState, type ReactNode } from 'react';
4
+
5
+ import { DuneAuthProviderContext } from './dune-auth-provider';
6
+ import { EMPTY_SDK } from './utils';
7
+
8
+ interface AppSdkAuthProviderProps {
9
+ children: ReactNode;
10
+ loadingComponent?: ReactNode;
11
+ errorComponent?: (error: string) => ReactNode;
12
+ }
13
+
14
+ /**
15
+ * Auth provider for apps running in the new Fusion app host (dune-app-host).
16
+ * Uses @cognite/app-sdk's Comlink handshake to connect to the host and obtain
17
+ * tokens on-demand. Exposes the same DuneContextValue ({ sdk, isLoading,
18
+ * error }) as DuneAuthProvider so useDune() works unchanged.
19
+ */
20
+ export const AppSdkAuthProvider = ({
21
+ children,
22
+ loadingComponent = <div>Loading CDF authentication...</div>,
23
+ errorComponent = (error: string) => <div>Authentication error: {error}</div>,
24
+ }: AppSdkAuthProviderProps) => {
25
+ const [sdk, setSdk] = useState<CogniteClient>(EMPTY_SDK);
26
+ const [isLoading, setIsLoading] = useState(true);
27
+ const [error, setError] = useState<string | undefined>();
28
+
29
+ useEffect(() => {
30
+ connectToHostApp()
31
+ .then(async ({ api }) => {
32
+ const [project, baseUrl] = await Promise.all([
33
+ api.getProject(),
34
+ api.getBaseUrl(),
35
+ ]);
36
+
37
+ const client = new CogniteClient({
38
+ appId: 'dune-app',
39
+ project,
40
+ baseUrl,
41
+ oidcTokenProvider: async () => api.getAccessToken(),
42
+ });
43
+ await client.authenticate();
44
+ setSdk(client);
45
+ setError(undefined);
46
+ })
47
+ .catch((err: unknown) => {
48
+ const message = err instanceof Error ? err.message : String(err);
49
+ setError(message);
50
+ })
51
+ .finally(() => {
52
+ setIsLoading(false);
53
+ });
54
+ }, []);
55
+
56
+ if (error && errorComponent) {
57
+ return <>{errorComponent(error)}</>;
58
+ }
59
+
60
+ if (isLoading && loadingComponent) {
61
+ return <>{loadingComponent}</>;
62
+ }
63
+
64
+ return (
65
+ <DuneAuthProviderContext.Provider value={{ sdk, isLoading, error }}>
66
+ {children}
67
+ </DuneAuthProviderContext.Provider>
68
+ );
69
+ };
package/src/auth/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  // Auth exports for React applications
2
+ export { AppSdkAuthProvider } from './app-sdk-auth-provider';
2
3
  export {
3
4
  DuneAuthProvider,
4
5
  DuneAuthProviderContext,