@platformatic/sql-openapi 3.4.1 → 3.5.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/README.md +1 -1
- package/eslint.config.js +2 -2
- package/index.d.ts +3 -0
- package/index.js +33 -34
- package/lib/cursor.js +94 -0
- package/lib/entity-to-routes.js +252 -202
- package/lib/errors.js +25 -8
- package/lib/many-to-many.js +99 -81
- package/lib/shared.js +220 -135
- package/lib/utils.js +2 -8
- package/package.json +21 -18
package/lib/shared.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { mapSQLTypeToOpenAPIType } from '@platformatic/sql-json-schema-mapper'
|
|
2
|
+
import { buildCursorUtils } from './cursor.js'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function generateArgs (entity, ignore) {
|
|
4
|
+
export function generateArgs (entity, ignore) {
|
|
6
5
|
const sortedEntityFields = Object.keys(entity.fields).sort()
|
|
7
6
|
|
|
8
7
|
const whereArgs = sortedEntityFields.reduce((acc, name) => {
|
|
@@ -18,7 +17,7 @@ function generateArgs (entity, ignore) {
|
|
|
18
17
|
acc[key] = { type: mapSQLTypeToOpenAPIType(field.sqlType) }
|
|
19
18
|
}
|
|
20
19
|
} else {
|
|
21
|
-
for (const modifier of ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'like']) {
|
|
20
|
+
for (const modifier of ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'like', 'ilike']) {
|
|
22
21
|
const key = baseKey + modifier
|
|
23
22
|
acc[key] = { type: mapSQLTypeToOpenAPIType(field.sqlType), enum: field.enum }
|
|
24
23
|
}
|
|
@@ -49,9 +48,9 @@ function generateArgs (entity, ignore) {
|
|
|
49
48
|
'where.or': {
|
|
50
49
|
type: 'array',
|
|
51
50
|
items: {
|
|
52
|
-
type: 'string'
|
|
53
|
-
}
|
|
54
|
-
}
|
|
51
|
+
type: 'string'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
Object.assign(whereArgs, whereOrArrayArgs)
|
|
@@ -59,111 +58,161 @@ function generateArgs (entity, ignore) {
|
|
|
59
58
|
return { whereArgs, orderByArgs }
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
export function rootEntityRoutes (
|
|
62
|
+
app,
|
|
63
|
+
entity,
|
|
64
|
+
whereArgs,
|
|
65
|
+
orderByArgs,
|
|
66
|
+
entityLinks,
|
|
67
|
+
entitySchema,
|
|
68
|
+
fields,
|
|
69
|
+
entitySchemaInput,
|
|
70
|
+
ignoreRoutes
|
|
71
|
+
) {
|
|
65
72
|
const ignoredGETRoute = ignoreRoutes.find(ignoreRoutes => {
|
|
66
73
|
return ignoreRoutes.path === app.prefix && ignoreRoutes.method === 'GET'
|
|
67
74
|
})
|
|
68
75
|
|
|
69
76
|
if (!ignoredGETRoute) {
|
|
70
|
-
app
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
const { buildCursorHeaders, transformQueryToCursor } = buildCursorUtils(app, entity)
|
|
78
|
+
|
|
79
|
+
app.get(
|
|
80
|
+
'/',
|
|
81
|
+
{
|
|
82
|
+
schema: {
|
|
83
|
+
operationId: 'get' + capitalize(entity.pluralName),
|
|
84
|
+
summary: `Get ${entity.pluralName}.`,
|
|
85
|
+
description: `Fetch ${entity.pluralName} from the database.`,
|
|
86
|
+
tags: [entity.table],
|
|
87
|
+
querystring: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
limit: {
|
|
91
|
+
type: 'integer',
|
|
92
|
+
description:
|
|
93
|
+
'Limit will be applied by default if not passed. If the provided value exceeds the maximum allowed value a validation error will be thrown'
|
|
94
|
+
},
|
|
95
|
+
offset: { type: 'integer' },
|
|
96
|
+
totalCount: { type: 'boolean', default: false },
|
|
97
|
+
cursor: {
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
default: false,
|
|
100
|
+
description: 'Include cursor headers in response. Cursor keys built from orderBy clause'
|
|
101
|
+
},
|
|
102
|
+
startAfter: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'Cursor for forward pagination. List objects after this cursor position',
|
|
105
|
+
format: 'byte'
|
|
106
|
+
},
|
|
107
|
+
endBefore: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: 'Cursor for backward pagination. List objects before this cursor position',
|
|
110
|
+
format: 'byte'
|
|
111
|
+
},
|
|
112
|
+
fields,
|
|
113
|
+
...whereArgs,
|
|
114
|
+
...orderByArgs
|
|
115
|
+
},
|
|
116
|
+
additionalProperties: false
|
|
92
117
|
},
|
|
93
|
-
|
|
118
|
+
response: {
|
|
119
|
+
200: {
|
|
120
|
+
type: 'array',
|
|
121
|
+
items: entitySchema
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
94
125
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
async function (request, reply) {
|
|
127
|
+
const query = request.query
|
|
128
|
+
const { limit, offset, fields, startAfter, endBefore } = query
|
|
129
|
+
const queryKeys = Object.keys(query)
|
|
130
|
+
const where = {}
|
|
131
|
+
const orderBy = []
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < queryKeys.length; i++) {
|
|
134
|
+
const key = queryKeys[i]
|
|
135
|
+
if (key.startsWith('where.or')) {
|
|
136
|
+
const orParam = query[key][0]
|
|
137
|
+
// NOTE: Remove the first and last character which are the brackets
|
|
138
|
+
// each or condition is separated by a pipe '|'
|
|
139
|
+
// the conditions inside the or statement are the same as it would normally be in the where statement
|
|
140
|
+
// except that the field name is not prefixed with 'where.'
|
|
141
|
+
// e.g. where.or=(name.eq=foo|name.eq=bar)
|
|
142
|
+
// e.g. where.or=(name.eq=foo|name.eq=bar|name.eq=baz)
|
|
143
|
+
//
|
|
144
|
+
// Also, the or statement supports in and nin operators
|
|
145
|
+
// e.g. where.or=(name.in=foo,bar|name.eq=baz)
|
|
146
|
+
const parsed = orParam
|
|
147
|
+
.substring(1, orParam.length - 1)
|
|
148
|
+
.split('|')
|
|
149
|
+
.map(v => v.split('='))
|
|
150
|
+
.reduce((acc, [k, v]) => {
|
|
151
|
+
const [field, modifier] = k.split('.')
|
|
152
|
+
if (modifier === 'in' || modifier === 'nin') {
|
|
153
|
+
// TODO handle escaping of ,
|
|
154
|
+
v = v.split(',')
|
|
155
|
+
/* istanbul ignore next */
|
|
156
|
+
if (mapSQLTypeToOpenAPIType(entity.camelCasedFields[field].sqlType) === 'integer') {
|
|
157
|
+
v = v.map(v => parseInt(v))
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
acc.push({ [field]: { [modifier]: parseNullableValue(field, v) } })
|
|
161
|
+
return acc
|
|
162
|
+
}, [])
|
|
163
|
+
where.or = parsed
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (key.startsWith('where.')) {
|
|
168
|
+
const [, field, modifier] = key.split('.')
|
|
169
|
+
where[field] ||= {}
|
|
170
|
+
let value = query[key]
|
|
117
171
|
if (modifier === 'in' || modifier === 'nin') {
|
|
118
172
|
// TODO handle escaping of ,
|
|
119
|
-
|
|
120
|
-
/* istanbul ignore next */
|
|
173
|
+
value = query[key].split(',')
|
|
121
174
|
if (mapSQLTypeToOpenAPIType(entity.camelCasedFields[field].sqlType) === 'integer') {
|
|
122
|
-
|
|
175
|
+
value = value.map(v => parseInt(v))
|
|
123
176
|
}
|
|
124
177
|
}
|
|
125
|
-
acc.push({ [field]: { [modifier]: v } })
|
|
126
|
-
return acc
|
|
127
|
-
}, [])
|
|
128
|
-
where.or = parsed
|
|
129
|
-
continue
|
|
130
|
-
}
|
|
131
178
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// TODO handle escaping of ,
|
|
138
|
-
value = query[key].split(',')
|
|
139
|
-
if (mapSQLTypeToOpenAPIType(entity.camelCasedFields[field].sqlType) === 'integer') {
|
|
140
|
-
value = value.map((v) => parseInt(v))
|
|
141
|
-
}
|
|
179
|
+
where[field][modifier] = parseNullableValue(field, value)
|
|
180
|
+
} else if (key.startsWith('orderby.')) {
|
|
181
|
+
const [, field] = key.split('.')
|
|
182
|
+
orderBy[field] ||= {}
|
|
183
|
+
orderBy.push({ field, direction: query[key] })
|
|
142
184
|
}
|
|
143
|
-
where[field][modifier] = value
|
|
144
|
-
} else if (key.startsWith('orderby.')) {
|
|
145
|
-
const [, field] = key.split('.')
|
|
146
|
-
orderBy[field] ||= {}
|
|
147
|
-
orderBy.push({ field, direction: query[key] })
|
|
148
185
|
}
|
|
149
|
-
}
|
|
150
186
|
|
|
151
|
-
|
|
152
|
-
|
|
187
|
+
const ctx = { app: this, reply }
|
|
188
|
+
const { cursor, nextPage } = transformQueryToCursor({ startAfter, endBefore })
|
|
189
|
+
const res = await entity.find({ limit, offset, fields, orderBy, where, ctx, cursor, nextPage })
|
|
153
190
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
// X-Total-Count header
|
|
192
|
+
if (query.totalCount) {
|
|
193
|
+
let totalCount
|
|
194
|
+
if (((offset ?? 0) === 0 || res.length > 0) && limit !== undefined && res.length < limit) {
|
|
195
|
+
totalCount = (offset ?? 0) + res.length
|
|
196
|
+
} else {
|
|
197
|
+
totalCount = await entity.count({ where, ctx })
|
|
198
|
+
}
|
|
199
|
+
reply.header('X-Total-Count', totalCount)
|
|
161
200
|
}
|
|
162
|
-
reply.header('X-Total-Count', totalCount)
|
|
163
|
-
}
|
|
164
201
|
|
|
165
|
-
|
|
166
|
-
|
|
202
|
+
// cursor headers
|
|
203
|
+
if ((query.cursor || startAfter || endBefore) && res.length > 0) {
|
|
204
|
+
const { startAfter, endBefore } = buildCursorHeaders({
|
|
205
|
+
findResult: res,
|
|
206
|
+
orderBy,
|
|
207
|
+
primaryKeys: entity.primaryKeys
|
|
208
|
+
})
|
|
209
|
+
reply.header('X-Start-After', startAfter)
|
|
210
|
+
reply.header('X-End-Before', endBefore)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return res
|
|
214
|
+
}
|
|
215
|
+
)
|
|
167
216
|
}
|
|
168
217
|
|
|
169
218
|
const ignoredPOSTRoute = ignoreRoutes.find(ignoreRoute => {
|
|
@@ -171,26 +220,68 @@ function rootEntityRoutes (app, entity, whereArgs, orderByArgs, entityLinks, ent
|
|
|
171
220
|
})
|
|
172
221
|
|
|
173
222
|
if (!ignoredPOSTRoute) {
|
|
174
|
-
app.post(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
223
|
+
app.post(
|
|
224
|
+
'/',
|
|
225
|
+
{
|
|
226
|
+
schema: {
|
|
227
|
+
operationId: 'create' + capitalize(entity.singularName),
|
|
228
|
+
summary: `Create ${entity.singularName}.`,
|
|
229
|
+
description: `Add new ${entity.singularName} to the database.`,
|
|
230
|
+
body: entitySchemaInput,
|
|
231
|
+
tags: [entity.table],
|
|
232
|
+
querystring: {
|
|
233
|
+
type: 'object',
|
|
234
|
+
properties: {
|
|
235
|
+
fields
|
|
236
|
+
},
|
|
237
|
+
additionalProperties: false
|
|
238
|
+
},
|
|
239
|
+
response: {
|
|
240
|
+
200: entitySchema
|
|
241
|
+
}
|
|
183
242
|
},
|
|
243
|
+
links: {
|
|
244
|
+
200: entityLinks
|
|
245
|
+
}
|
|
184
246
|
},
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
247
|
+
async function (request, reply) {
|
|
248
|
+
const { fields } = request.query
|
|
249
|
+
const ctx = { app: this, reply }
|
|
250
|
+
|
|
251
|
+
let queryFields
|
|
252
|
+
if (fields) {
|
|
253
|
+
queryFields = [...fields]
|
|
254
|
+
for (const key of entity.primaryKeys.values()) {
|
|
255
|
+
if (!fields.includes(key)) {
|
|
256
|
+
queryFields.push(key)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const res = await entity.save({ input: request.body, ctx, fields: queryFields })
|
|
262
|
+
|
|
263
|
+
reply.header('location', `${app.prefix}/${res[[...entity.primaryKeys][0]]}`)
|
|
264
|
+
|
|
265
|
+
if (fields) {
|
|
266
|
+
for (const key of entity.primaryKeys.values()) {
|
|
267
|
+
if (!fields.includes(key)) {
|
|
268
|
+
delete res[key]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return res
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const parseNullableValue = (field, value) => {
|
|
279
|
+
const fieldIsNullable = entity.camelCasedFields[field].isNullable
|
|
280
|
+
if (fieldIsNullable && typeof value === 'string' && value.toLowerCase() === 'null') {
|
|
281
|
+
return null
|
|
282
|
+
} else {
|
|
283
|
+
return value
|
|
284
|
+
}
|
|
194
285
|
}
|
|
195
286
|
|
|
196
287
|
const ignoredPUTRoute = ignoreRoutes.find(ignoreRoute => {
|
|
@@ -208,19 +299,19 @@ function rootEntityRoutes (app, entity, whereArgs, orderByArgs, entityLinks, ent
|
|
|
208
299
|
type: 'object',
|
|
209
300
|
properties: {
|
|
210
301
|
fields,
|
|
211
|
-
...whereArgs
|
|
302
|
+
...whereArgs
|
|
212
303
|
},
|
|
213
|
-
additionalProperties: false
|
|
304
|
+
additionalProperties: false
|
|
214
305
|
},
|
|
215
306
|
response: {
|
|
216
307
|
200: {
|
|
217
308
|
type: 'array',
|
|
218
|
-
items: entitySchema
|
|
219
|
-
}
|
|
220
|
-
}
|
|
309
|
+
items: entitySchema
|
|
310
|
+
}
|
|
311
|
+
}
|
|
221
312
|
},
|
|
222
313
|
links: {
|
|
223
|
-
200: entityLinks
|
|
314
|
+
200: entityLinks
|
|
224
315
|
},
|
|
225
316
|
async handler (request, reply) {
|
|
226
317
|
const ctx = { app: this, reply }
|
|
@@ -238,49 +329,43 @@ function rootEntityRoutes (app, entity, whereArgs, orderByArgs, entityLinks, ent
|
|
|
238
329
|
// TODO handle escaping of ,
|
|
239
330
|
value = query[key].split(',')
|
|
240
331
|
if (mapSQLTypeToOpenAPIType(entity.camelCasedFields[field].sqlType) === 'integer') {
|
|
241
|
-
value = value.map(
|
|
332
|
+
value = value.map(v => parseInt(v))
|
|
242
333
|
}
|
|
243
334
|
}
|
|
244
|
-
where[field][modifier] = value
|
|
335
|
+
where[field][modifier] = parseNullableValue(field, value)
|
|
245
336
|
}
|
|
246
337
|
}
|
|
247
338
|
|
|
248
339
|
const res = await entity.updateMany({
|
|
249
340
|
input: {
|
|
250
|
-
...request.body
|
|
341
|
+
...request.body
|
|
251
342
|
},
|
|
252
343
|
where,
|
|
253
344
|
fields: request.query.fields,
|
|
254
|
-
ctx
|
|
345
|
+
ctx
|
|
255
346
|
})
|
|
256
347
|
// TODO: Should find a way to test this line
|
|
257
348
|
// if (!res) return reply.callNotFound()
|
|
258
349
|
reply.header('location', `${app.prefix}`)
|
|
259
350
|
return res
|
|
260
|
-
}
|
|
351
|
+
}
|
|
261
352
|
})
|
|
262
353
|
}
|
|
263
354
|
}
|
|
264
355
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
function capitalize (str) {
|
|
356
|
+
export function capitalize (str) {
|
|
268
357
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
269
358
|
}
|
|
270
359
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
function getFieldsForEntity (entity, ignore) {
|
|
360
|
+
export function getFieldsForEntity (entity, ignore) {
|
|
274
361
|
return {
|
|
275
362
|
type: 'array',
|
|
276
363
|
items: {
|
|
277
364
|
type: 'string',
|
|
278
365
|
enum: Object.keys(entity.fields)
|
|
279
|
-
.map(
|
|
280
|
-
.filter(
|
|
281
|
-
.sort()
|
|
282
|
-
}
|
|
366
|
+
.map(field => entity.fields[field].camelcase)
|
|
367
|
+
.filter(field => !ignore[field])
|
|
368
|
+
.sort()
|
|
369
|
+
}
|
|
283
370
|
}
|
|
284
371
|
}
|
|
285
|
-
|
|
286
|
-
module.exports.getFieldsForEntity = getFieldsForEntity
|
package/lib/utils.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
1
|
const openApiPathItemNonOperationKeys = ['summary', 'description', 'servers', 'parameters']
|
|
4
2
|
|
|
5
3
|
function removeOperationPropertiesFromOpenApiPathItem (pathItem) {
|
|
@@ -13,7 +11,7 @@ function removeOperationPropertiesFromOpenApiPathItem (pathItem) {
|
|
|
13
11
|
}, {})
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
function getSchemaOverrideFromOpenApiPathItem (pathItem, method) {
|
|
14
|
+
export function getSchemaOverrideFromOpenApiPathItem (pathItem, method) {
|
|
17
15
|
method = method?.toLowerCase()
|
|
18
16
|
|
|
19
17
|
const schemaOverride = removeOperationPropertiesFromOpenApiPathItem(pathItem)
|
|
@@ -22,12 +20,8 @@ function getSchemaOverrideFromOpenApiPathItem (pathItem, method) {
|
|
|
22
20
|
return schemaOverride
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
Object.keys(pathItem[method]).forEach(
|
|
23
|
+
Object.keys(pathItem[method]).forEach(key => {
|
|
26
24
|
schemaOverride[key] = pathItem[method][key]
|
|
27
25
|
})
|
|
28
26
|
return schemaOverride
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
module.exports = {
|
|
32
|
-
getSchemaOverrideFromOpenApiPathItem,
|
|
33
|
-
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-openapi",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Map a SQL database to OpenAPI, for Fastify",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"types": "index.d.ts",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
10
11
|
},
|
|
11
|
-
"author": "
|
|
12
|
+
"author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
|
|
12
13
|
"license": "Apache-2.0",
|
|
13
14
|
"bugs": {
|
|
14
15
|
"url": "https://github.com/platformatic/platformatic/issues"
|
|
@@ -16,41 +17,43 @@
|
|
|
16
17
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@matteo.collina/snap": "^0.3.0",
|
|
19
|
-
"
|
|
20
|
-
"borp": "^0.17.0",
|
|
20
|
+
"cleaner-spec-reporter": "^0.5.0",
|
|
21
21
|
"eslint": "^9.4.0",
|
|
22
22
|
"fastify": "^5.0.0",
|
|
23
|
-
"neostandard": "^0.
|
|
23
|
+
"neostandard": "^0.12.0",
|
|
24
24
|
"openapi-types": "^12.1.3",
|
|
25
|
-
"tsd": "^0.
|
|
25
|
+
"tsd": "^0.33.0",
|
|
26
26
|
"typescript": "^5.5.4",
|
|
27
27
|
"why-is-node-running": "^2.2.2",
|
|
28
28
|
"yaml": "^2.4.1",
|
|
29
|
-
"@platformatic/sql-mapper": "3.
|
|
29
|
+
"@platformatic/sql-mapper": "3.5.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@fastify/deepmerge": "^
|
|
32
|
+
"@fastify/deepmerge": "^2.0.0",
|
|
33
33
|
"@fastify/error": "^4.0.0",
|
|
34
34
|
"@fastify/swagger": "^9.0.0",
|
|
35
|
-
"@scalar/fastify-api-reference": "
|
|
35
|
+
"@scalar/fastify-api-reference": "1.34.6",
|
|
36
36
|
"camelcase": "^6.3.0",
|
|
37
37
|
"fastify-plugin": "^5.0.0",
|
|
38
38
|
"inflected": "^2.1.0",
|
|
39
|
-
"@platformatic/
|
|
40
|
-
"@platformatic/
|
|
41
|
-
"@platformatic/
|
|
39
|
+
"@platformatic/foundation": "3.5.0",
|
|
40
|
+
"@platformatic/scalar-theme": "3.5.0",
|
|
41
|
+
"@platformatic/sql-json-schema-mapper": "3.5.0"
|
|
42
42
|
},
|
|
43
43
|
"tsd": {
|
|
44
44
|
"directory": "test/types"
|
|
45
45
|
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.19.0"
|
|
48
|
+
},
|
|
46
49
|
"scripts": {
|
|
47
50
|
"lint": "eslint",
|
|
48
|
-
"test": "npm run
|
|
49
|
-
"test:postgresql": "DB=postgresql
|
|
50
|
-
"test:mariadb": "DB=mariadb
|
|
51
|
-
"test:mysql": "DB=mysql
|
|
52
|
-
"test:mysql8": "DB=mysql8
|
|
53
|
-
"test:sqlite": "DB=sqlite
|
|
51
|
+
"test": "npm run test:typescript && npm run test:postgresql && npm run test:mariadb && npm run test:mysql && npm run test:mysql8 && npm run test:sqlite",
|
|
52
|
+
"test:postgresql": "DB=postgresql node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
53
|
+
"test:mariadb": "DB=mariadb node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
54
|
+
"test:mysql": "DB=mysql node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
55
|
+
"test:mysql8": "DB=mysql8 node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
56
|
+
"test:sqlite": "DB=sqlite node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
|
|
54
57
|
"test:typescript": "tsd"
|
|
55
58
|
}
|
|
56
59
|
}
|