@platformatic/sql-mapper 0.8.0 → 0.9.1

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/lib/entity.js CHANGED
@@ -1,14 +1,19 @@
1
1
  'use strict'
2
2
 
3
3
  const camelcase = require('camelcase')
4
- const { singularize } = require('inflected')
5
4
  const {
6
5
  toSingular,
7
- tableName
6
+ toUpperFirst,
7
+ tableName,
8
+ sanitizeLimit
8
9
  } = require('./utils')
9
10
 
10
- function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema) {
11
- const entityName = toSingular(table)
11
+ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) {
12
+ /* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
13
+ const entityName = useSchemaInName ? toUpperFirst(`${schema}${toSingular(table)}`) : toSingular(table)
14
+ /* istanbul ignore next */
15
+ const pluralName = camelcase(useSchemaInName ? camelcase(`${schema} ${table}`) : table)
16
+ const singularName = camelcase(entityName)
12
17
 
13
18
  // Fields remapping
14
19
  const fieldMapToRetrieve = {}
@@ -265,13 +270,12 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
265
270
  query = sql`${query} ORDER BY ${sql.join(orderBy, sql`, `)}`
266
271
  }
267
272
 
268
- if (opts.limit || opts.offset !== undefined) {
269
- // Use Number.MAX_SAFE_INTEGER as default value for limit because in the sql query you cannot add OFFSET without LIMIT
270
- const limit = (opts.limit !== undefined) ? opts.limit : Number.MAX_SAFE_INTEGER
271
- query = sql`${query} LIMIT ${limit}`
272
- if (opts.offset !== undefined) {
273
- query = sql`${query} OFFSET ${opts.offset}`
273
+ query = sql`${query} LIMIT ${sanitizeLimit(opts.limit, limitConfig)}`
274
+ if (opts.offset !== undefined) {
275
+ if (opts.offset < 0) {
276
+ throw new Error(`Param offset=${opts.offset} not allowed. It must be not negative value.`)
274
277
  }
278
+ query = sql`${query} OFFSET ${opts.offset}`
275
279
  }
276
280
 
277
281
  const res = await db.query(query)
@@ -303,8 +307,8 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
303
307
 
304
308
  return {
305
309
  name: entityName,
306
- singularName: camelcase(singularize(table)),
307
- pluralName: camelcase(table),
310
+ singularName,
311
+ pluralName,
308
312
  primaryKeys,
309
313
  table,
310
314
  schema,
@@ -321,7 +325,7 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
321
325
  }
322
326
  }
323
327
 
324
- async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, ignore) {
328
+ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema, useSchemaInName, ignore, limitConfig) {
325
329
  // Compute the columns
326
330
  const columns = (await queries.listColumns(db, sql, table, schema)).filter((c) => !ignore[c.column_name])
327
331
  const fields = columns.reduce((acc, column) => {
@@ -391,7 +395,7 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
391
395
  }
392
396
  }
393
397
 
394
- const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema)
398
+ const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema, useSchemaInName, limitConfig)
395
399
  entity.relations = currentRelations
396
400
 
397
401
  return entity
package/lib/queries/pg.js CHANGED
@@ -14,7 +14,6 @@ async function insertOne (db, sql, table, schema, input, primaryKeys, fieldsToRe
14
14
  const res = await db.query(insert)
15
15
  return res[0]
16
16
  }
17
-
18
17
  return shared.insertOne(db, sql, table, schema, input, primaryKeys, fieldsToRetrieve)
19
18
  }
20
19
 
package/lib/utils.js CHANGED
@@ -3,18 +3,49 @@
3
3
  const { singularize } = require('inflected')
4
4
  const camelcase = require('camelcase')
5
5
 
6
+ function toUpperFirst (str) {
7
+ return str[0].toUpperCase() + str.slice(1)
8
+ }
9
+
6
10
  function toSingular (str) {
7
11
  str = camelcase(singularize(str))
8
- str = str[0].toUpperCase() + str.slice(1)
12
+ str = toUpperFirst(str)
9
13
  return str
10
14
  }
11
15
 
16
+ /**
17
+ * If limit is not defined or invalid
18
+ * let's set a safe default value preventing to load huge amount of data in memory
19
+ */
20
+ function sanitizeLimit (unsafeLimit, conf) {
21
+ const defaultLimit = conf?.default ?? 10
22
+ const limit = (unsafeLimit !== undefined) ? unsafeLimit : defaultLimit
23
+ const max = conf?.max ?? 100
24
+
25
+ if (limit > max) {
26
+ throw new Error(`Param limit=${limit} not allowed. Max accepted value ${max}.`)
27
+ }
28
+
29
+ if (limit < 0) {
30
+ throw new Error(`Param limit=${limit} not allowed. It must be not negative value.`)
31
+ }
32
+
33
+ return limit
34
+ }
35
+
12
36
  function tableName (sql, table, schema) {
13
37
  /* istanbul ignore next */
14
38
  return schema ? sql.ident(schema, table) : sql.ident(table)
15
39
  }
16
40
 
41
+ function areSchemasSupported (sql) {
42
+ return !sql.isSQLite
43
+ }
44
+
17
45
  module.exports = {
18
46
  toSingular,
19
- tableName
47
+ toUpperFirst,
48
+ sanitizeLimit,
49
+ tableName,
50
+ areSchemasSupported
20
51
  }
package/mapper.js CHANGED
@@ -1,8 +1,7 @@
1
- 'use strict'
2
-
3
1
  const buildEntity = require('./lib/entity')
4
2
  const queriesFactory = require('./lib/queries')
5
3
  const fp = require('fastify-plugin')
4
+ const { areSchemasSupported } = require('./lib/utils')
6
5
 
7
6
  // Ignore the function as it is only used only for MySQL and PostgreSQL
8
7
  /* istanbul ignore next */
@@ -40,7 +39,7 @@ async function buildConnection (log, createConnectionPool, connectionString, poo
40
39
  return db
41
40
  }
42
41
 
43
- async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema }) {
42
+ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {} }) {
44
43
  // TODO validate config using the schema
45
44
  if (!connectionString) {
46
45
  throw new Error('connectionString is required')
@@ -50,15 +49,10 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
50
49
  let sql
51
50
  let db
52
51
 
53
- // Specify an empty array must be the same of specifying no schema
54
- const schemaList = schema?.length > 0 ? schema : null
55
-
56
52
  /* istanbul ignore next */
57
53
  if (connectionString.indexOf('postgres') === 0) {
58
54
  const createConnectionPoolPg = require('@databases/pg')
59
- // We pass schema here so @databases/pg set the schema in the search path. This is not stritly necessary, though,
60
- // because now we use fully qualified names in all queries.
61
- db = await buildConnection(log, createConnectionPoolPg, connectionString, poolSize, schemaList)
55
+ db = await buildConnection(log, createConnectionPoolPg, connectionString, poolSize)
62
56
  sql = createConnectionPoolPg.sql
63
57
  queries = queriesFactory.pg
64
58
  db.isPg = true
@@ -93,6 +87,11 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
93
87
  throw new Error('You must specify either postgres, mysql or sqlite as protocols')
94
88
  }
95
89
 
90
+ // Specify an empty array must be the same of specifying no schema
91
+ /* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
92
+ const schemaList = areSchemasSupported(db) && schema?.length > 0 ? schema : null
93
+ const useSchema = !!schemaList
94
+
96
95
  const entities = {}
97
96
 
98
97
  try {
@@ -102,14 +101,6 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
102
101
  }
103
102
 
104
103
  const tablesWithSchema = await queries.listTables(db, sql, schemaList)
105
- const tables = tablesWithSchema.map(({ table }) => table)
106
- const duplicates = tables.filter((table, index) => tables.indexOf(table) !== index)
107
-
108
- // Ignored because this never happens in sqlite
109
- /* istanbul ignore next */
110
- if (duplicates.length > 0) {
111
- throw new Error(`Conflicting table names: ${duplicates.join(', ')}`)
112
- }
113
104
 
114
105
  for (const { table, schema } of tablesWithSchema) {
115
106
  // The following line is a safety net when developing this module,
@@ -121,12 +112,14 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
121
112
  if (ignore[table] === true) {
122
113
  continue
123
114
  }
124
- const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, ignore[table] || {})
115
+ const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit)
125
116
  // Check for primary key of all entities
126
117
  if (entity.primaryKeys.size === 0) {
127
- throw new Error(`Cannot find any primary keys for ${entity.name} entity`)
118
+ throw Error(`Cannot find any primary keys for ${entity.name} entity`)
128
119
  }
120
+
129
121
  entities[entity.singularName] = entity
122
+
130
123
  if (hooks[entity.name]) {
131
124
  addEntityHooks(entity.singularName, hooks[entity.name])
132
125
  } else if (hooks[entity.singularName]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "repository": {
@@ -18,7 +18,7 @@
18
18
  "snazzy": "^9.0.0",
19
19
  "standard": "^17.0.0",
20
20
  "tap": "^16.0.0",
21
- "tsd": "^0.24.0"
21
+ "tsd": "^0.25.0"
22
22
  },
23
23
  "dependencies": {
24
24
  "@databases/mysql": "^5.2.0",
@@ -48,15 +48,15 @@ test('uses tables from different schemas', { skip: isSQLite }, async ({ pass, te
48
48
  hooks: {},
49
49
  schema: ['test1', 'test2']
50
50
  })
51
- const pageEntity = mapper.entities.page
52
- equal(pageEntity.name, 'Page')
53
- equal(pageEntity.singularName, 'page')
54
- equal(pageEntity.pluralName, 'pages')
51
+ const pageEntity = mapper.entities.test1Page
52
+ equal(pageEntity.name, 'Test1Page')
53
+ equal(pageEntity.singularName, 'test1Page')
54
+ equal(pageEntity.pluralName, 'test1Pages')
55
55
  equal(pageEntity.schema, 'test1')
56
- const userEntity = mapper.entities.user
57
- equal(userEntity.name, 'User')
58
- equal(userEntity.singularName, 'user')
59
- equal(userEntity.pluralName, 'users')
56
+ const userEntity = mapper.entities.test2User
57
+ equal(userEntity.name, 'Test2User')
58
+ equal(userEntity.singularName, 'test2User')
59
+ equal(userEntity.pluralName, 'test2Users')
60
60
  equal(userEntity.schema, 'test2')
61
61
  pass()
62
62
  })
@@ -98,61 +98,13 @@ test('find enums correctly using schemas', { skip: isSQLite }, async ({ pass, te
98
98
  hooks: {},
99
99
  schema: ['test1']
100
100
  })
101
- const pageEntity = mapper.entities.page
102
- equal(pageEntity.name, 'Page')
103
- equal(pageEntity.singularName, 'page')
104
- equal(pageEntity.pluralName, 'pages')
101
+ const pageEntity = mapper.entities.test1Page
102
+ equal(pageEntity.name, 'Test1Page')
103
+ equal(pageEntity.singularName, 'test1Page')
104
+ equal(pageEntity.pluralName, 'test1Pages')
105
105
  pass()
106
106
  })
107
107
 
108
- test('should fail if two schemas has the same table', { skip: isSQLite }, async ({ pass, teardown, equal }) => {
109
- async function onDatabaseLoad (db, sql) {
110
- await clear(db, sql)
111
- teardown(() => db.dispose())
112
-
113
- await db.query(sql`CREATE SCHEMA IF NOT EXISTS test1;`)
114
- if (isMysql || isMysql8) {
115
- await db.query(sql`CREATE TABLE IF NOT EXISTS \`test1\`.\`pages\` (
116
- id SERIAL PRIMARY KEY,
117
- title VARCHAR(255) NOT NULL
118
- );`)
119
- } else {
120
- await db.query(sql`CREATE TABLE IF NOT EXISTS "test1"."pages" (
121
- id SERIAL PRIMARY KEY,
122
- title VARCHAR(255) NOT NULL
123
- );`)
124
- }
125
-
126
- await db.query(sql`CREATE SCHEMA IF NOT EXISTS test2;`)
127
-
128
- if (isMysql || isMysql8) {
129
- await db.query(sql`CREATE TABLE IF NOT EXISTS \`test2\`.\`pages\` (
130
- id SERIAL PRIMARY KEY,
131
- username VARCHAR(255) NOT NULL
132
- );`)
133
- } else {
134
- await db.query(sql`CREATE TABLE IF NOT EXISTS "test2"."pages" (
135
- id SERIAL PRIMARY KEY,
136
- username VARCHAR(255) NOT NULL
137
- );`)
138
- }
139
- }
140
-
141
- try {
142
- await connect({
143
- connectionString: connInfo.connectionString,
144
- log: fakeLogger,
145
- onDatabaseLoad,
146
- ignore: {},
147
- hooks: {},
148
- schema: ['test1', 'test2']
149
- })
150
- } catch (err) {
151
- console.log(err.message)
152
- equal(err.message, 'Conflicting table names: pages')
153
- }
154
- })
155
-
156
108
  test('if schema is empty array, should not load entities from tables in explicit schema', { skip: isSQLite }, async ({ pass, teardown, equal }) => {
157
109
  async function onDatabaseLoad (db, sql) {
158
110
  await clear(db, sql)
@@ -194,7 +146,6 @@ test('if schema is empty array, should not load entities from tables in explicit
194
146
  schema: []
195
147
  })
196
148
 
197
- console.log(mapper.entities)
198
149
  equal(Object.keys(mapper.entities).length, 0)
199
150
  pass()
200
151
  })
@@ -259,3 +210,111 @@ test('[sqlite] if sqllite, ignores schema information', { skip: !isSQLite }, asy
259
210
  equal(pageEntity.schema, null)
260
211
  pass()
261
212
  })
213
+
214
+ test('addEntityHooks in entities with schema', { skip: isSQLite }, async ({ pass, teardown, same, equal, plan, fail, throws, end }) => {
215
+ async function onDatabaseLoad (db, sql) {
216
+ await clear(db, sql)
217
+ teardown(() => db.dispose())
218
+
219
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test1;`)
220
+ if (isMysql || isMysql8) {
221
+ await db.query(sql`CREATE TABLE IF NOT EXISTS \`test1\`.\`pages\` (
222
+ id SERIAL PRIMARY KEY,
223
+ title VARCHAR(255) NOT NULL
224
+ );`)
225
+ } else {
226
+ await db.query(sql`CREATE TABLE IF NOT EXISTS "test1"."pages" (
227
+ id SERIAL PRIMARY KEY,
228
+ title VARCHAR(255) NOT NULL
229
+ );`)
230
+ }
231
+ }
232
+
233
+ const mapper = await connect({
234
+ ...connInfo,
235
+ log: fakeLogger,
236
+ onDatabaseLoad,
237
+ schema: ['test1']
238
+ })
239
+
240
+ throws(() => mapper.addEntityHooks('user', {}), 'Cannot find entity user')
241
+
242
+ mapper.addEntityHooks('test1Page', {
243
+ noKey () {
244
+ fail('noKey should never be called')
245
+ },
246
+ async save (original, { input, ctx, fields }) {
247
+ pass('save called')
248
+
249
+ if (!input.id) {
250
+ same(input, {
251
+ title: 'Hello'
252
+ })
253
+
254
+ return original({
255
+ input: {
256
+ title: 'Hello from hook'
257
+ },
258
+ fields
259
+ })
260
+ } else {
261
+ same(input, {
262
+ id: 1,
263
+ title: 'Hello World'
264
+ })
265
+
266
+ return original({
267
+ input: {
268
+ id: 1,
269
+ title: 'Hello from hook 2'
270
+ },
271
+ fields
272
+ })
273
+ }
274
+ },
275
+ async find (original, args) {
276
+ pass('find called')
277
+
278
+ same(args.where, {
279
+ id: {
280
+ eq: '1'
281
+ }
282
+ })
283
+ args.where = {
284
+ id: {
285
+ eq: '2'
286
+ }
287
+ }
288
+ same(args.fields, ['id', 'title'])
289
+ return original(args)
290
+ },
291
+ async insert (original, args) {
292
+ pass('insert called')
293
+
294
+ same(args.inputs, [{
295
+ title: 'hello'
296
+ }, {
297
+ title: 'world'
298
+ }])
299
+ same(args.fields, ['id', 'title'])
300
+ return original(args)
301
+ }
302
+ })
303
+
304
+ const entity = mapper.entities.test1Page
305
+
306
+ same(await entity.save({ input: { title: 'Hello' } }), {
307
+ id: 1,
308
+ title: 'Hello from hook'
309
+ })
310
+
311
+ same(await entity.find({ where: { id: { eq: 1 } }, fields: ['id', 'title'] }), [])
312
+
313
+ same(await entity.save({ input: { id: 1, title: 'Hello World' } }), {
314
+ id: 1,
315
+ title: 'Hello from hook 2'
316
+ })
317
+
318
+ await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
319
+ end()
320
+ })
@@ -388,6 +388,170 @@ test('foreign keys', async ({ pass, teardown, same, equal }) => {
388
388
  }
389
389
  })
390
390
 
391
+ test('limit should be 10 by default 100 at max', async ({ pass, teardown, same, fail, match }) => {
392
+ const mapper = await connect({
393
+ ...connInfo,
394
+ log: fakeLogger,
395
+ async onDatabaseLoad (db, sql) {
396
+ teardown(() => db.dispose())
397
+ pass('onDatabaseLoad called')
398
+
399
+ await clear(db, sql)
400
+
401
+ if (isSQLite) {
402
+ await db.query(sql`CREATE TABLE posts (
403
+ id INTEGER PRIMARY KEY,
404
+ title VARCHAR(42),
405
+ long_text TEXT,
406
+ counter INTEGER
407
+ );`)
408
+ } else {
409
+ await db.query(sql`CREATE TABLE posts (
410
+ id SERIAL PRIMARY KEY,
411
+ title VARCHAR(42),
412
+ long_text TEXT,
413
+ counter INTEGER
414
+ );`)
415
+ }
416
+ }
417
+ })
418
+
419
+ const entity = mapper.entities.post
420
+
421
+ const posts = []
422
+
423
+ for (let i = 0; i <= 105; i++) {
424
+ posts.push({
425
+ title: 'Dog',
426
+ longText: 'Foo',
427
+ counter: i
428
+ })
429
+ }
430
+
431
+ await entity.insert({
432
+ inputs: posts
433
+ })
434
+
435
+ const defaultLimit = 10
436
+ const max = 100
437
+
438
+ same(await (await entity.find()).length, defaultLimit)
439
+
440
+ same(await (await entity.find({ limit: 1 })).length, 1)
441
+
442
+ same(await (await entity.find({ offset: 3 })).length, defaultLimit)
443
+
444
+ same(await (await entity.find({ limit: 1, offset: 0 })).length, 1)
445
+
446
+ same(await (await entity.find({ limit: 0 })).length, 0)
447
+
448
+ try {
449
+ await entity.find({ limit: -1 })
450
+ fail('Expected error for limit not allowed value')
451
+ } catch (e) {
452
+ match(e, new Error('Param limit=-1 not allowed. It must be not negative value.'))
453
+ }
454
+
455
+ same(await (await entity.find({ limit: 1, offset: 0 })).length, 1)
456
+
457
+ try {
458
+ await entity.find({ limit: 1, offset: -1 })
459
+ fail('Expected error for offset not allowed value')
460
+ } catch (e) {
461
+ match(e, new Error('Param offset=-1 not allowed. It must be not negative value.'))
462
+ }
463
+
464
+ try {
465
+ await entity.find({ limit: 200 })
466
+ fail('Expected error for limit exceeding max allowed value')
467
+ } catch (e) {
468
+ match(e, new Error(`Param limit=200 not allowed. Max accepted value ${max}.`))
469
+ }
470
+ })
471
+
472
+ test('limit must accept custom configuration', async ({ pass, teardown, same, fail, match }) => {
473
+ const customLimitConf = {
474
+ default: 1,
475
+ max: 5
476
+ }
477
+ const mapper = await connect({
478
+ ...connInfo,
479
+ log: fakeLogger,
480
+ async onDatabaseLoad (db, sql) {
481
+ teardown(() => db.dispose())
482
+ pass('onDatabaseLoad called')
483
+
484
+ await clear(db, sql)
485
+
486
+ if (isSQLite) {
487
+ await db.query(sql`CREATE TABLE posts (
488
+ id INTEGER PRIMARY KEY,
489
+ title VARCHAR(42),
490
+ long_text TEXT,
491
+ counter INTEGER
492
+ );`)
493
+ } else {
494
+ await db.query(sql`CREATE TABLE posts (
495
+ id SERIAL PRIMARY KEY,
496
+ title VARCHAR(42),
497
+ long_text TEXT,
498
+ counter INTEGER
499
+ );`)
500
+ }
501
+ },
502
+ limit: customLimitConf
503
+ })
504
+
505
+ const entity = mapper.entities.post
506
+
507
+ const posts = []
508
+
509
+ for (let i = 0; i <= 10; i++) {
510
+ posts.push({
511
+ title: 'Dog',
512
+ longText: 'Foo',
513
+ counter: i
514
+ })
515
+ }
516
+
517
+ await entity.insert({
518
+ inputs: posts
519
+ })
520
+
521
+ same(await (await entity.find()).length, customLimitConf.default)
522
+
523
+ same(await (await entity.find({ limit: 1 })).length, 1)
524
+
525
+ same(await (await entity.find({ offset: 3 })).length, customLimitConf.default)
526
+
527
+ same(await (await entity.find({ limit: 1, offset: 0 })).length, 1)
528
+
529
+ same(await (await entity.find({ limit: 0 })).length, 0)
530
+
531
+ try {
532
+ await entity.find({ limit: -1 })
533
+ fail('Expected error for limit not allowed value')
534
+ } catch (e) {
535
+ match(e, new Error('Param limit=-1 not allowed. It must be not negative value.'))
536
+ }
537
+
538
+ same(await (await entity.find({ limit: 1, offset: 0 })).length, 1)
539
+
540
+ try {
541
+ await entity.find({ limit: 1, offset: -1 })
542
+ fail('Expected error for offset not allowed value')
543
+ } catch (e) {
544
+ match(e, new Error('Param offset=-1 not allowed. It must be not negative value.'))
545
+ }
546
+
547
+ try {
548
+ await entity.find({ limit: 200 })
549
+ fail('Expected error for limit exceeding max allowed value')
550
+ } catch (e) {
551
+ match(e, new Error(`Param limit=200 not allowed. Max accepted value ${customLimitConf.max}.`))
552
+ }
553
+ })
554
+
391
555
  test('is NULL', async ({ pass, teardown, same, equal }) => {
392
556
  const mapper = await connect({
393
557
  ...connInfo,