feat(core-storage): add PouchEnvelope tests and LocalStorageService implementation

- Introduced tests for PouchEnvelope to validate envelope-aware CRUD operations using an in-memory PouchDB.
- Implemented LocalStorageService with encryption support for sensitive keys, including tests for setItem, getItem, removeItem, and clear methods.
- Defined a generic storage interface (IStorageService) to enforce type-safe serialization/deserialization across storage implementations.
This commit is contained in:
Firman Ramdhani
2026-07-06 11:38:37 +07:00
parent 7130eb3fe3
commit d1ce292e3c
17 changed files with 1568 additions and 215 deletions
@@ -0,0 +1,38 @@
import { PouchDBManager } from './pouch.manager';
import { PouchConfig } from '../services/pouch.service';
import { PouchEnvelopeService } from '../services/pouch-envelope.service';
/**
* IoC Factory for managing multiple Envelope-based PouchDB database instances.
* Inherits from `PouchDBManager`.
*/
export class PouchEnvelopeDBManager extends PouchDBManager {
/**
* Register and initialize a new PouchDB database using the Envelope pattern.
* If a database with the same `localName` already exists, returns the existing wrapper.
*
* @param config - Configuration for the database instance.
* @param entity - The domain entity name used as a prefix (e.g., 'item', 'booking').
* @returns A `PouchEnvelopeService` with envelope-aware CRUD + query helpers.
*/
override register<T extends object = any>(config: PouchConfig, entity?: string): PouchEnvelopeService<T> {
if (!entity) {
throw new Error(`[PouchEnvelopeManager] 'entity' parameter is required for database: ${config.localName}`);
}
if (this.databases.has(config.localName)) {
return this.databases.get(config.localName) as PouchEnvelopeService<T>;
}
const wrapper = new PouchEnvelopeService<T>(config, entity);
this.databases.set(config.localName, wrapper);
return wrapper;
}
/**
* Retrieve a previously registered envelope database wrapper by its local name.
*/
override get<T extends object = any>(localName: string): PouchEnvelopeService<T> | undefined {
return super.get(localName) as PouchEnvelopeService<T> | undefined;
}
}
@@ -0,0 +1,77 @@
import { PouchConfig, PouchService } from '../services/pouch.service';
/**
* IoC Factory for managing multiple PouchDB database instances with optional
* bi-directional CouchDB synchronization.
*
* `register()` returns a `PouchService` with full CRUD + query helpers,
* so developers never need to pass `dbName` into each operation.
*
* @example
* ```typescript
* const dbManager = new PouchDBManager();
*
* const itemDB = dbManager.register({ localName: 'items' });
* await itemDB.create({ _id: 'item-001', name: 'Widget', price: 9.99 });
*
* const results = await itemDB.find({ selector: { price: { $gt: 5 } } });
* console.log(results); // [{ _id: 'item-001', name: 'Widget', price: 9.99, ... }]
*
* const salesDB = dbManager.register({
* localName: 'sales',
* remoteUrl: 'http://admin:pass@localhost:5984/sales_db',
* });
* ```
*/
export class PouchDBManager {
protected databases = new Map<string, PouchService<any>>();
/**
* Register and initialize a new PouchDB database.
* If a database with the same `localName` already exists, returns the existing wrapper.
*
* @param config - Configuration for the database instance.
* @returns A `PouchService` with CRUD + query helpers.
*/
register<T extends object = any>(config: PouchConfig): PouchService<T> {
if (this.databases.has(config.localName)) {
return this.databases.get(config.localName) as PouchService<T>;
}
const wrapper = new PouchService<T>(config);
this.databases.set(config.localName, wrapper);
return wrapper;
}
/**
* Retrieve a previously registered database wrapper by its local name.
*/
get<T extends object = any>(localName: string): PouchService<T> | undefined {
return this.databases.get(localName) as PouchService<T> | undefined;
}
/**
* Destroy a specific registered database and remove it from the manager.
*/
async destroy(localName: string): Promise<void> {
const wrapper = this.databases.get(localName);
if (!wrapper) return;
await wrapper.destroy();
this.databases.delete(localName);
}
/**
* Destroy all managed databases. Useful for logout/cleanup scenarios.
*/
async destroyAll(): Promise<void> {
const names = Array.from(this.databases.keys());
await Promise.all(names.map((name) => this.destroy(name)));
}
/**
* List all currently registered database names.
*/
listDatabases(): string[] {
return Array.from(this.databases.keys());
}
}