@platformatic/sql-mapper 0.0.23
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/.nyc_output/0208d41a-48bc-4675-a861-0475eb461a17.json +1 -0
- package/.nyc_output/588169a6-88e9-4949-af5a-631822d7dc42.json +1 -0
- package/.nyc_output/5bbdf331-cd01-4869-9d54-3d708610224a.json +1 -0
- package/.nyc_output/6d7c60ad-a404-4a1d-af86-8a334cff5f02.json +1 -0
- package/.nyc_output/8dae7e8c-5022-4a0c-a5b3-bf547f97961b.json +1 -0
- package/.nyc_output/f63bf7c5-4f58-4b46-a822-6f5ccf5a54a8.json +1 -0
- package/.nyc_output/processinfo/0208d41a-48bc-4675-a861-0475eb461a17.json +1 -0
- package/.nyc_output/processinfo/588169a6-88e9-4949-af5a-631822d7dc42.json +1 -0
- package/.nyc_output/processinfo/5bbdf331-cd01-4869-9d54-3d708610224a.json +1 -0
- package/.nyc_output/processinfo/6d7c60ad-a404-4a1d-af86-8a334cff5f02.json +1 -0
- package/.nyc_output/processinfo/8dae7e8c-5022-4a0c-a5b3-bf547f97961b.json +1 -0
- package/.nyc_output/processinfo/f63bf7c5-4f58-4b46-a822-6f5ccf5a54a8.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/.taprc +1 -0
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +13 -0
- package/lib/entity.js +287 -0
- package/lib/queries/index.js +23 -0
- package/lib/queries/mariadb.js +11 -0
- package/lib/queries/mysql-shared.js +62 -0
- package/lib/queries/mysql.js +104 -0
- package/lib/queries/pg.js +79 -0
- package/lib/queries/shared.js +100 -0
- package/lib/queries/sqlite.js +169 -0
- package/lib/utils.js +14 -0
- package/mapper.d.ts +308 -0
- package/mapper.js +155 -0
- package/package.json +44 -0
- package/test/entity.test.js +344 -0
- package/test/helper.js +66 -0
- package/test/hooks.test.js +325 -0
- package/test/inserted_at_updated_at.test.js +132 -0
- package/test/mapper.test.js +288 -0
- package/test/types/mapper.test-d.ts +64 -0
- package/test/where.test.js +316 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const { connect } = require('..')
|
|
5
|
+
const { clear, connInfo, isSQLite } = require('./helper')
|
|
6
|
+
const fakeLogger = {
|
|
7
|
+
trace: () => {},
|
|
8
|
+
error: () => {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test('basic hooks', async ({ pass, teardown, same, equal, plan, fail }) => {
|
|
12
|
+
plan(14)
|
|
13
|
+
const mapper = await connect({
|
|
14
|
+
...connInfo,
|
|
15
|
+
log: fakeLogger,
|
|
16
|
+
async onDatabaseLoad (db, sql) {
|
|
17
|
+
teardown(() => db.dispose())
|
|
18
|
+
pass('onDatabaseLoad called')
|
|
19
|
+
|
|
20
|
+
await clear(db, sql)
|
|
21
|
+
|
|
22
|
+
if (isSQLite) {
|
|
23
|
+
await db.query(sql`CREATE TABLE pages (
|
|
24
|
+
id INTEGER PRIMARY KEY,
|
|
25
|
+
title VARCHAR(42)
|
|
26
|
+
);`)
|
|
27
|
+
} else {
|
|
28
|
+
await db.query(sql`CREATE TABLE pages (
|
|
29
|
+
id SERIAL PRIMARY KEY,
|
|
30
|
+
title VARCHAR(42)
|
|
31
|
+
);`)
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
hooks: {
|
|
35
|
+
Page: {
|
|
36
|
+
noKey () {
|
|
37
|
+
fail('noKey should never be called')
|
|
38
|
+
},
|
|
39
|
+
async save (original, { input, ctx, fields }) {
|
|
40
|
+
pass('save called')
|
|
41
|
+
|
|
42
|
+
if (!input.id) {
|
|
43
|
+
same(input, {
|
|
44
|
+
title: 'Hello'
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return original({
|
|
48
|
+
input: {
|
|
49
|
+
title: 'Hello from hook'
|
|
50
|
+
},
|
|
51
|
+
fields
|
|
52
|
+
})
|
|
53
|
+
} else {
|
|
54
|
+
same(input, {
|
|
55
|
+
id: 1,
|
|
56
|
+
title: 'Hello World'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return original({
|
|
60
|
+
input: {
|
|
61
|
+
id: 1,
|
|
62
|
+
title: 'Hello from hook 2'
|
|
63
|
+
},
|
|
64
|
+
fields
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async find (original, args) {
|
|
69
|
+
pass('find called')
|
|
70
|
+
|
|
71
|
+
same(args.where, {
|
|
72
|
+
id: {
|
|
73
|
+
eq: '1'
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
args.where = {
|
|
77
|
+
id: {
|
|
78
|
+
eq: '2'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
same(args.fields, ['id', 'title'])
|
|
82
|
+
return original(args)
|
|
83
|
+
},
|
|
84
|
+
async insert (original, args) {
|
|
85
|
+
pass('insert called')
|
|
86
|
+
|
|
87
|
+
same(args.inputs, [{
|
|
88
|
+
title: 'hello'
|
|
89
|
+
}, {
|
|
90
|
+
title: 'world'
|
|
91
|
+
}])
|
|
92
|
+
same(args.fields, ['id', 'title'])
|
|
93
|
+
return original(args)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const entity = mapper.entities.page
|
|
100
|
+
|
|
101
|
+
same(await entity.save({ input: { title: 'Hello' } }), {
|
|
102
|
+
id: 1,
|
|
103
|
+
title: 'Hello from hook'
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
same(await entity.find({ where: { id: { eq: 1 } }, fields: ['id', 'title'] }), [])
|
|
107
|
+
|
|
108
|
+
same(await entity.save({ input: { id: 1, title: 'Hello World' } }), {
|
|
109
|
+
id: 1,
|
|
110
|
+
title: 'Hello from hook 2'
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('addEntityHooks', async ({ pass, teardown, same, equal, plan, fail, throws }) => {
|
|
117
|
+
plan(15)
|
|
118
|
+
const mapper = await connect({
|
|
119
|
+
...connInfo,
|
|
120
|
+
log: fakeLogger,
|
|
121
|
+
async onDatabaseLoad (db, sql) {
|
|
122
|
+
teardown(() => db.dispose())
|
|
123
|
+
pass('onDatabaseLoad called')
|
|
124
|
+
|
|
125
|
+
await clear(db, sql)
|
|
126
|
+
|
|
127
|
+
if (isSQLite) {
|
|
128
|
+
await db.query(sql`CREATE TABLE pages (
|
|
129
|
+
id INTEGER PRIMARY KEY,
|
|
130
|
+
title VARCHAR(42)
|
|
131
|
+
);`)
|
|
132
|
+
} else {
|
|
133
|
+
await db.query(sql`CREATE TABLE pages (
|
|
134
|
+
id SERIAL PRIMARY KEY,
|
|
135
|
+
title VARCHAR(42)
|
|
136
|
+
);`)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
throws(() => mapper.addEntityHooks('user', {}), 'Cannot find entity user')
|
|
142
|
+
|
|
143
|
+
mapper.addEntityHooks('page', {
|
|
144
|
+
noKey () {
|
|
145
|
+
fail('noKey should never be called')
|
|
146
|
+
},
|
|
147
|
+
async save (original, { input, ctx, fields }) {
|
|
148
|
+
pass('save called')
|
|
149
|
+
|
|
150
|
+
if (!input.id) {
|
|
151
|
+
same(input, {
|
|
152
|
+
title: 'Hello'
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
return original({
|
|
156
|
+
input: {
|
|
157
|
+
title: 'Hello from hook'
|
|
158
|
+
},
|
|
159
|
+
fields
|
|
160
|
+
})
|
|
161
|
+
} else {
|
|
162
|
+
same(input, {
|
|
163
|
+
id: 1,
|
|
164
|
+
title: 'Hello World'
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return original({
|
|
168
|
+
input: {
|
|
169
|
+
id: 1,
|
|
170
|
+
title: 'Hello from hook 2'
|
|
171
|
+
},
|
|
172
|
+
fields
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
async find (original, args) {
|
|
177
|
+
pass('find called')
|
|
178
|
+
|
|
179
|
+
same(args.where, {
|
|
180
|
+
id: {
|
|
181
|
+
eq: '1'
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
args.where = {
|
|
185
|
+
id: {
|
|
186
|
+
eq: '2'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
same(args.fields, ['id', 'title'])
|
|
190
|
+
return original(args)
|
|
191
|
+
},
|
|
192
|
+
async insert (original, args) {
|
|
193
|
+
pass('insert called')
|
|
194
|
+
|
|
195
|
+
same(args.inputs, [{
|
|
196
|
+
title: 'hello'
|
|
197
|
+
}, {
|
|
198
|
+
title: 'world'
|
|
199
|
+
}])
|
|
200
|
+
same(args.fields, ['id', 'title'])
|
|
201
|
+
return original(args)
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const entity = mapper.entities.page
|
|
206
|
+
|
|
207
|
+
same(await entity.save({ input: { title: 'Hello' } }), {
|
|
208
|
+
id: 1,
|
|
209
|
+
title: 'Hello from hook'
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
same(await entity.find({ where: { id: { eq: 1 } }, fields: ['id', 'title'] }), [])
|
|
213
|
+
|
|
214
|
+
same(await entity.save({ input: { id: 1, title: 'Hello World' } }), {
|
|
215
|
+
id: 1,
|
|
216
|
+
title: 'Hello from hook 2'
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('basic hooks with smaller cap name', async ({ pass, teardown, same, equal, plan, fail }) => {
|
|
223
|
+
plan(14)
|
|
224
|
+
const mapper = await connect({
|
|
225
|
+
...connInfo,
|
|
226
|
+
log: fakeLogger,
|
|
227
|
+
async onDatabaseLoad (db, sql) {
|
|
228
|
+
teardown(() => db.dispose())
|
|
229
|
+
pass('onDatabaseLoad called')
|
|
230
|
+
|
|
231
|
+
await clear(db, sql)
|
|
232
|
+
|
|
233
|
+
if (isSQLite) {
|
|
234
|
+
await db.query(sql`CREATE TABLE pages (
|
|
235
|
+
id INTEGER PRIMARY KEY,
|
|
236
|
+
title VARCHAR(42)
|
|
237
|
+
);`)
|
|
238
|
+
} else {
|
|
239
|
+
await db.query(sql`CREATE TABLE pages (
|
|
240
|
+
id SERIAL PRIMARY KEY,
|
|
241
|
+
title VARCHAR(42)
|
|
242
|
+
);`)
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
hooks: {
|
|
246
|
+
page: {
|
|
247
|
+
noKey () {
|
|
248
|
+
fail('noKey should never be called')
|
|
249
|
+
},
|
|
250
|
+
async save (original, { input, ctx, fields }) {
|
|
251
|
+
pass('save called')
|
|
252
|
+
|
|
253
|
+
if (!input.id) {
|
|
254
|
+
same(input, {
|
|
255
|
+
title: 'Hello'
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
return original({
|
|
259
|
+
input: {
|
|
260
|
+
title: 'Hello from hook'
|
|
261
|
+
},
|
|
262
|
+
fields
|
|
263
|
+
})
|
|
264
|
+
} else {
|
|
265
|
+
same(input, {
|
|
266
|
+
id: 1,
|
|
267
|
+
title: 'Hello World'
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
return original({
|
|
271
|
+
input: {
|
|
272
|
+
id: 1,
|
|
273
|
+
title: 'Hello from hook 2'
|
|
274
|
+
},
|
|
275
|
+
fields
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
async find (original, args) {
|
|
280
|
+
pass('find called')
|
|
281
|
+
|
|
282
|
+
same(args.where, {
|
|
283
|
+
id: {
|
|
284
|
+
eq: '1'
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
args.where = {
|
|
288
|
+
id: {
|
|
289
|
+
eq: '2'
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
same(args.fields, ['id', 'title'])
|
|
293
|
+
return original(args)
|
|
294
|
+
},
|
|
295
|
+
async insert (original, args) {
|
|
296
|
+
pass('insert called')
|
|
297
|
+
|
|
298
|
+
same(args.inputs, [{
|
|
299
|
+
title: 'hello'
|
|
300
|
+
}, {
|
|
301
|
+
title: 'world'
|
|
302
|
+
}])
|
|
303
|
+
same(args.fields, ['id', 'title'])
|
|
304
|
+
return original(args)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const entity = mapper.entities.page
|
|
311
|
+
|
|
312
|
+
same(await entity.save({ input: { title: 'Hello' } }), {
|
|
313
|
+
id: 1,
|
|
314
|
+
title: 'Hello from hook'
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
same(await entity.find({ where: { id: { eq: 1 } }, fields: ['id', 'title'] }), [])
|
|
318
|
+
|
|
319
|
+
same(await entity.save({ input: { id: 1, title: 'Hello World' } }), {
|
|
320
|
+
id: 1,
|
|
321
|
+
title: 'Hello from hook 2'
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
|
|
325
|
+
})
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const { clear, connInfo, isSQLite, isMysql } = require('./helper')
|
|
5
|
+
const { setTimeout } = require('timers/promises')
|
|
6
|
+
const { connect } = require('..')
|
|
7
|
+
const fakeLogger = {
|
|
8
|
+
trace: () => {},
|
|
9
|
+
error: () => {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function createBasicPages (db, sql) {
|
|
13
|
+
if (isSQLite) {
|
|
14
|
+
await db.query(sql`CREATE TABLE pages (
|
|
15
|
+
id INTEGER PRIMARY KEY,
|
|
16
|
+
title VARCHAR(42),
|
|
17
|
+
inserted_at TIMESTAMP,
|
|
18
|
+
updated_at TIMESTAMP
|
|
19
|
+
);`)
|
|
20
|
+
} else if (isMysql) {
|
|
21
|
+
await db.query(sql`CREATE TABLE pages (
|
|
22
|
+
id SERIAL PRIMARY KEY,
|
|
23
|
+
title VARCHAR(42),
|
|
24
|
+
inserted_at TIMESTAMP NULL DEFAULT NULL,
|
|
25
|
+
updated_at TIMESTAMP NULL DEFAULT NULL
|
|
26
|
+
);`)
|
|
27
|
+
} else {
|
|
28
|
+
await db.query(sql`CREATE TABLE pages (
|
|
29
|
+
id SERIAL PRIMARY KEY,
|
|
30
|
+
title VARCHAR(42),
|
|
31
|
+
inserted_at TIMESTAMP,
|
|
32
|
+
updated_at TIMESTAMP
|
|
33
|
+
);`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test('inserted_at updated_at happy path', async ({ pass, teardown, same, equal, not, comment, notSame }) => {
|
|
38
|
+
const mapper = await connect({
|
|
39
|
+
...connInfo,
|
|
40
|
+
log: fakeLogger,
|
|
41
|
+
async onDatabaseLoad (db, sql) {
|
|
42
|
+
teardown(() => db.dispose())
|
|
43
|
+
pass('onDatabaseLoad called')
|
|
44
|
+
|
|
45
|
+
await clear(db, sql)
|
|
46
|
+
await createBasicPages(db, sql)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const entity = mapper.entities.page
|
|
51
|
+
|
|
52
|
+
equal(entity.fields.inserted_at.autoTimestamp, true)
|
|
53
|
+
equal(entity.fields.updated_at.autoTimestamp, true)
|
|
54
|
+
|
|
55
|
+
const original = await entity.save({
|
|
56
|
+
input: { title: 'Hello' }
|
|
57
|
+
})
|
|
58
|
+
not(original.insertedAt, null, 'insertedAt')
|
|
59
|
+
not(original.updatedAt, null, 'updatedAt')
|
|
60
|
+
comment(`insertedAt: ${original.insertedAt}`)
|
|
61
|
+
comment(`updatedAt: ${original.updatedAt}`)
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
const [data] = await entity.find({ where: { id: { eq: original.id } } })
|
|
65
|
+
same(data.insertedAt, original.insertedAt, 'insertedAt')
|
|
66
|
+
same(data.updatedAt, original.updatedAt, 'updatedAt')
|
|
67
|
+
comment(`insertedAt: ${data.insertedAt}`)
|
|
68
|
+
comment(`updatedAt: ${data.updatedAt}`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await setTimeout(1000) // await 1s
|
|
72
|
+
|
|
73
|
+
let updated
|
|
74
|
+
{
|
|
75
|
+
const data = await entity.save({
|
|
76
|
+
input: { id: original.id, title: 'Hello World' }
|
|
77
|
+
})
|
|
78
|
+
same(data.insertedAt, original.insertedAt, 'insertedAt')
|
|
79
|
+
notSame(data.updatedAt, original.updatedAt, 'updatedAt')
|
|
80
|
+
updated = data
|
|
81
|
+
comment(`insertedAt: ${data.insertedAt}`)
|
|
82
|
+
comment(`updatedAt: ${data.updatedAt}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
const [data] = await entity.find({ where: { id: { eq: original.id } } })
|
|
87
|
+
same(data.insertedAt, updated.insertedAt, 'insertedAt')
|
|
88
|
+
same(data.updatedAt, updated.updatedAt, 'updatedAt')
|
|
89
|
+
comment(`insertedAt: ${data.insertedAt}`)
|
|
90
|
+
comment(`updatedAt: ${data.updatedAt}`)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('bulk insert adds inserted_at updated_at', async ({ pass, teardown, same, equal, not, comment }) => {
|
|
95
|
+
const mapper = await connect({
|
|
96
|
+
...connInfo,
|
|
97
|
+
log: fakeLogger,
|
|
98
|
+
async onDatabaseLoad (db, sql) {
|
|
99
|
+
teardown(() => db.dispose())
|
|
100
|
+
pass('onDatabaseLoad called')
|
|
101
|
+
|
|
102
|
+
await clear(db, sql)
|
|
103
|
+
await createBasicPages(db, sql)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const entity = mapper.entities.page
|
|
108
|
+
|
|
109
|
+
{
|
|
110
|
+
const pages = await entity.insert({
|
|
111
|
+
inputs: [
|
|
112
|
+
{ title: 'Page 1' },
|
|
113
|
+
{ title: 'Page 2' },
|
|
114
|
+
{ title: 'Page 3' }
|
|
115
|
+
]
|
|
116
|
+
})
|
|
117
|
+
for (const page of pages) {
|
|
118
|
+
not(page.insertedAt, null, 'insertedAt')
|
|
119
|
+
not(page.updatedAt, null, 'updatedAt')
|
|
120
|
+
same(page.insertedAt, page.updatedAt, 'insertedAt === updatedAt')
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
const pages = await entity.find()
|
|
126
|
+
for (const page of pages) {
|
|
127
|
+
not(page.insertedAt, null, 'insertedAt')
|
|
128
|
+
not(page.updatedAt, null, 'updatedAt')
|
|
129
|
+
same(page.insertedAt, page.updatedAt, 'insertedAt === updatedAt')
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
})
|