Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(alpine): add new package #14

Merged
merged 18 commits into from
Dec 10, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(alpine): review and enhance
Co-authored-by: arashagp <arash.qardashpoor@gmail.com>
  • Loading branch information
njfamirm and arashagp committed Dec 10, 2024
commit 8a7129edbef39c080acedae7ef75a9be16b05a03
2 changes: 1 addition & 1 deletion packages/alpine/src/main.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,6 @@ import {packageTracer} from '@alwatr/package-tracer';

__dev_mode__: packageTracer.add(__package_name__, __package_version__);

export * from './store/base-store.js';
export * from './store/store-generator.js';
export * from './store/store.js';
export * from './store/store-with-backup.js';
14 changes: 0 additions & 14 deletions packages/alpine/src/store/base-store.ts

This file was deleted.

50 changes: 50 additions & 0 deletions packages/alpine/src/store/store-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {createLogger} from '@alwatr/logger';
import alpine from 'alpinejs';

const logger = createLogger(__package_name__);

/**
* Options for creating an Alpine store.
*
* @template T - The type of the default value.
* @property {string} name - The name of the store.
* @property {T} defaultValue - The default value of the store.
*/
export type AlpineStoreGeneratorOptions<T extends DictionaryReq> = {
name: string;
defaultValue: T;
};

/**
* Generates an Alpine.js store with default value.
*
* @template T - The type of the data.
* @param {AlpineStoreGeneratorOptions} config - The configuration object for the store.
* @returns {T} - The initialized store instance.
*
* @see https://alpinejs.dev/globals/alpine-store
*
* @example
* // Create a store with a default state
* const store = alpineStoreGenerator({
* name: 'user',
* defaultValue: {type: 'root'},
* });
*
* console.log(store.type); // Output: value
*
* @description
* This function uses Alpine.js to create a reactive store with a default value.
* The store is identified by a unique name and can be accessed and manipulated
* throughout the application. Alpine.js stores provide a simple way to manage
* state in your application, making it easy to keep your UI in sync with your data.
*/
export function alpineStoreGenerator<T extends DictionaryReq>(config: AlpineStoreGeneratorOptions<T>): T {
logger.logMethodArgs?.('alpineStoreGenerator', config);

alpine.store(config.name, config.defaultValue);

// Get store Proxy
const store = alpine.store(config.name) as T;
return store;
}
93 changes: 51 additions & 42 deletions packages/alpine/src/store/store-with-backup.ts
Original file line number Diff line number Diff line change
@@ -5,41 +5,44 @@ import {AlpineStore} from './store.js';

import type {EmptyObject} from '../type.js';

export type AlpineStoreWithBackupType = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: DictionaryReq<any> | null;
export type AlpineStoreWithBackupType<T extends DictionaryReq> = {
data: T | null;
};

export type AlpineStoreWithBackupConfig<T extends AlpineStoreWithBackupType> = {
export type AlpineStoreWithBackupConfig<TBase extends AlpineStoreWithBackupType<TData>, TData extends DictionaryReq> = {
name: string;
version: number;
defaultStore: T;
defaultValue: TBase;
expireDuration?: Duration;
};

const localStorageKey = '[nexim.store.v1]';
/**
* Schema version `sv` that's change just when the schema changes.
*/
const schemaVersion = 1;

/**
* StoreWithBackup class extends the Store class to provide backup and restore functionality
* with local storage support and expiration handling.
*/
export class AlpineStoreWithBackup<T extends AlpineStoreWithBackupType> extends AlpineStore<T> {
export class AlpineStoreWithBackup<TBase extends AlpineStoreWithBackupType<TData>, TData extends DictionaryReq> extends AlpineStore<TBase> {
/**
* Keys for storing data and expireTime in local storage.
*/
private localStorageKey__ = {
data: `${localStorageKey}:${this.config__.name}`,
expireTime: `${localStorageKey}:${this.config__.name}-expire-time`,
data: `[@nexim/alpine:data:sv${schemaVersion}]:${this.config__.name}`,
expireTime: `[@nexim/alpine:expire-time:sv${schemaVersion}]:${this.config__.name}`,
};

/**
* Constructor to initialize the StoreWithBackup instance.
* @param config__ - Configuration object containing name, version, defaultStore, and optional expireDuration.
* AlpineStoreWithBackup Constructor.
*
* @param {AlpineStoreWithBackupConfig} config__ - Configuration object.
*/
constructor(private config__: AlpineStoreWithBackupConfig<T>) {
constructor(private config__: AlpineStoreWithBackupConfig<TBase, TData>) {
super(config__);

this.handleExpireDuration__();
this.handleDataExpiration__();
this.load__();
}

@@ -48,7 +51,7 @@ export class AlpineStoreWithBackup<T extends AlpineStoreWithBackupType> extends
* Updates the expiration time.
*/
save(): void {
this.logger_.logMethodArgs?.(`${__package_name__}.save`, this.store.data);
this.logger_.logMethodArgs?.('save', {data: this.store.data});

if (this.store.data === null) {
this.clear();
@@ -60,31 +63,32 @@ export class AlpineStoreWithBackup<T extends AlpineStoreWithBackupType> extends
}

/**
* Clears the stored data, and expiration time from local storage.
* Resets the data to the default store configuration and clears the stored data.
*/
clear(): void {
this.logger_.logMethod?.(`${__package_name__}.clear`);
resetDataToDefault(): void {
this.logger_.logMethod?.('resetDataToDefault');

localJsonStorage.removeItem(this.localStorageKey__.data, this.config__.version);
localJsonStorage.removeItem(this.localStorageKey__.expireTime, this.config__.version);
this.store = this.config__.defaultValue;
this.clear();
}

/**
* Resets the data to the default store configuration and clears the stored data.
* Clears the stored data, and expiration time from local storage.
*/
resetDataToDefault(): void {
this.logger_.logMethod?.(`${__package_name__}.resetDataToDefault`);
this.store = this.config__.defaultStore;
this.clear();
clear(): void {
this.logger_.logMethod?.('clear');

localJsonStorage.removeItem(this.localStorageKey__.data, this.config__.version);
localJsonStorage.removeItem(this.localStorageKey__.expireTime, this.config__.version);
}

/**
* Handles the expiration duration by checking if the stored data has expired.
* If expired, it clears the stored data.
*/
private handleExpireDuration__(): void {
private handleDataExpiration__(): void {
if (this.config__.expireDuration == null) return;
this.logger_.logMethod?.(`${__package_name__}.handleExpireDuration__`);
this.logger_.logMethod?.('handleDataExpiration__');

const expireDuration = localJsonStorage.getItem<{time: number}>(
this.localStorageKey__.expireTime,
@@ -97,19 +101,6 @@ export class AlpineStoreWithBackup<T extends AlpineStoreWithBackupType> extends
}
}

/**
* Updates the expiration time in local storage to the current time plus the configured expiration duration.
*/
private updateExpireTime__(): void {
if (this.config__.expireDuration == null) return;
this.logger_.logMethod?.(`${__package_name__}.updateExpireTime__`);

const newExpireTime = Date.now() + parseDuration(this.config__.expireDuration);
localJsonStorage.setItem(this.localStorageKey__.expireTime, {time: newExpireTime}, this.config__.version);

this.logger_.logOther?.(`${__package_name__}.updated_expire_time`, {newExpireTime});
}

/**
* Loads data from local storage and updates the store's data.
*
@@ -121,18 +112,36 @@ export class AlpineStoreWithBackup<T extends AlpineStoreWithBackupType> extends
private load__(): void {
this.logger_.logMethod?.('load__');

const newData = localJsonStorage.getItem<T | EmptyObject>(this.localStorageKey__.data, {}, this.config__.version);
const newData = localJsonStorage.getItem<TData | EmptyObject>(this.localStorageKey__.data, {}, this.config__.version);

if (Object.keys(newData).length === 0) {
// empty object
const rawValue = localStorage.getItem(localJsonStorage.key_(this.localStorageKey__.data, this.config__.version));
if (rawValue === '{}' || rawValue === null) {
this.logger_.logOther?.('no_data');

this.resetDataToDefault();
return;
}

this.logger_.error(`${__package_name__}.load__`, 'data_not_parsed', {localStorageKey: this.localStorageKey__});
this.logger_.error('load__', 'data_not_parsed', {localStorageKey: this.localStorageKey__});
return;
}
// else: data is not empty

this.store.data = newData as TData;
}

/**
* Updates the expiration time in local storage to the current time plus the configured expiration duration.
*/
private updateExpireTime__(): void {
if (this.config__.expireDuration == null) return;
this.logger_.logMethod?.('updateExpireTime__');

const newExpireTime = Date.now() + parseDuration(this.config__.expireDuration);
localJsonStorage.setItem(this.localStorageKey__.expireTime, {time: newExpireTime}, this.config__.version);

this.store.data = newData;
this.logger_.logOther?.('updated_expire_time', {newExpireTime});
}
}
49 changes: 43 additions & 6 deletions packages/alpine/src/store/store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
import {createLogger, type AlwatrLogger} from '@alwatr/logger';

import {AlpineBaseStore} from './base-store.js';
import {alpineStoreGenerator} from './store-generator.js';

export class AlpineStore<T extends DictionaryReq> extends AlpineBaseStore<T> {
protected logger_: AlwatrLogger;
/**
* Options for creating an Alpine store.
*
* @template T - The type of the default value.
* @property {string} name - The name of the store.
* @property {T} defaultValue - The default state of the store.
*/
export type AlpineStoreOptions<T extends DictionaryReq> = {
name: string;
defaultValue: T;
};

constructor(config_: {name: string; defaultStore: T}) {
super(config_); // pass name and default store to set in the alpine store
/**
* Class representing an Alpine store.
*
* @template T - The type of the store's state.
* @description This class provides a simple way to create and manage a store with a default state and logging capabilities.
*
* @example
* const storeOptions = {
* name: 'exampleStore',
* defaultValue: { key: 'value' }
* };
* const store = new AlpineStore(storeOptions);
*
* console.log(store.store); // Output: { key: 'value' }
*
* @description
* The AlpineStore class leverages Alpine.js to create a reactive store that holds a default value.
* Each store is uniquely identified by a name and can be accessed and manipulated throughout the application.
* This class simplifies state management, ensuring that your UI remains in sync with the underlying data.
*/
export class AlpineStore<T extends DictionaryReq> {
store: T;
protected logger_: AlwatrLogger;

this.logger_ = createLogger(`[${__package_name__}:${config_.name}]`);
/**
* Create an Alpine store.
*
* @param {AlpineStoreOptions<T>} config - The configuration options for the store.
*/
constructor(config: AlpineStoreOptions<T>) {
this.store = alpineStoreGenerator<T>(config);
this.logger_ = createLogger(`[${__package_name__}:${config.name}]`);
}
}
4 changes: 1 addition & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -2,9 +2,7 @@
"extends": "@nexim/typescript-config/tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": [
"node"
]
"types": ["node"]
},
"files": [],
"exclude": [],