@platformatic/sql-mapper 0.31.0 → 0.32.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/lib/entity.js +21 -5
- package/lib/queries/pg.js +20 -0
- package/lib/queries/shared.js +6 -4
- package/mapper.d.ts +8 -7
- package/package.json +3 -3
- package/test/entity.test.js +84 -1
- package/test/types/mapper.test-d.ts +17 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @platformatic/sql-mapper
|
|
2
2
|
|
|
3
|
-
Check out the full documentation on [our website](https://
|
|
3
|
+
Check out the full documentation on [our website](https://docs.platformatic.dev/docs/reference/sql-mapper/introduction).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
package/lib/entity.js
CHANGED
|
@@ -199,7 +199,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
199
199
|
gte: '>=',
|
|
200
200
|
lt: '<',
|
|
201
201
|
lte: '<=',
|
|
202
|
-
like: 'LIKE'
|
|
202
|
+
like: 'LIKE',
|
|
203
|
+
any: 'ANY',
|
|
204
|
+
all: 'ALL'
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
function computeCriteria (opts) {
|
|
@@ -225,7 +227,17 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
225
227
|
throw new Error(`Unsupported where clause ${JSON.stringify(where[key])}`)
|
|
226
228
|
}
|
|
227
229
|
const fieldWrap = fields[field]
|
|
228
|
-
|
|
230
|
+
|
|
231
|
+
/* istanbul ignore next */
|
|
232
|
+
if (fieldWrap.isArray) {
|
|
233
|
+
if (operator === 'ANY') {
|
|
234
|
+
criteria.push(sql`${value[key]} = ANY (${sql.ident(field)})`)
|
|
235
|
+
} else if (operator === 'ALL') {
|
|
236
|
+
criteria.push(sql`${value[key]} = ALL (${sql.ident(field)})`)
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error('Unsupported operator for Array field')
|
|
239
|
+
}
|
|
240
|
+
} else if (operator === '=' && value[key] === null) {
|
|
229
241
|
criteria.push(sql`${sql.ident(field)} IS NULL`)
|
|
230
242
|
} else if (operator === '<>' && value[key] === null) {
|
|
231
243
|
criteria.push(sql`${sql.ident(field)} IS NOT NULL`)
|
|
@@ -237,6 +249,8 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
237
249
|
leftHand = sql`TRIM(CAST(${sql.ident(field)} AS CHAR(64)))`
|
|
238
250
|
}
|
|
239
251
|
criteria.push(sql`${leftHand} LIKE ${value[key]}`)
|
|
252
|
+
} else if (operator === 'ANY' || operator === 'ALL') {
|
|
253
|
+
throw new Error('Unsupported operator for non Array field')
|
|
240
254
|
} else {
|
|
241
255
|
criteria.push(sql`${sql.ident(field)} ${sql.__dangerous__rawValue(operator)} ${computeCriteriaValue(fieldWrap, value[key])}`)
|
|
242
256
|
}
|
|
@@ -292,8 +306,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
|
|
|
292
306
|
query = sql`${query} OFFSET ${opts.offset}`
|
|
293
307
|
}
|
|
294
308
|
|
|
295
|
-
const
|
|
296
|
-
|
|
309
|
+
const rows = await db.query(query)
|
|
310
|
+
const res = rows.map(fixOutput)
|
|
311
|
+
return res
|
|
297
312
|
}
|
|
298
313
|
|
|
299
314
|
async function count (opts = {}) {
|
|
@@ -345,7 +360,8 @@ function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSc
|
|
|
345
360
|
const fields = columns.reduce((acc, column) => {
|
|
346
361
|
acc[column.column_name] = {
|
|
347
362
|
sqlType: column.udt_name,
|
|
348
|
-
isNullable: column.is_nullable === 'YES'
|
|
363
|
+
isNullable: column.is_nullable === 'YES',
|
|
364
|
+
isArray: column.isArray
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
// To get enum values in mysql and mariadb
|
package/lib/queries/pg.js
CHANGED
|
@@ -43,12 +43,32 @@ async function listTables (db, sql, schemas) {
|
|
|
43
43
|
module.exports.listTables = listTables
|
|
44
44
|
|
|
45
45
|
async function listColumns (db, sql, table, schema) {
|
|
46
|
+
/*
|
|
46
47
|
return db.query(sql`
|
|
47
48
|
SELECT column_name, udt_name, is_nullable, is_generated
|
|
48
49
|
FROM information_schema.columns
|
|
49
50
|
WHERE table_name = ${table}
|
|
50
51
|
AND table_schema = ${schema}
|
|
51
52
|
`)
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
const res = await db.query(sql`
|
|
56
|
+
SELECT column_name, udt_name, is_nullable, is_generated, data_type
|
|
57
|
+
FROM information_schema.columns
|
|
58
|
+
WHERE table_name = ${table}
|
|
59
|
+
AND table_schema = ${schema}
|
|
60
|
+
`)
|
|
61
|
+
|
|
62
|
+
for (const col of res) {
|
|
63
|
+
if (col.data_type === 'ARRAY') {
|
|
64
|
+
col.udt_name = col.udt_name.replace(/^_/, '')
|
|
65
|
+
col.isArray = true
|
|
66
|
+
} else {
|
|
67
|
+
col.isArray = false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return res
|
|
52
72
|
}
|
|
53
73
|
|
|
54
74
|
module.exports.listColumns = listColumns
|
package/lib/queries/shared.js
CHANGED
|
@@ -22,9 +22,11 @@ async function insertOne (db, sql, table, schema, input, primaryKeysTypes, field
|
|
|
22
22
|
sql`, `
|
|
23
23
|
)
|
|
24
24
|
const values = sql.join(
|
|
25
|
-
Object.keys(input).map((key) =>
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
Object.keys(input).map((key) => {
|
|
26
|
+
const val = input[key]
|
|
27
|
+
return sql.value(val)
|
|
28
|
+
}), sql`, `)
|
|
29
|
+
|
|
28
30
|
const insert = sql`
|
|
29
31
|
INSERT INTO ${tableName(sql, table, schema)} (${keys})
|
|
30
32
|
VALUES (${values})
|
|
@@ -79,7 +81,7 @@ function insertPrep (inputs, inputToFieldMap, fields, sql) {
|
|
|
79
81
|
|
|
80
82
|
let value = input[key] ?? input[newKey]
|
|
81
83
|
|
|
82
|
-
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
84
|
+
if (value && !fields[newKey].isArray && typeof value === 'object' && !(value instanceof Date)) {
|
|
83
85
|
// This is a JSON field
|
|
84
86
|
value = JSON.stringify(value)
|
|
85
87
|
}
|
package/mapper.d.ts
CHANGED
|
@@ -242,13 +242,14 @@ export interface Entity<EntityFields = any> {
|
|
|
242
242
|
count: Count,
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
type EntityHook<T extends (...args: any) => any> = (original: T, ...options: Parameters<T>) => ReturnType<T>;
|
|
245
246
|
|
|
246
247
|
export interface EntityHooks<EntityFields = any> {
|
|
247
|
-
find?: Find<EntityFields
|
|
248
|
-
insert?: Insert<EntityFields
|
|
249
|
-
save?: Save<EntityFields
|
|
250
|
-
delete?: Delete<EntityFields
|
|
251
|
-
count?: Count
|
|
248
|
+
find?: EntityHook<Find<EntityFields>>,
|
|
249
|
+
insert?: EntityHook<Insert<EntityFields>>,
|
|
250
|
+
save?: EntityHook<Save<EntityFields>>,
|
|
251
|
+
delete?: EntityHook<Delete<EntityFields>>,
|
|
252
|
+
count?: EntityHook<Count>,
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
export interface SQLMapperPluginOptions {
|
|
@@ -304,7 +305,7 @@ export interface SQLMapperPluginInterface {
|
|
|
304
305
|
/**
|
|
305
306
|
* Adds hooks to the entity.
|
|
306
307
|
*/
|
|
307
|
-
addEntityHooks(entityName: string, hooks: EntityHooks): any
|
|
308
|
+
addEntityHooks<EntityFields>(entityName: string, hooks: EntityHooks<EntityFields>): any
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
// Extend the PlatformaticApp interface,
|
|
@@ -326,7 +327,7 @@ declare module '@platformatic/types' {
|
|
|
326
327
|
/**
|
|
327
328
|
* Adds hooks to the entity.
|
|
328
329
|
*/
|
|
329
|
-
addEntityHooks(entityName: string, hooks: EntityHooks): any
|
|
330
|
+
addEntityHooks<EntityFields>(entityName: string, hooks: EntityHooks<EntityFields>): any
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
333
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/sql-mapper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "A data mapper utility for SQL databases",
|
|
5
5
|
"main": "mapper.js",
|
|
6
6
|
"types": "mapper.d.ts",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"tsd": "^0.28.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@databases/mysql": "^
|
|
25
|
+
"@databases/mysql": "^6.0.0",
|
|
26
26
|
"@databases/pg": "^5.4.1",
|
|
27
27
|
"@databases/sql": "^3.3.0",
|
|
28
28
|
"@matteo.collina/sqlite-pool": "^0.3.0",
|
|
29
29
|
"camelcase": "^6.3.0",
|
|
30
30
|
"fastify-plugin": "^4.5.0",
|
|
31
31
|
"inflected": "^2.1.0",
|
|
32
|
-
"@platformatic/types": "0.
|
|
32
|
+
"@platformatic/types": "0.32.0"
|
|
33
33
|
},
|
|
34
34
|
"tsd": {
|
|
35
35
|
"directory": "test/types"
|
package/test/entity.test.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
|
-
|
|
5
4
|
const { clear, connInfo, isSQLite, isMysql, isPg, isMysql8 } = require('./helper')
|
|
6
5
|
const { connect } = require('..')
|
|
7
6
|
const fakeLogger = {
|
|
7
|
+
// trace: (...args) => { console.log(JSON.stringify(args, null, 2)) },
|
|
8
8
|
trace: () => {},
|
|
9
9
|
error: () => {}
|
|
10
10
|
}
|
|
@@ -1019,3 +1019,86 @@ test('nested transactions', async ({ equal, same, teardown, rejects }) => {
|
|
|
1019
1019
|
same(insertResult, { id: '1', theTitle: null })
|
|
1020
1020
|
})
|
|
1021
1021
|
})
|
|
1022
|
+
|
|
1023
|
+
test('array support (PG)', { skip: !(isPg) }, async ({ teardown, same, rejects }) => {
|
|
1024
|
+
async function onDatabaseLoad (db, sql) {
|
|
1025
|
+
await clear(db, sql)
|
|
1026
|
+
teardown(() => db.dispose())
|
|
1027
|
+
|
|
1028
|
+
await db.query(sql`CREATE TABLE generated_test (
|
|
1029
|
+
id SERIAL PRIMARY KEY,
|
|
1030
|
+
checkmark BOOLEAN NOT NULL DEFAULT true,
|
|
1031
|
+
test INTEGER[]
|
|
1032
|
+
);`)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const mapper = await connect({
|
|
1036
|
+
connectionString: connInfo.connectionString,
|
|
1037
|
+
log: fakeLogger,
|
|
1038
|
+
onDatabaseLoad,
|
|
1039
|
+
ignore: {},
|
|
1040
|
+
hooks: {}
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
const generatedTest = mapper.entities.generatedTest
|
|
1044
|
+
|
|
1045
|
+
// save - new record
|
|
1046
|
+
same(await generatedTest.save({
|
|
1047
|
+
input: { test: [1, 2, 3], checkmark: true }
|
|
1048
|
+
}), { id: 1, test: [1, 2, 3], checkmark: true })
|
|
1049
|
+
|
|
1050
|
+
// save - update
|
|
1051
|
+
same(await generatedTest.save({
|
|
1052
|
+
input: { id: 1, test: [4, 5, 6], checkmark: true }
|
|
1053
|
+
}), { id: 1, test: [4, 5, 6], checkmark: true })
|
|
1054
|
+
|
|
1055
|
+
// insert
|
|
1056
|
+
same(await generatedTest.insert({
|
|
1057
|
+
inputs: [{ test: [4], checkmark: true }]
|
|
1058
|
+
}), [{ id: 2, test: [4], checkmark: true }])
|
|
1059
|
+
|
|
1060
|
+
// where any
|
|
1061
|
+
same(await generatedTest.find({
|
|
1062
|
+
where: {
|
|
1063
|
+
test: { any: 4 }
|
|
1064
|
+
}
|
|
1065
|
+
}), [{ id: 1, test: [4, 5, 6], checkmark: true }, { id: 2, test: [4], checkmark: true }])
|
|
1066
|
+
|
|
1067
|
+
// where all
|
|
1068
|
+
same(await generatedTest.find({
|
|
1069
|
+
where: {
|
|
1070
|
+
test: { all: 4 }
|
|
1071
|
+
}
|
|
1072
|
+
}), [{ id: 2, test: [4], checkmark: true }])
|
|
1073
|
+
|
|
1074
|
+
// where eq
|
|
1075
|
+
await rejects(generatedTest.find({
|
|
1076
|
+
where: {
|
|
1077
|
+
test: { eq: 4 }
|
|
1078
|
+
}
|
|
1079
|
+
}))
|
|
1080
|
+
|
|
1081
|
+
// where any to non-array
|
|
1082
|
+
await rejects(generatedTest.find({
|
|
1083
|
+
where: {
|
|
1084
|
+
checkmark: { any: 4 }
|
|
1085
|
+
}
|
|
1086
|
+
}))
|
|
1087
|
+
|
|
1088
|
+
// where any to non-array
|
|
1089
|
+
await rejects(generatedTest.find({
|
|
1090
|
+
where: {
|
|
1091
|
+
checkmark: { all: 4 }
|
|
1092
|
+
}
|
|
1093
|
+
}))
|
|
1094
|
+
|
|
1095
|
+
// updateMany
|
|
1096
|
+
same(await generatedTest.updateMany({
|
|
1097
|
+
where: {
|
|
1098
|
+
checkmark: { eq: true }
|
|
1099
|
+
},
|
|
1100
|
+
input: {
|
|
1101
|
+
test: [8]
|
|
1102
|
+
}
|
|
1103
|
+
}), [{ id: 1, test: [8], checkmark: true }, { id: 2, test: [8], checkmark: true }])
|
|
1104
|
+
})
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Entity,
|
|
9
9
|
DBEntityField,
|
|
10
10
|
Database,
|
|
11
|
-
WhereCondition,
|
|
12
11
|
SQLMapperPluginInterface,
|
|
13
12
|
EntityHooks,
|
|
14
13
|
} from '../../mapper'
|
|
@@ -16,7 +15,6 @@ import {
|
|
|
16
15
|
const pluginOptions: SQLMapperPluginInterface = await connect({ connectionString: '' })
|
|
17
16
|
expectType<Database>(pluginOptions.db)
|
|
18
17
|
expectType<SQL>(pluginOptions.sql)
|
|
19
|
-
expectType<(entityName: string, hooks: EntityHooks) => any>(pluginOptions.addEntityHooks)
|
|
20
18
|
expectType<{ [entityName: string]: Entity }>(pluginOptions.entities)
|
|
21
19
|
|
|
22
20
|
interface EntityFields {
|
|
@@ -42,11 +40,11 @@ expectType<Partial<EntityFields>[]>(await entity.delete())
|
|
|
42
40
|
expectType<number>(await entity.count())
|
|
43
41
|
|
|
44
42
|
const entityHooks: EntityHooks = {
|
|
45
|
-
async find(options:
|
|
46
|
-
async insert(
|
|
47
|
-
async save(
|
|
48
|
-
async delete(
|
|
49
|
-
async count(
|
|
43
|
+
async find(originalFind: typeof entity.find, ...options: Parameters<typeof entity.find>): ReturnType<typeof entity.find> { return [] },
|
|
44
|
+
async insert(originalInsert: typeof entity.insert, ...options: Parameters<typeof entity.insert>): ReturnType<typeof entity.insert> { return [] },
|
|
45
|
+
async save(originalSave: typeof entity.save, ...options: Parameters<typeof entity.save>): ReturnType<typeof entity.save> { return {} },
|
|
46
|
+
async delete(originalDelete: typeof entity.delete, ...options: Parameters<typeof entity.delete>): ReturnType<typeof entity.delete> { return [] },
|
|
47
|
+
async count(originalCount: typeof entity.count, ...options: Parameters<typeof entity.count>): ReturnType<typeof entity.count> { return 0 },
|
|
50
48
|
}
|
|
51
49
|
expectType<EntityHooks>(entityHooks)
|
|
52
50
|
expectType<SQLMapperPluginInterface>(await connect({ connectionString: '' }))
|
|
@@ -74,6 +72,18 @@ const instance: FastifyInstance = fastify()
|
|
|
74
72
|
instance.register(plugin, { connectionString: '', autoTimestamp: true })
|
|
75
73
|
instance.register((instance) => {
|
|
76
74
|
expectType<SQLMapperPluginInterface>(instance.platformatic)
|
|
75
|
+
|
|
76
|
+
instance.platformatic.addEntityHooks<EntityFields>('something', {
|
|
77
|
+
async find (originalFind, options) {
|
|
78
|
+
expectType<Partial<EntityFields>[]>(await originalFind())
|
|
79
|
+
expectType<Parameters<typeof entity.find>[0]>(options)
|
|
80
|
+
|
|
81
|
+
return [{
|
|
82
|
+
id: 42
|
|
83
|
+
}]
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
77
87
|
instance.get('/', async (request, reply) => {
|
|
78
88
|
const ctx = request.platformaticContext
|
|
79
89
|
expectType<FastifyInstance>(ctx.app)
|