@raubjo/architect-core 0.1.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.
Files changed (43) hide show
  1. package/bun.lock +20 -0
  2. package/coverage/lcov.info +1078 -0
  3. package/package.json +43 -0
  4. package/src/cache/cache.ts +3 -0
  5. package/src/cache/manager.ts +115 -0
  6. package/src/config/app.ts +5 -0
  7. package/src/config/clone.ts +9 -0
  8. package/src/config/env.global.d.ts +5 -0
  9. package/src/config/env.ts +79 -0
  10. package/src/config/repository.ts +204 -0
  11. package/src/filesystem/adapters/local.ts +104 -0
  12. package/src/filesystem/filesystem.ts +21 -0
  13. package/src/foundation/application.ts +207 -0
  14. package/src/index.ts +33 -0
  15. package/src/rendering/adapters/react.tsx +27 -0
  16. package/src/rendering/renderer.ts +13 -0
  17. package/src/runtimes/react.tsx +22 -0
  18. package/src/storage/adapters/indexed-db.ts +180 -0
  19. package/src/storage/adapters/local-storage.ts +46 -0
  20. package/src/storage/adapters/memory.ts +35 -0
  21. package/src/storage/manager.ts +78 -0
  22. package/src/storage/storage.ts +8 -0
  23. package/src/support/facades/cache.ts +46 -0
  24. package/src/support/facades/config.ts +67 -0
  25. package/src/support/facades/facade.ts +42 -0
  26. package/src/support/facades/storage.ts +46 -0
  27. package/src/support/providers/config-service-provider.ts +19 -0
  28. package/src/support/service-provider.ts +25 -0
  29. package/src/support/str.ts +126 -0
  30. package/tests/application.test.ts +236 -0
  31. package/tests/cache-facade.test.ts +45 -0
  32. package/tests/cache.test.ts +68 -0
  33. package/tests/config-clone.test.ts +31 -0
  34. package/tests/config-env.test.ts +88 -0
  35. package/tests/config-facade.test.ts +96 -0
  36. package/tests/config-repository.test.ts +124 -0
  37. package/tests/facade-base.test.ts +80 -0
  38. package/tests/filesystem.test.ts +81 -0
  39. package/tests/runtime-react.test.tsx +37 -0
  40. package/tests/service-provider.test.ts +23 -0
  41. package/tests/storage-facade.test.ts +46 -0
  42. package/tests/storage.test.ts +264 -0
  43. package/tests/str.test.ts +73 -0
@@ -0,0 +1,207 @@
1
+ import CacheManager from "../cache/manager";
2
+ import { Container } from "inversify";
3
+ import appConfig from "../config/app";
4
+ import { cloneConfigItems } from "../config/clone";
5
+ import { registerGlobalEnv } from "../config/env";
6
+ import ConfigRepository, { type ConfigItems } from "../config/repository";
7
+ import { FileSystem } from "../filesystem/filesystem";
8
+ import LocalAdapter, { __localAdapterTesting } from "../filesystem/adapters/local";
9
+ import ReactRenderer from "../rendering/adapters/react";
10
+ import type { RendererAdapter, RootComponent } from "../rendering/renderer";
11
+ import StorageManager from "../storage/manager";
12
+ import ServiceProvider, {
13
+ type Cleanup,
14
+ type ServiceProviderContext,
15
+ } from "../support/service-provider";
16
+ import { registerGlobalStr } from "../support/str";
17
+ import Facade from "../support/facades/facade";
18
+
19
+ type StartupHandler = (context: ServiceProviderContext) => void | Cleanup;
20
+ type ServiceRegistrar = (context: ServiceProviderContext) => void | Cleanup;
21
+
22
+ registerGlobalEnv();
23
+ registerGlobalStr();
24
+
25
+ export const __applicationTesting = {
26
+ ...__localAdapterTesting,
27
+ };
28
+
29
+ export class Application {
30
+ protected static container: Container | null = null;
31
+ protected static configCache = new Map<string, ConfigItems>();
32
+ protected static fileSystem = new FileSystem(new LocalAdapter());
33
+
34
+ protected providers: ServiceProvider[];
35
+ protected serviceRegistrars: ServiceRegistrar[];
36
+ protected startupHandlers: StartupHandler[];
37
+ protected rootElementId: string;
38
+ protected RootComponent: RootComponent | null;
39
+ protected renderer: RendererAdapter | null;
40
+ protected basePath: string;
41
+
42
+ constructor(basePath = "./") {
43
+ this.basePath = basePath;
44
+ this.providers = this.getDefaultProviders();
45
+ this.serviceRegistrars = [];
46
+ this.startupHandlers = [];
47
+ this.rootElementId = "root";
48
+ this.RootComponent = null;
49
+ this.renderer = null;
50
+ }
51
+
52
+ protected getDefaultProviders(): ServiceProvider[] {
53
+ return [];
54
+ }
55
+
56
+ protected static getCachedConfigItems(basePath: string): ConfigItems {
57
+ const cacheKey = __localAdapterTesting.normalizeBasePath(basePath) || ".";
58
+
59
+ if (!Application.configCache.has(cacheKey)) {
60
+ const loaded = {
61
+ app: appConfig,
62
+ ...Application.fileSystem.loadConfigItems(basePath),
63
+ };
64
+ Application.configCache.set(cacheKey, loaded);
65
+ }
66
+
67
+ // Give each application instance its own mutable repository data.
68
+ return cloneConfigItems(Application.configCache.get(cacheKey) as ConfigItems);
69
+ }
70
+
71
+ static clearConfigCache(basePath?: string): void {
72
+ if (typeof basePath === "string") {
73
+ const cacheKey = __localAdapterTesting.normalizeBasePath(basePath) || ".";
74
+ Application.configCache.delete(cacheKey);
75
+ return;
76
+ }
77
+
78
+ Application.configCache.clear();
79
+ }
80
+
81
+ static configure(basePath = "./") {
82
+ return new Application(basePath);
83
+ }
84
+
85
+ static make<T>(identifier: Parameters<Container["get"]>[0]): T {
86
+ if (!Application.container) {
87
+ throw new Error("Application container is not available. Call run() first.");
88
+ }
89
+
90
+ return Application.container.get<T>(identifier);
91
+ }
92
+
93
+ withProviders(providers: ServiceProvider[]) {
94
+ this.providers.push(...providers);
95
+ return this;
96
+ }
97
+
98
+ withServices(registerServices: ServiceRegistrar) {
99
+ this.serviceRegistrars.push(registerServices);
100
+ return this;
101
+ }
102
+
103
+ withStartup(startupHandler: StartupHandler) {
104
+ this.startupHandlers.push(startupHandler);
105
+ return this;
106
+ }
107
+
108
+ withRoot(RootComponent: RootComponent, options: { rootElementId?: string } = {}) {
109
+ this.RootComponent = RootComponent;
110
+ this.rootElementId = options.rootElementId ?? this.rootElementId;
111
+ return this;
112
+ }
113
+
114
+ withRenderer(renderer: RendererAdapter) {
115
+ this.renderer = renderer;
116
+ return this;
117
+ }
118
+
119
+ run() {
120
+ const container = new Container({ defaultScope: "Singleton" });
121
+ Application.container = container;
122
+ Facade.clearResolvedInstances();
123
+ const context = { container };
124
+ const cleanupTasks: Cleanup[] = [];
125
+ const configRepository = new ConfigRepository(
126
+ Application.getCachedConfigItems(this.basePath),
127
+ );
128
+ const storageManager = StorageManager.fromConfig(configRepository);
129
+ const cacheManager = CacheManager.fromConfig(configRepository);
130
+
131
+ container.bind("config").toConstantValue(configRepository);
132
+ container.bind(ConfigRepository).toConstantValue(configRepository);
133
+ container.bind("storage").toConstantValue(storageManager);
134
+ container.bind(StorageManager).toConstantValue(storageManager);
135
+ container.bind("cache").toConstantValue(cacheManager);
136
+ container.bind(CacheManager).toConstantValue(cacheManager);
137
+
138
+ for (const provider of this.providers) {
139
+ if (typeof provider.register === "function") {
140
+ const cleanup = provider.register(context);
141
+ if (typeof cleanup === "function") {
142
+ cleanupTasks.push(cleanup);
143
+ }
144
+ }
145
+ }
146
+
147
+ for (const registerServices of this.serviceRegistrars) {
148
+ const cleanup = registerServices(context);
149
+ if (typeof cleanup === "function") {
150
+ cleanupTasks.push(cleanup);
151
+ }
152
+ }
153
+
154
+ for (const provider of this.providers) {
155
+ if (typeof provider.boot === "function") {
156
+ const cleanup = provider.boot(context);
157
+ if (typeof cleanup === "function") {
158
+ cleanupTasks.push(cleanup);
159
+ }
160
+ }
161
+ }
162
+
163
+ for (const startupHandler of this.startupHandlers) {
164
+ const cleanup = startupHandler(context);
165
+ if (typeof cleanup === "function") {
166
+ cleanupTasks.push(cleanup);
167
+ }
168
+ }
169
+
170
+ let rendererCleanup: Cleanup = () => {};
171
+ if (this.renderer) {
172
+ if (!this.RootComponent) {
173
+ throw new Error("Root component is required when using a custom renderer.");
174
+ }
175
+ rendererCleanup = this.renderer.render({
176
+ ...context,
177
+ RootComponent: this.RootComponent,
178
+ rootElementId: this.rootElementId,
179
+ }) ?? rendererCleanup;
180
+ } else if (this.RootComponent) {
181
+ rendererCleanup = new ReactRenderer().render({
182
+ ...context,
183
+ RootComponent: this.RootComponent,
184
+ rootElementId: this.rootElementId,
185
+ });
186
+ }
187
+
188
+ const stop: Cleanup = () => {
189
+ rendererCleanup();
190
+ for (const cleanup of cleanupTasks.reverse()) {
191
+ cleanup();
192
+ }
193
+ Facade.clearResolvedInstances();
194
+ container.unbindAll();
195
+ if (Application.container === container) {
196
+ Application.container = null;
197
+ }
198
+ };
199
+
200
+ window.addEventListener("beforeunload", stop, { once: true });
201
+
202
+ return {
203
+ container,
204
+ stop,
205
+ };
206
+ }
207
+ }
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /// <reference path="./config/env.global.d.ts" />
2
+
3
+ export { Application } from "./foundation/application";
4
+ export { default as ServiceProvider } from "./support/service-provider";
5
+ export { DeferrableServiceProvider } from "./support/service-provider";
6
+ export type { Cleanup, ServiceProviderContext } from "./support/service-provider";
7
+ export { default as ConfigServiceProvider } from "./support/providers/config-service-provider";
8
+
9
+ export { ApplicationProvider, useService } from "./runtimes/react";
10
+ export type {
11
+ RendererAdapter,
12
+ RendererContext,
13
+ RootComponent,
14
+ } from "./rendering/renderer";
15
+ export { default as ReactRenderer } from "./rendering/adapters/react";
16
+ export { default as ConfigRepository } from "./config/repository";
17
+ export { env } from "./config/env";
18
+ export { default as Str } from "./support/str";
19
+ export { default as ConfigFacade } from "./support/facades/config";
20
+ export { default as Config } from "./support/facades/config";
21
+ export { default as CacheManager } from "./cache/manager";
22
+ export type { CacheStore } from "./cache/cache";
23
+ export { default as CacheFacade } from "./support/facades/cache";
24
+ export { default as Cache } from "./support/facades/cache";
25
+ export { default as StorageManager } from "./storage/manager";
26
+ export { default as StorageFacade } from "./support/facades/storage";
27
+ export { default as Storage } from "./support/facades/storage";
28
+ export type { StorageAdapter } from "./storage/storage";
29
+ export { default as MemoryStorageAdapter } from "./storage/adapters/memory";
30
+ export { default as LocalStorageAdapter } from "./storage/adapters/local-storage";
31
+ export { default as IndexedDbAdapter } from "./storage/adapters/indexed-db";
32
+ export { FileSystem } from "./filesystem/filesystem";
33
+ export { default as LocalAdapter } from "./filesystem/adapters/local";
@@ -0,0 +1,27 @@
1
+ import ReactDOM from "react-dom/client";
2
+ import { createElement } from "react";
3
+ import { ApplicationProvider } from "../../runtimes/react";
4
+ import type { RendererAdapter, RendererContext } from "../renderer";
5
+ import type { Cleanup } from "../../support/service-provider";
6
+
7
+ export default class ReactRenderer implements RendererAdapter {
8
+ constructor() {}
9
+
10
+ render({ RootComponent, container, rootElementId }: RendererContext): Cleanup {
11
+ const mountNode = document.getElementById(rootElementId);
12
+ if (!mountNode) {
13
+ throw new Error(`Missing mount node #${rootElementId}.`);
14
+ }
15
+
16
+ const root = ReactDOM.createRoot(mountNode);
17
+ root.render(
18
+ createElement(
19
+ ApplicationProvider,
20
+ { container },
21
+ createElement(RootComponent),
22
+ ),
23
+ );
24
+
25
+ return () => root.unmount();
26
+ }
27
+ }
@@ -0,0 +1,13 @@
1
+ import type { Cleanup, ServiceProviderContext } from "../support/service-provider";
2
+ import type { ReactElement } from "react";
3
+
4
+ export type RootComponent = () => ReactElement | null;
5
+
6
+ export type RendererContext = ServiceProviderContext & {
7
+ RootComponent: RootComponent;
8
+ rootElementId: string;
9
+ };
10
+
11
+ export interface RendererAdapter {
12
+ render(context: RendererContext): void | Cleanup;
13
+ }
@@ -0,0 +1,22 @@
1
+ import { createContext, useContext, type ReactNode } from "react";
2
+ import type { Container } from "inversify";
3
+
4
+ const ContainerContext = createContext<Container | null>(null);
5
+
6
+ type ApplicationProviderProps = {
7
+ container: Container;
8
+ children?: ReactNode;
9
+ };
10
+
11
+ export function ApplicationProvider({ container, children }: ApplicationProviderProps) {
12
+ return <ContainerContext.Provider value={container}>{children}</ContainerContext.Provider>;
13
+ }
14
+
15
+ export function useService<T>(identifier: Parameters<Container["get"]>[0]): T {
16
+ const container = useContext(ContainerContext);
17
+ if (!container) {
18
+ throw new Error("Application container is not available in React context.");
19
+ }
20
+
21
+ return container.get<T>(identifier);
22
+ }
@@ -0,0 +1,180 @@
1
+ import MemoryStorageAdapter from "./memory";
2
+ import type { StorageAdapter } from "../storage";
3
+
4
+ type OpenFactory = Pick<IDBFactory, "open">;
5
+
6
+ export default class IndexedDbAdapter implements StorageAdapter {
7
+ protected fallback: StorageAdapter;
8
+ protected name: string;
9
+ protected factory: OpenFactory | null;
10
+ protected dbPromise: Promise<IDBDatabase> | null;
11
+
12
+ constructor(
13
+ options: {
14
+ factory?: OpenFactory | null;
15
+ name?: string;
16
+ fallback?: StorageAdapter;
17
+ } = {},
18
+ ) {
19
+ this.factory = options.factory ?? (globalThis.indexedDB ?? null);
20
+ this.name = options.name ?? "ioc-storage";
21
+ this.fallback = options.fallback ?? new MemoryStorageAdapter();
22
+ this.dbPromise = null;
23
+ }
24
+
25
+ protected req<T>(request: IDBRequest<T>): Promise<T> {
26
+ return new Promise<T>((resolve, reject) => {
27
+ request.onsuccess = () => resolve(request.result);
28
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed."));
29
+ });
30
+ }
31
+
32
+ protected openDb(): Promise<IDBDatabase> {
33
+ if (this.dbPromise) {
34
+ return this.dbPromise;
35
+ }
36
+
37
+ if (!this.factory) {
38
+ return Promise.reject(new Error("IndexedDB is not available."));
39
+ }
40
+
41
+ this.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
42
+ const request = this.factory?.open(this.name, 1);
43
+ if (!request) {
44
+ reject(new Error("IndexedDB open request could not be created."));
45
+ return;
46
+ }
47
+
48
+ request.onupgradeneeded = () => {
49
+ const db = request.result;
50
+ if (!db.objectStoreNames.contains("kv")) {
51
+ db.createObjectStore("kv");
52
+ }
53
+ };
54
+ request.onsuccess = () => resolve(request.result);
55
+ request.onerror = () =>
56
+ reject(request.error ?? new Error("IndexedDB database could not be opened."));
57
+ });
58
+
59
+ return this.dbPromise;
60
+ }
61
+
62
+ protected async withStore<T>(
63
+ mode: IDBTransactionMode,
64
+ action: (store: IDBObjectStore) => Promise<T>,
65
+ ): Promise<T> {
66
+ try {
67
+ const db = await this.openDb();
68
+ const tx = db.transaction("kv", mode);
69
+ const store = tx.objectStore("kv");
70
+ return await action(store);
71
+ } catch (_error) {
72
+ return actionFallback(this.fallback, mode, action);
73
+ }
74
+ }
75
+
76
+ async get<T = unknown>(key: string): Promise<T | null> {
77
+ return this.withStore("readonly", async (store) => {
78
+ const value = await this.req<unknown>(store.get(key));
79
+ return value === undefined ? null : (value as T);
80
+ });
81
+ }
82
+
83
+ async set<T = unknown>(key: string, value: T): Promise<void> {
84
+ await this.withStore("readwrite", async (store) => {
85
+ await this.req(store.put(value, key));
86
+ return undefined;
87
+ });
88
+ }
89
+
90
+ async has(key: string): Promise<boolean> {
91
+ return this.withStore("readonly", async (store) => {
92
+ const count = await this.req<number>(store.count(key));
93
+ return count > 0;
94
+ });
95
+ }
96
+
97
+ async delete(key: string): Promise<void> {
98
+ await this.withStore("readwrite", async (store) => {
99
+ await this.req(store.delete(key));
100
+ return undefined;
101
+ });
102
+ }
103
+
104
+ async clear(): Promise<void> {
105
+ await this.withStore("readwrite", async (store) => {
106
+ await this.req(store.clear());
107
+ return undefined;
108
+ });
109
+ }
110
+
111
+ async keys(): Promise<string[]> {
112
+ return this.withStore("readonly", async (store) => {
113
+ const keys = await this.req<Array<IDBValidKey>>(store.getAllKeys());
114
+ const normalized: string[] = [];
115
+ for (const key of keys) {
116
+ normalized.push(String(key));
117
+ }
118
+
119
+ return normalized;
120
+ });
121
+ }
122
+ }
123
+
124
+ async function actionFallback<T>(
125
+ fallback: StorageAdapter,
126
+ mode: IDBTransactionMode,
127
+ action: (store: IDBObjectStore) => Promise<T>,
128
+ ): Promise<T> {
129
+ // Keep call sites small: map IDB actions to the same short storage contract.
130
+ if (mode === "readonly") {
131
+ const store = createReadonlyProxy(fallback);
132
+ return action(store as unknown as IDBObjectStore);
133
+ }
134
+
135
+ const store = createReadWriteProxy(fallback);
136
+ return action(store as unknown as IDBObjectStore);
137
+ }
138
+
139
+ function createReadonlyProxy(fallback: StorageAdapter): Partial<IDBObjectStore> {
140
+ return {
141
+ get: (key: IDBValidKey) => wrapPromiseRequest(fallback.get(String(key))),
142
+ count: (key?: IDBValidKey | IDBKeyRange) =>
143
+ wrapPromiseRequest(
144
+ fallback.has(String(key as IDBValidKey)).then((exists) => (exists ? 1 : 0)),
145
+ ),
146
+ getAllKeys: () =>
147
+ wrapPromiseRequest(fallback.keys().then((keys) => keys as Array<IDBValidKey>)),
148
+ };
149
+ }
150
+
151
+ function createReadWriteProxy(fallback: StorageAdapter): Partial<IDBObjectStore> {
152
+ return {
153
+ ...createReadonlyProxy(fallback),
154
+ put: (value: unknown, key?: IDBValidKey) =>
155
+ wrapPromiseRequest(fallback.set(String(key as IDBValidKey), value)),
156
+ delete: (key: IDBValidKey | IDBKeyRange) =>
157
+ wrapPromiseRequest(fallback.delete(String(key as IDBValidKey))),
158
+ clear: () => wrapPromiseRequest(fallback.clear()),
159
+ };
160
+ }
161
+
162
+ function wrapPromiseRequest<T>(promise: Promise<T>): IDBRequest<T> {
163
+ const request: Partial<IDBRequest<T>> = {
164
+ onsuccess: null,
165
+ onerror: null,
166
+ };
167
+
168
+ promise.then(
169
+ (result) => {
170
+ request.result = result;
171
+ request.onsuccess?.call(request as IDBRequest<T>, new Event("success"));
172
+ },
173
+ (error) => {
174
+ request.error = error as DOMException;
175
+ request.onerror?.call(request as IDBRequest<T>, new Event("error"));
176
+ },
177
+ );
178
+
179
+ return request as IDBRequest<T>;
180
+ }
@@ -0,0 +1,46 @@
1
+ import type { StorageAdapter } from "../storage";
2
+
3
+ export default class LocalStorageAdapter implements StorageAdapter {
4
+ protected storage: Storage;
5
+
6
+ constructor(storage: Storage = window.localStorage) {
7
+ this.storage = storage;
8
+ }
9
+
10
+ async get<T = unknown>(key: string): Promise<T | null> {
11
+ const value = this.storage.getItem(key);
12
+ if (value === null) {
13
+ return null;
14
+ }
15
+
16
+ return JSON.parse(value) as T;
17
+ }
18
+
19
+ async set<T = unknown>(key: string, value: T): Promise<void> {
20
+ this.storage.setItem(key, JSON.stringify(value));
21
+ }
22
+
23
+ async has(key: string): Promise<boolean> {
24
+ return this.storage.getItem(key) !== null;
25
+ }
26
+
27
+ async delete(key: string): Promise<void> {
28
+ this.storage.removeItem(key);
29
+ }
30
+
31
+ async clear(): Promise<void> {
32
+ this.storage.clear();
33
+ }
34
+
35
+ async keys(): Promise<string[]> {
36
+ const keys: string[] = [];
37
+ for (let i = 0; i < this.storage.length; i += 1) {
38
+ const key = this.storage.key(i);
39
+ if (key !== null) {
40
+ keys.push(key);
41
+ }
42
+ }
43
+
44
+ return keys;
45
+ }
46
+ }
@@ -0,0 +1,35 @@
1
+ import type { StorageAdapter } from "../storage";
2
+
3
+ export default class MemoryStorageAdapter implements StorageAdapter {
4
+ protected items = new Map<string, unknown>();
5
+
6
+ constructor() {}
7
+
8
+ async get<T = unknown>(key: string): Promise<T | null> {
9
+ if (!this.items.has(key)) {
10
+ return null;
11
+ }
12
+
13
+ return this.items.get(key) as T;
14
+ }
15
+
16
+ async set<T = unknown>(key: string, value: T): Promise<void> {
17
+ this.items.set(key, value);
18
+ }
19
+
20
+ async has(key: string): Promise<boolean> {
21
+ return this.items.has(key);
22
+ }
23
+
24
+ async delete(key: string): Promise<void> {
25
+ this.items.delete(key);
26
+ }
27
+
28
+ async clear(): Promise<void> {
29
+ this.items.clear();
30
+ }
31
+
32
+ async keys(): Promise<string[]> {
33
+ return Array.from(this.items.keys());
34
+ }
35
+ }
@@ -0,0 +1,78 @@
1
+ import type ConfigRepository from "../config/repository";
2
+ import IndexedDbAdapter from "./adapters/indexed-db";
3
+ import LocalStorageAdapter from "./adapters/local-storage";
4
+ import MemoryStorageAdapter from "./adapters/memory";
5
+ import type { StorageAdapter } from "./storage";
6
+
7
+ type DriverName = "memory" | "local" | "indexed";
8
+
9
+ export default class StorageManager implements StorageAdapter {
10
+ protected adapters: Record<string, StorageAdapter>;
11
+ protected active: string;
12
+
13
+ constructor(adapters: Record<string, StorageAdapter>, active: string = "memory") {
14
+ this.adapters = adapters;
15
+ this.active = active in this.adapters ? active : "memory";
16
+ }
17
+
18
+ static fromConfig(config: ConfigRepository): StorageManager {
19
+ const adapters = StorageManager.defaultAdapters();
20
+ const driver = config.string("storage.driver", "memory");
21
+
22
+ return new StorageManager(adapters, driver);
23
+ }
24
+
25
+ static defaultAdapters(): Record<DriverName, StorageAdapter> {
26
+ const memory = new MemoryStorageAdapter();
27
+ const hasWindow = typeof window !== "undefined";
28
+ const hasLocal = hasWindow && typeof window.localStorage !== "undefined";
29
+ const hasIndexed = typeof globalThis.indexedDB !== "undefined";
30
+
31
+ return {
32
+ memory,
33
+ local: hasLocal ? new LocalStorageAdapter(window.localStorage) : memory,
34
+ indexed: hasIndexed ? new IndexedDbAdapter() : memory,
35
+ };
36
+ }
37
+
38
+ drv(name?: string): StorageAdapter {
39
+ if (typeof name === "string") {
40
+ if (!(name in this.adapters)) {
41
+ throw new Error(`Storage driver [${name}] is not defined.`);
42
+ }
43
+
44
+ return this.adapters[name];
45
+ }
46
+
47
+ return this.adapters[this.active];
48
+ }
49
+
50
+ use(name: string): this {
51
+ this.active = this.drv(name) ? name : this.active;
52
+ return this;
53
+ }
54
+
55
+ get<T = unknown>(key: string): Promise<T | null> {
56
+ return this.drv().get<T>(key);
57
+ }
58
+
59
+ set<T = unknown>(key: string, value: T): Promise<void> {
60
+ return this.drv().set<T>(key, value);
61
+ }
62
+
63
+ has(key: string): Promise<boolean> {
64
+ return this.drv().has(key);
65
+ }
66
+
67
+ delete(key: string): Promise<void> {
68
+ return this.drv().delete(key);
69
+ }
70
+
71
+ clear(): Promise<void> {
72
+ return this.drv().clear();
73
+ }
74
+
75
+ keys(): Promise<string[]> {
76
+ return this.drv().keys();
77
+ }
78
+ }
@@ -0,0 +1,8 @@
1
+ export interface StorageAdapter {
2
+ get<T = unknown>(key: string): Promise<T | null>;
3
+ set<T = unknown>(key: string, value: T): Promise<void>;
4
+ has(key: string): Promise<boolean>;
5
+ delete(key: string): Promise<void>;
6
+ clear(): Promise<void>;
7
+ keys(): Promise<string[]>;
8
+ }