Skip to content

Commit 90d3a4a

Browse files
authored
Story - Store code organisation (#9)
* Further organisational improvement of store functions code. * Further organisational improvement of store functions code, focusing on store get code. * Lift and shift more get store types * Move over main store types to type file rather than dir
1 parent f781397 commit 90d3a4a

24 files changed

+409
-344
lines changed

src/store/create/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { SimplePgClient } from 'simple-pg-client/dist/types'
2+
import { createValueList } from '../../dataFormat/sql'
3+
import { DataFormatDeclaration, DataFormat, CreateRecordOptions, ToRecord, ManualCreateRecordOptions } from '../../dataFormat/types'
4+
import { createInsertReturningSql } from '../../helpers/sql'
5+
import { objectPropsToCamelCase } from '../../helpers/string'
6+
7+
export const create = async <T extends DataFormatDeclaration>(
8+
db: SimplePgClient,
9+
df: DataFormat<T>,
10+
options: CreateRecordOptions<T>,
11+
): Promise<ToRecord<T>> => {
12+
const fieldNamesInProvidedCreateOptions = Object.keys(options)
13+
const fieldNames = df.createRecordFieldNames.filter(f => fieldNamesInProvidedCreateOptions.indexOf(f) !== -1)
14+
15+
const valueList = createValueList(df, options, fieldNames)
16+
const sql = createInsertReturningSql(df.sql.tableName, fieldNames.map(f => df.sql.columnNames[f]))
17+
const row = await db.queryGetFirstRow(sql, valueList)
18+
return objectPropsToCamelCase(row)
19+
}
20+
21+
export const createManual = async <T extends DataFormatDeclaration>(
22+
db: SimplePgClient,
23+
df: DataFormat<T>,
24+
options: ManualCreateRecordOptions<T>,
25+
): Promise<ToRecord<T>> => {
26+
const fieldNamesInProvidedCreateOptions = Object.keys(options)
27+
const fieldNames = df.fieldNameList.filter(f => fieldNamesInProvidedCreateOptions.indexOf(f) !== -1)
28+
const valueList = fieldNames.map(f => (options as any)[f])
29+
const sql = createInsertReturningSql(df.sql.tableName, fieldNames.map(f => df.sql.columnNames[f]))
30+
const row = await db.queryGetFirstRow(sql, valueList)
31+
return objectPropsToCamelCase(row)
32+
}

src/store/create/types.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { CreateRecordOptions, DataFormatDeclaration, DataFormatDeclarations, ManualCreateRecordOptions, ToRecord } from '../../dataFormat/types'
2+
import { RelationDeclarations } from '../../relations/types'
3+
4+
export type CreateSingleFunctionOptions<
5+
T extends DataFormatDeclaration = DataFormatDeclaration,
6+
> = CreateRecordOptions<T>
7+
8+
export type CreateSingleFunctionResult<
9+
T extends DataFormatDeclaration = DataFormatDeclaration,
10+
> = Promise<ToRecord<T>>
11+
12+
export type CreateSingleFunction<
13+
T extends DataFormatDeclarations,
14+
K extends RelationDeclarations<T>,
15+
L extends T[number],
16+
> = <TOptions extends CreateSingleFunctionOptions<L>>(
17+
options: TOptions,
18+
) => CreateSingleFunctionResult<L>
19+
20+
export type CreateManualSingleFunctionOptions<
21+
T extends DataFormatDeclaration = DataFormatDeclaration,
22+
> = ManualCreateRecordOptions<T>
23+
24+
export type CreateManualSingleFunctionResult<
25+
T extends DataFormatDeclaration = DataFormatDeclaration,
26+
> = Promise<ToRecord<T>>
27+
28+
export type CreateManualSingleFunction<
29+
T extends DataFormatDeclarations,
30+
K extends RelationDeclarations<T>,
31+
L extends T[number],
32+
> = <TOptions extends CreateManualSingleFunctionOptions<L>>(
33+
options: TOptions,
34+
) => CreateManualSingleFunctionResult<L>

src/store/delete/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { createDataFilter } from '@samhuk/data-filter'
2+
import { SimplePgClient } from 'simple-pg-client/dist/types'
3+
import { DataFormat } from '../../dataFormat/types'
4+
import { objectPropsToCamelCase } from '../../helpers/string'
5+
import { DeleteSingleFunctionOptions, DeleteSingleFunctionResult } from './types'
6+
7+
export const deleteSingle = async (
8+
db: SimplePgClient,
9+
df: DataFormat,
10+
options: DeleteSingleFunctionOptions,
11+
): Promise<DeleteSingleFunctionResult> => {
12+
const rootSql = df.sql.deleteSqlBase
13+
const whereClause = createDataFilter(options.filter).toSql({
14+
transformer: node => ({ left: df.sql.columnNames[node.field] }),
15+
})
16+
const returnRecord = options.return ?? false
17+
const sql = `${rootSql} where ${whereClause} returning ${returnRecord ? '*' : '1'}`
18+
19+
const row = await db.queryGetFirstRow(sql)
20+
return (returnRecord ? objectPropsToCamelCase(row) : row != null) as any
21+
}

src/store/delete/types.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { DataFilterNodeOrGroup } from '@samhuk/data-filter/dist/types'
2+
import { DataFormatDeclaration, DataFormatDeclarations, ToRecord } from '../../dataFormat/types'
3+
import { RelationDeclarations } from '../../relations/types'
4+
5+
export type DeleteSingleFunctionOptions<
6+
T extends DataFormatDeclaration = DataFormatDeclaration,
7+
> = {
8+
/**
9+
* Filter to select the single record to update.
10+
*/
11+
filter: DataFilterNodeOrGroup<T['fields'][number]['name']>
12+
/**
13+
* Determines whether the deleted record is returned.
14+
*
15+
* @default false
16+
*/
17+
return?: boolean
18+
}
19+
20+
export type DeleteSingleFunctionResult<
21+
T extends DataFormatDeclarations = DataFormatDeclarations,
22+
K extends RelationDeclarations<T> = RelationDeclarations<T>,
23+
L extends T[number] = T[number],
24+
TOptions extends DeleteSingleFunctionOptions<L> = DeleteSingleFunctionOptions<L>,
25+
> = TOptions extends { return: boolean }
26+
? TOptions['return'] extends true
27+
? ToRecord<L> | null
28+
: boolean
29+
: ToRecord<L> | null
30+
31+
export type DeleteSingleFunction<
32+
T extends DataFormatDeclarations,
33+
K extends RelationDeclarations<T>,
34+
L extends T[number],
35+
> = <TOptions extends DeleteSingleFunctionOptions<L>>(
36+
options: TOptions,
37+
) => DeleteSingleFunctionResult<T, K, L, TOptions>

src/store/get/index.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { createDataFilter } from '@samhuk/data-filter'
2+
import { createDataQuery } from '@samhuk/data-query'
3+
import { SimplePgClient } from 'simple-pg-client/dist/types'
4+
import { DataFormat } from '../../dataFormat/types'
5+
import { objectPropsToCamelCase } from '../../helpers/string'
6+
import { TsPgOrm } from '../../types'
7+
import { createQueryPlan } from './queryPlan/queryPlan'
8+
import { AnyGetFunctionOptions, AnyGetFunctionResult } from './types'
9+
10+
const createColumnsSqlForGetWithNoRelations = (
11+
options: AnyGetFunctionOptions<false>,
12+
dataFormat: DataFormat,
13+
) => (options.fields ?? dataFormat.fieldNameList)
14+
.map(fName => dataFormat.sql.columnNames[fName])
15+
.filter(cName => cName != null)
16+
.join(', ')
17+
18+
const createGetSingleWithNoRelationsSql = (
19+
options: AnyGetFunctionOptions<false>,
20+
dataFormat: DataFormat,
21+
): string => {
22+
const columnsSql = createColumnsSqlForGetWithNoRelations(options, dataFormat)
23+
24+
const whereClause = options.filter != null
25+
? createDataFilter(options.filter).toSql({ transformer: node => ({ left: dataFormat.sql.columnNames[node.field] }) })
26+
: null
27+
const rootSelectSql = `select ${columnsSql} from ${dataFormat.sql.tableName}`
28+
if (whereClause == null)
29+
return `${rootSelectSql} limit 1`
30+
31+
return `${rootSelectSql} where ${whereClause} limit 1`
32+
}
33+
34+
const getSingleWithNoRelations = async (
35+
options: AnyGetFunctionOptions<false>,
36+
dataFormat: DataFormat,
37+
db: SimplePgClient,
38+
): Promise<any> => {
39+
const sql = createGetSingleWithNoRelationsSql(options, dataFormat)
40+
41+
const row = await db.queryGetFirstRow(sql)
42+
43+
return objectPropsToCamelCase(row)
44+
}
45+
46+
const createGetMultipleWithNoRelationsSql = (
47+
options: AnyGetFunctionOptions<true>,
48+
dataFormat: DataFormat,
49+
): string => {
50+
const columnsSql = createColumnsSqlForGetWithNoRelations(options, dataFormat)
51+
52+
const querySql = options.query != null
53+
? createDataQuery(options.query).toSql({
54+
filterTransformer: node => ({ left: dataFormat.sql.columnNames[node.field] }),
55+
sortingTransformer: node => ({ left: dataFormat.sql.columnNames[node.field] }),
56+
})
57+
: null
58+
const rootSelectSql = `select ${columnsSql} from ${dataFormat.sql.tableName}`
59+
if (querySql == null)
60+
return `${rootSelectSql} limit 1`
61+
62+
return `${rootSelectSql} ${querySql.whereOrderByLimitOffset}`
63+
}
64+
65+
const getMultipleWithNoRelations = async (
66+
options: AnyGetFunctionOptions<false>,
67+
dataFormat: DataFormat,
68+
db: SimplePgClient,
69+
): Promise<any> => {
70+
const sql = createGetMultipleWithNoRelationsSql(options, dataFormat)
71+
72+
const rows = await db.queryGetRows(sql)
73+
74+
return rows.map(r => objectPropsToCamelCase(r))
75+
}
76+
77+
export const getSingle = async (
78+
tsPgOrm: TsPgOrm,
79+
db: SimplePgClient,
80+
df: DataFormat,
81+
options: AnyGetFunctionOptions<false>,
82+
): Promise<AnyGetFunctionResult<false>> => {
83+
// Performance optimization for query with no relations.
84+
if (options.relations == null || Object.keys(options.relations).length === 0) {
85+
const result = await getSingleWithNoRelations(options, df, db)
86+
return result
87+
}
88+
89+
const queryPlan = createQueryPlan(tsPgOrm.relations, tsPgOrm.dataFormats, df, false, options)
90+
const result = await queryPlan.execute(db)
91+
return result
92+
}
93+
94+
export const getMultiple = async (
95+
tsPgOrm: TsPgOrm,
96+
db: SimplePgClient,
97+
df: DataFormat,
98+
options: AnyGetFunctionOptions<true>,
99+
): Promise<AnyGetFunctionResult<true>> => {
100+
// Performance optimization for query with no relations.
101+
if (options.relations == null || Object.keys(options.relations).length === 0) {
102+
const result = await getMultipleWithNoRelations(options, df, db)
103+
return result
104+
}
105+
106+
const queryPlan = createQueryPlan(tsPgOrm.relations, tsPgOrm.dataFormats, df, true, options)
107+
// @ts-ignore
108+
const result = await queryPlan.execute(db)
109+
return result
110+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Operator } from '@samhuk/data-filter/dist/types'
2-
import { tsPgOrm } from '../../testData'
2+
import { tsPgOrm } from '../../../testData'
33
import { toDataNodes } from './dataNodes'
44

55
describe('dataNodes', () => {
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { DataFormatsDict, DataFormat, FieldRef, DataFormatDeclarations } from '../../dataFormat/types'
2-
import { removeDuplicates } from '../../helpers/array'
3-
import { toDict } from '../../helpers/dict'
4-
import { RelationsDict, RelationType, Relation, RelationDeclarations } from '../../relations/types'
5-
import { AnyGetFunctionOptions, GetFunctionOptions } from '../types/get'
1+
import { DataFormatsDict, DataFormat, FieldRef, DataFormatDeclarations } from '../../../dataFormat/types'
2+
import { removeDuplicates } from '../../../helpers/array'
3+
import { toDict } from '../../../helpers/dict'
4+
import { RelationsDict, RelationType, Relation, RelationDeclarations } from '../../../relations/types'
5+
import { AnyGetFunctionOptions, GetFunctionOptions } from '../types'
66
import { RelatedDataInfoDict, DataNodes, UnresolvedDataNodes, FieldsInfo, DataNode, PluralDataNode, NonPluralDataNode } from './types'
77

88
/**

src/store/get/queryNodeToSql.spec.ts renamed to src/store/get/queryPlan/queryNodeToSql.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Operator } from '@samhuk/data-filter/dist/types'
2-
import { tsPgOrm } from '../../testData'
2+
import { tsPgOrm } from '../../../testData'
33
import { toDataNodes } from './dataNodes'
44
import { toQueryNodes } from './queryNodes'
55
import { toSqlNew } from './queryNodeToSql'
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createDataFilter } from '@samhuk/data-filter'
22
import { createDataQuery } from '@samhuk/data-query'
3-
import { DataFormatDeclarations, DataType } from '../../dataFormat/types'
4-
import { filterForNotNullAndEmpty } from '../../helpers/string'
5-
import { Relation, RelationType } from '../../relations/types'
3+
import { DataFormatDeclarations, DataType } from '../../../dataFormat/types'
4+
import { filterForNotNullAndEmpty } from '../../../helpers/string'
5+
import { Relation, RelationType } from '../../../relations/types'
66
import { isDataNodePlural } from './dataNodes'
77
import { QueryNode, DataNode, QueryNodeSql } from './types'
88

src/store/get/queryNodes.spec.ts renamed to src/store/get/queryPlan/queryNodes.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Operator } from '@samhuk/data-filter/dist/types'
2-
import { tsPgOrm } from '../../testData'
2+
import { tsPgOrm } from '../../../testData'
33
import { toDataNodes } from './dataNodes'
44
import { toQueryNodes } from './queryNodes'
55

0 commit comments

Comments
 (0)