astro-tokenkit 1.0.6 → 1.0.7

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/README.md CHANGED
@@ -19,51 +19,47 @@ pnpm add astro-tokenkit
19
19
 
20
20
  ## Quick Start
21
21
 
22
- ### 1. Create your API Client
22
+ ### 1. Add the Integration
23
23
 
24
- ```typescript
25
- // src/lib/api.ts
26
- import { createClient } from 'astro-tokenkit';
27
-
28
- export const api = createClient({
29
- baseURL: 'https://api.yourserver.com',
30
- auth: {
31
- login: '/auth/login',
32
- refresh: '/auth/refresh',
33
- logout: '/auth/logout',
34
- }
35
- });
36
- ```
37
-
38
- ### 2. Add the Integration
24
+ Configure TokenKit in your `astro.config.mjs`. This sets the global configuration for the entire app.
39
25
 
40
26
  ```javascript
41
27
  // astro.config.mjs
42
28
  import { defineConfig } from 'astro/config';
43
29
  import { tokenKit } from 'astro-tokenkit';
44
- import { api } from './src/lib/api';
45
30
 
46
31
  export default defineConfig({
47
- integrations: [tokenKit(api)],
32
+ integrations: [
33
+ tokenKit({
34
+ baseURL: 'https://api.yourserver.com',
35
+ auth: {
36
+ login: '/auth/login',
37
+ refresh: '/auth/refresh',
38
+ }
39
+ })
40
+ ],
48
41
  });
49
42
  ```
50
43
 
51
- ### 3. Setup Middleware
44
+ ### 2. Setup Middleware
45
+
46
+ Create `src/middleware.ts` to automatically handle context binding and token rotation. You can use the exported `api` singleton's middleware:
52
47
 
53
48
  ```typescript
54
49
  // src/middleware.ts
55
- import { defineMiddleware } from 'astro-tokenkit';
56
- import { api } from './lib/api';
50
+ import { api } from 'astro-tokenkit';
57
51
 
58
- export const onRequest = defineMiddleware(api);
52
+ export const onRequest = api.middleware();
59
53
  ```
60
54
 
61
- ### 4. Use in Pages
55
+ ### 3. Use in Pages
56
+
57
+ Now you can use the `api` client anywhere in your Astro pages or components without worrying about passing context.
62
58
 
63
59
  ```astro
64
60
  ---
65
61
  // src/pages/profile.astro
66
- import { api } from '../lib/api';
62
+ import { api } from 'astro-tokenkit';
67
63
 
68
64
  // No need to pass context, it's handled by middleware!
69
65
  const user = await api.get('/me');
@@ -72,6 +68,40 @@ const user = await api.get('/me');
72
68
  <h1>Welcome, {user.name}</h1>
73
69
  ```
74
70
 
71
+ ### Global Configuration
72
+
73
+ TokenKit supports a global configuration via the `tokenKit` integration or `setConfig`. All `ClientConfig` properties can be set globally.
74
+
75
+ ```typescript
76
+ import { setConfig } from 'astro-tokenkit';
77
+
78
+ setConfig({
79
+ baseURL: 'https://api.example.com',
80
+ auth: {
81
+ login: '/auth/login',
82
+ refresh: '/auth/refresh',
83
+ }
84
+ });
85
+ ```
86
+
87
+ ### API Singleton
88
+
89
+ The library exports a global `api` instance that is automatically synchronized with your configuration.
90
+
91
+ - **Dynamic Sync**: If you update the configuration via `setConfig()`, the `api` instance immediately reflects these changes.
92
+ - **Shared Manager**: The `api` instance uses a global `TokenManager` which ensures that token refreshes are synchronized across all requests (preventing race conditions).
93
+ - **Middleware Integration**: Use `api.middleware()` for a seamless setup in Astro.
94
+
95
+ If you need a specialized client with a different configuration, you can still create one:
96
+
97
+ ```typescript
98
+ import { createClient } from 'astro-tokenkit';
99
+
100
+ const specializedClient = createClient({
101
+ baseURL: 'https://another-api.com'
102
+ });
103
+ ```
104
+
75
105
  ## Configuration
76
106
 
77
107
  ### Client Configuration
@@ -86,6 +116,7 @@ const user = await api.get('/me');
86
116
  | `interceptors`| `InterceptorsConfig` | Request/Response/Error interceptors. |
87
117
  | `context` | `AsyncLocalStorage` | External AsyncLocalStorage instance. |
88
118
  | `getContextStore`| `() => TokenKitContext`| Custom method to retrieve the context store. |
119
+ | `runWithContext`| `Function`| Custom runner to bind context. |
89
120
 
90
121
  ### Auth Configuration
91
122
 
@@ -95,6 +126,10 @@ const user = await api.get('/me');
95
126
  | `refresh` | `string` | Endpoint path for token refresh (POST). |
96
127
  | `logout` | `string` | Endpoint path for logout (POST). |
97
128
  | `fields` | `FieldMapping` | Custom mapping for token fields in API responses. |
129
+ | `parseLogin` | `Function` | Custom parser for login response: `(body: any) => TokenBundle`. |
130
+ | `parseRefresh`| `Function` | Custom parser for refresh response: `(body: any) => TokenBundle`. |
131
+ | `onLogin` | `Function` | Callback after login: `(bundle, body, ctx) => void`. |
132
+ | `injectToken` | `Function` | Custom token injection: `(token: string) => string` (default: Bearer). |
98
133
  | `cookies` | `CookieConfig` | Configuration for auth cookies. |
99
134
  | `policy` | `RefreshPolicy` | Strategy for when to trigger token refresh. |
100
135
 
@@ -1,14 +1,27 @@
1
- import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext } from '../types';
1
+ import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext, TokenKitConfig } from '../types';
2
2
  import { TokenManager } from '../auth/manager';
3
- import { type ContextOptions } from './context';
4
3
  /**
5
4
  * API Client
6
5
  */
7
6
  export declare class APIClient {
8
- tokenManager?: TokenManager;
9
- private config;
10
- contextOptions: ContextOptions;
11
- constructor(config: ClientConfig);
7
+ private customConfig?;
8
+ private _localTokenManager?;
9
+ private _lastUsedAuth?;
10
+ private _lastUsedBaseURL?;
11
+ constructor(config?: Partial<TokenKitConfig>);
12
+ /**
13
+ * Get current configuration (merged with global)
14
+ */
15
+ get config(): ClientConfig;
16
+ /**
17
+ * Get token manager
18
+ */
19
+ get tokenManager(): TokenManager | undefined;
20
+ /**
21
+ * Get middleware for context binding and automatic token rotation.
22
+ * This middleware uses the global configuration.
23
+ */
24
+ middleware(): import("astro").MiddlewareHandler;
12
25
  /**
13
26
  * GET request
14
27
  */
@@ -67,6 +80,15 @@ export declare class APIClient {
67
80
  getSession(ctx?: TokenKitContext): Session | null;
68
81
  }
69
82
  /**
70
- * Create API client
83
+ * Global API client instance.
84
+ *
85
+ * This client is automatically synchronized with the global configuration
86
+ * set via the Astro integration or setConfig().
87
+ */
88
+ export declare const api: APIClient;
89
+ /**
90
+ * Create API client.
91
+ *
92
+ * If no configuration is provided, it returns the global `api` singleton.
71
93
  */
72
- export declare function createClient(config: ClientConfig): APIClient;
94
+ export declare function createClient(config?: Partial<TokenKitConfig>): APIClient;
@@ -10,22 +10,60 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  import { APIError, AuthError, NetworkError, TimeoutError } from '../types';
12
12
  import { TokenManager } from '../auth/manager';
13
- import { getContext } from './context';
13
+ import { getContextStore } from './context';
14
14
  import { calculateDelay, shouldRetry, sleep } from '../utils/retry';
15
+ import { getConfig, getTokenManager } from '../config';
16
+ import { createMiddleware } from '../middleware';
15
17
  /**
16
18
  * API Client
17
19
  */
18
20
  export class APIClient {
19
21
  constructor(config) {
20
- this.config = config;
21
- this.contextOptions = {
22
- context: config.context,
23
- getContextStore: config.getContextStore,
24
- };
25
- // Initialize token manager if auth is configured
26
- if (config.auth) {
27
- this.tokenManager = new TokenManager(config.auth, config.baseURL);
22
+ this.customConfig = config;
23
+ }
24
+ /**
25
+ * Get current configuration (merged with global)
26
+ */
27
+ get config() {
28
+ // Merge global config with custom config
29
+ const globalConfig = getConfig();
30
+ // If no custom config, return global config directly
31
+ if (!this.customConfig)
32
+ return globalConfig;
33
+ // Merge custom config on top of global config
34
+ return Object.assign(Object.assign({}, globalConfig), this.customConfig);
35
+ }
36
+ /**
37
+ * Get token manager
38
+ */
39
+ get tokenManager() {
40
+ const config = this.config;
41
+ if (!config.auth)
42
+ return undefined;
43
+ const globalConfig = getConfig();
44
+ const globalManager = getTokenManager();
45
+ // Reuse global manager if it matches our configuration
46
+ if (globalManager &&
47
+ config.auth === globalConfig.auth &&
48
+ config.baseURL === globalConfig.baseURL) {
49
+ return globalManager;
28
50
  }
51
+ // Otherwise create/reuse a local manager for this client
52
+ if (!this._localTokenManager ||
53
+ this._lastUsedAuth !== config.auth ||
54
+ this._lastUsedBaseURL !== config.baseURL) {
55
+ this._localTokenManager = new TokenManager(config.auth, config.baseURL);
56
+ this._lastUsedAuth = config.auth;
57
+ this._lastUsedBaseURL = config.baseURL;
58
+ }
59
+ return this._localTokenManager;
60
+ }
61
+ /**
62
+ * Get middleware for context binding and automatic token rotation.
63
+ * This middleware uses the global configuration.
64
+ */
65
+ middleware() {
66
+ return createMiddleware();
29
67
  }
30
68
  /**
31
69
  * GET request
@@ -75,7 +113,7 @@ export class APIClient {
75
113
  */
76
114
  request(config) {
77
115
  return __awaiter(this, void 0, void 0, function* () {
78
- const ctx = getContext(config.ctx, this.contextOptions);
116
+ const ctx = getContextStore(config.ctx);
79
117
  let attempt = 0;
80
118
  let lastError;
81
119
  while (true) {
@@ -250,7 +288,7 @@ export class APIClient {
250
288
  if (!this.tokenManager) {
251
289
  throw new Error('Auth is not configured for this client');
252
290
  }
253
- const context = getContext(ctx, this.contextOptions);
291
+ const context = getContextStore(ctx);
254
292
  yield this.tokenManager.login(context, credentials);
255
293
  });
256
294
  }
@@ -262,7 +300,7 @@ export class APIClient {
262
300
  if (!this.tokenManager) {
263
301
  throw new Error('Auth is not configured for this client');
264
302
  }
265
- const context = getContext(ctx, this.contextOptions);
303
+ const context = getContextStore(ctx);
266
304
  yield this.tokenManager.logout(context);
267
305
  });
268
306
  }
@@ -272,7 +310,7 @@ export class APIClient {
272
310
  isAuthenticated(ctx) {
273
311
  if (!this.tokenManager)
274
312
  return false;
275
- const context = getContext(ctx, this.contextOptions);
313
+ const context = getContextStore(ctx);
276
314
  return this.tokenManager.isAuthenticated(context);
277
315
  }
278
316
  /**
@@ -281,13 +319,24 @@ export class APIClient {
281
319
  getSession(ctx) {
282
320
  if (!this.tokenManager)
283
321
  return null;
284
- const context = getContext(ctx, this.contextOptions);
322
+ const context = getContextStore(ctx);
285
323
  return this.tokenManager.getSession(context);
286
324
  }
287
325
  }
288
326
  /**
289
- * Create API client
327
+ * Global API client instance.
328
+ *
329
+ * This client is automatically synchronized with the global configuration
330
+ * set via the Astro integration or setConfig().
331
+ */
332
+ export const api = new APIClient();
333
+ /**
334
+ * Create API client.
335
+ *
336
+ * If no configuration is provided, it returns the global `api` singleton.
290
337
  */
291
338
  export function createClient(config) {
339
+ if (!config)
340
+ return api;
292
341
  return new APIClient(config);
293
342
  }
@@ -10,8 +10,8 @@ export declare function setSharedContextStorage(storage: AsyncLocalStorage<any>,
10
10
  /**
11
11
  * Get context from shared storage
12
12
  */
13
- export declare function getContext(explicitCtx?: TokenKitContext): TokenKitContext;
13
+ export declare function getContextStore(explicitCtx?: TokenKitContext): TokenKitContext;
14
14
  /**
15
15
  * Bind context (only needed if not using shared storage)
16
16
  */
17
- export declare function bindContext<T>(ctx: TokenKitContext, fn: () => T): T;
17
+ export declare function runWithContext<T>(ctx: TokenKitContext, fn: () => T): T;
@@ -1,4 +1,5 @@
1
1
  // packages/astro-tokenkit/src/client/context-shared.ts
2
+ import { getConfig } from '../config';
2
3
  /**
3
4
  * OPTION: Share AsyncLocalStorage with other libraries
4
5
  *
@@ -32,10 +33,17 @@ export function setSharedContextStorage(storage, key = 'astro') {
32
33
  /**
33
34
  * Get context from shared storage
34
35
  */
35
- export function getContext(explicitCtx) {
36
+ export function getContextStore(explicitCtx) {
36
37
  if (explicitCtx) {
37
38
  return explicitCtx;
38
39
  }
40
+ const config = getConfig();
41
+ const getStore = config.getContextStore;
42
+ if (getStore) {
43
+ const ctx = getStore();
44
+ if (ctx)
45
+ return ctx;
46
+ }
39
47
  if (sharedStorage && contextKey) {
40
48
  const store = sharedStorage.getStore();
41
49
  const ctx = store === null || store === void 0 ? void 0 : store[contextKey];
@@ -44,13 +52,19 @@ export function getContext(explicitCtx) {
44
52
  }
45
53
  }
46
54
  throw new Error('Astro context not found. Either:\n' +
47
- '1. Pass context explicitly: api.get("/path", { ctx: Astro })\n' +
48
- '2. Configure shared storage: setSharedContextStorage(storage, "key")');
55
+ '1. Use api.middleware() to bind context automatically, or\n' +
56
+ '2. Pass context explicitly: api.get("/path", { ctx: Astro })\n' +
57
+ '3. Configure shared storage: setSharedContextStorage(storage, "key")');
49
58
  }
50
59
  /**
51
60
  * Bind context (only needed if not using shared storage)
52
61
  */
53
- export function bindContext(ctx, fn) {
62
+ export function runWithContext(ctx, fn) {
63
+ const config = getConfig();
64
+ const runner = config.runWithContext;
65
+ if (runner) {
66
+ return runner(ctx, fn);
67
+ }
54
68
  if (sharedStorage && contextKey) {
55
69
  const currentStore = sharedStorage.getStore() || {};
56
70
  return sharedStorage.run(Object.assign(Object.assign({}, currentStore), { [contextKey]: ctx }), fn);
@@ -1,21 +1,13 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
1
  import type { TokenKitContext } from '../types';
3
- /**
4
- * Configuration for context handling
5
- */
6
- export interface ContextOptions {
7
- context?: AsyncLocalStorage<any>;
8
- getContextStore?: () => TokenKitContext | undefined | null;
9
- }
10
2
  /**
11
3
  * Bind Astro context for the current async scope
12
4
  */
13
- export declare function bindContext<T>(ctx: TokenKitContext, fn: () => T, options?: ContextOptions): T;
5
+ export declare function runWithContext<T>(ctx: TokenKitContext, fn: () => T): T;
14
6
  /**
15
7
  * Get current Astro context (from middleware binding or explicit)
16
8
  */
17
- export declare function getContext(explicitCtx?: TokenKitContext, options?: ContextOptions): TokenKitContext;
9
+ export declare function getContextStore(explicitCtx?: TokenKitContext): TokenKitContext;
18
10
  /**
19
11
  * Check if context is available
20
12
  */
21
- export declare function hasContext(explicitCtx?: TokenKitContext, options?: ContextOptions): boolean;
13
+ export declare function hasContext(explicitCtx?: TokenKitContext): boolean;
@@ -1,23 +1,31 @@
1
1
  // packages/astro-tokenkit/src/client/context.ts
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import { getConfig } from '../config';
3
4
  /**
4
5
  * Async local storage for Astro context
5
6
  */
6
- const defaultContextStorage = new AsyncLocalStorage();
7
+ const als = new AsyncLocalStorage();
7
8
  /**
8
9
  * Bind Astro context for the current async scope
9
10
  */
10
- export function bindContext(ctx, fn, options) {
11
- const storage = (options === null || options === void 0 ? void 0 : options.context) || defaultContextStorage;
12
- return storage.run(ctx, fn);
11
+ export function runWithContext(ctx, fn) {
12
+ const config = getConfig();
13
+ const runner = config.runWithContext;
14
+ if (runner) {
15
+ return runner(ctx, fn);
16
+ }
17
+ return als.run(ctx, fn);
13
18
  }
14
19
  /**
15
20
  * Get current Astro context (from middleware binding or explicit)
16
21
  */
17
- export function getContext(explicitCtx, options) {
18
- const store = (options === null || options === void 0 ? void 0 : options.getContextStore)
19
- ? options.getContextStore()
20
- : ((options === null || options === void 0 ? void 0 : options.context) || defaultContextStorage).getStore();
22
+ export function getContextStore(explicitCtx) {
23
+ const config = getConfig();
24
+ const getStore = config.getContextStore;
25
+ const context = config.context || als;
26
+ const store = getStore
27
+ ? getStore()
28
+ : context.getStore();
21
29
  const ctx = explicitCtx || store;
22
30
  if (!ctx) {
23
31
  throw new Error('Astro context not found. Either:\n' +
@@ -29,9 +37,12 @@ export function getContext(explicitCtx, options) {
29
37
  /**
30
38
  * Check if context is available
31
39
  */
32
- export function hasContext(explicitCtx, options) {
33
- const store = (options === null || options === void 0 ? void 0 : options.getContextStore)
34
- ? options.getContextStore()
35
- : ((options === null || options === void 0 ? void 0 : options.context) || defaultContextStorage).getStore();
40
+ export function hasContext(explicitCtx) {
41
+ const config = getConfig();
42
+ const getStore = config.getContextStore;
43
+ const context = config.context || als;
44
+ const store = getStore
45
+ ? getStore()
46
+ : context.getStore();
36
47
  return !!(explicitCtx || store);
37
48
  }
@@ -0,0 +1,24 @@
1
+ import type { TokenKitConfig } from "./types";
2
+ import { TokenManager } from "./auth/manager";
3
+ /**
4
+ * Internal config with defaults applied
5
+ */
6
+ export interface ResolvedConfig extends TokenKitConfig {
7
+ baseURL: string;
8
+ }
9
+ /**
10
+ * Set configuration
11
+ */
12
+ export declare function setConfig(userConfig: TokenKitConfig): void;
13
+ /**
14
+ * Get current configuration
15
+ */
16
+ export declare function getConfig(): ResolvedConfig;
17
+ /**
18
+ * Get global token manager
19
+ */
20
+ export declare function getTokenManager(): TokenManager | undefined;
21
+ /**
22
+ * Set global token manager (mainly for testing)
23
+ */
24
+ export declare function setTokenManager(manager: TokenManager | undefined): void;
package/dist/config.js ADDED
@@ -0,0 +1,37 @@
1
+ // packages/astro-tokenkit/src/config.ts
2
+ import { TokenManager } from "./auth/manager";
3
+ let config = {
4
+ runWithContext: undefined,
5
+ getContextStore: undefined,
6
+ baseURL: "",
7
+ };
8
+ let tokenManager;
9
+ /**
10
+ * Set configuration
11
+ */
12
+ export function setConfig(userConfig) {
13
+ // Store validated config
14
+ config = Object.assign(Object.assign({}, config), userConfig);
15
+ // Re-initialize global token manager if auth changed
16
+ if (config.auth) {
17
+ tokenManager = new TokenManager(config.auth, config.baseURL);
18
+ }
19
+ }
20
+ /**
21
+ * Get current configuration
22
+ */
23
+ export function getConfig() {
24
+ return config;
25
+ }
26
+ /**
27
+ * Get global token manager
28
+ */
29
+ export function getTokenManager() {
30
+ return tokenManager;
31
+ }
32
+ /**
33
+ * Set global token manager (mainly for testing)
34
+ */
35
+ export function setTokenManager(manager) {
36
+ tokenManager = manager;
37
+ }