Skip to content

Commit

Permalink
fix(storage-plugin): expose storage plugin internals privately throug…
Browse files Browse the repository at this point in the history
…h subpackage (#2052)

This commit exposes some of the storage plugin internals through a subpackage, following a
similar approach to how router plugin internals are exposed. These internals are necessary to
expose if someone wishes to extend the storage engine's behavior. For example, to check for the
default state key `@@STATE` or to use helper functions like extracting string keys.
  • Loading branch information
arturovt authored Sep 15, 2023
1 parent 22c3643 commit 07536db
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 83 deletions.
6 changes: 6 additions & 0 deletions packages/storage-plugin/internals/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"lib": {
"entryFile": "src/index.ts",
"flatModuleFile": "ngxs-storage-plugin-internals"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { InjectionToken, Injector } from '@angular/core';

import { exctractStringKey, isKeyWithExplicitEngine, StorageKey } from './storage-key';
import { NgxsStoragePluginOptions, StorageEngine, STORAGE_ENGINE } from '../symbols';
import { NgxsStoragePluginOptions, STORAGE_ENGINE, StorageEngine } from './symbols';
import { StorageKey, ɵextractStringKey, ɵisKeyWithExplicitEngine } from './storage-key';

export interface FinalNgxsStoragePluginOptions extends NgxsStoragePluginOptions {
export interface ɵFinalNgxsStoragePluginOptions extends NgxsStoragePluginOptions {
keysWithEngines: {
key: string;
engine: StorageEngine;
Expand All @@ -14,20 +14,19 @@ declare const ngDevMode: boolean;

const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;

export const FINAL_NGXS_STORAGE_PLUGIN_OPTIONS =
new InjectionToken<FinalNgxsStoragePluginOptions>(
NG_DEV_MODE ? 'FINAL_NGXS_STORAGE_PLUGIN_OPTIONS' : ''
);
export const ɵFINAL_NGXS_STORAGE_PLUGIN_OPTIONS = new InjectionToken<unknown>(
NG_DEV_MODE ? 'FINAL_NGXS_STORAGE_PLUGIN_OPTIONS' : ''
);

export function createFinalStoragePluginOptions(
export function ɵcreateFinalStoragePluginOptions(
injector: Injector,
options: NgxsStoragePluginOptions
): FinalNgxsStoragePluginOptions {
): ɵFinalNgxsStoragePluginOptions {
const storageKeys: StorageKey[] = Array.isArray(options.key) ? options.key : [options.key!];

const keysWithEngines = storageKeys.map((storageKey: StorageKey) => {
const key = exctractStringKey(storageKey);
const engine = isKeyWithExplicitEngine(storageKey)
const key = ɵextractStringKey(storageKey);
const engine = ɵisKeyWithExplicitEngine(storageKey)
? injector.get(storageKey.engine)
: injector.get(STORAGE_ENGINE);
return { key, engine };
Expand Down
3 changes: 3 additions & 0 deletions packages/storage-plugin/internals/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './symbols';
export * from './final-options';
export * from './storage-key';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { InjectionToken, Type } from '@angular/core';
import { StateToken } from '@ngxs/store';
import { StateClass } from '@ngxs/store/internals';

import { StorageEngine } from '../symbols';
import { StorageEngine } from './symbols';

/** This enables the user to provide a storage engine per individual key. */
export interface KeyWithExplicitEngine {
Expand All @@ -11,7 +11,7 @@ export interface KeyWithExplicitEngine {
}

/** Determines whether the provided key has the following structure. */
export function isKeyWithExplicitEngine(key: any): key is KeyWithExplicitEngine {
export function ɵisKeyWithExplicitEngine(key: any): key is KeyWithExplicitEngine {
return key != null && !!key.engine;
}

Expand All @@ -23,9 +23,9 @@ export type StorageKey = string | StateClass | StateToken<any> | KeyWithExplicit

/** This symbol is used to store the metadata on state classes. */
const META_OPTIONS_KEY = 'NGXS_OPTIONS_META';
export function exctractStringKey(storageKey: StorageKey): string {
export function ɵextractStringKey(storageKey: StorageKey): string {
// Extract the actual key out of the `{ key, engine }` structure.
if (isKeyWithExplicitEngine(storageKey)) {
if (ɵisKeyWithExplicitEngine(storageKey)) {
storageKey = storageKey.key;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { InjectionToken } from '@angular/core';

import { StorageKey } from './internals/storage-key';
import { StorageKey } from './storage-key';

/**
* The following key is used to store the entire serialized
* state when no specific state is provided.
*/
export const ɵDEFAULT_STATE_KEY = '@@STATE';

declare const ngDevMode: boolean;

const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;

export const enum StorageOption {
LocalStorage,
Expand Down Expand Up @@ -73,11 +83,7 @@ export interface NgxsStoragePluginOptions {
afterDeserialize?(obj: any, key: string): any;
}

declare const ngDevMode: boolean;

const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;

export const NGXS_STORAGE_PLUGIN_OPTIONS = new InjectionToken(
export const ɵNGXS_STORAGE_PLUGIN_OPTIONS = new InjectionToken(
NG_DEV_MODE ? 'NGXS_STORAGE_PLUGIN_OPTIONS' : ''
);

Expand Down
3 changes: 1 addition & 2 deletions packages/storage-plugin/src/engines.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { InjectionToken, PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

import { StorageEngine } from './symbols';
import { StorageEngine } from '@ngxs/storage-plugin/internals';

declare const ngDevMode: boolean;

Expand Down
16 changes: 7 additions & 9 deletions packages/storage-plugin/src/internals.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { isPlatformServer } from '@angular/common';

import { StorageOption, StorageEngine, NgxsStoragePluginOptions } from './symbols';

/**
* The following key is used to store the entire serialized
* state when there's no specific state provided.
*/
export const DEFAULT_STATE_KEY = '@@STATE';
import {
ɵDEFAULT_STATE_KEY,
StorageOption,
StorageEngine,
NgxsStoragePluginOptions
} from '@ngxs/storage-plugin/internals';

export function storageOptionsFactory(
options: NgxsStoragePluginOptions | undefined
): NgxsStoragePluginOptions {
return {
key: [DEFAULT_STATE_KEY],
key: [ɵDEFAULT_STATE_KEY],
storage: StorageOption.LocalStorage,
serialize: JSON.stringify,
deserialize: JSON.parse,
Expand Down
8 changes: 7 additions & 1 deletion packages/storage-plugin/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export { NgxsStoragePluginModule, withNgxsStoragePlugin } from './storage.module';
export { NgxsStoragePlugin } from './storage.plugin';
export * from './symbols';
export * from './engines';

export {
StorageOption,
NgxsStoragePluginOptions,
STORAGE_ENGINE,
StorageEngine
} from '@ngxs/storage-plugin/internals';
32 changes: 15 additions & 17 deletions packages/storage-plugin/src/storage.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@ import {
makeEnvironmentProviders
} from '@angular/core';
import { NGXS_PLUGINS, withNgxsPlugin } from '@ngxs/store';

import {
NgxsStoragePluginOptions,
STORAGE_ENGINE,
NGXS_STORAGE_PLUGIN_OPTIONS
} from './symbols';
ɵNGXS_STORAGE_PLUGIN_OPTIONS,
ɵcreateFinalStoragePluginOptions,
ɵFINAL_NGXS_STORAGE_PLUGIN_OPTIONS
} from '@ngxs/storage-plugin/internals';

import { NgxsStoragePlugin } from './storage.plugin';
import { engineFactory, storageOptionsFactory } from './internals';
import {
createFinalStoragePluginOptions,
FINAL_NGXS_STORAGE_PLUGIN_OPTIONS
} from './internals/final-options';

declare const ngDevMode: boolean;

Expand All @@ -45,19 +43,19 @@ export class NgxsStoragePluginModule {
useValue: options
},
{
provide: NGXS_STORAGE_PLUGIN_OPTIONS,
provide: ɵNGXS_STORAGE_PLUGIN_OPTIONS,
useFactory: storageOptionsFactory,
deps: [USER_OPTIONS]
},
{
provide: STORAGE_ENGINE,
useFactory: engineFactory,
deps: [NGXS_STORAGE_PLUGIN_OPTIONS, PLATFORM_ID]
deps: [ɵNGXS_STORAGE_PLUGIN_OPTIONS, PLATFORM_ID]
},
{
provide: FINAL_NGXS_STORAGE_PLUGIN_OPTIONS,
useFactory: createFinalStoragePluginOptions,
deps: [Injector, NGXS_STORAGE_PLUGIN_OPTIONS]
provide: ɵFINAL_NGXS_STORAGE_PLUGIN_OPTIONS,
useFactory: ɵcreateFinalStoragePluginOptions,
deps: [Injector, ɵNGXS_STORAGE_PLUGIN_OPTIONS]
}
]
};
Expand All @@ -74,19 +72,19 @@ export function withNgxsStoragePlugin(
useValue: options
},
{
provide: NGXS_STORAGE_PLUGIN_OPTIONS,
provide: ɵNGXS_STORAGE_PLUGIN_OPTIONS,
useFactory: storageOptionsFactory,
deps: [USER_OPTIONS]
},
{
provide: STORAGE_ENGINE,
useFactory: engineFactory,
deps: [NGXS_STORAGE_PLUGIN_OPTIONS, PLATFORM_ID]
deps: [ɵNGXS_STORAGE_PLUGIN_OPTIONS, PLATFORM_ID]
},
{
provide: FINAL_NGXS_STORAGE_PLUGIN_OPTIONS,
useFactory: createFinalStoragePluginOptions,
deps: [Injector, NGXS_STORAGE_PLUGIN_OPTIONS]
provide: ɵFINAL_NGXS_STORAGE_PLUGIN_OPTIONS,
useFactory: ɵcreateFinalStoragePluginOptions,
deps: [Injector, ɵNGXS_STORAGE_PLUGIN_OPTIONS]
}
]);
}
20 changes: 11 additions & 9 deletions packages/storage-plugin/src/storage.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import {
actionMatcher,
NgxsNextPluginFn
} from '@ngxs/store';
import {
ɵDEFAULT_STATE_KEY,
ɵFinalNgxsStoragePluginOptions,
ɵFINAL_NGXS_STORAGE_PLUGIN_OPTIONS
} from '@ngxs/storage-plugin/internals';
import { tap } from 'rxjs/operators';

import { DEFAULT_STATE_KEY, getStorageKey } from './internals';
import {
FinalNgxsStoragePluginOptions,
FINAL_NGXS_STORAGE_PLUGIN_OPTIONS
} from './internals/final-options';
import { getStorageKey } from './internals';

declare const ngDevMode: boolean;

Expand All @@ -25,12 +26,13 @@ const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode;
@Injectable()
export class NgxsStoragePlugin implements NgxsPlugin {
private _keysWithEngines = this._options.keysWithEngines;
// We default to `[DEFAULT_STATE_KEY]` if the user explicitly does not provide the `key` option.
// We default to `[ɵDEFAULT_STATE_KEY]` if the user explicitly does not provide the `key` option.
private _usesDefaultStateKey =
this._keysWithEngines.length === 1 && this._keysWithEngines[0].key === DEFAULT_STATE_KEY;
this._keysWithEngines.length === 1 && this._keysWithEngines[0].key === ɵDEFAULT_STATE_KEY;

constructor(
@Inject(FINAL_NGXS_STORAGE_PLUGIN_OPTIONS) private _options: FinalNgxsStoragePluginOptions,
@Inject(ɵFINAL_NGXS_STORAGE_PLUGIN_OPTIONS)
private _options: ɵFinalNgxsStoragePluginOptions,
@Inject(PLATFORM_ID) private _platformId: string
) {}

Expand Down Expand Up @@ -140,7 +142,7 @@ export class NgxsStoragePlugin implements NgxsPlugin {

const storageKey = getStorageKey(key, this._options);

if (key !== DEFAULT_STATE_KEY) {
if (key !== ɵDEFAULT_STATE_KEY) {
storedValue = getValue(nextState, key);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { Observable } from 'rxjs';
import { NgxsRouterPluginModule } from '@ngxs/router-plugin';
import { NgxsModule, NgxsOnInit, State, StateContext, Store } from '@ngxs/store';
import { freshPlatform, skipConsoleLogging } from '@ngxs/store/internals/testing';
import { ɵDEFAULT_STATE_KEY } from '@ngxs/storage-plugin/internals';

import { NgxsStoragePluginModule } from '../../';
import { DEFAULT_STATE_KEY } from '../../src/internals';

describe('Invalid state re-hydration (https://github.com/ngxs/store/issues/1146)', () => {
afterEach(() => localStorage.clear());
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('Invalid state re-hydration (https://github.com/ngxs/store/issues/1146)
freshPlatform(async () => {
// Arrange & act
localStorage.setItem(
DEFAULT_STATE_KEY,
ɵDEFAULT_STATE_KEY,
JSON.stringify({
counter: -1,
router: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { NgxsModule, State, Store } from '@ngxs/store';
import { freshPlatform, skipConsoleLogging } from '@ngxs/store/internals/testing';
import { ɵDEFAULT_STATE_KEY } from '@ngxs/storage-plugin/internals';

import { NgxsStoragePluginModule } from '../../';
import { DEFAULT_STATE_KEY } from '../../src/internals';

describe('Update for lazy state (https://github.com/ngxs/store/issues/1857)', () => {
afterEach(() => localStorage.clear());
Expand Down Expand Up @@ -57,7 +57,7 @@ describe('Update for lazy state (https://github.com/ngxs/store/issues/1857)', ()
'should deserialize the feature state if the key is a master key (@@STATE)',
freshPlatform(async () => {
// Arrange & act
localStorage.setItem(DEFAULT_STATE_KEY, JSON.stringify({ feature: { name: 'NGXS' } }));
localStorage.setItem(ɵDEFAULT_STATE_KEY, JSON.stringify({ feature: { name: 'NGXS' } }));
const { injector } = await skipConsoleLogging(() =>
platformBrowserDynamic().bootstrapModule(TestModule)
);
Expand Down
Loading

0 comments on commit 07536db

Please sign in to comment.