Skip to content

Commit

Permalink
Story - V5 (#16)
Browse files Browse the repository at this point in the history
* initial commit of v5

* add relations, and very basic createTsPgOrm

* complete bulk of data formats and relations. Begin stores.

* Complete migration of get and getMany store functions

* recursively expand but leaf node at Date objects

* Complete bulk of v5. Remove all v4 code. Adapt integration tests. Remaining: unit tests.

* Add temporary ts-ignore

* Early unit test drive

* Early unit test drive

* Add create sql fns

* Update README partially. Update record types.

* version to 5.0.0. Etc.
  • Loading branch information
samhuk authored Oct 3, 2022
1 parent 1006909 commit d01db86
Show file tree
Hide file tree
Showing 85 changed files with 2,393 additions and 4,932 deletions.
42 changes: 16 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,22 @@ Start by viewing the [Getting Started](https://github.com/samhuk/ts-pg-orm/wiki/

## Usage Overview

Define your data formats, relations, and database connectivity to create type-safe auto-completing data stores.
Define data formats, relations, and database connectivity to create type-safe auto-completing data stores:

```typescript
// -- Set up TsPgOrm instance and data stores
const ORM = createTsPgOrm()
.loadDataFormats([...] as const)
.loadRelations(dfs => [...] as const)
await ORM.initDbClient({ host: 'localhost', port: 5432, ... })
const stores = await ORM.createStores()
const userDF = createDataFormat(...)
const orm = createTsPgOrm([userDF, ...] as const).setRelations([...] as const)
await orm.initDbClient({ host: 'localhost', port: 5432, ... })
await orm.provisionStores()
```

Perform CRUD operations on data stores
Perform CRUD operations on data stores:

```typescript
// Create
const userCreated = await stores.user.create({ name: 'alice' })
const userCreated = await orm.stores.user.create({ name: 'alice' })
// Get
const userFound = await stores.user.get({
const userFound = await orm.stores.user.get({
fields: ['name'],
filter: { field: 'id', op: Operator.EQUALS, val: 1 },
relations: { // Recursively include related data
Expand All @@ -56,23 +54,23 @@ const userFound = await stores.user.get({
},
})
// Update
const userUpdated = await stores.user.update({
const userUpdated = await orm.stores.user.update({
query: {
filter: { ... },
},
record: { name: 'bob' },
return: true,
})
// Delete
const userDeleted = await stores.user.delete({
const userDeleted = await orm.stores.user.delete({
query: {
filter: { ... },
},
return: true,
})
```

Create types to use throughout your application
Create types to use throughout your application:

```typescript
export type UserRecord = ToRecord<typeof USER_DFD>
Expand All @@ -83,28 +81,20 @@ export type UserGroupRecord = ToRecord<typeof USER_GROUP_DFD>
// { id: number, name: string, ... }
```
Use type-enforced sql information to create bespoke SQL statements
Use type-enforced sql information to create bespoke SQL statements:
```typescript
const sql = ORM.dataFormats.user.sql
const sql = orm.dataFormats.user.sql
const customUserSql = `select ${sql.columnNames.name} from ${sql.tableName}`
```

## Examples

Examples can be found within ./src/examples, showing more complete and realistic usages.
### Integration Tests

### Article API
The integration test suite connects to a real PostgreSQL server at (by default) postgres@localhost:5432 and performs various ts-pg-orm queries with a set of example data formats and relations.

This is a simple single-endpoint api that has two data formats - "User" and "UserArticle", with a single relation linking them.

Run `npm run article-api` to build and start the server (don't forget to run `npm i` first if you have not already). Once running, try sending a HTTP GET request to http://localhost:3000/userProfile/1.

### Real DB Test

This connects to a real PostgreSQL server at (by default) postgres@localhost:5432 and sets up a new ts-pg-orm instance with some sample data formats and relations, making queries to retreive records with related data.

Run `npm run real-db-test` to build and run this.
Run `npm run integration-tests` to build and run these. The database connection configuration is at `/.env-cmdrc.json`.

---

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-pg-orm",
"version": "4.3.5",
"version": "5.0.0",
"description": "Typescript PostgreSQL ORM",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
100 changes: 66 additions & 34 deletions src/common/obj.spec.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,89 @@
import { setObjPropDeep } from './obj'
import { readObjPropDeep, setObjPropDeep } from './obj'

describe('setObjPropDeep', () => {
const fn = setObjPropDeep
describe('common/obj', () => {
describe('setObjPropDeep', () => {
const fn = setObjPropDeep

test('basic test', () => {
const obj = {
a: {
b: {
c: {
test('basic test', () => {
const obj = {
a: {
b: {
c: {

},
},
},
},
}
}

const newObj = fn(obj, ['a', 'b', 'c', 'd'], 'new value')
const newObj = fn(obj, ['a', 'b', 'c', 'd'], 'new value')

expect(newObj).toEqual({
a: {
b: {
c: {
d: 'new value',
expect(newObj).toEqual({
a: {
b: {
c: {
d: 'new value',
},
},
},
},
})
})
})

test('basic test - createIfNotExists = true', () => {
const obj = {
a: { },
}
test('basic test - createIfNotExists = true', () => {
const obj = {
a: { },
}

const newObj = fn(obj, ['a', 'b', 'c', 'd'], 'new value', true)
const newObj = fn(obj, ['a', 'b', 'c', 'd'], 'new value', true)

expect(newObj).toEqual({
a: {
b: {
c: {
d: 'new value',
expect(newObj).toEqual({
a: {
b: {
c: {
d: 'new value',
},
},
},
},
})
})

test('basic test - single path segment', () => {
const obj = {}

const newObj = fn(obj, ['a'], 'new value', false)

expect(newObj).toEqual({
a: 'new value',
})
})
})

test('basic test - single path segment', () => {
const obj = {}
describe('readObjPropDeep', () => {
const fn = readObjPropDeep

test('basic test', () => {
const obj = {
a: {
b: {
c: {
d: 'val',
},
},
},
}

expect(fn(obj, ['a', 'b', 'c'])).toEqual({ d: 'val' })
expect(fn(obj, ['a', 'b', 'c', 'd'])).toEqual('val')
})

test('basic test - single path segment', () => {
const obj: any = {}
expect(fn(obj, ['a'])).toEqual(undefined)

const newObj = fn(obj, ['a'], 'new value', false)
obj.a = null
expect(fn(obj, ['a'])).toEqual(null)

expect(newObj).toEqual({
a: 'new value',
obj.a = 'val'
expect(fn(obj, ['a'])).toEqual('val')
})
})
})
121 changes: 0 additions & 121 deletions src/dataFormat/common.ts

This file was deleted.

10 changes: 10 additions & 0 deletions src/dataFormat/createRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DataType, NumSubType, StrSubType } from './types/dataType'
import { FieldList } from './types/field'

export const filterForCreateRecordField = (fieldList: FieldList) => (
fieldList.filter(f => !(
(f.type === DataType.NUM && f.subType === NumSubType.SERIAL)
|| (f.type === DataType.STR && f.subType === StrSubType.UUID_V4 && f.autoGenerate)
|| (f.type === DataType.EPOCH && f.defaultToCurrentEpoch)
))
)
16 changes: 16 additions & 0 deletions src/dataFormat/field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { camelCaseToSnakeCase, quote } from '../helpers/string'
import { Field, FieldOptions, FieldsOptions } from './types/field'

export const createCommonFields = <T extends FieldsOptions>(v: T): T => v

export const createField = (fieldName: string, fieldOptions: FieldOptions): Field => {
const unquotedColumnName = fieldOptions.columnName ?? camelCaseToSnakeCase(fieldName)
return {
...fieldOptions,
name: fieldName,
sql: {
columnName: quote(unquotedColumnName),
unquotedColumnName,
},
}
}
Loading

0 comments on commit d01db86

Please sign in to comment.