@vunexa/lixa 0.1.6-alpha.3 → 0.1.6-alpha.4

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
@@ -187,15 +187,16 @@ app.get("/connect/github/callback", async (req, res) => {
187
187
  res.redirect("/dashboard");
188
188
  });
189
189
 
190
- // 3. Query Connected Resource Access Token
190
+ // 3. Query Connected Resource Access Token (Auto-Refreshes Expired Tokens)
191
191
  app.get("/api/github/repos", async (req, res) => {
192
+ // Queries by active session OR user ID, auto-refreshing expired access tokens
192
193
  const resource = await lixa.getConnectedResource(req.cookies.session_id, "github");
193
194
 
194
195
  if (!resource) {
195
196
  return res.status(403).json({ error: "GitHub resource not connected" });
196
197
  }
197
198
 
198
- // Call GitHub API with resource access token
199
+ // Call GitHub API with active resource access token
199
200
  const response = await fetch("https://api.github.com/user/repos", {
200
201
  headers: { Authorization: `Bearer ${resource.accessToken}` },
201
202
  });
@@ -204,7 +205,10 @@ app.get("/api/github/repos", async (req, res) => {
204
205
  res.json(repos);
205
206
  });
206
207
 
207
- // 4. Disconnect Resource Provider
208
+ // 4. Query Resource Directly by User ID (e.g. inside background cron jobs or webhooks)
209
+ const githubResource = await lixa.getUserResource(userId, "github");
210
+
211
+ // 5. Disconnect Resource Provider
208
212
  app.delete("/connect/github", async (req, res) => {
209
213
  await lixa.disconnectResource(req.cookies.session_id, "github");
210
214
  res.json({ success: true });
@@ -503,7 +507,113 @@ export const lixa = new Lixa({
503
507
  "connectedAt": 1771657300000
504
508
  }
505
509
  }
510
+ ---
511
+
512
+ ## Custom Resource Storage Implementation (`ResourceStorage`)
513
+
514
+ While `SessionStorage` manages short-lived user authentication sessions (indexed by `sessionId`), **`ResourceStorage`** manages long-lived third-party API tokens (**AuthZ**) bound directly to a **User ID** (or Email). This ensures that third-party credentials (such as GitHub Repositories or Google Drive) persist across session expirations and logouts.
515
+
516
+ ### `ResourceStorage` Interface
517
+
518
+ ```typescript
519
+ export interface ResourceStorage {
520
+ saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void>;
521
+ getResource(userId: string, provider: string): Promise<ConnectedResource | null>;
522
+ getUserResources(userId: string): Promise<Record<string, ConnectedResource>>;
523
+ deleteResource(userId: string, provider: string): Promise<void>;
524
+ }
525
+ ```
526
+
527
+ ### 1. SQLite Resource Storage (`better-sqlite3`)
528
+
529
+ #### Table Schema (SQL DDL)
530
+
531
+ ```sql
532
+ CREATE TABLE IF NOT EXISTS user_resources (
533
+ user_id TEXT NOT NULL,
534
+ provider TEXT NOT NULL,
535
+ data TEXT NOT NULL,
536
+ updated_at INTEGER NOT NULL,
537
+ PRIMARY KEY (user_id, provider)
538
+ );
539
+ ```
540
+
541
+ #### TypeScript Implementation
542
+
543
+ ```typescript
544
+ import Database from "better-sqlite3";
545
+ import { Lixa, type ResourceStorage, type ConnectedResource } from "@vunexa/lixa";
546
+
547
+ export class SqliteResourceStorage implements ResourceStorage {
548
+ private db = new Database("lixa_resources.db");
549
+
550
+ constructor() {
551
+ this.db.exec(`
552
+ CREATE TABLE IF NOT EXISTS user_resources (
553
+ user_id TEXT NOT NULL,
554
+ provider TEXT NOT NULL,
555
+ data TEXT NOT NULL,
556
+ updated_at INTEGER NOT NULL,
557
+ PRIMARY KEY (user_id, provider)
558
+ );
559
+ `);
506
560
  }
561
+
562
+ async saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void> {
563
+ const stmt = this.db.prepare(`
564
+ INSERT INTO user_resources (user_id, provider, data, updated_at)
565
+ VALUES (?, ?, ?, ?)
566
+ ON CONFLICT(user_id, provider) DO UPDATE SET
567
+ data = excluded.data,
568
+ updated_at = excluded.updated_at
569
+ `);
570
+ stmt.run(userId, provider.toLowerCase(), JSON.stringify(resource), Date.now());
571
+ }
572
+
573
+ async getResource(userId: string, provider: string): Promise<ConnectedResource | null> {
574
+ const stmt = this.db.prepare(`SELECT data FROM user_resources WHERE user_id = ? AND provider = ?`);
575
+ const row = stmt.get(userId, provider.toLowerCase()) as { data: string } | undefined;
576
+ return row ? (JSON.parse(row.data) as ConnectedResource) : null;
577
+ }
578
+
579
+ async getUserResources(userId: string): Promise<Record<string, ConnectedResource>> {
580
+ const stmt = this.db.prepare(`SELECT provider, data FROM user_resources WHERE user_id = ?`);
581
+ const rows = stmt.all(userId) as Array<{ provider: string; data: string }>;
582
+ const result: Record<string, ConnectedResource> = {};
583
+ for (const r of rows) {
584
+ result[r.provider] = JSON.parse(r.data);
585
+ }
586
+ return result;
587
+ }
588
+
589
+ async deleteResource(userId: string, provider: string): Promise<void> {
590
+ const stmt = this.db.prepare(`DELETE FROM user_resources WHERE user_id = ? AND provider = ?`);
591
+ stmt.run(userId, provider.toLowerCase());
592
+ }
593
+ }
594
+
595
+ // Pass to Lixa instance via resourceHandler
596
+ export const lixa = new Lixa({
597
+ resourceHandler: {
598
+ resourceStorage: new SqliteResourceStorage(),
599
+ },
600
+ providers: { /* ... */ },
601
+ });
602
+ ```
603
+
604
+ ### 2. Querying Connected Resources Directly by User ID
605
+
606
+ `ResourceStorage` enables background workers, cron jobs, and webhooks to access third-party API credentials by `userId` without an active HTTP session:
607
+
608
+ ```typescript
609
+ // Background worker querying user's GitHub Repos token
610
+ const githubResource = await lixa.getUserResource(user.id, "github");
611
+
612
+ if (githubResource) {
613
+ // Lixa auto-refreshes expired access tokens transparently!
614
+ const response = await fetch("https://api.github.com/user/repos", {
615
+ headers: { Authorization: `Bearer ${githubResource.accessToken}` },
616
+ });
507
617
  }
508
618
  ```
509
619
 
@@ -277,15 +277,16 @@ app.get("/connect/github/callback", async (req, res) => {
277
277
  res.redirect("/dashboard");
278
278
  });
279
279
 
280
- // 3. Query Connected Resource Access Token
280
+ // 3. Query Connected Resource Access Token (Auto-Refreshes Expired Tokens)
281
281
  app.get("/api/github/repos", async (req, res) => {
282
+ // Queries by active session OR user ID, auto-refreshing expired access tokens
282
283
  const resource = await lixa.getConnectedResource(req.cookies.session_id, "github");
283
284
 
284
285
  if (!resource) {
285
286
  return res.status(403).json({ error: "GitHub resource not connected" });
286
287
  }
287
288
 
288
- // Call GitHub API with resource access token
289
+ // Call GitHub API with active resource access token
289
290
  const response = await fetch("https://api.github.com/user/repos", {
290
291
  headers: { Authorization: `Bearer ${resource.accessToken}` },
291
292
  });
@@ -294,7 +295,10 @@ app.get("/api/github/repos", async (req, res) => {
294
295
  res.json(repos);
295
296
  });
296
297
 
297
- // 4. Disconnect Resource Provider
298
+ // 4. Query Resource Directly by User ID (e.g. inside background cron jobs or webhooks)
299
+ const githubResource = await lixa.getUserResource(userId, "github");
300
+
301
+ // 5. Disconnect Resource Provider
298
302
  app.delete("/connect/github", async (req, res) => {
299
303
  await lixa.disconnectResource(req.cookies.session_id, "github");
300
304
  res.json({ success: true });
@@ -593,7 +597,113 @@ export const lixa = new Lixa({
593
597
  "connectedAt": 1771657300000
594
598
  }
595
599
  }
600
+ ---
601
+
602
+ ## Custom Resource Storage Implementation (`ResourceStorage`)
603
+
604
+ While `SessionStorage` manages short-lived user authentication sessions (indexed by `sessionId`), **`ResourceStorage`** manages long-lived third-party API tokens (**AuthZ**) bound directly to a **User ID** (or Email). This ensures that third-party credentials (such as GitHub Repositories or Google Drive) persist across session expirations and logouts.
605
+
606
+ ### `ResourceStorage` Interface
607
+
608
+ ```typescript
609
+ export interface ResourceStorage {
610
+ saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void>;
611
+ getResource(userId: string, provider: string): Promise<ConnectedResource | null>;
612
+ getUserResources(userId: string): Promise<Record<string, ConnectedResource>>;
613
+ deleteResource(userId: string, provider: string): Promise<void>;
614
+ }
615
+ ```
616
+
617
+ ### 1. SQLite Resource Storage (`better-sqlite3`)
618
+
619
+ #### Table Schema (SQL DDL)
620
+
621
+ ```sql
622
+ CREATE TABLE IF NOT EXISTS user_resources (
623
+ user_id TEXT NOT NULL,
624
+ provider TEXT NOT NULL,
625
+ data TEXT NOT NULL,
626
+ updated_at INTEGER NOT NULL,
627
+ PRIMARY KEY (user_id, provider)
628
+ );
629
+ ```
630
+
631
+ #### TypeScript Implementation
632
+
633
+ ```typescript
634
+ import Database from "better-sqlite3";
635
+ import { Lixa, type ResourceStorage, type ConnectedResource } from "@vunexa/lixa";
636
+
637
+ export class SqliteResourceStorage implements ResourceStorage {
638
+ private db = new Database("lixa_resources.db");
639
+
640
+ constructor() {
641
+ this.db.exec(`
642
+ CREATE TABLE IF NOT EXISTS user_resources (
643
+ user_id TEXT NOT NULL,
644
+ provider TEXT NOT NULL,
645
+ data TEXT NOT NULL,
646
+ updated_at INTEGER NOT NULL,
647
+ PRIMARY KEY (user_id, provider)
648
+ );
649
+ `);
596
650
  }
651
+
652
+ async saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void> {
653
+ const stmt = this.db.prepare(`
654
+ INSERT INTO user_resources (user_id, provider, data, updated_at)
655
+ VALUES (?, ?, ?, ?)
656
+ ON CONFLICT(user_id, provider) DO UPDATE SET
657
+ data = excluded.data,
658
+ updated_at = excluded.updated_at
659
+ `);
660
+ stmt.run(userId, provider.toLowerCase(), JSON.stringify(resource), Date.now());
661
+ }
662
+
663
+ async getResource(userId: string, provider: string): Promise<ConnectedResource | null> {
664
+ const stmt = this.db.prepare(`SELECT data FROM user_resources WHERE user_id = ? AND provider = ?`);
665
+ const row = stmt.get(userId, provider.toLowerCase()) as { data: string } | undefined;
666
+ return row ? (JSON.parse(row.data) as ConnectedResource) : null;
667
+ }
668
+
669
+ async getUserResources(userId: string): Promise<Record<string, ConnectedResource>> {
670
+ const stmt = this.db.prepare(`SELECT provider, data FROM user_resources WHERE user_id = ?`);
671
+ const rows = stmt.all(userId) as Array<{ provider: string; data: string }>;
672
+ const result: Record<string, ConnectedResource> = {};
673
+ for (const r of rows) {
674
+ result[r.provider] = JSON.parse(r.data);
675
+ }
676
+ return result;
677
+ }
678
+
679
+ async deleteResource(userId: string, provider: string): Promise<void> {
680
+ const stmt = this.db.prepare(`DELETE FROM user_resources WHERE user_id = ? AND provider = ?`);
681
+ stmt.run(userId, provider.toLowerCase());
682
+ }
683
+ }
684
+
685
+ // Pass to Lixa instance via resourceHandler
686
+ export const lixa = new Lixa({
687
+ resourceHandler: {
688
+ resourceStorage: new SqliteResourceStorage(),
689
+ },
690
+ providers: { /* ... */ },
691
+ });
692
+ ```
693
+
694
+ ### 2. Querying Connected Resources Directly by User ID
695
+
696
+ `ResourceStorage` enables background workers, cron jobs, and webhooks to access third-party API credentials by `userId` without an active HTTP session:
697
+
698
+ ```typescript
699
+ // Background worker querying user's GitHub Repos token
700
+ const githubResource = await lixa.getUserResource(user.id, "github");
701
+
702
+ if (githubResource) {
703
+ // Lixa auto-refreshes expired access tokens transparently!
704
+ const response = await fetch("https://api.github.com/user/repos", {
705
+ headers: { Authorization: `Bearer ${githubResource.accessToken}` },
706
+ });
597
707
  }
598
708
  ```
599
709
 
@@ -220,6 +220,53 @@ export interface SessionStorage {
220
220
  session: T;
221
221
  } | null>;
222
222
  }
223
+ /**
224
+ * Resource storage operations interface.
225
+ *
226
+ * @remarks
227
+ * Manages long-lived third-party resource provider tokens (AuthZ) bound to a user account
228
+ * (User ID or Email), independent of short-lived user sessions.
229
+ *
230
+ * @public
231
+ */
232
+ export interface ResourceStorage {
233
+ /**
234
+ * Saves a connected resource token for a user.
235
+ *
236
+ * @param userId - Unique user identifier or email
237
+ * @param provider - Resource provider name (e.g. 'github', 'google')
238
+ * @param resource - Connected resource details including access & refresh tokens
239
+ */
240
+ saveResource(userId: string, provider: string, resource: import("../models/session").ConnectedResource): Promise<void>;
241
+ /**
242
+ * Retrieves a connected resource token for a user.
243
+ *
244
+ * @param userId - Unique user identifier or email
245
+ * @param provider - Resource provider name
246
+ */
247
+ getResource(userId: string, provider: string): Promise<import("../models/session").ConnectedResource | null>;
248
+ /**
249
+ * Retrieves all connected resources for a user.
250
+ *
251
+ * @param userId - Unique user identifier or email
252
+ */
253
+ getUserResources(userId: string): Promise<Record<string, import("../models/session").ConnectedResource>>;
254
+ /**
255
+ * Deletes a connected resource token for a user.
256
+ *
257
+ * @param userId - Unique user identifier or email
258
+ * @param provider - Resource provider name
259
+ */
260
+ deleteResource(userId: string, provider: string): Promise<void>;
261
+ }
262
+ /**
263
+ * Resource handler configuration.
264
+ *
265
+ * @public
266
+ */
267
+ export interface ResourceHandler {
268
+ resourceStorage?: ResourceStorage;
269
+ }
223
270
  /**
224
271
  * Session handler for OAuth authentication.
225
272
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dao/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnF;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAEnD;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,aAAa,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;IAE9E;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,WAAW,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvG;;;;;OAKG;IACH,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEpE;;;;OAIG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACzG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACH,eAAe,CAAC,CAAC,CAAC,SAAS,OAAO,EAChC,SAAS,EAAE,OAAO,mBAAmB,EAAE,kBAAkB,EACzD,gBAAgB,EAAE,OAAO,mBAAmB,EAAE,gBAAgB,GAC7D,OAAO,CAAC,CAAC,CAAC,CAAC;IAEd;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;;;OAKG;IACH,eAAe,CAAC,CAAC,CAAC,SAAS,OAAO,EAChC,SAAS,EAAE,OAAO,mBAAmB,EAAE,kBAAkB,EACzD,gBAAgB,EAAE,OAAO,mBAAmB,EAAE,gBAAgB,GAC7D,OAAO,CAAC,CAAC,CAAC,CAAC;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/dao/types.ts"],"names":[],"mappings":"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"}
@@ -141,6 +141,8 @@ export declare interface ConnectedResource {
141
141
  accessToken: string;
142
142
  /** Optional refresh token for offline resource access */
143
143
  refreshToken?: string | undefined;
144
+ /** Unix timestamp in milliseconds when the resource access token expires */
145
+ expiresAt?: number | undefined;
144
146
  /** Resource scopes granted by the user */
145
147
  scopes: string[];
146
148
  /** Full raw token response from provider */
@@ -437,6 +439,7 @@ export declare class Lixa<TConfig extends LixaConfig<Record<string, ProviderConf
437
439
  private config;
438
440
  private stateHandler;
439
441
  private sessionHandler;
442
+ private resourceHandler;
440
443
  private debug;
441
444
  /**
442
445
  * Creates a new Lixa instance with the provided configuration.
@@ -749,6 +752,12 @@ export declare class Lixa<TConfig extends LixaConfig<Record<string, ProviderConf
749
752
  * @param params - Object containing sessionId, provider, code, state, and requested scopes
750
753
  * @returns Updated Session containing stored resource tokens under session.resources[provider]
751
754
  */
755
+ private static userResourceStore;
756
+ static LOCAL_RESOURCE_HANDLER: ResourceHandler;
757
+ private getUserKeyFromSession;
758
+ /**
759
+ * Handles the OAuth callback for a connected resource provider and stores resource tokens bound to user account.
760
+ */
752
761
  handleResourceCallback(params: {
753
762
  sessionId: string;
754
763
  provider: ConfiguredProviderKey<TConfig> | string;
@@ -757,17 +766,41 @@ export declare class Lixa<TConfig extends LixaConfig<Record<string, ProviderConf
757
766
  scopes?: string[];
758
767
  }): Promise<Session>;
759
768
  /**
760
- * Retrieves a connected resource provider token for an active session.
769
+ * Retrieves a connected resource for a specific User ID / Email directly (independent of session IDs).
770
+ * Automatically refreshes expired access tokens if a refresh token is present.
771
+ *
772
+ * @param userIdOrEmail - User identifier or email
773
+ * @param provider - Resource provider identifier (e.g. 'github', 'google')
774
+ */
775
+ getUserResource(userIdOrEmail: string, provider: string): Promise<ConnectedResource | null>;
776
+ /**
777
+ * Retrieves all connected resources for a specific User ID / Email.
778
+ */
779
+ getUserResources(userIdOrEmail: string): Promise<Record<string, ConnectedResource>>;
780
+ /**
781
+ * Retrieves a connected resource provider token for an active session, auto-refreshing expired tokens if possible.
761
782
  *
762
783
  * @param sessionId - Active session ID
763
- * @param provider - Provider identifier (e.g. 'github')
784
+ * @param provider - Provider identifier (e.g. 'github', 'google')
764
785
  */
765
786
  getConnectedResource(sessionId: string, provider: string): Promise<ConnectedResource | null>;
766
787
  /**
767
- * Disconnects a resource provider from an active session.
788
+ * Refreshes a user's resource access token using its refresh token.
768
789
  *
769
- * @param sessionId - Active session ID
770
- * @param provider - Provider identifier to disconnect
790
+ * @param userIdOrEmail - User identifier or email
791
+ * @param provider - Provider identifier (e.g. 'google', 'github')
792
+ */
793
+ refreshUserResourceToken(userIdOrEmail: string, provider: string, existingResource?: ConnectedResource): Promise<ConnectedResource>;
794
+ /**
795
+ * Refreshes a connected resource access token for an active session.
796
+ */
797
+ refreshResourceToken(sessionId: string, provider: string): Promise<ConnectedResource>;
798
+ /**
799
+ * Disconnects a resource provider for a specific User ID / Email.
800
+ */
801
+ disconnectUserResource(userIdOrEmail: string, provider: string): Promise<boolean>;
802
+ /**
803
+ * Disconnects a resource provider from an active session and user account.
771
804
  */
772
805
  disconnectResource(sessionId: string, provider: string): Promise<boolean>;
773
806
  fetchSessionInfo(sessionId: string): Promise<Session | null>;
@@ -815,6 +848,14 @@ export declare interface LixaConfig<TProviders extends Record<string, ProviderCo
815
848
  * @see {@link SessionHandler}
816
849
  */
817
850
  sessionHandler?: SessionHandler;
851
+ /**
852
+ * Optional custom resource handler.
853
+ * Handles storage and management of long-lived third-party resource provider tokens (AuthZ)
854
+ * bound directly to user accounts (User ID or Email), independent of transient session IDs.
855
+ *
856
+ * @see {@link ResourceHandler}
857
+ */
858
+ resourceHandler?: ResourceHandler;
818
859
  /**
819
860
  * Enable debug logging.
820
861
  * When enabled, outputs structured logs for initialization, auth flow, and errors.
@@ -945,6 +986,55 @@ export declare interface ProviderMetadata {
945
986
  };
946
987
  }
947
988
 
989
+ /**
990
+ * Resource handler configuration.
991
+ *
992
+ * @public
993
+ */
994
+ export declare interface ResourceHandler {
995
+ resourceStorage?: ResourceStorage;
996
+ }
997
+
998
+ /**
999
+ * Resource storage operations interface.
1000
+ *
1001
+ * @remarks
1002
+ * Manages long-lived third-party resource provider tokens (AuthZ) bound to a user account
1003
+ * (User ID or Email), independent of short-lived user sessions.
1004
+ *
1005
+ * @public
1006
+ */
1007
+ export declare interface ResourceStorage {
1008
+ /**
1009
+ * Saves a connected resource token for a user.
1010
+ *
1011
+ * @param userId - Unique user identifier or email
1012
+ * @param provider - Resource provider name (e.g. 'github', 'google')
1013
+ * @param resource - Connected resource details including access & refresh tokens
1014
+ */
1015
+ saveResource(userId: string, provider: string, resource: ConnectedResource): Promise<void>;
1016
+ /**
1017
+ * Retrieves a connected resource token for a user.
1018
+ *
1019
+ * @param userId - Unique user identifier or email
1020
+ * @param provider - Resource provider name
1021
+ */
1022
+ getResource(userId: string, provider: string): Promise<ConnectedResource | null>;
1023
+ /**
1024
+ * Retrieves all connected resources for a user.
1025
+ *
1026
+ * @param userId - Unique user identifier or email
1027
+ */
1028
+ getUserResources(userId: string): Promise<Record<string, ConnectedResource>>;
1029
+ /**
1030
+ * Deletes a connected resource token for a user.
1031
+ *
1032
+ * @param userId - Unique user identifier or email
1033
+ * @param provider - Resource provider name
1034
+ */
1035
+ deleteResource(userId: string, provider: string): Promise<void>;
1036
+ }
1037
+
948
1038
  /**
949
1039
  * Helper type to create a configuration with only registered providers.
950
1040
  * Use this with Lixa.createConfig() for type safety.