Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

![Unit Tests](https://github.com/monolithst/functional-models-orm-memory/actions/workflows/ut.yml/badge.svg?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/monolithst/functional-models-orm-memory/badge.svg?branch=master)](https://coveralls.io/github/monolithst/functional-models-orm-memory?branch=master)

An ORM datastore adapter that is in-memory.

Useful for testing and some caching applications.
Expand Down
22 changes: 22 additions & 0 deletions features/api.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: API Functions

Scenario: A simple "Cruds" on a model
Given a datastore using seed data SeedData1 is created
When count is called with Test1Models and Undefined
Then the result is 4
When retrieve is called with Test1Models and Test1ModelDataId1
Then the result is undefined
When save is called with Test1Models and data Test1ModelData
Then the result matches Test1ModelData
When retrieve is called with Test1Models and Test1ModelDataId1
Then the result matches Test1ModelData
When count is called with Test1Models and Test1ModelDataId1
Then the result is 5
When search is called with Test1Models and SearchTest1Models1
Then the result matches SearchTest1Models1Result
When delete is called with Test1Models and Test1ModelDataId1
Then the result is undefined
When retrieve is called with Test1Models and Test1ModelDataId1
Then the result is undefined
When count is called with Test1Models and Test1ModelDataId1
Then the result is 4
96 changes: 96 additions & 0 deletions features/step_definitions/steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { assert } from 'chai'
import { Given, When, Then } from '@cucumber/cucumber'
import { datastoreAdapter } from '../../src'
import { createOrm, Orm, PrimaryKeyUuidProperty, queryBuilder, TextProperty } from 'functional-models'

const SeedData = {
SeedData1: () => {
return {
'functional-models-orm-memory-test-1-models': {
'29a766b5-e77b-4099-a7f2-61cda0a29cc3': {
id: '29a766b5-e77b-4099-a7f2-61cda0a29cc3',
name: 'my-name-1',
},
'032c282c-b367-4d15-b19a-01c855b38f44': {
id: '032c282c-b367-4d15-b19a-01c855b38f44',
name: 'my-name-2',
},
'49dd6b5b-cb33-4331-8224-98cc4fd4595a': {
id: '49dd6b5b-cb33-4331-8224-98cc4fd4595a',
name: 'my-name-3',
},
'd84734d3-2ec4-4da4-8cac-e2953394b7f4': {
id: 'd84734d3-2ec4-4da4-8cac-e2953394b7f4',
name: 'my-name-4',
},
},
}
}
}

const Data = {
Undefined: () => undefined,
Test1ModelDataId1: () => 'cbac64c0-36c0-42c8-b736-57c5b7ba1c5a',
Test1ModelData: () => ({
id: 'cbac64c0-36c0-42c8-b736-57c5b7ba1c5a',
name: 'another-name'
}),
SearchTest1Models1: () => queryBuilder()
.property('name', 'another-name')
.compile(),
SearchTest1Models1Result: () => ({
instances: [{
id: 'cbac64c0-36c0-42c8-b736-57c5b7ba1c5a',
name: 'another-name'
}],
page: undefined,
})
}

const Models = {
Test1Models: (orm: Orm) => {
return orm.Model({
pluralName: 'Test1Models',
namespace: 'functional-models-orm-memory',
properties: {
id: PrimaryKeyUuidProperty(),
name: TextProperty(),
}
})
}
}

Given('a datastore using seed data {word} is created', function(key: string) {
const seedData = SeedData[key]()
this.datastoreAdapter = datastoreAdapter.create({seedData})
this.orm = createOrm({ datastoreAdapter: this.datastoreAdapter })
})

When('{word} is called with {word} and {word}', async function(funcKey: string, modelKey: string, dataKey: string) {
const model = Models[modelKey](this.orm)
const data = Data[dataKey]()
this.result = await this.datastoreAdapter[funcKey](model, data)
})

When('save is called with {word} and data {word}', async function(modelKey: string, dataKey: string) {
const model = Models[modelKey](this.orm)
const data = Data[dataKey]()
this.result = await this.datastoreAdapter.save(model.create(data))
})

Then('the result is {int}', function(value: any) {
const actual = this.result
const expected = value
assert.deepEqual(actual, expected)
})

Then('the result is undefined', function() {
const actual = this.result
assert.isUndefined(actual)
})

Then('the result matches {word}', function(dataKey: string) {
const expected = Data[dataKey]()
const actual = this.result
assert.deepEqual(actual, expected)
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"typescript": "^5.7.2"
},
"dependencies": {
"date-fns": "^3.6.0",
"functional-models": "^3.0.12",
"lodash": "^4.17.21"
}
Expand Down
90 changes: 88 additions & 2 deletions src/datastoreAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,91 @@
const create = () => {
return {}
import {
DataDescription,
DatastoreAdapter,
DatastoreSearchResult,
Maybe,
ModelInstance,
ModelType,
OrmModel,
OrmSearch,
PrimaryKeyType,
ToObjectResult,
} from 'functional-models'
import clone from 'lodash/clone'
import merge from 'lodash/merge'
import { filterResults } from './lib'

type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }

type Props = {
seedData?: Record<string, Record<string | number, any>>
}

const create = ({ seedData }: Props = {}): WithRequired<
DatastoreAdapter,
'count'
> => {
const database: Record<string, Record<string | number, any>> = clone(
seedData
) || {}

const _getRecords = <TData extends DataDescription>(
model: ModelType<TData>
) => {
const name = model.getName()
if (!(name in database)) {
// eslint-disable-next-line functional/immutable-data
database[name] = {}
}
return database[name] as unknown as Record<string, ToObjectResult<TData>>
}

return {
delete: <TData extends DataDescription>(
model: OrmModel<TData>,
id: PrimaryKeyType
): Promise<void> => {
const records = _getRecords(model)
// eslint-disable-next-line functional/immutable-data
delete records[id]
return Promise.resolve(undefined)
},
retrieve: <TData extends DataDescription>(
model: OrmModel<TData>,
primaryKey: PrimaryKeyType
): Promise<Maybe<ToObjectResult<TData>>> => {
return Promise.resolve().then(() => {
const records = _getRecords(model)
return records[primaryKey] as unknown as
| ToObjectResult<TData>
| undefined
})
},
save: async <TData extends DataDescription>(
instance: ModelInstance<TData>
): Promise<ToObjectResult<TData>> => {
return Promise.resolve().then(async () => {
const model = instance.getModel()
const data = await instance.toObj<TData>()
const records = _getRecords(model)
merge(records, { [await instance.getPrimaryKey()]: data })
return data
})
},
search: <TData extends DataDescription>(
model: OrmModel<TData>,
query: OrmSearch
): Promise<DatastoreSearchResult<TData>> => {
const records = _getRecords(model)
const instances = filterResults(query, Object.values(records))
return Promise.resolve({ instances: instances, page: undefined })
},
count: <TData extends DataDescription>(model: OrmModel<TData>) => {
return Promise.resolve().then(() => {
const records = _getRecords(model)
return Object.keys(records).length
})
},
}
}

export { create }
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * as datastoreAdapter from './datastoreAdapter'
export { filterResults } from './lib'
Loading