diff --git a/README.md b/README.md index 2c9554a1..ab9fd915 100644 --- a/README.md +++ b/README.md @@ -657,6 +657,23 @@ const db = factory({...}) drop(db) ``` +#### `snapshot` + +Create a snapshot for entities present in the database. +The snapshot is useful to restore the database later e.g. between tests. + +```js +import { factory, snapshot } from '@mswjs/data' + +const db = factory({...}) + +const restore = snapshot(db) + +// Make some update to the database + +restore() +``` + ### Usage with `faker` Libraries like [`faker`](https://github.com/Marak/Faker.js) can help you generate fake data for your models. diff --git a/src/db/snapshot.ts b/src/db/snapshot.ts new file mode 100644 index 00000000..fa1e4eef --- /dev/null +++ b/src/db/snapshot.ts @@ -0,0 +1,22 @@ +import { ModelDictionary, FactoryAPI, Entity } from '../glossary' + +export function snapshot( + db: FactoryAPI, +) { + const dbSnapshot = Object.keys(db).reduce< + Record[]> + >((acc, entityName) => { + acc[entityName] = db[entityName].getAll() + return acc + }, {}) + + return () => { + Object.keys(db).forEach((entityName) => { + const snapshotEntities = dbSnapshot[entityName] + db[entityName].deleteMany({ where: {} }) + snapshotEntities.forEach((entity) => { + db[entityName].create(entity) + }) + }) + } +} diff --git a/src/index.ts b/src/index.ts index 6056c1d5..3730633c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,4 +3,5 @@ export { primaryKey } from './primaryKey' export { oneOf } from './relations/oneOf' export { manyOf } from './relations/manyOf' export { drop } from './db/drop' +export { snapshot } from './db/snapshot' export { identity } from './utils/identity' diff --git a/test/db/snapshot.test.ts b/test/db/snapshot.test.ts new file mode 100644 index 00000000..a7e277c2 --- /dev/null +++ b/test/db/snapshot.test.ts @@ -0,0 +1,149 @@ +import { factory, primaryKey, snapshot, oneOf, manyOf } from '@mswjs/data' + +it('should restore database without updatates made after snapshot', () => { + const db = factory({ + user: { + id: primaryKey(String), + firstName: String, + }, + }) + + db.user.create({ id: '123', firstName: 'John' }) + db.user.create({ id: '456', firstName: 'Kate' }) + + const restore = snapshot(db) + + db.user.create({ id: '789', firstName: 'Joe' }) + + expect(db.user.getAll()).toEqual([ + { + id: '123', + firstName: 'John', + }, + { + id: '456', + firstName: 'Kate', + }, + { + id: '789', + firstName: 'Joe', + }, + ]) + + restore() + + expect(db.user.getAll()).toEqual([ + { + id: '123', + firstName: 'John', + }, + { + id: '456', + firstName: 'Kate', + }, + ]) +}) + +it('should restore database with all realations', () => { + const db = factory({ + post: { + id: primaryKey(String), + title: String, + user: oneOf('user'), + }, + user: { + id: primaryKey(String), + firstName: String, + roles: manyOf('role'), + }, + role: { + id: primaryKey(String), + title: String, + }, + }) + + const admin = db.role.create({ id: '123', title: 'Admin' }) + const writer = db.role.create({ id: '456', title: 'Writer' }) + + const john = db.user.create({ + id: '123', + firstName: 'John', + roles: [admin, writer], + }) + const kate = db.user.create({ id: '456', firstName: 'Kate', roles: [admin] }) + const joe = db.user.create({ id: '789', firstName: 'Joe', roles: [writer] }) + + db.post.create({ id: '123', title: 'How to use MSW', user: john }) + db.post.create({ + id: '456', + title: 'A new way to model your data', + user: kate, + }) + + const restore = snapshot(db) + + db.post.create({ + id: '789', + title: 'Best practices with service workers', + user: joe, + }) + + expect(db.post.getAll()).toEqual([ + { + id: '123', + title: 'How to use MSW', + user: { + id: '123', + firstName: 'John', + roles: [ + { id: '123', title: 'Admin' }, + { id: '456', title: 'Writer' }, + ], + }, + }, + { + id: '456', + title: 'A new way to model your data', + user: { + id: '456', + firstName: 'Kate', + roles: [{ id: '123', title: 'Admin' }], + }, + }, + { + id: '789', + title: 'Best practices with service workers', + user: { + id: '789', + firstName: 'Joe', + roles: [{ id: '456', title: 'Writer' }], + }, + }, + ]) + + restore() + + expect(db.post.getAll()).toEqual([ + { + id: '123', + title: 'How to use MSW', + user: { + id: '123', + firstName: 'John', + roles: [ + { id: '123', title: 'Admin' }, + { id: '456', title: 'Writer' }, + ], + }, + }, + { + id: '456', + title: 'A new way to model your data', + user: { + id: '456', + firstName: 'Kate', + roles: [{ id: '123', title: 'Admin' }], + }, + }, + ]) +}) diff --git a/test/performance/performance.test.ts b/test/performance/performance.test.ts index fb63688e..77a0bbb1 100644 --- a/test/performance/performance.test.ts +++ b/test/performance/performance.test.ts @@ -2,7 +2,7 @@ import { datatype, random, name } from 'faker' import { factory, primaryKey } from '@mswjs/data' import { measurePerformance, repeat } from '../testUtils' -test('creates a 1000 records in under 100ms', async () => { +test('creates a 1000 records in under 350ms', async () => { const db = factory({ user: { id: primaryKey(datatype.uuid), @@ -20,7 +20,7 @@ test('creates a 1000 records in under 100ms', async () => { expect(createPerformance.duration).toBeLessThanOrEqual(350) }) -test('queries through a 1000 records in under 100ms', async () => { +test('queries through a 1000 records in under 350ms', async () => { const db = factory({ user: { id: primaryKey(datatype.uuid), @@ -45,7 +45,7 @@ test('queries through a 1000 records in under 100ms', async () => { expect(findManyPerformance.duration).toBeLessThanOrEqual(350) }) -test('updates a single record under 100ms', async () => { +test('updates a single record under 350ms', async () => { const db = factory({ user: { id: primaryKey(datatype.uuid), @@ -73,7 +73,7 @@ test('updates a single record under 100ms', async () => { expect(updatePerformance.duration).toBeLessThanOrEqual(350) }) -test('deletes a single record in under 100ms', async () => { +test('deletes a single record in under 350ms', async () => { const db = factory({ user: { id: primaryKey(datatype.uuid), @@ -99,7 +99,7 @@ test('deletes a single record in under 100ms', async () => { expect(deletePerformance.duration).toBeLessThanOrEqual(350) }) -test('deletes multiple records in under 100ms', async () => { +test('deletes multiple records in under 350ms', async () => { const db = factory({ user: { id: primaryKey(datatype.uuid), diff --git a/test/performance/snapshot.test.ts b/test/performance/snapshot.test.ts new file mode 100644 index 00000000..aa671a07 --- /dev/null +++ b/test/performance/snapshot.test.ts @@ -0,0 +1,43 @@ +import { datatype, random, name } from 'faker' +import { factory, primaryKey, snapshot } from '@mswjs/data' +import { measurePerformance, repeat } from '../testUtils' + +test('snapshot a database with 1000 records in under 350ms', async () => { + const db = factory({ + user: { + id: primaryKey(datatype.uuid), + firstName: name.firstName, + lastName: name.lastName, + age: datatype.number, + role: random.word, + }, + }) + repeat(db.user.create, 1000) + + const snapshotPerformance = await measurePerformance('snapshot', () => { + snapshot(db) + }) + + expect(snapshotPerformance.duration).toBeLessThanOrEqual(350) +}) + +it('restore a database with 1000 records in under 350ms', async () => { + const db = factory({ + user: { + id: primaryKey(datatype.uuid), + firstName: name.firstName, + lastName: name.lastName, + age: datatype.number, + role: random.word, + }, + }) + repeat(db.user.create, 1000) + + const restore = snapshot(db) + + const restorePerformance = await measurePerformance('restore', () => { + restore() + }) + + expect(restorePerformance.duration).toBeLessThanOrEqual(350) +})