Skip to content

Commit b0538d9

Browse files
committed
type safe table field and name access
1 parent 044a0c7 commit b0538d9

File tree

14 files changed

+545
-207
lines changed

14 files changed

+545
-207
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ docker-compose run k6 run /benchmark/composed-schema.js
5858
Our benchmark suite is running in the CI.
5959

6060
## Contributing
61+
6162
❤️ contributions!
6263

6364
I will happily accept your pull request if it:
6465

6566
- has tests
6667
- looks reasonable
67-
- follows the [code of conduct](./CODE_OF_CONDUCT.md)
68+
- follows the [code of conduct](./CODE_OF_CONDUCT.md)

docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,4 @@ POST - `/schema/garbage_collect` Removes all schemas except the most recent N of
126126

127127
### Check if registry is reachable
128128

129-
GET - `/health` healthcheck endpoint.
129+
GET - `/health` healthcheck endpoint.

docs/auth.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ You have to set `JWT_SECRET=secret` to enable jwt. The jwt payload must match th
1414
}
1515
```
1616

17-
This activates authorization in the `/schema/push` endpoint. Only the client with the valid jwt is be able to register schemas in the name of the services. The client will have access to all available graphs. You can use [jwt.io](https://jwt.io/) to construct a jwt.
17+
This activates authorization in the `/schema/push` endpoint. Only the client with the valid jwt is be able to register schemas in the name of the services. The client will have access to all available graphs. You can use [jwt.io](https://jwt.io/) to construct a jwt.

insomnia.json

Lines changed: 317 additions & 1 deletion
Large diffs are not rendered by default.

src/core/models/graphModel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ export type GraphDBModel = {
44
isActive?: boolean
55
createdAt: Date
66
updatedAt?: Date
7-
}
7+
}

src/core/repositories/GraphRepository.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,27 @@ import { Knex } from 'knex'
22
import { GraphDBModel } from '../models/graphModel'
33

44
export default class GraphRepository {
5-
#table = 'graph'
5+
static table = 'graph'
6+
static field = (name: keyof GraphDBModel) => GraphRepository.table + '.' + name
67
#knex: Knex
78
constructor(knex: Knex) {
89
this.#knex = knex
910
}
1011
async exists({ name }: { name: string }) {
1112
const knex = this.#knex
12-
const result = await knex
13-
.from(this.#table)
14-
.count('id')
15-
.where('name', knex.raw('?', name))
16-
.first<{ count: number }>()
13+
const table = GraphRepository.table
14+
const result = await knex.from(table).count('id').where('name', knex.raw('?', name)).first<{ count: number }>()
1715

1816
return result.count > 0
1917
}
2018
findFirst({ name }: { name: string }) {
2119
const knex = this.#knex
22-
return knex
23-
.from(this.#table)
24-
.where('name', knex.raw('?', name))
25-
.first<GraphDBModel>()
20+
const table = GraphRepository.table
21+
return knex.from(table).where('name', knex.raw('?', name)).first<GraphDBModel>()
2622
}
2723
async create(entity: Omit<GraphDBModel, 'id' | 'createdAt'>) {
2824
const knex = this.#knex
29-
const table = this.#table
30-
25+
const table = GraphRepository.table
3126
const [first] = await knex(table)
3227
.insert({
3328
...entity,
Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,106 @@
11
import { Knex } from 'knex'
22
import { SchemaDBModel } from '../models/schemaModel'
33
import { LastUpdatedSchema } from '../types'
4+
import GraphRepository from './GraphRepository'
5+
import SchemaTagRepository from './SchemaTagRepository'
6+
import ServiceRepository from './ServiceRepository'
47

58
export default class SchemaRepository {
6-
#table = 'schema'
9+
static table = 'schema'
10+
static field = (name: keyof SchemaDBModel) => SchemaRepository.table + '.' + name
711
#knex: Knex
812
constructor(knex: Knex) {
913
this.#knex = knex
1014
}
1115
findById(id: number) {
1216
const knex = this.#knex
13-
const table = this.#table
17+
const table = SchemaRepository.table
1418
return knex
15-
.from(this.#table)
16-
.where(`${table}.isActive`, knex.raw('?', true))
17-
.where(`${table}.id`, knex.raw('?', id))
19+
.from(table)
20+
.where(`${SchemaRepository.field('isActive')}`, knex.raw('?', true))
21+
.where(`${SchemaRepository.field('id')}`, knex.raw('?', id))
1822
.first<SchemaDBModel>()
1923
}
2024
findFirst({ graphName, typeDefs, serviceName }: { graphName: string; typeDefs: string; serviceName: string }) {
2125
const knex = this.#knex
22-
const table = this.#table
26+
const table = SchemaRepository.table
2327
return knex
2428
.from(table)
25-
.join('graph', function () {
26-
this.on(`${table}.graphId`, '=', 'graph.id')
27-
.andOn('graph.isActive', '=', knex.raw('?', true))
28-
.andOn('graph.name', '=', knex.raw('?', graphName))
29+
.join(`${GraphRepository.table}`, function () {
30+
this.on(`${SchemaRepository.field('graphId')}`, '=', `${GraphRepository.field('id')}`)
31+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
32+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
2933
})
30-
.join('service', function () {
31-
this.on(`${table}.serviceId`, '=', 'service.id')
32-
.andOn('service.isActive', '=', knex.raw('?', true))
33-
.andOn('service.name', '=', knex.raw('?', serviceName))
34+
.join(`${ServiceRepository.table}`, function () {
35+
this.on(`${SchemaRepository.field('serviceId')}`, '=', `${ServiceRepository.field('id')}`)
36+
.andOn(`${ServiceRepository.field('isActive')}`, '=', knex.raw('?', true))
37+
.andOn(`${ServiceRepository.field('name')}`, '=', knex.raw('?', serviceName))
3438
})
35-
.where(`${table}.typeDefs`, knex.raw('?', typeDefs))
39+
.where(`${SchemaRepository.field('typeDefs')}`, knex.raw('?', typeDefs))
3640
.select(`${table}.*`)
3741
.first<SchemaDBModel>()
3842
}
3943
findLastUpdated({ serviceName, graphName }: { graphName: string; serviceName: string }) {
4044
const knex = this.#knex
41-
const table = this.#table
45+
const table = SchemaRepository.table
4246
return knex
4347
.from(table)
44-
.select([`${table}.id`, `${table}.typeDefs`, `schema_tag.version`])
45-
.join('graph', function () {
46-
this.on(`${table}.graphId`, '=', 'graph.id')
47-
.andOn('graph.isActive', '=', knex.raw('?', true))
48-
.andOn('graph.name', '=', knex.raw('?', graphName))
48+
.select([
49+
`${SchemaRepository.field('id')}`,
50+
`${SchemaRepository.field('typeDefs')}`,
51+
`${SchemaTagRepository.field('version')}`,
52+
])
53+
.join(`${GraphRepository.table}`, function () {
54+
this.on(`${SchemaRepository.field('graphId')}`, '=', `${GraphRepository.field('id')}`)
55+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
56+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
4957
})
50-
.join('service', function () {
51-
this.on(`${table}.serviceId`, '=', 'service.id')
52-
.andOn('service.isActive', '=', knex.raw('?', true))
53-
.andOn('service.name', '=', knex.raw('?', serviceName))
58+
.join(`${ServiceRepository.table}`, function () {
59+
this.on(`${SchemaRepository.field('serviceId')}`, '=', `${ServiceRepository.field('id')}`)
60+
.andOn(`${ServiceRepository.field('isActive')}`, '=', knex.raw('?', true))
61+
.andOn(`${ServiceRepository.field('name')}`, '=', knex.raw('?', serviceName))
5462
})
55-
.leftJoin('schema_tag', function () {
56-
this.on(`${table}.id`, '=', 'schema_tag.schemaId').andOn('schema_tag.isActive', '=', knex.raw('?', true))
63+
.leftJoin(`${SchemaTagRepository.table}`, function () {
64+
this.on(`${SchemaRepository.field('id')}`, '=', `${SchemaTagRepository.field('schemaId')}`).andOn(
65+
`${SchemaTagRepository.field('isActive')}`,
66+
'=',
67+
knex.raw('?', true),
68+
)
5769
})
58-
.where(`${table}.isActive`, knex.raw('?', true))
70+
.where(`${SchemaRepository.field('isActive')}`, knex.raw('?', true))
5971
.orderBy([
60-
{ column: `${table}.updatedAt`, order: 'desc' },
61-
{ column: `schema_tag.createdAt`, order: 'desc' },
72+
{ column: `${SchemaRepository.field('updatedAt')}`, order: 'desc' },
73+
{ column: `${SchemaTagRepository.field('createdAt')}`, order: 'desc' },
6274
])
6375
.first<LastUpdatedSchema>()
6476
}
6577
findBySchemaTag({ graphName, version, serviceName }: { graphName: string; version: string; serviceName: string }) {
6678
const knex = this.#knex
67-
const table = this.#table
79+
const table = SchemaRepository.table
6880
return knex
6981
.from(table)
70-
.join('graph', function () {
71-
this.on(`${table}.graphId`, '=', 'graph.id')
72-
.andOn('graph.isActive', '=', knex.raw('?', true))
73-
.andOn('graph.name', '=', knex.raw('?', graphName))
82+
.join(`${GraphRepository.table}`, function () {
83+
this.on(`${SchemaRepository.field('graphId')}`, '=', `${GraphRepository.field('id')}`)
84+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
85+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
7486
})
75-
.join('service', function () {
76-
this.on(`${table}.serviceId`, '=', 'service.id')
77-
.andOn('service.isActive', '=', knex.raw('?', true))
78-
.andOn('service.name', '=', knex.raw('?', serviceName))
87+
.join(`${ServiceRepository.table}`, function () {
88+
this.on(`${SchemaRepository.field('serviceId')}`, '=', `${ServiceRepository.field('id')}`)
89+
.andOn(`${ServiceRepository.field('isActive')}`, '=', knex.raw('?', true))
90+
.andOn(`${ServiceRepository.field('name')}`, '=', knex.raw('?', serviceName))
7991
})
80-
.join('schema_tag', function () {
81-
this.on(`${table}.id`, '=', 'schema_tag.id')
82-
.andOn('schema_tag.isActive', '=', knex.raw('?', true))
83-
.andOn('schema_tag.version', '=', knex.raw('?', version))
92+
.join(`${SchemaTagRepository.table}`, function () {
93+
this.on(`${SchemaRepository.field('id')}`, '=', `${SchemaTagRepository.field('id')}`)
94+
.andOn(`${SchemaTagRepository.table}.isActive`, '=', knex.raw('?', true))
95+
.andOn(`${SchemaTagRepository.table}.version`, '=', knex.raw('?', version))
8496
})
85-
.where(`${table}.isActive`, knex.raw('?', true))
97+
.where(`${SchemaRepository.field('isActive')}`, knex.raw('?', true))
8698
.select(`${table}.*`)
8799
.first<SchemaDBModel>()
88100
}
89101
async create(entity: Omit<SchemaDBModel, 'id' | 'createdAt'>) {
90102
const knex = this.#knex
91-
const table = this.#table
103+
const table = SchemaRepository.table
92104
const [first] = await knex<SchemaDBModel>(table)
93105
.insert({
94106
...entity,
@@ -101,7 +113,10 @@ export default class SchemaRepository {
101113
}
102114
async updateById(schemaId: number, entity: Partial<SchemaDBModel>) {
103115
const knex = this.#knex
104-
const table = this.#table
105-
return knex(table).update(entity).where(`${table}.id`, '=', schemaId).returning<SchemaDBModel[]>('*')
116+
const table = SchemaRepository.table
117+
return knex(table)
118+
.update(entity)
119+
.where(`${SchemaRepository.field('id')}`, '=', schemaId)
120+
.returning<SchemaDBModel[]>('*')
106121
}
107122
}

src/core/repositories/SchemaTagRepository.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
import { Knex } from 'knex'
22
import { SchemaTagDBModel } from '../models/schemaTagModel'
3+
import SchemaRepository from './SchemaRepository'
34

45
export default class SchemaTagRepository {
5-
#table = 'schema_tag'
6+
static table = 'schema_tag'
7+
static field = (name: keyof SchemaTagDBModel) => SchemaTagRepository.table + '.' + name
68
#knex: Knex
79
constructor(knex: Knex) {
810
this.#knex = knex
911
}
1012
findByVersion({ version, schemaId }: { version: string; schemaId: number; serviceId: number }) {
1113
const knex = this.#knex
12-
const table = this.#table
14+
const table = SchemaTagRepository.table
1315
return knex
1416
.from(table)
15-
.join('schema', function () {
16-
this.on(`${table}.schemaId`, '=', knex.raw('?', schemaId)).andOn('schema.isActive', '=', knex.raw('?', true))
17+
.join(`${SchemaRepository.table}`, function () {
18+
this.on(`${SchemaTagRepository.field('schemaId')}`, '=', knex.raw('?', schemaId)).andOn(
19+
`${SchemaRepository.field('isActive')}`,
20+
'=',
21+
knex.raw('?', true),
22+
)
1723
})
18-
.where(`${table}.version`, knex.raw('?', version))
24+
.where(`${SchemaTagRepository.field('version')}`, knex.raw('?', version))
1925
.select(`${table}.*`)
2026
.first<SchemaTagDBModel>()
2127
}
2228
async create(entity: Omit<SchemaTagDBModel, 'id' | 'createdAt'>) {
2329
const knex = this.#knex
24-
const table = this.#table
30+
const table = SchemaTagRepository.table
2531
const [first] = await knex(table)
2632
.insert({
2733
...entity,

src/core/repositories/ServiceRepository.ts

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,75 @@
11
import { Knex } from 'knex'
22
import { ServiceDBModel } from '../models/serviceModel'
3+
import GraphRepository from './GraphRepository'
34

45
export default class ServiceRepository {
5-
#table = 'service'
6+
static table = 'service'
7+
static field = (name: keyof ServiceDBModel) => ServiceRepository.table + '.' + name
68
#knex: Knex
79
constructor(knex: Knex) {
810
this.#knex = knex
911
}
1012
findFirst({ graphName, name }: { graphName: string; name: string }) {
1113
const knex = this.#knex
12-
const table = this.#table
14+
const table = ServiceRepository.table
1315
return knex
14-
.from(this.#table)
15-
.join('graph', function () {
16-
this.on(`${table}.graphId`, '=', 'graph.id')
17-
.andOn('graph.isActive', '=', knex.raw('?', true))
18-
.andOn('graph.name', '=', knex.raw('?', graphName))
16+
.from(table)
17+
.join(`${GraphRepository.table}`, function () {
18+
this.on(`${table}.graphId`, '=', `${GraphRepository.field('id')}`)
19+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
20+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
1921
})
20-
.where(`${table}.name`, knex.raw('?', name))
22+
.where(`${ServiceRepository.field('name')}`, knex.raw('?', name))
2123
.select(`${table}.*`)
2224
.first<ServiceDBModel>()
2325
}
2426
findByNames({ graphName }: { graphName: string }, serviceNames: string[]): Promise<ServiceDBModel[]> {
2527
const knex = this.#knex
26-
const table = this.#table
28+
const table = ServiceRepository.table
2729
return knex
2830
.from(table)
2931
.select([`${table}.*`])
30-
.join('graph', function () {
31-
this.on(`${table}.graphId`, '=', 'graph.id')
32-
.andOn('graph.isActive', '=', knex.raw('?', true))
33-
.andOn('graph.name', '=', knex.raw('?', graphName))
32+
.join(`${GraphRepository.table}`, function () {
33+
this.on(`${ServiceRepository.field('graphId')}`, '=', `${GraphRepository.field('id')}`)
34+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
35+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
3436
})
35-
.where(`${table}.isActive`, knex.raw('?', true))
36-
.whereIn(`${table}.name`, serviceNames)
37-
.orderBy(`${table}.updatedAt`, 'desc')
37+
.where(`${ServiceRepository.field('isActive')}`, knex.raw('?', true))
38+
.whereIn(`${ServiceRepository.field('name')}`, serviceNames)
39+
.orderBy(`${ServiceRepository.field('updatedAt')}`, 'desc')
3840
}
3941
findManyExceptWithName({ graphName }: { graphName: string }, exceptService: string): Promise<ServiceDBModel[]> {
4042
const knex = this.#knex
41-
const table = this.#table
43+
const table = ServiceRepository.table
4244
return knex
4345
.from<ServiceDBModel>(table)
4446
.select([`${table}.*`])
45-
.join('graph', function () {
46-
this.on(`${table}.graphId`, '=', 'graph.id')
47-
.andOn('graph.isActive', '=', knex.raw('?', true))
48-
.andOn('graph.name', '=', knex.raw('?', graphName))
47+
.join(`${GraphRepository.table}`, function () {
48+
this.on(`${ServiceRepository.field('graphId')}`, '=', `${GraphRepository.table}.id`)
49+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
50+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
4951
})
50-
.where(`${table}.isActive`, knex.raw('?', true))
51-
.whereNot(`${table}.name`, exceptService)
52-
.orderBy(`${table}.updatedAt`, 'desc')
52+
.where(`${ServiceRepository.field('isActive')}`, knex.raw('?', true))
53+
.whereNot(`${ServiceRepository.field('name')}`, exceptService)
54+
.orderBy(`${ServiceRepository.field('updatedAt')}`, 'desc')
5355
}
5456
findMany({ graphName }: { graphName: string }): Promise<ServiceDBModel[]> {
5557
const knex = this.#knex
56-
const table = this.#table
58+
const table = ServiceRepository.table
5759
return knex
5860
.from(table)
5961
.select([`${table}.*`])
60-
.join('graph', function () {
61-
this.on(`${table}.graphId`, '=', 'graph.id')
62-
.andOn('graph.isActive', '=', knex.raw('?', true))
63-
.andOn('graph.name', '=', knex.raw('?', graphName))
62+
.join(`${GraphRepository.table}`, function () {
63+
this.on(`${ServiceRepository.field('graphId')}`, '=', `${GraphRepository.field('id')}`)
64+
.andOn(`${GraphRepository.field('isActive')}`, '=', knex.raw('?', true))
65+
.andOn(`${GraphRepository.field('name')}`, '=', knex.raw('?', graphName))
6466
})
65-
.where(`${table}.isActive`, knex.raw('?', true))
66-
.orderBy(`${table}.updatedAt`, 'desc')
67+
.where(`${ServiceRepository.field('isActive')}`, knex.raw('?', true))
68+
.orderBy(`${ServiceRepository.field('updatedAt')}`, 'desc')
6769
}
6870
async create(entity: Omit<ServiceDBModel, 'id' | 'createdAt'>) {
6971
const knex = this.#knex
70-
const table = this.#table
72+
const table = ServiceRepository.table
7173

7274
const [first] = await knex(table)
7375
.insert({

src/core/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ export type ErrorResponse = {
2121
error: string
2222
}
2323

24-
export type LastUpdatedSchema = Pick<SchemaTagDBModel, 'version'> & Pick<SchemaDBModel, 'id' | 'typeDefs'>
24+
export type LastUpdatedSchema = Pick<SchemaTagDBModel, 'version'> & Pick<SchemaDBModel, 'id' | 'typeDefs'>

0 commit comments

Comments
 (0)