Skip to content

Commit

Permalink
Uses dictionary structure to store records in the database
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito committed Dec 22, 2020
1 parent 30e3bde commit d0f48e6
Show file tree
Hide file tree
Showing 30 changed files with 432 additions and 190 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ $ npm install @mswjs/data --save-dev

```js
// src/mocks/db.js
import { factory } from '@mswjs/data'
import { factory, primaryKey } from '@mswjs/data'

export const db = factory({
user: {
id: () => 'abc-123',
id: primaryKey(() => 'abc-123'),
firstName: () => 'John',
lastName: () => 'Maverick',
},
Expand Down Expand Up @@ -243,14 +243,14 @@ const popularPosts = db.post.findMany({
#### One-to-one

```js
import { factory, oneOf } from '@mswjs/data'
import { factory, primaryKey, oneOf } from '@mswjs/data'

const db = factory({
user: {
id: String
id: primaryKey(String)
},
post: {
it: String
id: String
title: String
// The `post` model has the `author` property
// that points to the `user` entity.
Expand All @@ -270,11 +270,11 @@ db.post.create({

```js
import { random, name } from 'faker'
import { factory } from '@mswjs/data'
import { factory, primaryKey } from '@mswjs/data'

factory({
user: {
id: random.uuid,
id: primaryKey(random.uuid),
firstName: name.firstName,
},
})
Expand Down
165 changes: 61 additions & 104 deletions src/factory.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
import { mergeDeepRight } from 'ramda'
import {
FactoryAPI,
Database,
OneOf,
ManyOf,
ModelAPI,
BaseTypes,
ModelDeclaration,
EntityInstance,
ModelDictionary,
Value,
} from './glossary'
import { first } from './utils/first'
import { executeQuery } from './query/executeQuery'
import { compileQuery } from './query/compileQuery'
import { parseModelDeclaration } from './model/parseModelDeclaration'
import { createModel } from './model/createModel'
import { invariant } from './utils/invariant'
import { updateEntity } from './model/updateEntity'

/**
* Create a database with the given models.
*/
export function factory<Dictionary extends ModelDictionary>(
dict: Dictionary,
): FactoryAPI<Dictionary> {
const db: Database<EntityInstance<any, any>> = Object.keys(dict).reduce(
(acc, modelName) => {
acc[modelName] = []
return acc
},
{},
)
const db: Database = Object.keys(dict).reduce((acc, modelName) => {
acc[modelName] = new Map<string, EntityInstance<Dictionary, string>>()
return acc
}, {})

return Object.entries(dict).reduce<any>((acc, [modelName, props]) => {
acc[modelName] = createModelApi<Dictionary, typeof modelName>(
Expand All @@ -43,136 +37,99 @@ export function factory<Dictionary extends ModelDictionary>(
function createModelApi<
Dictionary extends ModelDictionary,
ModelName extends string
>(
modelName: ModelName,
declaration: Record<string, (() => BaseTypes) | OneOf<any> | ManyOf<any>>,
db: Database<EntityInstance<Dictionary, ModelName>>,
): ModelAPI<Dictionary, ModelName> {
return {
>(modelName: ModelName, declaration: ModelDeclaration, db: Database) {
const api: ModelAPI<Dictionary, ModelName> = {
create(initialValues = {}) {
const { properties, relations } = parseModelDeclaration<
const { primaryKey, properties, relations } = parseModelDeclaration<
Dictionary,
ModelName
>(modelName, declaration, initialValues)

const model = createModel<Dictionary, ModelName>(
modelName,
primaryKey,
properties,
relations,
db,
)
const modelPrimaryKey = model[model.__primaryKey]

// Prevent creation of multiple entities with the same primary key value.
invariant(
db[modelName].has(modelPrimaryKey as string),
`Failed to create "${modelName}": entity with the primary key "${modelPrimaryKey}" ("${model.__primaryKey}") already exists.`,
)

db[modelName].set(modelPrimaryKey as string, model)

db[modelName].push(model)
return model
},
count() {
return db[modelName].length
return db[modelName].size
},
findFirst(query) {
const results = executeQuery(modelName, query, db)
const results = executeQuery(modelName, 'PRIMARY_KEY', query, db)
return first(results)
},
findMany(query) {
return executeQuery(modelName, query, db)
return executeQuery(modelName, 'PRIMARY_KEY', query, db)
},
getAll() {
return Object.values(db[modelName])
return Array.from(db[modelName].values())
},
update(query) {
const executeQuery = compileQuery(query)
const prevRecords = db[modelName]
let nextRecord: EntityInstance<Dictionary, ModelName>

for (let index = 0; index < prevRecords.length; index++) {
const record = prevRecords[index]

if (executeQuery(record)) {
nextRecord = mergeDeepRight<
EntityInstance<Dictionary, ModelName>,
any
>(record, query.data)
db[modelName].splice(index, -1, nextRecord)
break
}
const record = api.findFirst(query)

if (!record) {
return null
}

const nextRecord = updateEntity(record, query.data)

db[modelName].set(record[record.__primaryKey] as string, nextRecord)

return nextRecord
},
updateMany(query) {
const executeQuery = compileQuery(query)
const prevRecords = db[modelName]

const { updatedRecords, nextRecords } = prevRecords.reduce(
(acc, record) => {
if (executeQuery(record)) {
const evaluatedData = Object.entries(query.data).reduce(
(acc, [property, propertyValue]) => {
const nextValue =
typeof propertyValue === 'function'
? propertyValue(record[property])
: propertyValue
acc[property] = nextValue
return acc
},
{},
)

const nextRecord = mergeDeepRight<
EntityInstance<Dictionary, ModelName>,
any
>(record, evaluatedData)
acc.updatedRecords.push(nextRecord)
acc.nextRecords.push(nextRecord)
} else {
acc.nextRecords.push(record)
}

return acc
},
{ updatedRecords: [], nextRecords: [] },
)
const records = api.findMany(query)
const updatedRecords = []

db[modelName] = nextRecords
if (!records) {
return null
}

records.forEach((record) => {
const nextRecord = updateEntity(record, query.data)
db[modelName].set(record[record.__primaryKey] as string, nextRecord)
updatedRecords.push(nextRecord)
})

return updatedRecords
},
delete(query) {
let deletedRecord: EntityInstance<Dictionary, ModelName>
const executeQuery = compileQuery(query)
const prevRecords = db[modelName]

for (let index = 0; index < prevRecords.length; index++) {
const record = prevRecords[index]

if (executeQuery(record)) {
deletedRecord = record
db[modelName].splice(index, 1)
break
}
const record = api.findFirst(query)

if (!record) {
return null
}

return deletedRecord
db[modelName].delete(record[record.__primaryKey] as string)
return record
},
deleteMany(query) {
const executeQuery = compileQuery(query)
const prevRecords = db[modelName]
const { deletedRecords, nextRecords } = prevRecords.reduce<{
deletedRecords: EntityInstance<Dictionary, ModelName>[]
nextRecords: EntityInstance<Dictionary, ModelName>[]
}>(
(acc, record) => {
if (executeQuery(record)) {
acc.deletedRecords.push(record)
} else {
acc.nextRecords.push(record)
}
return acc
},
{ deletedRecords: [], nextRecords: [] },
)
const records = api.findMany(query)

if (!records) {
return null
}

db[modelName] = nextRecords
records.forEach((record) => {
db[modelName].delete(record[record.__primaryKey] as string)
})

return deletedRecords
return records
},
}

return api
}
17 changes: 16 additions & 1 deletion src/glossary.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { QuerySelector } from './query/queryTypes'

export type PrimaryKeyType = string
export type BaseTypes = string | number | boolean | Date
export type KeyType = string | number | symbol

export interface PrimaryKeyDeclaration {
isPrimaryKey: boolean
getValue(): PrimaryKeyType
}

export enum RelationKind {
OneOf = 'oneOf',
ManyOf = 'manyOf',
Expand All @@ -24,13 +30,19 @@ export type ManyOf<T extends KeyType> = {
modelName: T
}

export type ModelDeclaration = Record<
string,
(() => BaseTypes) | OneOf<any> | ManyOf<any> | PrimaryKeyDeclaration
>

export type FactoryAPI<Dictionary extends Record<string, any>> = {
[K in keyof Dictionary]: ModelAPI<Dictionary, K>
}

export interface InternalEntityProperties<ModelName extends KeyType> {
readonly __type: ModelName
readonly __nodeId: string
readonly __primaryKey: PrimaryKeyType
}

export type EntityInstance<
Expand All @@ -45,6 +57,7 @@ export type Limit<T extends Record<string, any>> = {
[RK in keyof T]: {
[SK in keyof T[RK]]: T[RK][SK] extends
| (() => BaseTypes)
| PrimaryKeyDeclaration
| OneOf<keyof T>
| ManyOf<keyof T>
? T[RK][SK]
Expand Down Expand Up @@ -132,7 +145,9 @@ export type Value<
? EntityInstance<Parent, T[K]['modelName']>
: T[K] extends ManyOf<any>
? EntityInstance<Parent, T[K]['modelName']>[]
: T[K] extends PrimaryKeyDeclaration
? ReturnType<T[K]['getValue']>
: ReturnType<T[K]>
}

export type Database<EntityType> = Record<KeyType, EntityType[]>
export type Database = Record<KeyType, Map<string, EntityInstance<any, any>>>
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { factory } from './factory'
export { primaryKey } from './primaryKey'
export { oneOf } from './relations/oneOf'
export { manyOf } from './relations/manyOf'
8 changes: 5 additions & 3 deletions src/model/createModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Database,
InternalEntityProperties,
ModelDictionary,
EntityInstance,
PrimaryKeyType,
Value,
} from '../glossary'
import { defineRelationalProperties } from './defineRelationalProperties'
Expand All @@ -16,15 +16,17 @@ export function createModel<
ModelName extends string
>(
modelName: ModelName,
primaryKey: PrimaryKeyType,
properties: Value<Dictionary[ModelName], Dictionary>,
relations: Record<string, any>,
db: Database<EntityInstance<Dictionary, ModelName>>,
db: Database,
) {
log('creating model', modelName, properties, relations)
log('creating model', modelName, primaryKey, properties, relations)

const internalProperties: InternalEntityProperties<ModelName> = {
__type: modelName,
__nodeId: v4(),
__primaryKey: primaryKey,
}
const model = Object.assign({}, properties, internalProperties)
defineRelationalProperties(model, relations, db)
Expand Down
3 changes: 2 additions & 1 deletion src/model/defineRelationalProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const log = debug('relationalProperty')
export function defineRelationalProperties(
entity: Record<string, any>,
relations: Record<string, RelationalNode<any>>,
db: Database<any>,
db: Database,
): void {
const properties = Object.entries(relations).reduce(
(acc, [property, relation]) => {
Expand All @@ -20,6 +20,7 @@ export function defineRelationalProperties(
return acc.concat(
executeQuery(
node.__type,
null,
{
which: {
__nodeId: {
Expand Down

0 comments on commit d0f48e6

Please sign in to comment.