@platformatic/sql-graphql 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +21 -4
- package/lib/entity-to-type.js +11 -2
- package/package.json +4 -4
- package/test/composite.test.js +143 -0
- package/test/ignore.test.js +90 -0
package/index.js
CHANGED
|
@@ -16,6 +16,7 @@ async function mapperToGraphql (app, opts) {
|
|
|
16
16
|
const loaders = {}
|
|
17
17
|
const federationReplacements = []
|
|
18
18
|
const relations = []
|
|
19
|
+
const ignore = opts.ignore || {}
|
|
19
20
|
|
|
20
21
|
const graphOpts = {
|
|
21
22
|
queryTopFields,
|
|
@@ -35,8 +36,11 @@ async function mapperToGraphql (app, opts) {
|
|
|
35
36
|
}
|
|
36
37
|
} else {
|
|
37
38
|
for (const entity of Object.values(mapper.entities)) {
|
|
39
|
+
if (ignore[entity.pluralName] === true) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
38
42
|
relations.push(...entity.relations)
|
|
39
|
-
const meta = constructGraph(app, entity, graphOpts)
|
|
43
|
+
const meta = constructGraph(app, entity, graphOpts, ignore[entity.pluralName] || {})
|
|
40
44
|
metaMap.set(entity, meta)
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -89,10 +93,23 @@ async function mapperToGraphql (app, opts) {
|
|
|
89
93
|
|
|
90
94
|
let subscription
|
|
91
95
|
if (app.platformatic.mq) {
|
|
92
|
-
|
|
93
|
-
const ignoreList = opts.subscriptionIgnore || []
|
|
96
|
+
// Create a copy because we will alter it
|
|
97
|
+
const ignoreList = Array.from(opts.subscriptionIgnore || [])
|
|
98
|
+
|
|
99
|
+
const entitiesList = []
|
|
100
|
+
|
|
101
|
+
for (const entity of Object.values(app.platformatic.entities)) {
|
|
102
|
+
if (ignoreList.includes(entity.singularName)) {
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
// We currently do not support subscriptions for join tables
|
|
106
|
+
if (entity.primaryKeys.size > 1) {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
entitiesList.push(entity)
|
|
110
|
+
}
|
|
94
111
|
|
|
95
|
-
if (entitiesList.length
|
|
112
|
+
if (entitiesList.length > 0) {
|
|
96
113
|
opts.subscription = {
|
|
97
114
|
emitter: app.platformatic.mq
|
|
98
115
|
}
|
package/lib/entity-to-type.js
CHANGED
|
@@ -15,7 +15,12 @@ const ascDesc = new graphql.GraphQLEnumType({
|
|
|
15
15
|
}
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const limitType = new graphql.GraphQLScalarType({
|
|
19
|
+
name: 'LimitInt',
|
|
20
|
+
description: 'Limit will be applied by default if not passed. If the provided value exceeds the maximum allowed value a validation error will be thrown'
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
function constructGraph (app, entity, opts, ignore) {
|
|
19
24
|
const primaryKeys = Array.from(entity.primaryKeys).map((key) => camelcase(key))
|
|
20
25
|
const relationalFields = entity.relations
|
|
21
26
|
.map((relation) => relation.column_name)
|
|
@@ -35,6 +40,10 @@ function constructGraph (app, entity, opts) {
|
|
|
35
40
|
|
|
36
41
|
for (const key of Object.keys(entity.fields)) {
|
|
37
42
|
const field = entity.fields[key]
|
|
43
|
+
if (ignore[field.camelcase]) {
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
38
47
|
const meta = { field }
|
|
39
48
|
|
|
40
49
|
// sqlite doesn't support enums
|
|
@@ -134,7 +143,7 @@ function constructGraph (app, entity, opts) {
|
|
|
134
143
|
queryTopFields[plural] = {
|
|
135
144
|
type: new graphql.GraphQLList(type),
|
|
136
145
|
args: {
|
|
137
|
-
limit: { type:
|
|
146
|
+
limit: { type: limitType },
|
|
138
147
|
offset: { type: graphql.GraphQLInt },
|
|
139
148
|
orderBy: {
|
|
140
149
|
type: new graphql.GraphQLList(new graphql.GraphQLInputObjectType({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Map SQL dbs to GraphQL",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
},
|
|
15
15
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@platformatic/sql-events": "0.
|
|
18
|
-
"@platformatic/sql-mapper": "0.
|
|
17
|
+
"@platformatic/sql-events": "0.9.0",
|
|
18
|
+
"@platformatic/sql-mapper": "0.9.0",
|
|
19
19
|
"fastify": "^4.6.0",
|
|
20
20
|
"snazzy": "^9.0.0",
|
|
21
21
|
"standard": "^17.0.0",
|
|
22
22
|
"tap": "^16.0.0",
|
|
23
|
-
"tsd": "^0.
|
|
23
|
+
"tsd": "^0.25.0",
|
|
24
24
|
"ws": "^8.9.0"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
package/test/composite.test.js
CHANGED
|
@@ -4,6 +4,7 @@ const { clear, connInfo, isSQLite, isMysql, isPg } = require('./helper')
|
|
|
4
4
|
const { test } = require('tap')
|
|
5
5
|
const fastify = require('fastify')
|
|
6
6
|
const sqlMapper = require('@platformatic/sql-mapper')
|
|
7
|
+
const sqlEvents = require('@platformatic/sql-events')
|
|
7
8
|
const sqlGraphQL = require('..')
|
|
8
9
|
|
|
9
10
|
test('composite primary keys', async ({ equal, same, teardown, rejects }) => {
|
|
@@ -76,6 +77,7 @@ test('composite primary keys', async ({ equal, same, teardown, rejects }) => {
|
|
|
76
77
|
...connInfo,
|
|
77
78
|
onDatabaseLoad
|
|
78
79
|
})
|
|
80
|
+
app.register(sqlEvents) // needed as if it's present it might throw
|
|
79
81
|
app.register(sqlGraphQL)
|
|
80
82
|
teardown(app.close.bind(app))
|
|
81
83
|
|
|
@@ -329,3 +331,144 @@ test('composite primary keys', async ({ equal, same, teardown, rejects }) => {
|
|
|
329
331
|
}, 'editor response')
|
|
330
332
|
}
|
|
331
333
|
})
|
|
334
|
+
|
|
335
|
+
test('composite primary keys with no foreign keys', async ({ equal, same, teardown, rejects }) => {
|
|
336
|
+
/* https://github.com/platformatic/platformatic/issues/299 */
|
|
337
|
+
async function onDatabaseLoad (db, sql) {
|
|
338
|
+
await clear(db, sql)
|
|
339
|
+
|
|
340
|
+
await db.query(sql`CREATE TABLE editors (
|
|
341
|
+
page_id INTEGER NOT NULL,
|
|
342
|
+
user_id INTEGER NOT NULL,
|
|
343
|
+
role VARCHAR(255) NOT NULL,
|
|
344
|
+
PRIMARY KEY (page_id, user_id)
|
|
345
|
+
);`)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const app = fastify()
|
|
349
|
+
app.register(sqlMapper, {
|
|
350
|
+
...connInfo,
|
|
351
|
+
onDatabaseLoad
|
|
352
|
+
})
|
|
353
|
+
app.register(sqlEvents) // needed as if it's present it will throw
|
|
354
|
+
app.register(sqlGraphQL)
|
|
355
|
+
teardown(app.close.bind(app))
|
|
356
|
+
|
|
357
|
+
await app.ready()
|
|
358
|
+
|
|
359
|
+
{
|
|
360
|
+
const res = await app.inject({
|
|
361
|
+
method: 'POST',
|
|
362
|
+
url: '/graphql',
|
|
363
|
+
body: {
|
|
364
|
+
query: `
|
|
365
|
+
mutation {
|
|
366
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "admin" }) {
|
|
367
|
+
userId
|
|
368
|
+
pageId
|
|
369
|
+
role
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
`
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
376
|
+
same(res.json(), {
|
|
377
|
+
data: {
|
|
378
|
+
saveEditor: {
|
|
379
|
+
userId: '1',
|
|
380
|
+
pageId: '1',
|
|
381
|
+
role: 'admin'
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}, 'saveEditor response')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
{
|
|
388
|
+
const res = await app.inject({
|
|
389
|
+
method: 'POST',
|
|
390
|
+
url: '/graphql',
|
|
391
|
+
body: {
|
|
392
|
+
query: `
|
|
393
|
+
mutation {
|
|
394
|
+
saveEditor(input: { userId: "2", pageId: "1", role: "author" }) {
|
|
395
|
+
userId
|
|
396
|
+
pageId
|
|
397
|
+
role
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
`
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
404
|
+
same(res.json(), {
|
|
405
|
+
data: {
|
|
406
|
+
saveEditor: {
|
|
407
|
+
userId: '2',
|
|
408
|
+
pageId: '1',
|
|
409
|
+
role: 'author'
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}, 'saveEditor response')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
{
|
|
416
|
+
const res = await app.inject({
|
|
417
|
+
method: 'POST',
|
|
418
|
+
url: '/graphql',
|
|
419
|
+
body: {
|
|
420
|
+
query: `
|
|
421
|
+
mutation {
|
|
422
|
+
saveEditor(input: { userId: "1", pageId: "1", role: "captain" }) {
|
|
423
|
+
userId
|
|
424
|
+
pageId
|
|
425
|
+
role
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
`
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
equal(res.statusCode, 200, 'saveEditor status code')
|
|
432
|
+
same(res.json(), {
|
|
433
|
+
data: {
|
|
434
|
+
saveEditor: {
|
|
435
|
+
userId: 1,
|
|
436
|
+
pageId: 1,
|
|
437
|
+
role: 'captain'
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}, 'saveEditor response')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
{
|
|
444
|
+
const res = await app.inject({
|
|
445
|
+
method: 'post',
|
|
446
|
+
url: '/graphql',
|
|
447
|
+
body: {
|
|
448
|
+
query: `
|
|
449
|
+
query {
|
|
450
|
+
editors(orderBy: { field: role, direction: DESC }) {
|
|
451
|
+
userId
|
|
452
|
+
pageId
|
|
453
|
+
role
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
`
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
equal(res.statusCode, 200, 'editors status code')
|
|
460
|
+
same(res.json(), {
|
|
461
|
+
data: {
|
|
462
|
+
editors: [{
|
|
463
|
+
userId: '1',
|
|
464
|
+
pageId: '1',
|
|
465
|
+
role: 'captain'
|
|
466
|
+
}, {
|
|
467
|
+
userId: '2',
|
|
468
|
+
pageId: '1',
|
|
469
|
+
role: 'author'
|
|
470
|
+
}]
|
|
471
|
+
}
|
|
472
|
+
}, 'editor response')
|
|
473
|
+
}
|
|
474
|
+
})
|
package/test/ignore.test.js
CHANGED
|
@@ -115,3 +115,93 @@ test('ignore a column', async ({ pass, teardown, equal }) => {
|
|
|
115
115
|
equal(Category.fields.find((f) => f.name === 'name'), undefined, 'name column is ignored')
|
|
116
116
|
}
|
|
117
117
|
})
|
|
118
|
+
|
|
119
|
+
test('ignore a table via sql-graphql option', async ({ pass, teardown, equal }) => {
|
|
120
|
+
const app = fastify()
|
|
121
|
+
app.register(sqlMapper, {
|
|
122
|
+
...connInfo,
|
|
123
|
+
async onDatabaseLoad (db, sql) {
|
|
124
|
+
pass('onDatabaseLoad called')
|
|
125
|
+
|
|
126
|
+
await clear(db, sql)
|
|
127
|
+
await createBasicPages(db, sql)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
app.register(sqlGraphQL, {
|
|
131
|
+
ignore: {
|
|
132
|
+
categories: true
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
teardown(app.close.bind(app))
|
|
136
|
+
|
|
137
|
+
await app.ready()
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
const res = await app.inject({
|
|
141
|
+
method: 'POST',
|
|
142
|
+
url: '/graphql',
|
|
143
|
+
body: {
|
|
144
|
+
query: `
|
|
145
|
+
query {
|
|
146
|
+
__schema {
|
|
147
|
+
types {
|
|
148
|
+
name
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
equal(res.statusCode, 200, 'introspection query status code')
|
|
156
|
+
const data = res.json().data
|
|
157
|
+
equal(data.__schema.types.find((t) => t.name === 'Category'), undefined, 'Category type is ignored')
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('ignore a column via sql-graphql option', async ({ pass, teardown, equal }) => {
|
|
162
|
+
const app = fastify()
|
|
163
|
+
app.register(sqlMapper, {
|
|
164
|
+
...connInfo,
|
|
165
|
+
async onDatabaseLoad (db, sql) {
|
|
166
|
+
pass('onDatabaseLoad called')
|
|
167
|
+
|
|
168
|
+
await clear(db, sql)
|
|
169
|
+
await createBasicPages(db, sql)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
app.register(sqlGraphQL, {
|
|
173
|
+
ignore: {
|
|
174
|
+
categories: {
|
|
175
|
+
name: true
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
teardown(app.close.bind(app))
|
|
180
|
+
|
|
181
|
+
await app.ready()
|
|
182
|
+
|
|
183
|
+
{
|
|
184
|
+
const res = await app.inject({
|
|
185
|
+
method: 'POST',
|
|
186
|
+
url: '/graphql',
|
|
187
|
+
body: {
|
|
188
|
+
query: `
|
|
189
|
+
query {
|
|
190
|
+
__schema {
|
|
191
|
+
types {
|
|
192
|
+
name
|
|
193
|
+
fields {
|
|
194
|
+
name
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
equal(res.statusCode, 200, 'introspection query status code')
|
|
203
|
+
const data = res.json().data
|
|
204
|
+
const Category = data.__schema.types.find((t) => t.name === 'Category')
|
|
205
|
+
equal(Category.fields.find((f) => f.name === 'name'), undefined, 'name column is ignored')
|
|
206
|
+
}
|
|
207
|
+
})
|