astro-tokenkit 1.0.6 → 1.0.8

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,9 +126,19 @@ 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
+ | `injectToken` | `Function` | Custom token injection: `(token: string) => string` (default: Bearer). |
98
132
  | `cookies` | `CookieConfig` | Configuration for auth cookies. |
99
133
  | `policy` | `RefreshPolicy` | Strategy for when to trigger token refresh. |
100
134
 
135
+ ### Login Options
136
+
137
+ | Property | Type | Description |
138
+ | :--- | :--- | :--- |
139
+ | `ctx` | `TokenKitContext` | Optional Astro context. |
140
+ | `onLogin` | `Function` | Callback after successful login: `(bundle, body, ctx) => void`. |
141
+
101
142
  ## Advanced Usage
102
143
 
103
144
  ### Manual Context
@@ -128,7 +169,12 @@ const api = createClient({
128
169
 
129
170
  ```typescript
130
171
  // In an API route or server-side component
131
- await api.login({ username, password });
172
+ await api.login({ username, password }, {
173
+ onLogin: (bundle, body, ctx) => {
174
+ // Post-login logic (e.g., sync session to another store)
175
+ console.log('User logged in!', bundle.sessionPayload);
176
+ }
177
+ });
132
178
 
133
179
  await api.logout();
134
180
  ```
@@ -1,4 +1,4 @@
1
- import type { TokenBundle, Session, AuthConfig, TokenKitContext } from '../types';
1
+ import type { TokenBundle, Session, AuthConfig, TokenKitContext, OnLoginCallback } from '../types';
2
2
  /**
3
3
  * Token Manager handles all token operations
4
4
  */
@@ -10,7 +10,7 @@ export declare class TokenManager {
10
10
  /**
11
11
  * Perform login
12
12
  */
13
- login(ctx: TokenKitContext, credentials: any): Promise<TokenBundle>;
13
+ login(ctx: TokenKitContext, credentials: any, onLogin?: OnLoginCallback): Promise<TokenBundle>;
14
14
  /**
15
15
  * Perform token refresh
16
16
  */
@@ -52,7 +52,7 @@ export class TokenManager {
52
52
  /**
53
53
  * Perform login
54
54
  */
55
- login(ctx, credentials) {
55
+ login(ctx, credentials, onLogin) {
56
56
  return __awaiter(this, void 0, void 0, function* () {
57
57
  const url = this.baseURL + this.config.login;
58
58
  const response = yield fetch(url, {
@@ -79,8 +79,8 @@ export class TokenManager {
79
79
  // Store in cookies
80
80
  storeTokens(ctx, bundle, this.config.cookies);
81
81
  // Call onLogin callback if provided
82
- if (this.config.onLogin) {
83
- yield this.config.onLogin(bundle, body, ctx);
82
+ if (onLogin) {
83
+ yield onLogin(bundle, body, ctx);
84
84
  }
85
85
  return bundle;
86
86
  });
@@ -1,14 +1,27 @@
1
- import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext } from '../types';
1
+ import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext, TokenKitConfig, LoginOptions } 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
  */
@@ -52,7 +65,7 @@ export declare class APIClient {
52
65
  /**
53
66
  * Login
54
67
  */
55
- login(credentials: any, ctx?: TokenKitContext): Promise<void>;
68
+ login(credentials: any, options?: LoginOptions | TokenKitContext): Promise<void>;
56
69
  /**
57
70
  * Logout
58
71
  */
@@ -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;
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;
28
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) {
@@ -245,13 +283,23 @@ export class APIClient {
245
283
  /**
246
284
  * Login
247
285
  */
248
- login(credentials, ctx) {
286
+ login(credentials, options) {
249
287
  return __awaiter(this, void 0, void 0, function* () {
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);
254
- yield this.tokenManager.login(context, credentials);
291
+ let ctx;
292
+ let onLogin;
293
+ if (options && 'cookies' in options) {
294
+ ctx = options;
295
+ }
296
+ else if (options) {
297
+ const opt = options;
298
+ ctx = opt.ctx;
299
+ onLogin = opt.onLogin;
300
+ }
301
+ const context = getContextStore(ctx);
302
+ yield this.tokenManager.login(context, credentials, onLogin);
255
303
  });
256
304
  }
257
305
  /**
@@ -262,7 +310,7 @@ export class APIClient {
262
310
  if (!this.tokenManager) {
263
311
  throw new Error('Auth is not configured for this client');
264
312
  }
265
- const context = getContext(ctx, this.contextOptions);
313
+ const context = getContextStore(ctx);
266
314
  yield this.tokenManager.logout(context);
267
315
  });
268
316
  }
@@ -272,7 +320,7 @@ export class APIClient {
272
320
  isAuthenticated(ctx) {
273
321
  if (!this.tokenManager)
274
322
  return false;
275
- const context = getContext(ctx, this.contextOptions);
323
+ const context = getContextStore(ctx);
276
324
  return this.tokenManager.isAuthenticated(context);
277
325
  }
278
326
  /**
@@ -281,13 +329,24 @@ export class APIClient {
281
329
  getSession(ctx) {
282
330
  if (!this.tokenManager)
283
331
  return null;
284
- const context = getContext(ctx, this.contextOptions);
332
+ const context = getContextStore(ctx);
285
333
  return this.tokenManager.getSession(context);
286
334
  }
287
335
  }
288
336
  /**
289
- * Create API client
337
+ * Global API client instance.
338
+ *
339
+ * This client is automatically synchronized with the global configuration
340
+ * set via the Astro integration or setConfig().
341
+ */
342
+ export const api = new APIClient();
343
+ /**
344
+ * Create API client.
345
+ *
346
+ * If no configuration is provided, it returns the global `api` singleton.
290
347
  */
291
348
  export function createClient(config) {
349
+ if (!config)
350
+ return api;
292
351
  return new APIClient(config);
293
352
  }
@@ -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
+ }