@datalayer/core 0.0.9 → 0.0.11

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.
Files changed (199) hide show
  1. package/lib/__tests__/shared/cleanup-shared.d.ts +4 -0
  2. package/lib/__tests__/shared/cleanup-shared.js +228 -0
  3. package/lib/__tests__/shared/test-config.d.ts +51 -0
  4. package/lib/__tests__/shared/test-config.js +110 -0
  5. package/lib/__tests__/shared/test-constants.d.ts +66 -0
  6. package/lib/__tests__/shared/test-constants.js +79 -0
  7. package/lib/api/DatalayerApi.d.ts +1 -1
  8. package/lib/api/DatalayerApi.js +73 -42
  9. package/lib/api/__tests__/iam.authentication.integration.test.d.ts +1 -0
  10. package/lib/api/__tests__/iam.authentication.integration.test.js +247 -0
  11. package/lib/api/__tests__/iam.healthz.integration.test.d.ts +1 -0
  12. package/lib/api/__tests__/iam.healthz.integration.test.js +63 -0
  13. package/lib/api/__tests__/iam.profile.integration.test.d.ts +1 -0
  14. package/lib/api/__tests__/iam.profile.integration.test.js +252 -0
  15. package/lib/api/__tests__/runtimes.environments.integration.test.d.ts +1 -0
  16. package/lib/api/__tests__/runtimes.environments.integration.test.js +122 -0
  17. package/lib/api/__tests__/runtimes.healthz.integration.test.d.ts +1 -0
  18. package/lib/api/__tests__/runtimes.healthz.integration.test.js +50 -0
  19. package/lib/api/__tests__/runtimes.integration.test.d.ts +1 -0
  20. package/lib/api/__tests__/runtimes.integration.test.js +369 -0
  21. package/lib/api/__tests__/spacer.healthz.integration.test.d.ts +1 -0
  22. package/lib/api/__tests__/spacer.healthz.integration.test.js +50 -0
  23. package/lib/api/__tests__/spacer.integration.test.d.ts +1 -0
  24. package/lib/api/__tests__/spacer.integration.test.js +519 -0
  25. package/lib/api/constants.d.ts +19 -0
  26. package/lib/api/constants.js +23 -0
  27. package/lib/api/iam/__tests__/authentication.unit.test.d.ts +1 -0
  28. package/lib/api/iam/__tests__/authentication.unit.test.js +63 -0
  29. package/lib/api/iam/__tests__/healthz.unit.test.d.ts +1 -0
  30. package/lib/api/iam/__tests__/healthz.unit.test.js +60 -0
  31. package/lib/api/iam/__tests__/profile.unit.test.d.ts +1 -0
  32. package/lib/api/iam/__tests__/profile.unit.test.js +57 -0
  33. package/lib/api/iam/authentication.d.ts +40 -0
  34. package/lib/api/iam/authentication.js +128 -0
  35. package/lib/api/iam/healthz.d.ts +15 -0
  36. package/lib/api/iam/healthz.js +43 -0
  37. package/lib/api/iam/index.d.ts +12 -0
  38. package/lib/api/iam/index.js +17 -0
  39. package/lib/api/iam/profile.d.ts +15 -0
  40. package/lib/api/iam/profile.js +41 -0
  41. package/lib/api/index.d.ts +20 -3
  42. package/lib/api/index.js +22 -3
  43. package/lib/api/runtimes/__tests__/environments.unit.test.d.ts +1 -0
  44. package/lib/api/runtimes/__tests__/environments.unit.test.js +77 -0
  45. package/lib/api/runtimes/__tests__/healthz.unit.test.d.ts +1 -0
  46. package/lib/api/runtimes/__tests__/healthz.unit.test.js +57 -0
  47. package/lib/api/runtimes/__tests__/runtimes.unit.test.d.ts +1 -0
  48. package/lib/api/runtimes/__tests__/runtimes.unit.test.js +139 -0
  49. package/lib/api/runtimes/__tests__/snapshots.unit.test.d.ts +1 -0
  50. package/lib/api/runtimes/__tests__/snapshots.unit.test.js +96 -0
  51. package/lib/api/runtimes/environments.d.ts +9 -0
  52. package/lib/api/runtimes/environments.js +28 -0
  53. package/lib/api/runtimes/healthz.d.ts +25 -0
  54. package/lib/api/runtimes/healthz.js +43 -0
  55. package/lib/api/runtimes/index.d.ts +10 -5
  56. package/lib/api/runtimes/index.js +10 -5
  57. package/lib/api/runtimes/runtimes.d.ts +54 -0
  58. package/lib/api/runtimes/runtimes.js +169 -0
  59. package/lib/api/runtimes/snapshots.d.ts +34 -21
  60. package/lib/api/runtimes/snapshots.js +69 -138
  61. package/lib/api/spacer/__tests__/healthz.unit.test.d.ts +1 -0
  62. package/lib/api/spacer/__tests__/healthz.unit.test.js +57 -0
  63. package/lib/api/spacer/__tests__/items.unit.test.d.ts +1 -0
  64. package/lib/api/spacer/__tests__/items.unit.test.js +165 -0
  65. package/lib/api/spacer/__tests__/lexicals.unit.test.d.ts +1 -0
  66. package/lib/api/spacer/__tests__/lexicals.unit.test.js +323 -0
  67. package/lib/api/spacer/__tests__/notebooks.unit.test.d.ts +1 -0
  68. package/lib/api/spacer/__tests__/notebooks.unit.test.js +224 -0
  69. package/lib/api/spacer/__tests__/users.unit.test.d.ts +1 -0
  70. package/lib/api/spacer/__tests__/users.unit.test.js +132 -0
  71. package/lib/api/spacer/healthz.d.ts +25 -0
  72. package/lib/api/spacer/healthz.js +43 -0
  73. package/lib/api/spacer/index.d.ts +13 -0
  74. package/lib/api/spacer/index.js +17 -0
  75. package/lib/api/spacer/items.d.ts +17 -0
  76. package/lib/api/spacer/items.js +40 -0
  77. package/lib/api/spacer/lexicals.d.ts +26 -0
  78. package/lib/api/spacer/lexicals.js +74 -0
  79. package/lib/api/spacer/notebooks.d.ts +26 -0
  80. package/lib/api/spacer/notebooks.js +74 -0
  81. package/lib/api/spacer/spaces.d.ts +9 -0
  82. package/lib/api/spacer/spaces.js +29 -0
  83. package/lib/api/spacer/users.d.ts +9 -0
  84. package/lib/api/spacer/users.js +28 -0
  85. package/lib/api/types/iam.d.ts +180 -0
  86. package/lib/api/types/index.d.ts +32 -0
  87. package/lib/api/types/index.js +36 -0
  88. package/lib/api/types/runtimes.d.ts +235 -0
  89. package/lib/api/types/runtimes.js +5 -0
  90. package/lib/api/types/spacer.d.ts +271 -0
  91. package/lib/api/types/spacer.js +5 -0
  92. package/lib/api/utils/__tests__/validation.test.d.ts +1 -0
  93. package/lib/api/utils/__tests__/validation.test.js +109 -0
  94. package/lib/api/utils/validation.d.ts +24 -0
  95. package/lib/api/utils/validation.js +133 -0
  96. package/lib/components/display/JupyterDialog.js +4 -8
  97. package/lib/components/progress/CreditsIndicator.d.ts +1 -1
  98. package/lib/components/runtimes/RuntimeCellVariablesDialog.js +2 -2
  99. package/lib/components/runtimes/RuntimeLauncherDialog.d.ts +1 -1
  100. package/lib/components/runtimes/RuntimeLauncherDialog.js +5 -2
  101. package/lib/components/runtimes/RuntimePickerBase.d.ts +1 -1
  102. package/lib/components/runtimes/RuntimePickerBase.js +1 -1
  103. package/lib/components/runtimes/RuntimePickerCell.js +2 -1
  104. package/lib/components/runtimes/RuntimePickerNotebook.d.ts +1 -1
  105. package/lib/components/runtimes/RuntimePickerNotebook.js +1 -1
  106. package/lib/components/runtimes/RuntimeSimplePicker.js +2 -1
  107. package/lib/components/runtimes/RuntimeTransfer.d.ts +1 -1
  108. package/lib/components/runtimes/RuntimeUtils.d.ts +1 -1
  109. package/lib/components/snapshots/RuntimeSnapshotMenu.d.ts +1 -1
  110. package/lib/components/snapshots/RuntimeSnapshotMenu.js +2 -2
  111. package/lib/components/snippets/SnippetDialog.js +1 -1
  112. package/lib/components/storage/ContentsBrowser.js +2 -2
  113. package/lib/components/tables/DataTable.js +2 -1
  114. package/lib/hooks/useDatalayer.d.ts +1 -1
  115. package/lib/hooks/useDatalayer.js +1 -1
  116. package/lib/hooks/useIAM.js +1 -1
  117. package/lib/hooks/useRuntimes.js +1 -1
  118. package/lib/index.d.ts +9 -0
  119. package/lib/index.js +10 -0
  120. package/lib/sdk/client/__tests__/sdk.health.integration.test.d.ts +1 -0
  121. package/lib/sdk/client/__tests__/sdk.health.integration.test.js +110 -0
  122. package/lib/sdk/client/__tests__/sdk.iam.integration.test.d.ts +1 -0
  123. package/lib/sdk/client/__tests__/sdk.iam.integration.test.js +179 -0
  124. package/lib/sdk/client/__tests__/sdk.models.integration.test.d.ts +1 -0
  125. package/lib/sdk/client/__tests__/sdk.models.integration.test.js +376 -0
  126. package/lib/sdk/client/__tests__/sdk.runtimes.integration.test.d.ts +1 -0
  127. package/lib/sdk/client/__tests__/sdk.runtimes.integration.test.js +276 -0
  128. package/lib/sdk/client/__tests__/sdk.spacer.integration.test.d.ts +1 -0
  129. package/lib/sdk/client/__tests__/sdk.spacer.integration.test.js +361 -0
  130. package/lib/sdk/client/base.d.ts +88 -0
  131. package/lib/sdk/client/base.js +112 -0
  132. package/lib/sdk/client/index.d.ts +192 -0
  133. package/lib/sdk/client/index.js +128 -0
  134. package/lib/sdk/client/mixins/HealthMixin.d.ts +100 -0
  135. package/lib/sdk/client/mixins/HealthMixin.js +133 -0
  136. package/lib/sdk/client/mixins/IAMMixin.d.ts +59 -0
  137. package/lib/sdk/client/mixins/IAMMixin.js +83 -0
  138. package/lib/sdk/client/mixins/RuntimesMixin.d.ts +134 -0
  139. package/lib/sdk/client/mixins/RuntimesMixin.js +221 -0
  140. package/lib/sdk/client/mixins/SpacerMixin.d.ts +184 -0
  141. package/lib/sdk/client/mixins/SpacerMixin.js +278 -0
  142. package/lib/sdk/client/models/Lexical.d.ts +156 -0
  143. package/lib/sdk/client/models/Lexical.js +275 -0
  144. package/lib/sdk/client/models/Notebook.d.ts +174 -0
  145. package/lib/sdk/client/models/Notebook.js +311 -0
  146. package/lib/sdk/client/models/Runtime.d.ts +221 -0
  147. package/lib/sdk/client/models/Runtime.js +341 -0
  148. package/lib/sdk/client/models/Snapshot.d.ts +156 -0
  149. package/lib/sdk/client/models/Snapshot.js +244 -0
  150. package/lib/sdk/client/models/Space.d.ts +182 -0
  151. package/lib/sdk/client/models/Space.js +276 -0
  152. package/lib/sdk/client/models/__tests__/Lexical.test.d.ts +1 -0
  153. package/lib/sdk/client/models/__tests__/Lexical.test.js +288 -0
  154. package/lib/sdk/client/models/__tests__/Notebook.test.d.ts +1 -0
  155. package/lib/sdk/client/models/__tests__/Notebook.test.js +206 -0
  156. package/lib/sdk/client/models/__tests__/Runtime.test.d.ts +1 -0
  157. package/lib/sdk/client/models/__tests__/Runtime.test.js +133 -0
  158. package/lib/sdk/client/models/__tests__/Snapshot.test.d.ts +1 -0
  159. package/lib/sdk/client/models/__tests__/Snapshot.test.js +244 -0
  160. package/lib/sdk/client/models/__tests__/Space.test.d.ts +1 -0
  161. package/lib/sdk/client/models/__tests__/Space.test.js +334 -0
  162. package/lib/sdk/client/models/index.d.ts +30 -0
  163. package/lib/sdk/client/models/index.js +30 -0
  164. package/lib/sdk/client/utils/mixins.d.ts +42 -0
  165. package/lib/sdk/client/utils/mixins.js +47 -0
  166. package/lib/sdk/index.d.ts +26 -0
  167. package/lib/sdk/index.js +32 -0
  168. package/lib/sdk/stateful/index.d.ts +3 -0
  169. package/lib/sdk/stateful/index.js +7 -0
  170. package/lib/{api → sdk/stateful}/runtimes/actions.d.ts +1 -1
  171. package/lib/{api → sdk/stateful}/runtimes/actions.js +3 -3
  172. package/lib/{api → sdk/stateful}/runtimes/apis.d.ts +1 -1
  173. package/lib/sdk/stateful/runtimes/apis.js +5 -0
  174. package/lib/sdk/stateful/runtimes/index.d.ts +5 -0
  175. package/lib/sdk/stateful/runtimes/index.js +9 -0
  176. package/lib/sdk/stateful/runtimes/snapshots.d.ts +25 -0
  177. package/lib/sdk/stateful/runtimes/snapshots.js +150 -0
  178. package/lib/services/DatalayerServiceManager.js +1 -1
  179. package/lib/state/substates/IAMState.js +1 -1
  180. package/lib/state/substates/RuntimesState.d.ts +1 -1
  181. package/lib/state/substates/RuntimesState.js +1 -1
  182. package/lib/state/substates/SurveysState.js +1 -1
  183. package/lib/test-setup.js +1 -0
  184. package/package.json +19 -9
  185. /package/lib/api/{runtimes/apis.js → types/iam.js} +0 -0
  186. /package/lib/{api → sdk/stateful}/jupyter/exec/Python.d.ts +0 -0
  187. /package/lib/{api → sdk/stateful}/jupyter/exec/Python.js +0 -0
  188. /package/lib/{api → sdk/stateful}/jupyter/exec/Snippets.d.ts +0 -0
  189. /package/lib/{api → sdk/stateful}/jupyter/exec/Snippets.js +0 -0
  190. /package/lib/{api → sdk/stateful}/jupyter/exec/index.d.ts +0 -0
  191. /package/lib/{api → sdk/stateful}/jupyter/exec/index.js +0 -0
  192. /package/lib/{api → sdk/stateful}/jupyter/index.d.ts +0 -0
  193. /package/lib/{api → sdk/stateful}/jupyter/index.js +0 -0
  194. /package/lib/{api → sdk/stateful}/jupyter/kernelsHandler.d.ts +0 -0
  195. /package/lib/{api → sdk/stateful}/jupyter/kernelsHandler.js +0 -0
  196. /package/lib/{api → sdk/stateful}/runtimes/settings.d.ts +0 -0
  197. /package/lib/{api → sdk/stateful}/runtimes/settings.js +0 -0
  198. /package/lib/{api → sdk/stateful}/runtimes/utils.d.ts +0 -0
  199. /package/lib/{api → sdk/stateful}/runtimes/utils.js +0 -0
@@ -0,0 +1,60 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { ping } from '../healthz';
7
+ import { requestDatalayerAPI } from '../../DatalayerApi';
8
+ // Mock the DatalayerAPI module
9
+ vi.mock('../../DatalayerApi');
10
+ describe('IAM Healthz Unit Tests', () => {
11
+ beforeEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+ describe('ping', () => {
15
+ it('should return health status on successful health check', async () => {
16
+ const mockResponse = {
17
+ success: true,
18
+ message: 'datalayer_iam is up and running.',
19
+ status: { status: 'OK' },
20
+ version: '1.0.7',
21
+ };
22
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(mockResponse);
23
+ const result = await ping();
24
+ expect(result).toEqual(mockResponse);
25
+ expect(requestDatalayerAPI).toHaveBeenCalledWith({
26
+ url: 'https://prod1.datalayer.run/api/iam/v1/ping',
27
+ method: 'GET',
28
+ });
29
+ });
30
+ it('should use custom base URL when provided', async () => {
31
+ const mockResponse = {
32
+ success: true,
33
+ message: 'Service is healthy',
34
+ status: { status: 'OK' },
35
+ version: '2.0.0',
36
+ };
37
+ const customUrl = 'https://custom.example.com';
38
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(mockResponse);
39
+ const result = await ping(customUrl);
40
+ expect(result).toEqual(mockResponse);
41
+ expect(requestDatalayerAPI).toHaveBeenCalledWith({
42
+ url: `${customUrl}/api/iam/v1/ping`,
43
+ method: 'GET',
44
+ });
45
+ });
46
+ it('should throw error with status code on failure', async () => {
47
+ const mockError = {
48
+ response: { status: 500 },
49
+ message: 'Internal Server Error',
50
+ };
51
+ vi.mocked(requestDatalayerAPI).mockRejectedValueOnce(mockError);
52
+ await expect(ping()).rejects.toThrow('Health check failed: Service unhealthy (status 500) - Internal Server Error');
53
+ });
54
+ it('should throw generic error on network failure', async () => {
55
+ const mockError = new Error('Network error');
56
+ vi.mocked(requestDatalayerAPI).mockRejectedValueOnce(mockError);
57
+ await expect(ping()).rejects.toThrow('Health check failed: Network error');
58
+ });
59
+ });
60
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { describe, it, expect } from 'vitest';
6
+ import { profile } from '..';
7
+ describe('IAM Profile Unit Tests', () => {
8
+ describe('parameter validation', () => {
9
+ it('should handle undefined token parameter at runtime', async () => {
10
+ console.log('Testing that undefined token is caught by validation...');
11
+ // In JavaScript/TypeScript at runtime, calling a function without
12
+ // required parameters just passes undefined for those parameters.
13
+ // Our validation code inside the function should catch this.
14
+ // Test me() with undefined token
15
+ await expect(profile.me(undefined)).rejects.toThrow('Authentication token is required');
16
+ // Test whoami() with undefined token
17
+ await expect(profile.whoami(undefined)).rejects.toThrow('Authentication token is required');
18
+ console.log('Validation correctly rejects undefined token');
19
+ });
20
+ it('should handle null token parameter at runtime', async () => {
21
+ console.log('Testing that null token is caught by validation...');
22
+ // Test me() with null token
23
+ await expect(profile.me(null)).rejects.toThrow('Authentication token is required');
24
+ // Test whoami() with null token
25
+ await expect(profile.whoami(null)).rejects.toThrow('Authentication token is required');
26
+ console.log('Validation correctly rejects null token');
27
+ });
28
+ it('should handle empty string token', async () => {
29
+ console.log('Testing that empty string token is caught by validation...');
30
+ // Test me() with empty string
31
+ await expect(profile.me('')).rejects.toThrow('Authentication token is required');
32
+ // Test whoami() with empty string
33
+ await expect(profile.whoami('')).rejects.toThrow('Authentication token is required');
34
+ console.log('Validation correctly rejects empty string token');
35
+ });
36
+ it('should handle whitespace-only token', async () => {
37
+ console.log('Testing that whitespace-only token is caught by validation...');
38
+ // Test me() with whitespace
39
+ await expect(profile.me(' ')).rejects.toThrow('Authentication token is required');
40
+ // Test whoami() with whitespace
41
+ await expect(profile.whoami(' ')).rejects.toThrow('Authentication token is required');
42
+ console.log('Validation correctly rejects whitespace-only token');
43
+ });
44
+ it('demonstrates JavaScript runtime behavior with missing parameters', () => {
45
+ console.log('Demonstrating JS runtime behavior...');
46
+ // This demonstrates that JavaScript doesn't throw errors for missing parameters
47
+ // The function receives undefined for missing parameters
48
+ function testFunction(required) {
49
+ return required === undefined;
50
+ }
51
+ // @ts-expect-error TypeScript knows this is wrong, but JS allows it
52
+ const result = testFunction();
53
+ expect(result).toBe(true); // The parameter is undefined
54
+ console.log('JavaScript passes undefined for missing parameters');
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,40 @@
1
+ import { LoginRequest, LoginResponse } from '../types/iam';
2
+ /**
3
+ * Authenticate a user with credentials or token
4
+ * @param data - Login credentials (either handle+password or token)
5
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
6
+ * @returns Login response with tokens
7
+ * @throws {Error} If both credential and token authentication are provided
8
+ * @throws {Error} If neither authentication method is provided
9
+ * @throws {Error} If handle is provided without password or vice versa
10
+ * @throws {Error} If server returns unexpected status code (expects 201 for success, 401 for failure)
11
+ *
12
+ * @description
13
+ * Expected status codes:
14
+ * - 201: Login succeeded
15
+ * - 401: Login failed (invalid credentials)
16
+ */
17
+ export declare const login: (data: LoginRequest, baseUrl?: string) => Promise<LoginResponse>;
18
+ /**
19
+ * Log out the current user
20
+ * @param token - Authentication token
21
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
22
+ * @returns Void response
23
+ */
24
+ export declare const logout: (token: string, baseUrl?: string) => Promise<void>;
25
+ /**
26
+ * Validate authentication through proxy
27
+ * @param token - Authentication token (required)
28
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
29
+ * @returns Promise that resolves if authenticated (200), rejects if unauthorized (401) or forbidden (403)
30
+ * @throws {Error} If authentication token is missing or invalid
31
+ *
32
+ * @description
33
+ * This endpoint validates authentication status through a proxy.
34
+ *
35
+ * Expected status codes:
36
+ * - 200: Authenticated successfully
37
+ * - 401: Unauthorized - invalid or missing credentials
38
+ * - 403: Forbidden - valid credentials but access denied
39
+ */
40
+ export declare const proxyAuth: (token: string, baseUrl?: string) => Promise<void>;
@@ -0,0 +1,128 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * @module api/iam/authentication
7
+ * @description Authentication API functions for the Datalayer platform.
8
+ *
9
+ * Provides functions for user login, logout, and authentication management.
10
+ */
11
+ import { requestDatalayerAPI } from '../DatalayerApi';
12
+ import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants';
13
+ import { validateToken } from '../utils/validation';
14
+ /**
15
+ * Authenticate a user with credentials or token
16
+ * @param data - Login credentials (either handle+password or token)
17
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
18
+ * @returns Login response with tokens
19
+ * @throws {Error} If both credential and token authentication are provided
20
+ * @throws {Error} If neither authentication method is provided
21
+ * @throws {Error} If handle is provided without password or vice versa
22
+ * @throws {Error} If server returns unexpected status code (expects 201 for success, 401 for failure)
23
+ *
24
+ * @description
25
+ * Expected status codes:
26
+ * - 201: Login succeeded
27
+ * - 401: Login failed (invalid credentials)
28
+ */
29
+ export const login = async (data, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
30
+ // Validate that we have either handle+password OR token, but not both
31
+ const hasCredentials = Boolean(data.handle || data.password);
32
+ const hasToken = Boolean(data.token);
33
+ if (hasCredentials && hasToken) {
34
+ throw new Error('Cannot provide both credentials (handle/password) and token. Use either handle+password or token.');
35
+ }
36
+ if (!hasCredentials && !hasToken) {
37
+ throw new Error('Must provide either credentials (handle+password) or token for authentication.');
38
+ }
39
+ // If using credentials, ensure both handle and password are provided
40
+ if (hasCredentials) {
41
+ if (!data.handle || !data.password) {
42
+ throw new Error('Both handle and password are required for credential-based authentication.');
43
+ }
44
+ }
45
+ try {
46
+ const response = await requestDatalayerAPI({
47
+ url: `${baseUrl}${API_BASE_PATHS.IAM}/login`,
48
+ method: 'POST',
49
+ body: data,
50
+ });
51
+ // Note: requestDatalayerAPI already handles successful responses (2xx status codes)
52
+ // If we get here, the login was successful
53
+ return response;
54
+ }
55
+ catch (error) {
56
+ // Check if it's a response error with status code information
57
+ if (error.response) {
58
+ const status = error.response.status;
59
+ // Expected error: 401 for invalid credentials
60
+ if (status === 401) {
61
+ throw new Error(`Login failed: Invalid credentials (${status})`);
62
+ }
63
+ // Unexpected status codes
64
+ throw new Error(`Login failed: Unexpected status code ${status} - ${error.message}`);
65
+ }
66
+ // Re-throw other errors (network errors, etc.)
67
+ throw error;
68
+ }
69
+ };
70
+ /**
71
+ * Log out the current user
72
+ * @param token - Authentication token
73
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
74
+ * @returns Void response
75
+ */
76
+ export const logout = async (token, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
77
+ validateToken(token);
78
+ return requestDatalayerAPI({
79
+ url: `${baseUrl}${API_BASE_PATHS.IAM}/logout`,
80
+ method: 'GET',
81
+ token,
82
+ });
83
+ };
84
+ /**
85
+ * Validate authentication through proxy
86
+ * @param token - Authentication token (required)
87
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
88
+ * @returns Promise that resolves if authenticated (200), rejects if unauthorized (401) or forbidden (403)
89
+ * @throws {Error} If authentication token is missing or invalid
90
+ *
91
+ * @description
92
+ * This endpoint validates authentication status through a proxy.
93
+ *
94
+ * Expected status codes:
95
+ * - 200: Authenticated successfully
96
+ * - 401: Unauthorized - invalid or missing credentials
97
+ * - 403: Forbidden - valid credentials but access denied
98
+ */
99
+ export const proxyAuth = async (token, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
100
+ validateToken(token);
101
+ try {
102
+ const response = await requestDatalayerAPI({
103
+ url: `${baseUrl}${API_BASE_PATHS.IAM}/proxy-auth`,
104
+ method: 'GET',
105
+ token,
106
+ });
107
+ // Note: requestDatalayerAPI already handles successful responses (2xx status codes)
108
+ // If we get here, authentication was successful (200)
109
+ return response;
110
+ }
111
+ catch (error) {
112
+ // Check if it's a response error with status code information
113
+ if (error.response) {
114
+ const status = error.response.status;
115
+ // Expected errors
116
+ if (status === 401) {
117
+ throw new Error(`Proxy authentication failed: Unauthorized (${status})`);
118
+ }
119
+ if (status === 403) {
120
+ throw new Error(`Proxy authentication failed: Forbidden (${status})`);
121
+ }
122
+ // Unexpected status codes
123
+ throw new Error(`Proxy authentication failed: Unexpected status code ${status} - ${error.message}`);
124
+ }
125
+ // Re-throw other errors (network errors, etc.)
126
+ throw error;
127
+ }
128
+ };
@@ -0,0 +1,15 @@
1
+ import { HealthzPingResponse } from '../types/iam';
2
+ /**
3
+ * Health check ping endpoint
4
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
5
+ * @returns Health check response with user count
6
+ * @throws {Error} If the health check fails
7
+ *
8
+ * @description
9
+ * This endpoint provides a basic health check for the IAM service.
10
+ * It returns the current user count in the system.
11
+ *
12
+ * Expected status codes:
13
+ * - 200: Service is healthy
14
+ */
15
+ export declare const ping: (baseUrl?: string) => Promise<HealthzPingResponse>;
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * @module api/iam/healthz
7
+ * @description Health check API functions for the Datalayer IAM service.
8
+ *
9
+ * Provides functions for checking the health status of the IAM service.
10
+ */
11
+ import { requestDatalayerAPI } from '../DatalayerApi';
12
+ import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants';
13
+ /**
14
+ * Health check ping endpoint
15
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
16
+ * @returns Health check response with user count
17
+ * @throws {Error} If the health check fails
18
+ *
19
+ * @description
20
+ * This endpoint provides a basic health check for the IAM service.
21
+ * It returns the current user count in the system.
22
+ *
23
+ * Expected status codes:
24
+ * - 200: Service is healthy
25
+ */
26
+ export const ping = async (baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
27
+ try {
28
+ const response = await requestDatalayerAPI({
29
+ url: `${baseUrl}${API_BASE_PATHS.IAM}/ping`,
30
+ method: 'GET',
31
+ });
32
+ return response;
33
+ }
34
+ catch (error) {
35
+ // Check if it's a response error with status code information
36
+ if (error.response) {
37
+ const status = error.response.status;
38
+ throw new Error(`Health check failed: Service unhealthy (status ${status}) - ${error.message}`);
39
+ }
40
+ // Re-throw other errors (network errors, etc.)
41
+ throw new Error(`Health check failed: ${error.message}`);
42
+ }
43
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module api/iam
3
+ * @description IAM (Identity and Access Management) API exports.
4
+ *
5
+ * Provides organized access to authentication and user profile functionality.
6
+ */
7
+ export * as authentication from './authentication';
8
+ export * as profile from './profile';
9
+ export * as healthz from './healthz';
10
+ export { login, logout, proxyAuth } from './authentication';
11
+ export { me, whoami } from './profile';
12
+ export { ping } from './healthz';
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * @module api/iam
7
+ * @description IAM (Identity and Access Management) API exports.
8
+ *
9
+ * Provides organized access to authentication and user profile functionality.
10
+ */
11
+ export * as authentication from './authentication';
12
+ export * as profile from './profile';
13
+ export * as healthz from './healthz';
14
+ // For backward compatibility, export the old API structure
15
+ export { login, logout, proxyAuth } from './authentication';
16
+ export { me, whoami } from './profile';
17
+ export { ping } from './healthz';
@@ -0,0 +1,15 @@
1
+ import { UserMeResponse, WhoAmIResponse } from '../types/iam';
2
+ /**
3
+ * Get current authenticated user profile
4
+ * @param token - Authentication token (required)
5
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
6
+ * @returns Current user profile information
7
+ */
8
+ export declare const me: (token: string, baseUrl?: string) => Promise<UserMeResponse>;
9
+ /**
10
+ * Get current user identity information
11
+ * @param token - Authentication token (required)
12
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
13
+ * @returns Current user identity and profile information
14
+ */
15
+ export declare const whoami: (token: string, baseUrl?: string) => Promise<WhoAmIResponse>;
@@ -0,0 +1,41 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * @module api/iam/profile
7
+ * @description User profile management API functions for the Datalayer platform.
8
+ *
9
+ * Provides functions for retrieving and managing user profile information.
10
+ */
11
+ import { requestDatalayerAPI } from '../DatalayerApi';
12
+ import { API_BASE_PATHS, DEFAULT_SERVICE_URLS } from '../constants';
13
+ import { validateToken } from '../utils/validation';
14
+ /**
15
+ * Get current authenticated user profile
16
+ * @param token - Authentication token (required)
17
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
18
+ * @returns Current user profile information
19
+ */
20
+ export const me = async (token, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
21
+ validateToken(token);
22
+ return requestDatalayerAPI({
23
+ url: `${baseUrl}${API_BASE_PATHS.IAM}/me`,
24
+ method: 'GET',
25
+ token,
26
+ });
27
+ };
28
+ /**
29
+ * Get current user identity information
30
+ * @param token - Authentication token (required)
31
+ * @param baseUrl - Base URL for the API (defaults to production IAM URL)
32
+ * @returns Current user identity and profile information
33
+ */
34
+ export const whoami = async (token, baseUrl = DEFAULT_SERVICE_URLS.IAM) => {
35
+ validateToken(token);
36
+ return requestDatalayerAPI({
37
+ url: `${baseUrl}${API_BASE_PATHS.IAM}/whoami`,
38
+ method: 'GET',
39
+ token,
40
+ });
41
+ };
@@ -1,3 +1,20 @@
1
- export * from './jupyter';
2
- export * from './runtimes';
3
- export * from './DatalayerApi';
1
+ /**
2
+ * @module @datalayer/core/api
3
+ * @description Minimal API layer for the Datalayer platform providing base HTTP client and functional API methods.
4
+ *
5
+ * This module contains the low-level API functionality. For high-level object-oriented
6
+ * SDK classes, use @datalayer/core/sdk instead.
7
+ */
8
+ export { requestDatalayerAPI, RunResponseError, NetworkError, } from './DatalayerApi';
9
+ export type { IRequestDatalayerAPIOptions } from './DatalayerApi';
10
+ export * from './types';
11
+ export * as iam from './iam';
12
+ export * as runtimes from './runtimes';
13
+ export * as spacer from './spacer';
14
+ /**
15
+ * @deprecated Please import directly from '@datalayer/core/sdk/stateful' instead.
16
+ * This module provides backward compatibility for the moved apiv1 modules.
17
+ */
18
+ export * from '../sdk/stateful/jupyter';
19
+ export * from '../sdk/stateful/runtimes';
20
+ export * from '../api/DatalayerApi';
package/lib/api/index.js CHANGED
@@ -2,6 +2,25 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- export * from './jupyter';
6
- export * from './runtimes';
7
- export * from './DatalayerApi';
5
+ /**
6
+ * @module @datalayer/core/api
7
+ * @description Minimal API layer for the Datalayer platform providing base HTTP client and functional API methods.
8
+ *
9
+ * This module contains the low-level API functionality. For high-level object-oriented
10
+ * SDK classes, use @datalayer/core/sdk instead.
11
+ */
12
+ // Base client exports
13
+ export { requestDatalayerAPI, RunResponseError, NetworkError, } from './DatalayerApi';
14
+ // Type exports
15
+ export * from './types';
16
+ // Domain-organized API exports
17
+ export * as iam from './iam';
18
+ export * as runtimes from './runtimes';
19
+ export * as spacer from './spacer';
20
+ /**
21
+ * @deprecated Please import directly from '@datalayer/core/sdk/stateful' instead.
22
+ * This module provides backward compatibility for the moved apiv1 modules.
23
+ */
24
+ export * from '../sdk/stateful/jupyter';
25
+ export * from '../sdk/stateful/runtimes';
26
+ export * from '../api/DatalayerApi';
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { environments } from '..';
7
+ import { requestDatalayerAPI } from '../../DatalayerApi';
8
+ import { MOCK_JWT_TOKEN, MOCK_ENVIRONMENTS_RESPONSE, } from '../../../__tests__/shared/test-constants';
9
+ // Mock the DatalayerAPI module
10
+ vi.mock('../../DatalayerApi');
11
+ describe('Runtimes Environments Unit Tests', () => {
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+ describe('listEnvironments', () => {
16
+ it('should list environments with valid token', async () => {
17
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(MOCK_ENVIRONMENTS_RESPONSE);
18
+ const result = await environments.listEnvironments(MOCK_JWT_TOKEN);
19
+ expect(result).toEqual(MOCK_ENVIRONMENTS_RESPONSE);
20
+ expect(requestDatalayerAPI).toHaveBeenCalledWith({
21
+ url: 'https://prod1.datalayer.run/api/runtimes/v1/environments',
22
+ method: 'GET',
23
+ token: MOCK_JWT_TOKEN,
24
+ });
25
+ });
26
+ it('should use custom base URL when provided', async () => {
27
+ const mockResponse = {
28
+ success: true,
29
+ message: 'Environments retrieved',
30
+ environments: [],
31
+ };
32
+ const customUrl = 'https://staging.datalayer.run';
33
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(mockResponse);
34
+ const result = await environments.listEnvironments(MOCK_JWT_TOKEN, customUrl);
35
+ expect(result).toEqual(mockResponse);
36
+ expect(requestDatalayerAPI).toHaveBeenCalledWith({
37
+ url: `${customUrl}/api/runtimes/v1/environments`,
38
+ method: 'GET',
39
+ token: MOCK_JWT_TOKEN,
40
+ });
41
+ });
42
+ it('should throw error when token is missing', async () => {
43
+ await expect(environments.listEnvironments('')).rejects.toThrow('Authentication token is required');
44
+ expect(requestDatalayerAPI).not.toHaveBeenCalled();
45
+ });
46
+ it('should throw error when token is null', async () => {
47
+ // @ts-expect-error Testing null token
48
+ await expect(environments.listEnvironments(null)).rejects.toThrow('Authentication token is required');
49
+ expect(requestDatalayerAPI).not.toHaveBeenCalled();
50
+ });
51
+ it('should throw error when token is undefined', async () => {
52
+ // @ts-expect-error Testing undefined token
53
+ await expect(environments.listEnvironments(undefined)).rejects.toThrow('Authentication token is required');
54
+ expect(requestDatalayerAPI).not.toHaveBeenCalled();
55
+ });
56
+ it('should throw error when token is only whitespace', async () => {
57
+ await expect(environments.listEnvironments(' ')).rejects.toThrow('Authentication token is required');
58
+ expect(requestDatalayerAPI).not.toHaveBeenCalled();
59
+ });
60
+ it('should handle API errors properly', async () => {
61
+ const mockError = new Error('Network error');
62
+ vi.mocked(requestDatalayerAPI).mockRejectedValueOnce(mockError);
63
+ await expect(environments.listEnvironments(MOCK_JWT_TOKEN)).rejects.toThrow('Network error');
64
+ });
65
+ it('should handle empty environments list', async () => {
66
+ const mockResponse = {
67
+ success: true,
68
+ message: 'No environments available',
69
+ environments: [],
70
+ };
71
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(mockResponse);
72
+ const result = await environments.listEnvironments(MOCK_JWT_TOKEN);
73
+ expect(result).toEqual(mockResponse);
74
+ expect(result.environments).toHaveLength(0);
75
+ });
76
+ });
77
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { healthz } from '..';
7
+ import { requestDatalayerAPI } from '../../DatalayerApi';
8
+ // Mock the DatalayerAPI module
9
+ vi.mock('../../DatalayerApi');
10
+ describe('Runtimes Healthz Unit Tests', () => {
11
+ beforeEach(() => {
12
+ vi.clearAllMocks();
13
+ });
14
+ describe('ping', () => {
15
+ it('should successfully ping the service', async () => {
16
+ const mockResponse = {
17
+ success: true,
18
+ message: 'Runtimes service is healthy',
19
+ status: {
20
+ status: 'OK',
21
+ },
22
+ version: '1.0.0',
23
+ };
24
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(mockResponse);
25
+ const result = await healthz.ping('https://test.datalayer.run');
26
+ expect(result).toEqual(mockResponse);
27
+ expect(requestDatalayerAPI).toHaveBeenCalledWith({
28
+ url: 'https://test.datalayer.run/api/runtimes/v1/ping',
29
+ method: 'GET',
30
+ });
31
+ });
32
+ it('should use default URL when not provided', async () => {
33
+ const mockResponse = {
34
+ success: true,
35
+ message: 'Service is healthy',
36
+ };
37
+ vi.mocked(requestDatalayerAPI).mockResolvedValueOnce(mockResponse);
38
+ const result = await healthz.ping();
39
+ expect(result).toEqual(mockResponse);
40
+ expect(requestDatalayerAPI).toHaveBeenCalledWith({
41
+ url: 'https://prod1.datalayer.run/api/runtimes/v1/ping',
42
+ method: 'GET',
43
+ });
44
+ });
45
+ it('should handle service unhealthy errors', async () => {
46
+ const mockError = new Error('Service error');
47
+ mockError.response = { status: 500 };
48
+ vi.mocked(requestDatalayerAPI).mockRejectedValueOnce(mockError);
49
+ await expect(healthz.ping()).rejects.toThrow('Health check failed: Service unhealthy (status 500)');
50
+ });
51
+ it('should handle network errors', async () => {
52
+ const mockError = new Error('Network error');
53
+ vi.mocked(requestDatalayerAPI).mockRejectedValueOnce(mockError);
54
+ await expect(healthz.ping()).rejects.toThrow('Health check failed: Network error');
55
+ });
56
+ });
57
+ });
@@ -0,0 +1 @@
1
+ export {};