-
-
Notifications
You must be signed in to change notification settings - Fork 52
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: persist extension #277
base: main
Are you sure you want to change the base?
feat: persist extension #277
Conversation
factory: FactoryAPI<any>, | ||
options: ExtensionOption = {}, | ||
) { | ||
if (!isBrowser() || (!options.storage && !supports.sessionStorage())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great if server-side persistence could be supported too. I have that need in my project. I want to serialise my mock DB and store it in Redis. Seems doable if all persist
function needs is a storage method that has getItem/setItem methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If @kettanaito will ok with this API, I could change interface for custom storage
@kettanaito would you please take a look on this? |
Is it possible to merge it? |
According to this #285 no new features will be merged. I have modified @noveogroup-amorgunov 's code to be used outside of mswjs-data internal code. You can see it being used in this project https://github.com/Kamahl19/react-starter/blob/main/src/mocks/persist.ts . I will keep the most up-to-date version there. Usage: import { factory, primaryKey } from '@mswjs/data';
const db = factory({ ... });
persist(db); Create import debounce from 'lodash/debounce';
import {
DATABASE_INSTANCE,
ENTITY_TYPE,
PRIMARY_KEY,
type FactoryAPI,
type Entity,
type ModelDictionary,
type PrimaryKeyType,
} from '@mswjs/data/lib/glossary';
import {
type SerializedEntity,
SERIALIZED_INTERNAL_PROPERTIES_KEY,
} from '@mswjs/data/lib/db/Database';
import { inheritInternalProperties } from '@mswjs/data/lib/utils/inheritInternalProperties';
const STORAGE_KEY_PREFIX = 'mswjs-data';
// Timout to persist state with some delay
const DEBOUNCE_PERSIST_TIME_MS = 10;
type Models<Dictionary extends ModelDictionary> = Record<
keyof Dictionary,
Map<PrimaryKeyType, Entity<Dictionary, any>> // eslint-disable-line @typescript-eslint/no-explicit-any
>;
type SerializedModels<Dictionary extends ModelDictionary> = Record<
keyof Dictionary,
Map<PrimaryKeyType, SerializedEntity>
>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function persist<Dictionary extends ModelDictionary>(
factory: FactoryAPI<Dictionary>,
) {
if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') {
return;
}
const db = factory[DATABASE_INSTANCE];
const key = `${STORAGE_KEY_PREFIX}/${db.id}`;
const persistState = debounce(function persistState() {
// eslint-disable-next-line @typescript-eslint/dot-notation, @typescript-eslint/consistent-type-assertions
const models = db['models'] as Models<Dictionary>;
// eslint-disable-next-line @typescript-eslint/dot-notation, @typescript-eslint/consistent-type-assertions
const serializeEntity = db['serializeEntity'] as (
entity: Entity<Dictionary, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
) => SerializedEntity;
const json = Object.fromEntries(
Object.entries(models).map(([modelName, entities]) => [
modelName,
Array.from(entities, ([, entity]) => serializeEntity(entity)),
]),
);
sessionStorage.setItem(key, JSON.stringify(json));
}, DEBOUNCE_PERSIST_TIME_MS);
function hydrateState() {
const initialState = sessionStorage.getItem(key);
if (initialState) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const data = JSON.parse(initialState) as SerializedModels<Dictionary>;
for (const [modelName, entities] of Object.entries(data)) {
for (const entity of entities.values()) {
db.create(modelName, deserializeEntity(entity));
}
}
}
// Add event listeners only after hydration
db.events.on('create', persistState);
db.events.on('update', persistState);
db.events.on('delete', persistState);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', hydrateState);
} else {
hydrateState();
}
}
function deserializeEntity(entity: SerializedEntity) {
const { [SERIALIZED_INTERNAL_PROPERTIES_KEY]: internalProperties, ...publicProperties } = entity;
inheritInternalProperties(publicProperties, {
[ENTITY_TYPE]: internalProperties.entityType,
[PRIMARY_KEY]: internalProperties.primaryKey,
});
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
return publicProperties as Entity<any, any>;
} |
@Kamahl19 thanks so much for this! I'm using @msw/data to help our sales team run more effective product demos, and your One thing I've found: collocating updates when a model has a |
Hey. I tried to implement persist layer #49 . This PR is based on #87
You can see example of usage in my pet project: https://github.com/noveogroup-amorgunov/nukeapp/blob/90a0f75f57f877d4889033456f3628c5d1e34699/src/shared/lib/server/serverDb.ts#L49-L51 (PR: noveogroup-amorgunov/nukeapp#14)
If this approach is OK, I am going to write tests.