better-auth-offline 0.0.0
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/dist/adapters/indexeddb.cjs +108 -0
- package/dist/adapters/indexeddb.cjs.map +1 -0
- package/dist/adapters/indexeddb.d.cts +1 -0
- package/dist/adapters/indexeddb.d.ts +1 -0
- package/dist/adapters/indexeddb.js +7 -0
- package/dist/adapters/indexeddb.js.map +1 -0
- package/dist/chunk-JPTDSCSW.js +84 -0
- package/dist/chunk-JPTDSCSW.js.map +1 -0
- package/dist/index.cjs +269 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +164 -0
- package/dist/index.js.map +1 -0
- package/dist/indexeddb-CkUWH5Sl.d.cts +47 -0
- package/dist/indexeddb-CkUWH5Sl.d.ts +47 -0
- package/package.json +72 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/indexeddb.ts
|
|
21
|
+
var indexeddb_exports = {};
|
|
22
|
+
__export(indexeddb_exports, {
|
|
23
|
+
createIndexedDBAdapter: () => createIndexedDBAdapter
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(indexeddb_exports);
|
|
26
|
+
var DEFAULT_DB_NAME = "better-auth-offline";
|
|
27
|
+
var STORE_NAME = "cache";
|
|
28
|
+
function createIndexedDBAdapter(dbName = DEFAULT_DB_NAME) {
|
|
29
|
+
let dbPromise = null;
|
|
30
|
+
function openDB() {
|
|
31
|
+
if (dbPromise) return dbPromise;
|
|
32
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
33
|
+
const request = indexedDB.open(dbName, 1);
|
|
34
|
+
request.onupgradeneeded = () => {
|
|
35
|
+
const db = request.result;
|
|
36
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
37
|
+
db.createObjectStore(STORE_NAME);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
request.onsuccess = () => resolve(request.result);
|
|
41
|
+
request.onerror = () => {
|
|
42
|
+
dbPromise = null;
|
|
43
|
+
reject(request.error);
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
return dbPromise;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
async get(key) {
|
|
50
|
+
try {
|
|
51
|
+
const db = await openDB();
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
54
|
+
const store = tx.objectStore(STORE_NAME);
|
|
55
|
+
const request = store.get(key);
|
|
56
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
57
|
+
request.onerror = () => reject(request.error);
|
|
58
|
+
});
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async set(key, value) {
|
|
64
|
+
try {
|
|
65
|
+
const db = await openDB();
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
68
|
+
const store = tx.objectStore(STORE_NAME);
|
|
69
|
+
const request = store.put(value, key);
|
|
70
|
+
request.onsuccess = () => resolve();
|
|
71
|
+
request.onerror = () => reject(request.error);
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
async delete(key) {
|
|
77
|
+
try {
|
|
78
|
+
const db = await openDB();
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
81
|
+
const store = tx.objectStore(STORE_NAME);
|
|
82
|
+
const request = store.delete(key);
|
|
83
|
+
request.onsuccess = () => resolve();
|
|
84
|
+
request.onerror = () => reject(request.error);
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async clear() {
|
|
90
|
+
try {
|
|
91
|
+
const db = await openDB();
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
94
|
+
const store = tx.objectStore(STORE_NAME);
|
|
95
|
+
const request = store.clear();
|
|
96
|
+
request.onsuccess = () => resolve();
|
|
97
|
+
request.onerror = () => reject(request.error);
|
|
98
|
+
});
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
105
|
+
0 && (module.exports = {
|
|
106
|
+
createIndexedDBAdapter
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=indexeddb.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/indexeddb.ts"],"sourcesContent":["import type { StorageAdapter, CacheEntry } from \"../types.js\";\n\nconst DEFAULT_DB_NAME = \"better-auth-offline\";\nconst STORE_NAME = \"cache\";\n\n/**\n * Creates an IndexedDB-backed storage adapter.\n *\n * @param dbName - Name of the IndexedDB database. Defaults to \"better-auth-offline\".\n */\nexport function createIndexedDBAdapter(\n dbName: string = DEFAULT_DB_NAME,\n): StorageAdapter {\n let dbPromise: Promise<IDBDatabase> | null = null;\n\n function openDB(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(dbName, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n };\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => {\n dbPromise = null;\n reject(request.error);\n };\n });\n\n return dbPromise;\n }\n\n return {\n async get(key: string): Promise<CacheEntry | null> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readonly\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(key);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n } catch {\n return null;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.put(value, key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Fire-and-forget: swallow errors for cache writes\n }\n },\n\n async delete(key: string): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.delete(key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Swallow errors\n }\n },\n\n async clear(): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.clear();\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Swallow errors\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAM,kBAAkB;AACxB,IAAM,aAAa;AAOZ,SAAS,uBACd,SAAiB,iBACD;AAChB,MAAI,YAAyC;AAE7C,WAAS,SAA+B;AACtC,QAAI,UAAW,QAAO;AAEtB,gBAAY,IAAI,QAAqB,CAAC,SAAS,WAAW;AACxD,YAAM,UAAU,UAAU,KAAK,QAAQ,CAAC;AAExC,cAAQ,kBAAkB,MAAM;AAC9B,cAAM,KAAK,QAAQ;AACnB,YAAI,CAAC,GAAG,iBAAiB,SAAS,UAAU,GAAG;AAC7C,aAAG,kBAAkB,UAAU;AAAA,QACjC;AAAA,MACF;AAEA,cAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,cAAQ,UAAU,MAAM;AACtB,oBAAY;AACZ,eAAO,QAAQ,KAAK;AAAA,MACtB;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,IAAI,KAAyC;AACjD,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,UAAU;AAChD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,IAAI,GAAG;AAC7B,kBAAQ,YAAY,MAAM,QAAQ,QAAQ,UAAU,IAAI;AACxD,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAAa,OAA+B;AACpD,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,IAAI,OAAO,GAAG;AACpC,kBAAQ,YAAY,MAAM,QAAQ;AAClC,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,KAA4B;AACvC,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,OAAO,GAAG;AAChC,kBAAQ,YAAY,MAAM,QAAQ;AAClC,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,MAAM,QAAuB;AAC3B,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,MAAM;AAC5B,kBAAQ,YAAY,MAAM,QAAQ;AAClC,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { c as createIndexedDBAdapter } from '../indexeddb-CkUWH5Sl.cjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { c as createIndexedDBAdapter } from '../indexeddb-CkUWH5Sl.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/adapters/indexeddb.ts
|
|
2
|
+
var DEFAULT_DB_NAME = "better-auth-offline";
|
|
3
|
+
var STORE_NAME = "cache";
|
|
4
|
+
function createIndexedDBAdapter(dbName = DEFAULT_DB_NAME) {
|
|
5
|
+
let dbPromise = null;
|
|
6
|
+
function openDB() {
|
|
7
|
+
if (dbPromise) return dbPromise;
|
|
8
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
9
|
+
const request = indexedDB.open(dbName, 1);
|
|
10
|
+
request.onupgradeneeded = () => {
|
|
11
|
+
const db = request.result;
|
|
12
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
13
|
+
db.createObjectStore(STORE_NAME);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
request.onsuccess = () => resolve(request.result);
|
|
17
|
+
request.onerror = () => {
|
|
18
|
+
dbPromise = null;
|
|
19
|
+
reject(request.error);
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
return dbPromise;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
async get(key) {
|
|
26
|
+
try {
|
|
27
|
+
const db = await openDB();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
30
|
+
const store = tx.objectStore(STORE_NAME);
|
|
31
|
+
const request = store.get(key);
|
|
32
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
33
|
+
request.onerror = () => reject(request.error);
|
|
34
|
+
});
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async set(key, value) {
|
|
40
|
+
try {
|
|
41
|
+
const db = await openDB();
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
44
|
+
const store = tx.objectStore(STORE_NAME);
|
|
45
|
+
const request = store.put(value, key);
|
|
46
|
+
request.onsuccess = () => resolve();
|
|
47
|
+
request.onerror = () => reject(request.error);
|
|
48
|
+
});
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
async delete(key) {
|
|
53
|
+
try {
|
|
54
|
+
const db = await openDB();
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
57
|
+
const store = tx.objectStore(STORE_NAME);
|
|
58
|
+
const request = store.delete(key);
|
|
59
|
+
request.onsuccess = () => resolve();
|
|
60
|
+
request.onerror = () => reject(request.error);
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
async clear() {
|
|
66
|
+
try {
|
|
67
|
+
const db = await openDB();
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
70
|
+
const store = tx.objectStore(STORE_NAME);
|
|
71
|
+
const request = store.clear();
|
|
72
|
+
request.onsuccess = () => resolve();
|
|
73
|
+
request.onerror = () => reject(request.error);
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
createIndexedDBAdapter
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=chunk-JPTDSCSW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/indexeddb.ts"],"sourcesContent":["import type { StorageAdapter, CacheEntry } from \"../types.js\";\n\nconst DEFAULT_DB_NAME = \"better-auth-offline\";\nconst STORE_NAME = \"cache\";\n\n/**\n * Creates an IndexedDB-backed storage adapter.\n *\n * @param dbName - Name of the IndexedDB database. Defaults to \"better-auth-offline\".\n */\nexport function createIndexedDBAdapter(\n dbName: string = DEFAULT_DB_NAME,\n): StorageAdapter {\n let dbPromise: Promise<IDBDatabase> | null = null;\n\n function openDB(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(dbName, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n };\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => {\n dbPromise = null;\n reject(request.error);\n };\n });\n\n return dbPromise;\n }\n\n return {\n async get(key: string): Promise<CacheEntry | null> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readonly\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(key);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n } catch {\n return null;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.put(value, key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Fire-and-forget: swallow errors for cache writes\n }\n },\n\n async delete(key: string): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.delete(key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Swallow errors\n }\n },\n\n async clear(): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.clear();\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Swallow errors\n }\n },\n };\n}\n"],"mappings":";AAEA,IAAM,kBAAkB;AACxB,IAAM,aAAa;AAOZ,SAAS,uBACd,SAAiB,iBACD;AAChB,MAAI,YAAyC;AAE7C,WAAS,SAA+B;AACtC,QAAI,UAAW,QAAO;AAEtB,gBAAY,IAAI,QAAqB,CAAC,SAAS,WAAW;AACxD,YAAM,UAAU,UAAU,KAAK,QAAQ,CAAC;AAExC,cAAQ,kBAAkB,MAAM;AAC9B,cAAM,KAAK,QAAQ;AACnB,YAAI,CAAC,GAAG,iBAAiB,SAAS,UAAU,GAAG;AAC7C,aAAG,kBAAkB,UAAU;AAAA,QACjC;AAAA,MACF;AAEA,cAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAChD,cAAQ,UAAU,MAAM;AACtB,oBAAY;AACZ,eAAO,QAAQ,KAAK;AAAA,MACtB;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,IAAI,KAAyC;AACjD,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,UAAU;AAChD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,IAAI,GAAG;AAC7B,kBAAQ,YAAY,MAAM,QAAQ,QAAQ,UAAU,IAAI;AACxD,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,MAAM,IAAI,KAAa,OAA+B;AACpD,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,IAAI,OAAO,GAAG;AACpC,kBAAQ,YAAY,MAAM,QAAQ;AAClC,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,KAA4B;AACvC,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,OAAO,GAAG;AAChC,kBAAQ,YAAY,MAAM,QAAQ;AAClC,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,MAAM,QAAuB;AAC3B,UAAI;AACF,cAAM,KAAK,MAAM,OAAO;AACxB,eAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,gBAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,gBAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,gBAAM,UAAU,MAAM,MAAM;AAC5B,kBAAQ,YAAY,MAAM,QAAQ;AAClC,kBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAAA,QAC9C,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
createIndexedDBAdapter: () => createIndexedDBAdapter,
|
|
24
|
+
createOnlineStatusAtom: () => createOnlineStatusAtom,
|
|
25
|
+
offlinePlugin: () => offlinePlugin
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
|
|
29
|
+
// src/allowlist.ts
|
|
30
|
+
var DEFAULT_ALLOWLIST = [
|
|
31
|
+
// Core
|
|
32
|
+
"/get-session",
|
|
33
|
+
"/list-sessions",
|
|
34
|
+
"/list-accounts",
|
|
35
|
+
"/account-info",
|
|
36
|
+
// Organization
|
|
37
|
+
"/organization/list",
|
|
38
|
+
"/organization/get-active-member",
|
|
39
|
+
"/organization/get-active-member-role",
|
|
40
|
+
"/organization/get-full-organization",
|
|
41
|
+
"/organization/list-members",
|
|
42
|
+
"/organization/list-teams",
|
|
43
|
+
"/organization/list-invitations",
|
|
44
|
+
// Admin
|
|
45
|
+
"/admin/list-users",
|
|
46
|
+
// Multi-session
|
|
47
|
+
"/multi-session/list-device-sessions",
|
|
48
|
+
// Passkey
|
|
49
|
+
"/passkey/list-user-passkeys",
|
|
50
|
+
// API Key
|
|
51
|
+
"/api-key/get",
|
|
52
|
+
"/api-key/list"
|
|
53
|
+
];
|
|
54
|
+
function getAllowlist(options) {
|
|
55
|
+
if (options.mode === "custom") {
|
|
56
|
+
return options.allowlist;
|
|
57
|
+
}
|
|
58
|
+
const exclude = new Set(options.excludePaths ?? []);
|
|
59
|
+
const base = exclude.size > 0 ? DEFAULT_ALLOWLIST.filter((p) => !exclude.has(p)) : DEFAULT_ALLOWLIST;
|
|
60
|
+
return [...base, ...options.includePaths ?? []];
|
|
61
|
+
}
|
|
62
|
+
function isAllowlisted(path, allowlist) {
|
|
63
|
+
return allowlist.some(
|
|
64
|
+
(allowed) => path === allowed || path.endsWith(allowed) || path.includes(allowed + "/")
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/fetch-plugin.ts
|
|
69
|
+
function extractPath(urlOrPath) {
|
|
70
|
+
try {
|
|
71
|
+
const url = typeof urlOrPath === "string" && urlOrPath.startsWith("http") ? new URL(urlOrPath) : null;
|
|
72
|
+
if (url) return url.pathname;
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
const str = typeof urlOrPath === "string" ? urlOrPath : urlOrPath.pathname;
|
|
76
|
+
const qIndex = str.indexOf("?");
|
|
77
|
+
return qIndex >= 0 ? str.slice(0, qIndex) : str;
|
|
78
|
+
}
|
|
79
|
+
function isNetworkError(error) {
|
|
80
|
+
if (error instanceof TypeError) return true;
|
|
81
|
+
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function createOfflineFetchPlugin(storage, options) {
|
|
85
|
+
const allowlist = getAllowlist(options);
|
|
86
|
+
return {
|
|
87
|
+
id: "better-auth-offline",
|
|
88
|
+
name: "better-auth-offline",
|
|
89
|
+
init(url, fetchOptions) {
|
|
90
|
+
const originalFetch = fetchOptions?.customFetchImpl ?? globalThis.fetch;
|
|
91
|
+
const method = (fetchOptions?.method ?? "GET").toUpperCase();
|
|
92
|
+
const path = extractPath(url);
|
|
93
|
+
const shouldCache = method === "GET" && isAllowlisted(path, allowlist);
|
|
94
|
+
const wrappedFetch = async (input, init) => {
|
|
95
|
+
if (!shouldCache) {
|
|
96
|
+
return originalFetch(input, init);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const response = await originalFetch(input, init);
|
|
100
|
+
if (response.ok) {
|
|
101
|
+
const clone = response.clone();
|
|
102
|
+
clone.json().then((data) => {
|
|
103
|
+
const entry = { data, cachedAt: Date.now() };
|
|
104
|
+
storage.set(path, entry);
|
|
105
|
+
}).catch(() => {
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return response;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (isNetworkError(error)) {
|
|
111
|
+
const cached = await storage.get(path);
|
|
112
|
+
if (cached) {
|
|
113
|
+
return new Response(JSON.stringify(cached.data), {
|
|
114
|
+
status: 200,
|
|
115
|
+
headers: {
|
|
116
|
+
"Content-Type": "application/json",
|
|
117
|
+
"X-Offline-Cache": "true"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
if (fetchOptions) {
|
|
126
|
+
fetchOptions.customFetchImpl = wrappedFetch;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
url,
|
|
130
|
+
options: fetchOptions ?? { customFetchImpl: wrappedFetch }
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/online-status.ts
|
|
137
|
+
var import_nanostores = require("nanostores");
|
|
138
|
+
function createOnlineStatusAtom() {
|
|
139
|
+
const isOnline = (0, import_nanostores.atom)(
|
|
140
|
+
typeof navigator !== "undefined" && typeof navigator.onLine === "boolean" ? navigator.onLine : true
|
|
141
|
+
);
|
|
142
|
+
if (typeof window !== "undefined") {
|
|
143
|
+
window.addEventListener("online", () => isOnline.set(true));
|
|
144
|
+
window.addEventListener("offline", () => isOnline.set(false));
|
|
145
|
+
}
|
|
146
|
+
return isOnline;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/adapters/indexeddb.ts
|
|
150
|
+
var DEFAULT_DB_NAME = "better-auth-offline";
|
|
151
|
+
var STORE_NAME = "cache";
|
|
152
|
+
function createIndexedDBAdapter(dbName = DEFAULT_DB_NAME) {
|
|
153
|
+
let dbPromise = null;
|
|
154
|
+
function openDB() {
|
|
155
|
+
if (dbPromise) return dbPromise;
|
|
156
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
157
|
+
const request = indexedDB.open(dbName, 1);
|
|
158
|
+
request.onupgradeneeded = () => {
|
|
159
|
+
const db = request.result;
|
|
160
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
161
|
+
db.createObjectStore(STORE_NAME);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
request.onsuccess = () => resolve(request.result);
|
|
165
|
+
request.onerror = () => {
|
|
166
|
+
dbPromise = null;
|
|
167
|
+
reject(request.error);
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
return dbPromise;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
async get(key) {
|
|
174
|
+
try {
|
|
175
|
+
const db = await openDB();
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const tx = db.transaction(STORE_NAME, "readonly");
|
|
178
|
+
const store = tx.objectStore(STORE_NAME);
|
|
179
|
+
const request = store.get(key);
|
|
180
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
181
|
+
request.onerror = () => reject(request.error);
|
|
182
|
+
});
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
async set(key, value) {
|
|
188
|
+
try {
|
|
189
|
+
const db = await openDB();
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
192
|
+
const store = tx.objectStore(STORE_NAME);
|
|
193
|
+
const request = store.put(value, key);
|
|
194
|
+
request.onsuccess = () => resolve();
|
|
195
|
+
request.onerror = () => reject(request.error);
|
|
196
|
+
});
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
async delete(key) {
|
|
201
|
+
try {
|
|
202
|
+
const db = await openDB();
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
205
|
+
const store = tx.objectStore(STORE_NAME);
|
|
206
|
+
const request = store.delete(key);
|
|
207
|
+
request.onsuccess = () => resolve();
|
|
208
|
+
request.onerror = () => reject(request.error);
|
|
209
|
+
});
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
async clear() {
|
|
214
|
+
try {
|
|
215
|
+
const db = await openDB();
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
218
|
+
const store = tx.objectStore(STORE_NAME);
|
|
219
|
+
const request = store.clear();
|
|
220
|
+
request.onsuccess = () => resolve();
|
|
221
|
+
request.onerror = () => reject(request.error);
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/index.ts
|
|
230
|
+
var SIGN_OUT_PATHS = ["/sign-out", "/signout", "/logout"];
|
|
231
|
+
var SIGN_IN_PATHS = ["/sign-in", "/signin", "/login"];
|
|
232
|
+
function isAuthChangePath(path) {
|
|
233
|
+
return SIGN_OUT_PATHS.some((p) => path.includes(p)) || SIGN_IN_PATHS.some((p) => path.includes(p));
|
|
234
|
+
}
|
|
235
|
+
function offlinePlugin(options = {}) {
|
|
236
|
+
const storage = options.storage ?? createIndexedDBAdapter();
|
|
237
|
+
return {
|
|
238
|
+
id: "better-auth-offline",
|
|
239
|
+
getAtoms() {
|
|
240
|
+
return {
|
|
241
|
+
onlineStatus: createOnlineStatusAtom()
|
|
242
|
+
};
|
|
243
|
+
},
|
|
244
|
+
fetchPlugins: [createOfflineFetchPlugin(storage, options)],
|
|
245
|
+
atomListeners: [
|
|
246
|
+
{
|
|
247
|
+
matcher(path) {
|
|
248
|
+
return isAuthChangePath(path);
|
|
249
|
+
},
|
|
250
|
+
signal: "$sessionSignal",
|
|
251
|
+
callback() {
|
|
252
|
+
storage.clear();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
getActions() {
|
|
257
|
+
return {
|
|
258
|
+
clearCache: () => storage.clear()
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
264
|
+
0 && (module.exports = {
|
|
265
|
+
createIndexedDBAdapter,
|
|
266
|
+
createOnlineStatusAtom,
|
|
267
|
+
offlinePlugin
|
|
268
|
+
});
|
|
269
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/allowlist.ts","../src/fetch-plugin.ts","../src/online-status.ts","../src/adapters/indexeddb.ts"],"sourcesContent":["import type { BetterAuthClientPlugin } from \"better-auth/client\";\nimport type { OfflinePluginOptions, StorageAdapter } from \"./types.js\";\nimport { createOfflineFetchPlugin } from \"./fetch-plugin.js\";\nimport { createOnlineStatusAtom } from \"./online-status.js\";\nimport { createIndexedDBAdapter } from \"./adapters/indexeddb.js\";\n\n// Re-export types for consumers\nexport type { StorageAdapter, OfflinePluginOptions, CacheEntry } from \"./types.js\";\nexport { createIndexedDBAdapter } from \"./adapters/indexeddb.js\";\nexport { createOnlineStatusAtom } from \"./online-status.js\";\n\nconst SIGN_OUT_PATHS = [\"/sign-out\", \"/signout\", \"/logout\"];\nconst SIGN_IN_PATHS = [\"/sign-in\", \"/signin\", \"/login\"];\n\nfunction isAuthChangePath(path: string): boolean {\n return (\n SIGN_OUT_PATHS.some((p) => path.includes(p)) ||\n SIGN_IN_PATHS.some((p) => path.includes(p))\n );\n}\n\n/**\n * better-auth offline plugin.\n *\n * Transparently caches GET API responses and serves them when offline.\n * Drop-in: no consumer code changes required.\n *\n * @example\n * ```ts\n * import { createAuthClient } from \"better-auth/client\";\n * import { offlinePlugin } from \"better-auth-offline\";\n *\n * const authClient = createAuthClient({\n * plugins: [offlinePlugin()],\n * });\n * ```\n */\nexport function offlinePlugin(\n options: OfflinePluginOptions = {},\n): BetterAuthClientPlugin {\n const storage: StorageAdapter =\n options.storage ?? createIndexedDBAdapter();\n\n return {\n id: \"better-auth-offline\",\n\n getAtoms() {\n return {\n onlineStatus: createOnlineStatusAtom(),\n };\n },\n\n fetchPlugins: [createOfflineFetchPlugin(storage, options)],\n\n atomListeners: [\n {\n matcher(path: string) {\n return isAuthChangePath(path);\n },\n signal: \"$sessionSignal\",\n callback() {\n // Clear cache on sign-out / sign-in to prevent cross-user data leaks\n storage.clear();\n },\n },\n ],\n\n getActions() {\n return {\n clearCache: () => storage.clear(),\n };\n },\n };\n}\n","import type { OfflinePluginOptions } from \"./types.js\";\n\n/**\n * Default paths that are cached for offline access.\n * Only GET requests to these paths will be cached.\n */\nconst DEFAULT_ALLOWLIST = [\n // Core\n \"/get-session\",\n \"/list-sessions\",\n \"/list-accounts\",\n \"/account-info\",\n // Organization\n \"/organization/list\",\n \"/organization/get-active-member\",\n \"/organization/get-active-member-role\",\n \"/organization/get-full-organization\",\n \"/organization/list-members\",\n \"/organization/list-teams\",\n \"/organization/list-invitations\",\n // Admin\n \"/admin/list-users\",\n // Multi-session\n \"/multi-session/list-device-sessions\",\n // Passkey\n \"/passkey/list-user-passkeys\",\n // API Key\n \"/api-key/get\",\n \"/api-key/list\",\n];\n\n/**\n * Get the effective allowlist based on plugin options.\n */\nexport function getAllowlist(options: OfflinePluginOptions): string[] {\n if (options.mode === \"custom\") {\n return options.allowlist;\n }\n const exclude = new Set(options.excludePaths ?? []);\n const base = exclude.size > 0\n ? DEFAULT_ALLOWLIST.filter((p) => !exclude.has(p))\n : DEFAULT_ALLOWLIST;\n return [...base, ...(options.includePaths ?? [])];\n}\n\n/**\n * Check if a path should be cached.\n * Uses suffix matching to handle configurable base path prefixes.\n */\nexport function isAllowlisted(\n path: string,\n allowlist: string[],\n): boolean {\n return allowlist.some(\n (allowed) =>\n path === allowed ||\n path.endsWith(allowed) ||\n path.includes(allowed + \"/\"),\n );\n}\n","import type { BetterFetchPlugin } from \"@better-fetch/fetch\";\nimport type { StorageAdapter, CacheEntry, OfflinePluginOptions } from \"./types.js\";\nimport { getAllowlist, isAllowlisted } from \"./allowlist.js\";\n\n/**\n * Extracts the pathname from a URL string, stripping query params.\n * Used as the cache key.\n */\nexport function extractPath(urlOrPath: string | URL): string {\n try {\n const url = typeof urlOrPath === \"string\" && urlOrPath.startsWith(\"http\")\n ? new URL(urlOrPath)\n : null;\n if (url) return url.pathname;\n } catch {\n // Not a valid URL, treat as path\n }\n // Strip query string from path\n const str = typeof urlOrPath === \"string\" ? urlOrPath : urlOrPath.pathname;\n const qIndex = str.indexOf(\"?\");\n return qIndex >= 0 ? str.slice(0, qIndex) : str;\n}\n\n/**\n * Checks if a network error is a connectivity failure (not an HTTP error).\n */\nfunction isNetworkError(error: unknown): boolean {\n if (error instanceof TypeError) return true;\n if (error instanceof DOMException && error.name === \"AbortError\") return true;\n return false;\n}\n\n/**\n * Creates the BetterFetchPlugin that provides offline caching.\n *\n * Strategy: Network-First with Cache Fallback\n * - Uses `init` to inject a custom fetch that wraps the real fetch\n * - On successful GET: caches the response body (fire-and-forget)\n * - On network error for GET: serves cached response if available\n * - Only allowlisted GET paths are cached; everything else passes through\n */\nexport function createOfflineFetchPlugin(\n storage: StorageAdapter,\n options: OfflinePluginOptions,\n): BetterFetchPlugin {\n const allowlist = getAllowlist(options);\n\n return {\n id: \"better-auth-offline\",\n name: \"better-auth-offline\",\n\n init(url, fetchOptions) {\n const originalFetch = fetchOptions?.customFetchImpl ?? globalThis.fetch;\n const method = (fetchOptions?.method ?? \"GET\").toUpperCase();\n const path = extractPath(url);\n const shouldCache = method === \"GET\" && isAllowlisted(path, allowlist);\n\n const wrappedFetch: typeof globalThis.fetch = async (input, init) => {\n if (!shouldCache) {\n return originalFetch(input, init);\n }\n\n try {\n const response = await originalFetch(input, init);\n\n // Cache successful GET responses (fire-and-forget)\n if (response.ok) {\n // Clone before reading body so the original response remains usable\n const clone = response.clone();\n clone.json().then((data) => {\n const entry: CacheEntry = { data, cachedAt: Date.now() };\n storage.set(path, entry);\n }).catch(() => {\n // Response wasn't JSON or read failed — skip caching\n });\n }\n\n return response;\n } catch (error) {\n // Network error — try serving from cache\n if (isNetworkError(error)) {\n const cached = await storage.get(path) as CacheEntry | null;\n if (cached) {\n return new Response(JSON.stringify(cached.data), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Offline-Cache\": \"true\",\n },\n });\n }\n }\n // No cache hit or not a network error — rethrow\n throw error;\n }\n };\n\n // Mutate the original options object directly rather than spreading\n // into a new one. @better-fetch/fetch's initializePlugins passes the\n // same `options` reference to every plugin's init(). If we return a\n // new object, a later plugin (e.g. applySchemaPlugin) that returns\n // the original reference will overwrite our customFetchImpl, silently\n // disabling the offline cache. By mutating in place, the wrapping\n // survives regardless of plugin ordering.\n if (fetchOptions) {\n fetchOptions.customFetchImpl = wrappedFetch;\n }\n\n return {\n url,\n options: fetchOptions ?? { customFetchImpl: wrappedFetch },\n };\n },\n };\n}\n","import { atom } from \"nanostores\";\n\n/**\n * Creates a reactive atom that tracks the browser's online/offline status.\n * Defaults to `true` in non-browser environments (SSR).\n */\nexport function createOnlineStatusAtom() {\n const isOnline = atom<boolean>(\n typeof navigator !== \"undefined\" && typeof navigator.onLine === \"boolean\"\n ? navigator.onLine\n : true,\n );\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"online\", () => isOnline.set(true));\n window.addEventListener(\"offline\", () => isOnline.set(false));\n }\n\n return isOnline;\n}\n","import type { StorageAdapter, CacheEntry } from \"../types.js\";\n\nconst DEFAULT_DB_NAME = \"better-auth-offline\";\nconst STORE_NAME = \"cache\";\n\n/**\n * Creates an IndexedDB-backed storage adapter.\n *\n * @param dbName - Name of the IndexedDB database. Defaults to \"better-auth-offline\".\n */\nexport function createIndexedDBAdapter(\n dbName: string = DEFAULT_DB_NAME,\n): StorageAdapter {\n let dbPromise: Promise<IDBDatabase> | null = null;\n\n function openDB(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n const request = indexedDB.open(dbName, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME);\n }\n };\n\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => {\n dbPromise = null;\n reject(request.error);\n };\n });\n\n return dbPromise;\n }\n\n return {\n async get(key: string): Promise<CacheEntry | null> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readonly\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(key);\n request.onsuccess = () => resolve(request.result ?? null);\n request.onerror = () => reject(request.error);\n });\n } catch {\n return null;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.put(value, key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Fire-and-forget: swallow errors for cache writes\n }\n },\n\n async delete(key: string): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.delete(key);\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Swallow errors\n }\n },\n\n async clear(): Promise<void> {\n try {\n const db = await openDB();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, \"readwrite\");\n const store = tx.objectStore(STORE_NAME);\n const request = store.clear();\n request.onsuccess = () => resolve();\n request.onerror = () => reject(request.error);\n });\n } catch {\n // Swallow errors\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BetterAuthClientPlugin } from 'better-auth/client';
|
|
2
|
+
import { O as OfflinePluginOptions } from './indexeddb-CkUWH5Sl.cjs';
|
|
3
|
+
export { C as CacheEntry, S as StorageAdapter, c as createIndexedDBAdapter } from './indexeddb-CkUWH5Sl.cjs';
|
|
4
|
+
import * as nanostores from 'nanostores';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a reactive atom that tracks the browser's online/offline status.
|
|
8
|
+
* Defaults to `true` in non-browser environments (SSR).
|
|
9
|
+
*/
|
|
10
|
+
declare function createOnlineStatusAtom(): nanostores.PreinitializedWritableAtom<boolean> & object;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* better-auth offline plugin.
|
|
14
|
+
*
|
|
15
|
+
* Transparently caches GET API responses and serves them when offline.
|
|
16
|
+
* Drop-in: no consumer code changes required.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { createAuthClient } from "better-auth/client";
|
|
21
|
+
* import { offlinePlugin } from "better-auth-offline";
|
|
22
|
+
*
|
|
23
|
+
* const authClient = createAuthClient({
|
|
24
|
+
* plugins: [offlinePlugin()],
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function offlinePlugin(options?: OfflinePluginOptions): BetterAuthClientPlugin;
|
|
29
|
+
|
|
30
|
+
export { OfflinePluginOptions, createOnlineStatusAtom, offlinePlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BetterAuthClientPlugin } from 'better-auth/client';
|
|
2
|
+
import { O as OfflinePluginOptions } from './indexeddb-CkUWH5Sl.js';
|
|
3
|
+
export { C as CacheEntry, S as StorageAdapter, c as createIndexedDBAdapter } from './indexeddb-CkUWH5Sl.js';
|
|
4
|
+
import * as nanostores from 'nanostores';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a reactive atom that tracks the browser's online/offline status.
|
|
8
|
+
* Defaults to `true` in non-browser environments (SSR).
|
|
9
|
+
*/
|
|
10
|
+
declare function createOnlineStatusAtom(): nanostores.PreinitializedWritableAtom<boolean> & object;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* better-auth offline plugin.
|
|
14
|
+
*
|
|
15
|
+
* Transparently caches GET API responses and serves them when offline.
|
|
16
|
+
* Drop-in: no consumer code changes required.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { createAuthClient } from "better-auth/client";
|
|
21
|
+
* import { offlinePlugin } from "better-auth-offline";
|
|
22
|
+
*
|
|
23
|
+
* const authClient = createAuthClient({
|
|
24
|
+
* plugins: [offlinePlugin()],
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function offlinePlugin(options?: OfflinePluginOptions): BetterAuthClientPlugin;
|
|
29
|
+
|
|
30
|
+
export { OfflinePluginOptions, createOnlineStatusAtom, offlinePlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createIndexedDBAdapter
|
|
3
|
+
} from "./chunk-JPTDSCSW.js";
|
|
4
|
+
|
|
5
|
+
// src/allowlist.ts
|
|
6
|
+
var DEFAULT_ALLOWLIST = [
|
|
7
|
+
// Core
|
|
8
|
+
"/get-session",
|
|
9
|
+
"/list-sessions",
|
|
10
|
+
"/list-accounts",
|
|
11
|
+
"/account-info",
|
|
12
|
+
// Organization
|
|
13
|
+
"/organization/list",
|
|
14
|
+
"/organization/get-active-member",
|
|
15
|
+
"/organization/get-active-member-role",
|
|
16
|
+
"/organization/get-full-organization",
|
|
17
|
+
"/organization/list-members",
|
|
18
|
+
"/organization/list-teams",
|
|
19
|
+
"/organization/list-invitations",
|
|
20
|
+
// Admin
|
|
21
|
+
"/admin/list-users",
|
|
22
|
+
// Multi-session
|
|
23
|
+
"/multi-session/list-device-sessions",
|
|
24
|
+
// Passkey
|
|
25
|
+
"/passkey/list-user-passkeys",
|
|
26
|
+
// API Key
|
|
27
|
+
"/api-key/get",
|
|
28
|
+
"/api-key/list"
|
|
29
|
+
];
|
|
30
|
+
function getAllowlist(options) {
|
|
31
|
+
if (options.mode === "custom") {
|
|
32
|
+
return options.allowlist;
|
|
33
|
+
}
|
|
34
|
+
const exclude = new Set(options.excludePaths ?? []);
|
|
35
|
+
const base = exclude.size > 0 ? DEFAULT_ALLOWLIST.filter((p) => !exclude.has(p)) : DEFAULT_ALLOWLIST;
|
|
36
|
+
return [...base, ...options.includePaths ?? []];
|
|
37
|
+
}
|
|
38
|
+
function isAllowlisted(path, allowlist) {
|
|
39
|
+
return allowlist.some(
|
|
40
|
+
(allowed) => path === allowed || path.endsWith(allowed) || path.includes(allowed + "/")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/fetch-plugin.ts
|
|
45
|
+
function extractPath(urlOrPath) {
|
|
46
|
+
try {
|
|
47
|
+
const url = typeof urlOrPath === "string" && urlOrPath.startsWith("http") ? new URL(urlOrPath) : null;
|
|
48
|
+
if (url) return url.pathname;
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
const str = typeof urlOrPath === "string" ? urlOrPath : urlOrPath.pathname;
|
|
52
|
+
const qIndex = str.indexOf("?");
|
|
53
|
+
return qIndex >= 0 ? str.slice(0, qIndex) : str;
|
|
54
|
+
}
|
|
55
|
+
function isNetworkError(error) {
|
|
56
|
+
if (error instanceof TypeError) return true;
|
|
57
|
+
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function createOfflineFetchPlugin(storage, options) {
|
|
61
|
+
const allowlist = getAllowlist(options);
|
|
62
|
+
return {
|
|
63
|
+
id: "better-auth-offline",
|
|
64
|
+
name: "better-auth-offline",
|
|
65
|
+
init(url, fetchOptions) {
|
|
66
|
+
const originalFetch = fetchOptions?.customFetchImpl ?? globalThis.fetch;
|
|
67
|
+
const method = (fetchOptions?.method ?? "GET").toUpperCase();
|
|
68
|
+
const path = extractPath(url);
|
|
69
|
+
const shouldCache = method === "GET" && isAllowlisted(path, allowlist);
|
|
70
|
+
const wrappedFetch = async (input, init) => {
|
|
71
|
+
if (!shouldCache) {
|
|
72
|
+
return originalFetch(input, init);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const response = await originalFetch(input, init);
|
|
76
|
+
if (response.ok) {
|
|
77
|
+
const clone = response.clone();
|
|
78
|
+
clone.json().then((data) => {
|
|
79
|
+
const entry = { data, cachedAt: Date.now() };
|
|
80
|
+
storage.set(path, entry);
|
|
81
|
+
}).catch(() => {
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return response;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (isNetworkError(error)) {
|
|
87
|
+
const cached = await storage.get(path);
|
|
88
|
+
if (cached) {
|
|
89
|
+
return new Response(JSON.stringify(cached.data), {
|
|
90
|
+
status: 200,
|
|
91
|
+
headers: {
|
|
92
|
+
"Content-Type": "application/json",
|
|
93
|
+
"X-Offline-Cache": "true"
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
if (fetchOptions) {
|
|
102
|
+
fetchOptions.customFetchImpl = wrappedFetch;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
url,
|
|
106
|
+
options: fetchOptions ?? { customFetchImpl: wrappedFetch }
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/online-status.ts
|
|
113
|
+
import { atom } from "nanostores";
|
|
114
|
+
function createOnlineStatusAtom() {
|
|
115
|
+
const isOnline = atom(
|
|
116
|
+
typeof navigator !== "undefined" && typeof navigator.onLine === "boolean" ? navigator.onLine : true
|
|
117
|
+
);
|
|
118
|
+
if (typeof window !== "undefined") {
|
|
119
|
+
window.addEventListener("online", () => isOnline.set(true));
|
|
120
|
+
window.addEventListener("offline", () => isOnline.set(false));
|
|
121
|
+
}
|
|
122
|
+
return isOnline;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/index.ts
|
|
126
|
+
var SIGN_OUT_PATHS = ["/sign-out", "/signout", "/logout"];
|
|
127
|
+
var SIGN_IN_PATHS = ["/sign-in", "/signin", "/login"];
|
|
128
|
+
function isAuthChangePath(path) {
|
|
129
|
+
return SIGN_OUT_PATHS.some((p) => path.includes(p)) || SIGN_IN_PATHS.some((p) => path.includes(p));
|
|
130
|
+
}
|
|
131
|
+
function offlinePlugin(options = {}) {
|
|
132
|
+
const storage = options.storage ?? createIndexedDBAdapter();
|
|
133
|
+
return {
|
|
134
|
+
id: "better-auth-offline",
|
|
135
|
+
getAtoms() {
|
|
136
|
+
return {
|
|
137
|
+
onlineStatus: createOnlineStatusAtom()
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
fetchPlugins: [createOfflineFetchPlugin(storage, options)],
|
|
141
|
+
atomListeners: [
|
|
142
|
+
{
|
|
143
|
+
matcher(path) {
|
|
144
|
+
return isAuthChangePath(path);
|
|
145
|
+
},
|
|
146
|
+
signal: "$sessionSignal",
|
|
147
|
+
callback() {
|
|
148
|
+
storage.clear();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
getActions() {
|
|
153
|
+
return {
|
|
154
|
+
clearCache: () => storage.clear()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
createIndexedDBAdapter,
|
|
161
|
+
createOnlineStatusAtom,
|
|
162
|
+
offlinePlugin
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/allowlist.ts","../src/fetch-plugin.ts","../src/online-status.ts","../src/index.ts"],"sourcesContent":["import type { OfflinePluginOptions } from \"./types.js\";\n\n/**\n * Default paths that are cached for offline access.\n * Only GET requests to these paths will be cached.\n */\nconst DEFAULT_ALLOWLIST = [\n // Core\n \"/get-session\",\n \"/list-sessions\",\n \"/list-accounts\",\n \"/account-info\",\n // Organization\n \"/organization/list\",\n \"/organization/get-active-member\",\n \"/organization/get-active-member-role\",\n \"/organization/get-full-organization\",\n \"/organization/list-members\",\n \"/organization/list-teams\",\n \"/organization/list-invitations\",\n // Admin\n \"/admin/list-users\",\n // Multi-session\n \"/multi-session/list-device-sessions\",\n // Passkey\n \"/passkey/list-user-passkeys\",\n // API Key\n \"/api-key/get\",\n \"/api-key/list\",\n];\n\n/**\n * Get the effective allowlist based on plugin options.\n */\nexport function getAllowlist(options: OfflinePluginOptions): string[] {\n if (options.mode === \"custom\") {\n return options.allowlist;\n }\n const exclude = new Set(options.excludePaths ?? []);\n const base = exclude.size > 0\n ? DEFAULT_ALLOWLIST.filter((p) => !exclude.has(p))\n : DEFAULT_ALLOWLIST;\n return [...base, ...(options.includePaths ?? [])];\n}\n\n/**\n * Check if a path should be cached.\n * Uses suffix matching to handle configurable base path prefixes.\n */\nexport function isAllowlisted(\n path: string,\n allowlist: string[],\n): boolean {\n return allowlist.some(\n (allowed) =>\n path === allowed ||\n path.endsWith(allowed) ||\n path.includes(allowed + \"/\"),\n );\n}\n","import type { BetterFetchPlugin } from \"@better-fetch/fetch\";\nimport type { StorageAdapter, CacheEntry, OfflinePluginOptions } from \"./types.js\";\nimport { getAllowlist, isAllowlisted } from \"./allowlist.js\";\n\n/**\n * Extracts the pathname from a URL string, stripping query params.\n * Used as the cache key.\n */\nexport function extractPath(urlOrPath: string | URL): string {\n try {\n const url = typeof urlOrPath === \"string\" && urlOrPath.startsWith(\"http\")\n ? new URL(urlOrPath)\n : null;\n if (url) return url.pathname;\n } catch {\n // Not a valid URL, treat as path\n }\n // Strip query string from path\n const str = typeof urlOrPath === \"string\" ? urlOrPath : urlOrPath.pathname;\n const qIndex = str.indexOf(\"?\");\n return qIndex >= 0 ? str.slice(0, qIndex) : str;\n}\n\n/**\n * Checks if a network error is a connectivity failure (not an HTTP error).\n */\nfunction isNetworkError(error: unknown): boolean {\n if (error instanceof TypeError) return true;\n if (error instanceof DOMException && error.name === \"AbortError\") return true;\n return false;\n}\n\n/**\n * Creates the BetterFetchPlugin that provides offline caching.\n *\n * Strategy: Network-First with Cache Fallback\n * - Uses `init` to inject a custom fetch that wraps the real fetch\n * - On successful GET: caches the response body (fire-and-forget)\n * - On network error for GET: serves cached response if available\n * - Only allowlisted GET paths are cached; everything else passes through\n */\nexport function createOfflineFetchPlugin(\n storage: StorageAdapter,\n options: OfflinePluginOptions,\n): BetterFetchPlugin {\n const allowlist = getAllowlist(options);\n\n return {\n id: \"better-auth-offline\",\n name: \"better-auth-offline\",\n\n init(url, fetchOptions) {\n const originalFetch = fetchOptions?.customFetchImpl ?? globalThis.fetch;\n const method = (fetchOptions?.method ?? \"GET\").toUpperCase();\n const path = extractPath(url);\n const shouldCache = method === \"GET\" && isAllowlisted(path, allowlist);\n\n const wrappedFetch: typeof globalThis.fetch = async (input, init) => {\n if (!shouldCache) {\n return originalFetch(input, init);\n }\n\n try {\n const response = await originalFetch(input, init);\n\n // Cache successful GET responses (fire-and-forget)\n if (response.ok) {\n // Clone before reading body so the original response remains usable\n const clone = response.clone();\n clone.json().then((data) => {\n const entry: CacheEntry = { data, cachedAt: Date.now() };\n storage.set(path, entry);\n }).catch(() => {\n // Response wasn't JSON or read failed — skip caching\n });\n }\n\n return response;\n } catch (error) {\n // Network error — try serving from cache\n if (isNetworkError(error)) {\n const cached = await storage.get(path) as CacheEntry | null;\n if (cached) {\n return new Response(JSON.stringify(cached.data), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Offline-Cache\": \"true\",\n },\n });\n }\n }\n // No cache hit or not a network error — rethrow\n throw error;\n }\n };\n\n // Mutate the original options object directly rather than spreading\n // into a new one. @better-fetch/fetch's initializePlugins passes the\n // same `options` reference to every plugin's init(). If we return a\n // new object, a later plugin (e.g. applySchemaPlugin) that returns\n // the original reference will overwrite our customFetchImpl, silently\n // disabling the offline cache. By mutating in place, the wrapping\n // survives regardless of plugin ordering.\n if (fetchOptions) {\n fetchOptions.customFetchImpl = wrappedFetch;\n }\n\n return {\n url,\n options: fetchOptions ?? { customFetchImpl: wrappedFetch },\n };\n },\n };\n}\n","import { atom } from \"nanostores\";\n\n/**\n * Creates a reactive atom that tracks the browser's online/offline status.\n * Defaults to `true` in non-browser environments (SSR).\n */\nexport function createOnlineStatusAtom() {\n const isOnline = atom<boolean>(\n typeof navigator !== \"undefined\" && typeof navigator.onLine === \"boolean\"\n ? navigator.onLine\n : true,\n );\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"online\", () => isOnline.set(true));\n window.addEventListener(\"offline\", () => isOnline.set(false));\n }\n\n return isOnline;\n}\n","import type { BetterAuthClientPlugin } from \"better-auth/client\";\nimport type { OfflinePluginOptions, StorageAdapter } from \"./types.js\";\nimport { createOfflineFetchPlugin } from \"./fetch-plugin.js\";\nimport { createOnlineStatusAtom } from \"./online-status.js\";\nimport { createIndexedDBAdapter } from \"./adapters/indexeddb.js\";\n\n// Re-export types for consumers\nexport type { StorageAdapter, OfflinePluginOptions, CacheEntry } from \"./types.js\";\nexport { createIndexedDBAdapter } from \"./adapters/indexeddb.js\";\nexport { createOnlineStatusAtom } from \"./online-status.js\";\n\nconst SIGN_OUT_PATHS = [\"/sign-out\", \"/signout\", \"/logout\"];\nconst SIGN_IN_PATHS = [\"/sign-in\", \"/signin\", \"/login\"];\n\nfunction isAuthChangePath(path: string): boolean {\n return (\n SIGN_OUT_PATHS.some((p) => path.includes(p)) ||\n SIGN_IN_PATHS.some((p) => path.includes(p))\n );\n}\n\n/**\n * better-auth offline plugin.\n *\n * Transparently caches GET API responses and serves them when offline.\n * Drop-in: no consumer code changes required.\n *\n * @example\n * ```ts\n * import { createAuthClient } from \"better-auth/client\";\n * import { offlinePlugin } from \"better-auth-offline\";\n *\n * const authClient = createAuthClient({\n * plugins: [offlinePlugin()],\n * });\n * ```\n */\nexport function offlinePlugin(\n options: OfflinePluginOptions = {},\n): BetterAuthClientPlugin {\n const storage: StorageAdapter =\n options.storage ?? createIndexedDBAdapter();\n\n return {\n id: \"better-auth-offline\",\n\n getAtoms() {\n return {\n onlineStatus: createOnlineStatusAtom(),\n };\n },\n\n fetchPlugins: [createOfflineFetchPlugin(storage, options)],\n\n atomListeners: [\n {\n matcher(path: string) {\n return isAuthChangePath(path);\n },\n signal: \"$sessionSignal\",\n callback() {\n // Clear cache on sign-out / sign-in to prevent cross-user data leaks\n storage.clear();\n },\n },\n ],\n\n getActions() {\n return {\n clearCache: () => storage.clear(),\n };\n },\n };\n}\n"],"mappings":";;;;;AAMA,IAAM,oBAAoB;AAAA;AAAA,EAExB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AACF;AAKO,SAAS,aAAa,SAAyC;AACpE,MAAI,QAAQ,SAAS,UAAU;AAC7B,WAAO,QAAQ;AAAA,EACjB;AACA,QAAM,UAAU,IAAI,IAAI,QAAQ,gBAAgB,CAAC,CAAC;AAClD,QAAM,OAAO,QAAQ,OAAO,IACxB,kBAAkB,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC,IAC/C;AACJ,SAAO,CAAC,GAAG,MAAM,GAAI,QAAQ,gBAAgB,CAAC,CAAE;AAClD;AAMO,SAAS,cACd,MACA,WACS;AACT,SAAO,UAAU;AAAA,IACf,CAAC,YACC,SAAS,WACT,KAAK,SAAS,OAAO,KACrB,KAAK,SAAS,UAAU,GAAG;AAAA,EAC/B;AACF;;;ACnDO,SAAS,YAAY,WAAiC;AAC3D,MAAI;AACF,UAAM,MAAM,OAAO,cAAc,YAAY,UAAU,WAAW,MAAM,IACpE,IAAI,IAAI,SAAS,IACjB;AACJ,QAAI,IAAK,QAAO,IAAI;AAAA,EACtB,QAAQ;AAAA,EAER;AAEA,QAAM,MAAM,OAAO,cAAc,WAAW,YAAY,UAAU;AAClE,QAAM,SAAS,IAAI,QAAQ,GAAG;AAC9B,SAAO,UAAU,IAAI,IAAI,MAAM,GAAG,MAAM,IAAI;AAC9C;AAKA,SAAS,eAAe,OAAyB;AAC/C,MAAI,iBAAiB,UAAW,QAAO;AACvC,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AACzE,SAAO;AACT;AAWO,SAAS,yBACd,SACA,SACmB;AACnB,QAAM,YAAY,aAAa,OAAO;AAEtC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IAEN,KAAK,KAAK,cAAc;AACtB,YAAM,gBAAgB,cAAc,mBAAmB,WAAW;AAClE,YAAM,UAAU,cAAc,UAAU,OAAO,YAAY;AAC3D,YAAM,OAAO,YAAY,GAAG;AAC5B,YAAM,cAAc,WAAW,SAAS,cAAc,MAAM,SAAS;AAErE,YAAM,eAAwC,OAAO,OAAO,SAAS;AACnE,YAAI,CAAC,aAAa;AAChB,iBAAO,cAAc,OAAO,IAAI;AAAA,QAClC;AAEA,YAAI;AACF,gBAAM,WAAW,MAAM,cAAc,OAAO,IAAI;AAGhD,cAAI,SAAS,IAAI;AAEf,kBAAM,QAAQ,SAAS,MAAM;AAC7B,kBAAM,KAAK,EAAE,KAAK,CAAC,SAAS;AAC1B,oBAAM,QAAoB,EAAE,MAAM,UAAU,KAAK,IAAI,EAAE;AACvD,sBAAQ,IAAI,MAAM,KAAK;AAAA,YACzB,CAAC,EAAE,MAAM,MAAM;AAAA,YAEf,CAAC;AAAA,UACH;AAEA,iBAAO;AAAA,QACT,SAAS,OAAO;AAEd,cAAI,eAAe,KAAK,GAAG;AACzB,kBAAM,SAAS,MAAM,QAAQ,IAAI,IAAI;AACrC,gBAAI,QAAQ;AACV,qBAAO,IAAI,SAAS,KAAK,UAAU,OAAO,IAAI,GAAG;AAAA,gBAC/C,QAAQ;AAAA,gBACR,SAAS;AAAA,kBACP,gBAAgB;AAAA,kBAChB,mBAAmB;AAAA,gBACrB;AAAA,cACF,CAAC;AAAA,YACH;AAAA,UACF;AAEA,gBAAM;AAAA,QACR;AAAA,MACF;AASA,UAAI,cAAc;AAChB,qBAAa,kBAAkB;AAAA,MACjC;AAEA,aAAO;AAAA,QACL;AAAA,QACA,SAAS,gBAAgB,EAAE,iBAAiB,aAAa;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACF;;;AClHA,SAAS,YAAY;AAMd,SAAS,yBAAyB;AACvC,QAAM,WAAW;AAAA,IACf,OAAO,cAAc,eAAe,OAAO,UAAU,WAAW,YAC5D,UAAU,SACV;AAAA,EACN;AAEA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,iBAAiB,UAAU,MAAM,SAAS,IAAI,IAAI,CAAC;AAC1D,WAAO,iBAAiB,WAAW,MAAM,SAAS,IAAI,KAAK,CAAC;AAAA,EAC9D;AAEA,SAAO;AACT;;;ACRA,IAAM,iBAAiB,CAAC,aAAa,YAAY,SAAS;AAC1D,IAAM,gBAAgB,CAAC,YAAY,WAAW,QAAQ;AAEtD,SAAS,iBAAiB,MAAuB;AAC/C,SACE,eAAe,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,KAC3C,cAAc,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;AAE9C;AAkBO,SAAS,cACd,UAAgC,CAAC,GACT;AACxB,QAAM,UACJ,QAAQ,WAAW,uBAAuB;AAE5C,SAAO;AAAA,IACL,IAAI;AAAA,IAEJ,WAAW;AACT,aAAO;AAAA,QACL,cAAc,uBAAuB;AAAA,MACvC;AAAA,IACF;AAAA,IAEA,cAAc,CAAC,yBAAyB,SAAS,OAAO,CAAC;AAAA,IAEzD,eAAe;AAAA,MACb;AAAA,QACE,QAAQ,MAAc;AACpB,iBAAO,iBAAiB,IAAI;AAAA,QAC9B;AAAA,QACA,QAAQ;AAAA,QACR,WAAW;AAET,kBAAQ,MAAM;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,aAAa;AACX,aAAO;AAAA,QACL,YAAY,MAAM,QAAQ,MAAM;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface StorageAdapter {
|
|
2
|
+
get(key: string): Promise<unknown | null>;
|
|
3
|
+
set(key: string, value: unknown): Promise<void>;
|
|
4
|
+
delete(key: string): Promise<void>;
|
|
5
|
+
clear(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
interface BaseOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Custom storage adapter. Defaults to IndexedDB adapter.
|
|
10
|
+
*/
|
|
11
|
+
storage?: StorageAdapter;
|
|
12
|
+
}
|
|
13
|
+
interface DefaultAllowlistOptions extends BaseOptions {
|
|
14
|
+
mode?: "default";
|
|
15
|
+
/**
|
|
16
|
+
* Additional paths to cache (extends the default allowlist).
|
|
17
|
+
*/
|
|
18
|
+
includePaths?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Paths to remove from the default allowlist.
|
|
21
|
+
*/
|
|
22
|
+
excludePaths?: string[];
|
|
23
|
+
}
|
|
24
|
+
interface CustomAllowlistOptions extends BaseOptions {
|
|
25
|
+
mode: "custom";
|
|
26
|
+
/**
|
|
27
|
+
* Only these paths are cached (default allowlist is ignored).
|
|
28
|
+
*/
|
|
29
|
+
allowlist: string[];
|
|
30
|
+
}
|
|
31
|
+
type OfflinePluginOptions = DefaultAllowlistOptions | CustomAllowlistOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Shape of a cached response entry.
|
|
34
|
+
*/
|
|
35
|
+
interface CacheEntry {
|
|
36
|
+
data: unknown;
|
|
37
|
+
cachedAt: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates an IndexedDB-backed storage adapter.
|
|
42
|
+
*
|
|
43
|
+
* @param dbName - Name of the IndexedDB database. Defaults to "better-auth-offline".
|
|
44
|
+
*/
|
|
45
|
+
declare function createIndexedDBAdapter(dbName?: string): StorageAdapter;
|
|
46
|
+
|
|
47
|
+
export { type CacheEntry as C, type OfflinePluginOptions as O, type StorageAdapter as S, createIndexedDBAdapter as c };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface StorageAdapter {
|
|
2
|
+
get(key: string): Promise<unknown | null>;
|
|
3
|
+
set(key: string, value: unknown): Promise<void>;
|
|
4
|
+
delete(key: string): Promise<void>;
|
|
5
|
+
clear(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
interface BaseOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Custom storage adapter. Defaults to IndexedDB adapter.
|
|
10
|
+
*/
|
|
11
|
+
storage?: StorageAdapter;
|
|
12
|
+
}
|
|
13
|
+
interface DefaultAllowlistOptions extends BaseOptions {
|
|
14
|
+
mode?: "default";
|
|
15
|
+
/**
|
|
16
|
+
* Additional paths to cache (extends the default allowlist).
|
|
17
|
+
*/
|
|
18
|
+
includePaths?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Paths to remove from the default allowlist.
|
|
21
|
+
*/
|
|
22
|
+
excludePaths?: string[];
|
|
23
|
+
}
|
|
24
|
+
interface CustomAllowlistOptions extends BaseOptions {
|
|
25
|
+
mode: "custom";
|
|
26
|
+
/**
|
|
27
|
+
* Only these paths are cached (default allowlist is ignored).
|
|
28
|
+
*/
|
|
29
|
+
allowlist: string[];
|
|
30
|
+
}
|
|
31
|
+
type OfflinePluginOptions = DefaultAllowlistOptions | CustomAllowlistOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Shape of a cached response entry.
|
|
34
|
+
*/
|
|
35
|
+
interface CacheEntry {
|
|
36
|
+
data: unknown;
|
|
37
|
+
cachedAt: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates an IndexedDB-backed storage adapter.
|
|
42
|
+
*
|
|
43
|
+
* @param dbName - Name of the IndexedDB database. Defaults to "better-auth-offline".
|
|
44
|
+
*/
|
|
45
|
+
declare function createIndexedDBAdapter(dbName?: string): StorageAdapter;
|
|
46
|
+
|
|
47
|
+
export { type CacheEntry as C, type OfflinePluginOptions as O, type StorageAdapter as S, createIndexedDBAdapter as c };
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "better-auth-offline",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Caches better-auth API responses so your app keeps working when the network doesn't.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Roman Sirokov",
|
|
7
|
+
"email": "roman@mrlightful.com",
|
|
8
|
+
"url": "https://github.com/MrLightful"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/MrLightful/better-auth-offline.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/MrLightful/better-auth-offline",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/MrLightful/better-auth-offline/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"better-auth",
|
|
21
|
+
"offline",
|
|
22
|
+
"offline-first",
|
|
23
|
+
"local",
|
|
24
|
+
"local-first",
|
|
25
|
+
"pwa",
|
|
26
|
+
"cache",
|
|
27
|
+
"service-worker",
|
|
28
|
+
"authentication",
|
|
29
|
+
"auth",
|
|
30
|
+
"session"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js",
|
|
40
|
+
"require": "./dist/index.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./adapters": {
|
|
43
|
+
"types": "./dist/adapters/indexeddb.d.ts",
|
|
44
|
+
"import": "./dist/adapters/indexeddb.js",
|
|
45
|
+
"require": "./dist/adapters/indexeddb.cjs"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "bun run tsup",
|
|
53
|
+
"dev": "bun run tsup --watch",
|
|
54
|
+
"test": "bun run vitest run",
|
|
55
|
+
"test:watch": "bun run vitest",
|
|
56
|
+
"typecheck": "bun run tsc --noEmit"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"better-auth": ">=1.0.0"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"nanostores": "^1.2.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@better-fetch/fetch": "^1.1.21",
|
|
66
|
+
"better-auth": "^1.5.6",
|
|
67
|
+
"fake-indexeddb": "^6.2.5",
|
|
68
|
+
"tsup": "^8.5.1",
|
|
69
|
+
"typescript": "^5.9.3",
|
|
70
|
+
"vitest": "^3.2.4"
|
|
71
|
+
}
|
|
72
|
+
}
|