@platformatic/sql-mapper 0.40.0 → 0.41.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/lib/entity.js CHANGED
@@ -223,6 +223,9 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
223
223
  }
224
224
  const value = where[key]
225
225
  const field = inputToFieldMap[key]
226
+ if (!field) {
227
+ throw new Error(`Unknown field ${key}`)
228
+ }
226
229
  for (const key of Object.keys(value)) {
227
230
  const operator = whereMap[key]
228
231
  /* istanbul ignore next */
@@ -231,7 +234,6 @@ function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relation
231
234
  throw new Error(`Unsupported where clause ${JSON.stringify(where[key])}`)
232
235
  }
233
236
  const fieldWrap = fields[field]
234
-
235
237
  /* istanbul ignore next */
236
238
  if (fieldWrap.isArray) {
237
239
  if (operator === 'ANY') {
package/mapper.d.ts CHANGED
@@ -277,19 +277,38 @@ export interface EntityHooks<EntityFields = any> {
277
277
  count?: EntityHook<Count>,
278
278
  }
279
279
 
280
- export interface SQLMapperPluginOptions {
280
+ interface BasePoolOptions {
281
281
  /**
282
282
  * Database connection string.
283
283
  */
284
284
  connectionString: string,
285
+
285
286
  /**
286
- * Set to true to enable auto timestamping for updated_at and inserted_at fields.
287
+ * The maximum number of connections to create at once. Default is 10.
288
+ * @default 10
287
289
  */
288
- autoTimestamp?: boolean,
290
+ poolSize?: number
291
+ }
292
+
293
+ export interface CreateConnectionPoolOptions extends BasePoolOptions {
294
+ /**
295
+ * A logger object (like [Pino](https://getpino.io))
296
+ */
297
+ log: ILogger
298
+ }
299
+
300
+ export function createConnectionPool(options: CreateConnectionPoolOptions) : Promise<{ db: Database, sql: SQL }>
301
+
302
+ export interface SQLMapperPluginOptions extends BasePoolOptions {
289
303
  /**
290
304
  * A logger object (like [Pino](https://getpino.io))
291
305
  */
292
306
  log?: ILogger,
307
+
308
+ /**
309
+ * Set to true to enable auto timestamping for updated_at and inserted_at fields.
310
+ */
311
+ autoTimestamp?: boolean,
293
312
  /**
294
313
  * Database table to ignore when mapping to entities.
295
314
  */
package/mapper.js CHANGED
@@ -47,25 +47,17 @@ const defaultAutoTimestampFields = {
47
47
  updatedAt: 'updated_at'
48
48
  }
49
49
 
50
- async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {}, dbschema }) {
51
- if (typeof autoTimestamp === 'boolean' && autoTimestamp === true) {
52
- autoTimestamp = defaultAutoTimestampFields
53
- }
54
- // TODO validate config using the schema
55
- if (!connectionString) {
56
- throw new Error('connectionString is required')
57
- }
58
-
59
- let queries
60
- let sql
50
+ async function createConnectionPool ({ log, connectionString, poolSize }) {
61
51
  let db
52
+ let sql
53
+
54
+ poolSize = poolSize || 10
62
55
 
63
56
  /* istanbul ignore next */
64
57
  if (connectionString.indexOf('postgres') === 0) {
65
58
  const createConnectionPoolPg = require('@databases/pg')
66
59
  db = await buildConnection(log, createConnectionPoolPg, connectionString, poolSize)
67
60
  sql = createConnectionPoolPg.sql
68
- queries = queriesFactory.pg
69
61
  db.isPg = true
70
62
  } else if (connectionString.indexOf('mysql') === 0) {
71
63
  const createConnectionPoolMysql = require('@databases/mysql')
@@ -74,11 +66,8 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
74
66
  const version = (await db.query(sql`SELECT VERSION()`))[0]['VERSION()']
75
67
  db.version = version
76
68
  db.isMariaDB = version.indexOf('maria') !== -1
77
- if (db.isMariaDB) {
78
- queries = queriesFactory.mariadb
79
- } else {
69
+ if (!db.isMariaDB) {
80
70
  db.isMySql = true
81
- queries = queriesFactory.mysql
82
71
  }
83
72
  } else if (connectionString.indexOf('sqlite') === 0) {
84
73
  const sqlite = require('@matteo.collina/sqlite-pool')
@@ -98,12 +87,37 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
98
87
  }
99
88
  })
100
89
  sql = sqlite.sql
101
- queries = queriesFactory.sqlite
102
90
  db.isSQLite = true
103
91
  } else {
104
92
  throw new Error('You must specify either postgres, mysql or sqlite as protocols')
105
93
  }
106
94
 
95
+ return { db, sql }
96
+ }
97
+
98
+ async function connect ({ connectionString, log, onDatabaseLoad, poolSize, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {}, dbschema }) {
99
+ if (typeof autoTimestamp === 'boolean' && autoTimestamp === true) {
100
+ autoTimestamp = defaultAutoTimestampFields
101
+ }
102
+ // TODO validate config using the schema
103
+ if (!connectionString) {
104
+ throw new Error('connectionString is required')
105
+ }
106
+
107
+ let queries
108
+ const { db, sql } = await createConnectionPool({ log, connectionString, poolSize })
109
+
110
+ /* istanbul ignore next */
111
+ if (db.isPg) {
112
+ queries = queriesFactory.pg
113
+ } else if (db.isMySql) {
114
+ queries = queriesFactory.mysql
115
+ } else if (db.isMariaDB) {
116
+ queries = queriesFactory.mariadb
117
+ } else if (db.isSQLite) {
118
+ queries = queriesFactory.sqlite
119
+ }
120
+
107
121
  // Specify an empty array must be the same of specifying no schema
108
122
  /* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
109
123
  const schemaList = areSchemasSupported(db) && schema?.length > 0 ? schema : null
@@ -222,5 +236,6 @@ async function sqlMapper (app, opts) {
222
236
 
223
237
  module.exports = fp(sqlMapper)
224
238
  module.exports.connect = connect
239
+ module.exports.createConnectionPool = createConnectionPool
225
240
  module.exports.plugin = module.exports
226
241
  module.exports.utils = require('./lib/utils')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "types": "mapper.d.ts",
@@ -19,7 +19,7 @@
19
19
  "snazzy": "^9.0.0",
20
20
  "standard": "^17.1.0",
21
21
  "tap": "^16.3.6",
22
- "tsd": "^0.28.1"
22
+ "tsd": "^0.29.0"
23
23
  },
24
24
  "dependencies": {
25
25
  "@databases/mysql": "^6.0.0",
@@ -30,7 +30,7 @@
30
30
  "camelcase": "^6.3.0",
31
31
  "fastify-plugin": "^4.5.0",
32
32
  "inflected": "^2.1.0",
33
- "@platformatic/types": "0.40.0"
33
+ "@platformatic/types": "0.41.0"
34
34
  },
35
35
  "tsd": {
36
36
  "directory": "test/types"
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const { clear, connInfo, isSQLite } = require('./helper')
5
+ const { createConnectionPool } = require('..')
6
+ const fakeLogger = {
7
+ trace: () => {},
8
+ error: () => {}
9
+ }
10
+
11
+ test('createConnectionPool', async ({ equal, same, teardown, rejects }) => {
12
+ const { db, sql } = await createConnectionPool({
13
+ connectionString: connInfo.connectionString,
14
+ log: fakeLogger
15
+ })
16
+ await clear(db, sql)
17
+ teardown(() => db.dispose())
18
+ if (isSQLite) {
19
+ await db.query(sql`CREATE TABLE pages (
20
+ id INTEGER PRIMARY KEY,
21
+ the_title VARCHAR(42),
22
+ is_published BOOLEAN NOT NULL
23
+ );`)
24
+ } else {
25
+ await db.query(sql`CREATE TABLE pages (
26
+ id SERIAL PRIMARY KEY,
27
+ the_title VARCHAR(255) NOT NULL,
28
+ is_published BOOLEAN NOT NULL
29
+ );`)
30
+ }
31
+ await db.query(sql`INSERT INTO pages (the_title, is_published) VALUES ('foo', true)`)
32
+
33
+ const res = await db.query(sql`SELECT * FROM pages`)
34
+ same(res, [{
35
+ id: 1,
36
+ the_title: 'foo',
37
+ is_published: true
38
+ }])
39
+ })
@@ -10,9 +10,16 @@ import {
10
10
  Database,
11
11
  SQLMapperPluginInterface,
12
12
  EntityHooks,
13
+ createConnectionPool
13
14
  } from '../../mapper'
14
15
 
15
- const pluginOptions: SQLMapperPluginInterface = await connect({ connectionString: '' })
16
+ const log = {
17
+ trace() {},
18
+ error() {},
19
+ warn() {}
20
+ }
21
+
22
+ const pluginOptions: SQLMapperPluginInterface = await connect({ connectionString: '', log })
16
23
  expectType<Database>(pluginOptions.db)
17
24
  expectType<SQL>(pluginOptions.sql)
18
25
  expectType<{ [entityName: string]: Entity }>(pluginOptions.entities)
@@ -49,17 +56,18 @@ const entityHooks: EntityHooks = {
49
56
  async count(originalCount: typeof entity.count, ...options: Parameters<typeof entity.count>): ReturnType<typeof entity.count> { return 0 },
50
57
  }
51
58
  expectType<EntityHooks>(entityHooks)
52
- expectType<SQLMapperPluginInterface>(await connect({ connectionString: '' }))
53
- expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', autoTimestamp: true }))
54
- expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', hooks: {} }))
59
+ expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', log }))
60
+ expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', autoTimestamp: true, log }))
61
+ expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', hooks: {}, log }))
55
62
  expectType<SQLMapperPluginInterface>(await connect({
56
63
  connectionString: '', hooks: {
57
64
  Page: entityHooks
58
- }
65
+ },
66
+ log
59
67
  }))
60
- expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', ignore: {} }))
68
+ expectType<SQLMapperPluginInterface>(await connect({ connectionString: '', ignore: {}, log }))
61
69
  expectType<SQLMapperPluginInterface>(await connect({
62
- connectionString: '', onDatabaseLoad(db: Database, sql: SQL) {
70
+ connectionString: '', log, onDatabaseLoad(db: Database, sql: SQL) {
63
71
  expectType<(query: SQLQuery) => Promise<any[]>>(db.query)
64
72
  expectType<() => Promise<void>>(db.dispose)
65
73
  expectType<boolean | undefined>(pluginOptions.db.isMySql)
@@ -95,3 +103,5 @@ instance.register((instance) => {
95
103
  })
96
104
 
97
105
  expectType<(str: string) => string>(utils.toSingular)
106
+
107
+ expectType<Promise<{ db: Database, sql: SQL }>>(createConnectionPool({ connectionString: '', log }))
@@ -4,6 +4,7 @@ const { test } = require('tap')
4
4
  const { connect } = require('..')
5
5
  const { clear, connInfo, isSQLite, isMysql } = require('./helper')
6
6
  const { setTimeout } = require('timers/promises')
7
+
7
8
  const fakeLogger = {
8
9
  trace: () => {},
9
10
  error: () => {}
@@ -3,12 +3,13 @@
3
3
  const { test } = require('tap')
4
4
  const { connect } = require('..')
5
5
  const { clear, connInfo, isMysql, isSQLite } = require('./helper')
6
+
6
7
  const fakeLogger = {
7
8
  trace: () => {},
8
9
  error: () => {}
9
10
  }
10
11
 
11
- test('list', async ({ pass, teardown, same, equal }) => {
12
+ test('list', async ({ pass, teardown, same, rejects }) => {
12
13
  const mapper = await connect({
13
14
  ...connInfo,
14
15
  log: fakeLogger,
@@ -60,6 +61,8 @@ test('list', async ({ pass, teardown, same, equal }) => {
60
61
  inputs: posts
61
62
  })
62
63
 
64
+ rejects(entity.find.bind(entity, { where: { invalidField: { eq: 'Dog' } } }), 'Unknown field invalidField')
65
+
63
66
  same(await entity.find({ where: { title: { eq: 'Dog' } }, fields: ['id', 'title', 'longText'] }), [{
64
67
  id: '1',
65
68
  title: 'Dog',