@platformatic/sql-mapper 3.4.1 → 3.5.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @platformatic/sql-mapper
2
2
 
3
- Check out the full documentation on [our website](https://docs.platformatic.dev/docs/packages/sql-mapper/fastify-plugin/#sql-mapper-fastify-plugin).
3
+ Check out the full documentation on [our website](https://docs.platformatic.dev/docs/reference/sql-mapper/overview).
4
4
 
5
5
  ## Install
6
6
 
package/eslint.config.js CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict'
1
+ import neostandard from 'neostandard'
2
2
 
3
- module.exports = require('neostandard')({ ts: true })
3
+ export default neostandard({ ts: true })
@@ -69,6 +69,12 @@ export interface DBEntityField {
69
69
  autoTimestamp?: boolean
70
70
  }
71
71
 
72
+ export type WhereClause = {
73
+ or?: WhereCondition[];
74
+ } | {
75
+ [columnName: string]: WhereCondition[string];
76
+ }
77
+
72
78
  export interface WhereCondition {
73
79
  [columnName: string]: {
74
80
  /**
@@ -134,12 +140,16 @@ export interface WhereCondition {
134
140
  }
135
141
  }
136
142
 
143
+ export type Cursor = {
144
+ [columnName: string]: string | number | boolean | null,
145
+ }
146
+
137
147
  interface Find<EntityFields> {
138
148
  (options?: {
139
149
  /**
140
150
  * SQL where condition.
141
151
  */
142
- where?: WhereCondition,
152
+ where?: WhereClause,
143
153
  /**
144
154
  * List of fields to be returned for each object
145
155
  */
@@ -156,10 +166,29 @@ interface Find<EntityFields> {
156
166
  * Number of entities to skip.
157
167
  */
158
168
  offset?: number,
169
+ /**
170
+ * If false pagination is disabled.
171
+ * @default true
172
+ */
173
+ paginate?: boolean,
174
+ /**
175
+ * Cursor to paginate the results.
176
+ */
177
+ cursor?: Cursor,
178
+ /**
179
+ * If set to false, the previous page will be fetched in cursor pagination.
180
+ * @default true
181
+ */
182
+ nextPage?: boolean,
159
183
  /**
160
184
  * If present, the entity participates in transaction
161
185
  */
162
186
  tx?: Database
187
+ /**
188
+ * Passing this to all sql-mapper functions allow to apply
189
+ * authorization rules to the database queries (amongst other things).
190
+ */
191
+ ctx?: PlatformaticContext
163
192
  }): Promise<Partial<EntityFields>[]>
164
193
  }
165
194
 
@@ -168,11 +197,16 @@ interface Count {
168
197
  /**
169
198
  * SQL where condition.
170
199
  */
171
- where?: WhereCondition,
200
+ where?: WhereClause,
172
201
  /**
173
202
  * If present, the entity participates in transaction
174
203
  */
175
- tx?: Database
204
+ tx?: Database,
205
+ /**
206
+ * Passing this to all sql-mapper functions allow to apply
207
+ * authorization rules to the database queries (amongst other things).
208
+ */
209
+ ctx?: PlatformaticContext
176
210
  }): Promise<number>
177
211
  }
178
212
 
@@ -189,7 +223,12 @@ interface Insert<EntityFields> {
189
223
  /**
190
224
  * If present, the entity participates in transaction
191
225
  */
192
- tx?: Database
226
+ tx?: Database,
227
+ /**
228
+ * Passing this to all sql-mapper functions allow to apply
229
+ * authorization rules to the database queries (amongst other things).
230
+ */
231
+ ctx?: PlatformaticContext
193
232
  }): Promise<Partial<EntityFields>[]>
194
233
  }
195
234
 
@@ -206,7 +245,12 @@ interface Save<EntityFields> {
206
245
  /**
207
246
  * If present, the entity participates in transaction
208
247
  */
209
- tx?: Database
248
+ tx?: Database,
249
+ /**
250
+ * Passing this to all sql-mapper functions allow to apply
251
+ * authorization rules to the database queries (amongst other things).
252
+ */
253
+ ctx?: PlatformaticContext
210
254
  }): Promise<Partial<EntityFields>>
211
255
  }
212
256
 
@@ -215,7 +259,7 @@ interface Delete<EntityFields> {
215
259
  /**
216
260
  * SQL where condition.
217
261
  */
218
- where?: WhereCondition,
262
+ where?: WhereClause,
219
263
  /**
220
264
  * List of fields to be returned for each object
221
265
  */
@@ -223,7 +267,12 @@ interface Delete<EntityFields> {
223
267
  /**
224
268
  * If present, the entity participates in transaction
225
269
  */
226
- tx?: Database
270
+ tx?: Database,
271
+ /**
272
+ * Passing this to all sql-mapper functions allow to apply
273
+ * authorization rules to the database queries (amongst other things).
274
+ */
275
+ ctx?: PlatformaticContext
227
276
  }): Promise<Partial<EntityFields>[]>,
228
277
  }
229
278
 
@@ -232,11 +281,11 @@ interface UpdateMany<EntityFields> {
232
281
  /**
233
282
  * SQL where condition.
234
283
  */
235
- where: WhereCondition,
284
+ where?: WhereClause,
236
285
  /**
237
286
  * Entity fields to update.
238
287
  */
239
- input: EntityFields,
288
+ input: Partial<EntityFields>,
240
289
  /**
241
290
  * List of fields to be returned for each object
242
291
  */
@@ -244,7 +293,12 @@ interface UpdateMany<EntityFields> {
244
293
  /**
245
294
  * If present, the entity participates in transaction
246
295
  */
247
- tx?: Database
296
+ tx?: Database,
297
+ /**
298
+ * Passing this to all sql-mapper functions allow to apply
299
+ * authorization rules to the database queries (amongst other things).
300
+ */
301
+ ctx?: PlatformaticContext
248
302
  }): Promise<Partial<EntityFields>[]>
249
303
  }
250
304
 
@@ -265,6 +319,10 @@ export interface Entity<EntityFields = any> {
265
319
  * The primary key of the database entity.
266
320
  */
267
321
  primaryKey: string,
322
+ /**
323
+ * Primary keys of the database entity.
324
+ */
325
+ primaryKeys: Set<string>,
268
326
  /**
269
327
  * The table of the database entity.
270
328
  */
@@ -1,19 +1,31 @@
1
- 'use strict'
2
-
3
- const fp = require('fastify-plugin')
4
- const { findNearestString } = require('@platformatic/utils')
5
- const buildEntity = require('./lib/entity')
6
- const buildCleanUp = require('./lib/clean-up')
7
- const queriesFactory = require('./lib/queries')
8
- const { areSchemasSupported } = require('./lib/utils')
9
- const errors = require('./lib/errors')
10
- const setupCache = require('./lib/cache')
11
- const { setupTelemetry } = require('./lib/telemetry')
12
- const { getConnectionInfo } = require('./lib/connection-info')
1
+ import { findNearestString } from '@platformatic/foundation'
2
+ import fp from 'fastify-plugin'
3
+ import { setupCache } from './lib/cache.js'
4
+ import { buildCleanUp } from './lib/clean-up.js'
5
+ import { getConnectionInfo } from './lib/connection-info.js'
6
+ import { buildEntity } from './lib/entity.js'
7
+ import {
8
+ CannotFindEntityError,
9
+ ConnectionStringRequiredError,
10
+ SpecifyProtocolError,
11
+ TableMustBeAStringError
12
+ } from './lib/errors.js'
13
+ import * as queriesFactory from './lib/queries/index.js'
14
+ import { setupTelemetry } from './lib/telemetry.js'
15
+ import { areSchemasSupported } from './lib/utils.js'
13
16
 
14
17
  // Ignore the function as it is only used only for MySQL and PostgreSQL
15
18
  /* istanbul ignore next */
16
- async function buildConnection (log, createConnectionPool, connectionString, poolSize, schema, idleTimeoutMilliseconds, queueTimeoutMilliseconds, acquireLockTimeoutMilliseconds) {
19
+ async function buildConnection (
20
+ log,
21
+ createConnectionPool,
22
+ connectionString,
23
+ poolSize,
24
+ schema,
25
+ idleTimeoutMilliseconds,
26
+ queueTimeoutMilliseconds,
27
+ acquireLockTimeoutMilliseconds
28
+ ) {
17
29
  const db = await createConnectionPool({
18
30
  connectionString,
19
31
  bigIntMode: 'string',
@@ -22,40 +34,56 @@ async function buildConnection (log, createConnectionPool, connectionString, poo
22
34
  queueTimeoutMilliseconds,
23
35
  acquireLockTimeoutMilliseconds,
24
36
  onQueryStart: (_query, { text, values }) => {
25
- log.trace({
26
- query: {
27
- text,
28
- values,
37
+ log.trace(
38
+ {
39
+ query: {
40
+ text,
41
+ values
42
+ }
29
43
  },
30
- }, 'start query')
44
+ 'start query'
45
+ )
31
46
  },
32
47
  onQueryResults: (_query, { text }, results) => {
33
- log.trace({
34
- query: {
35
- text,
36
- results: results.length,
48
+ log.trace(
49
+ {
50
+ query: {
51
+ text,
52
+ results: results.length
53
+ }
37
54
  },
38
- }, 'end query')
55
+ 'end query'
56
+ )
39
57
  },
40
58
  onQueryError: (_query, { text }, err) => {
41
- log.error({
42
- query: {
43
- text,
44
- error: err.message,
59
+ log.error(
60
+ {
61
+ query: {
62
+ text,
63
+ error: err.message
64
+ }
45
65
  },
46
- }, 'query error')
66
+ 'query error'
67
+ )
47
68
  },
48
- schema,
69
+ schema
49
70
  })
50
71
  return db
51
72
  }
52
73
 
53
74
  const defaultAutoTimestampFields = {
54
75
  createdAt: 'created_at',
55
- updatedAt: 'updated_at',
76
+ updatedAt: 'updated_at'
56
77
  }
57
78
 
58
- async function createConnectionPool ({ log, connectionString, poolSize, idleTimeoutMilliseconds, queueTimeoutMilliseconds, acquireLockTimeoutMilliseconds }) {
79
+ export async function createConnectionPool ({
80
+ log,
81
+ connectionString,
82
+ poolSize,
83
+ idleTimeoutMilliseconds,
84
+ queueTimeoutMilliseconds,
85
+ acquireLockTimeoutMilliseconds
86
+ }) {
59
87
  let db
60
88
  let sql
61
89
 
@@ -63,13 +91,31 @@ async function createConnectionPool ({ log, connectionString, poolSize, idleTime
63
91
 
64
92
  /* istanbul ignore next */
65
93
  if (connectionString.indexOf('postgres') === 0) {
66
- const createConnectionPoolPg = require('@databases/pg')
67
- db = await buildConnection(log, createConnectionPoolPg, connectionString, poolSize, null, idleTimeoutMilliseconds, queueTimeoutMilliseconds, acquireLockTimeoutMilliseconds)
94
+ const { default: createConnectionPoolPg } = await import('@databases/pg')
95
+ db = await buildConnection(
96
+ log,
97
+ createConnectionPoolPg,
98
+ connectionString,
99
+ poolSize,
100
+ null,
101
+ idleTimeoutMilliseconds,
102
+ queueTimeoutMilliseconds,
103
+ acquireLockTimeoutMilliseconds
104
+ )
68
105
  sql = createConnectionPoolPg.sql
69
106
  db.isPg = true
70
107
  } else if (connectionString.indexOf('mysql') === 0) {
71
- const createConnectionPoolMysql = require('@databases/mysql')
72
- db = await buildConnection(log, createConnectionPoolMysql, connectionString, poolSize, null, idleTimeoutMilliseconds, queueTimeoutMilliseconds, acquireLockTimeoutMilliseconds)
108
+ const { default: createConnectionPoolMysql } = await import('@databases/mysql')
109
+ db = await buildConnection(
110
+ log,
111
+ createConnectionPoolMysql,
112
+ connectionString,
113
+ poolSize,
114
+ null,
115
+ idleTimeoutMilliseconds,
116
+ queueTimeoutMilliseconds,
117
+ acquireLockTimeoutMilliseconds
118
+ )
73
119
  sql = createConnectionPoolMysql.sql
74
120
  const version = (await db.query(sql`SELECT VERSION()`))[0]['VERSION()']
75
121
  db.version = version
@@ -78,27 +124,34 @@ async function createConnectionPool ({ log, connectionString, poolSize, idleTime
78
124
  db.isMySql = true
79
125
  }
80
126
  } else if (connectionString.indexOf('sqlite') === 0) {
81
- const sqlite = require('@matteo.collina/sqlite-pool')
127
+ const { default: sqlite } = await import('@matteo.collina/sqlite-pool')
82
128
  const path = connectionString.replace('sqlite://', '')
83
- db = sqlite.default(connectionString === 'sqlite://:memory:' ? undefined : path, {}, {
84
- // TODO make this configurable
85
- maxSize: 1,
86
- // TODO make this configurable
87
- // 10s max time to wait for a connection
88
- releaseTimeoutMilliseconds: 10000,
89
- onQuery ({ text, values }) {
90
- log.trace({
91
- query: {
92
- text,
93
- },
94
- }, 'query')
95
- },
96
- })
129
+ db = sqlite.default(
130
+ connectionString === 'sqlite://:memory:' ? undefined : path,
131
+ {},
132
+ {
133
+ // TODO make this configurable
134
+ maxSize: 1,
135
+ // TODO make this configurable
136
+ // 10s max time to wait for a connection
137
+ releaseTimeoutMilliseconds: 10000,
138
+ onQuery ({ text, values }) {
139
+ log.trace(
140
+ {
141
+ query: {
142
+ text
143
+ }
144
+ },
145
+ 'query'
146
+ )
147
+ }
148
+ }
149
+ )
97
150
  sql = sqlite.sql
98
151
  db.isSQLite = true
99
152
  db.sql = sql
100
153
  } else {
101
- throw new errors.SpecifyProtocolError()
154
+ throw new SpecifyProtocolError()
102
155
  }
103
156
 
104
157
  // These info are necessary for telemetry attributes
@@ -108,17 +161,40 @@ async function createConnectionPool ({ log, connectionString, poolSize, idleTime
108
161
  return { db, sql }
109
162
  }
110
163
 
111
- async function connect ({ connectionString, log, onDatabaseLoad, poolSize, include = {}, ignore = {}, autoTimestamp = true, hooks = {}, schema, limit = {}, dbschema, cache, idleTimeoutMilliseconds, queueTimeoutMilliseconds, acquireLockTimeoutMilliseconds }) {
164
+ export async function connect ({
165
+ connectionString,
166
+ log,
167
+ onDatabaseLoad,
168
+ poolSize,
169
+ include = {},
170
+ ignore = {},
171
+ autoTimestamp = true,
172
+ hooks = {},
173
+ schema,
174
+ limit = {},
175
+ dbschema,
176
+ cache,
177
+ idleTimeoutMilliseconds,
178
+ queueTimeoutMilliseconds,
179
+ acquireLockTimeoutMilliseconds
180
+ }) {
112
181
  if (typeof autoTimestamp === 'boolean' && autoTimestamp === true) {
113
182
  autoTimestamp = defaultAutoTimestampFields
114
183
  }
115
184
  // TODO validate config using the schema
116
185
  if (!connectionString) {
117
- throw new errors.ConnectionStringRequiredError()
186
+ throw new ConnectionStringRequiredError()
118
187
  }
119
188
 
120
189
  let queries
121
- const { db, sql } = await createConnectionPool({ log, connectionString, poolSize, queueTimeoutMilliseconds, acquireLockTimeoutMilliseconds, idleTimeoutMilliseconds })
190
+ const { db, sql } = await createConnectionPool({
191
+ log,
192
+ connectionString,
193
+ poolSize,
194
+ queueTimeoutMilliseconds,
195
+ acquireLockTimeoutMilliseconds,
196
+ idleTimeoutMilliseconds
197
+ })
122
198
 
123
199
  /* istanbul ignore next */
124
200
  if (db.isPg) {
@@ -200,7 +276,7 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize, inclu
200
276
  // it should never happen.
201
277
  /* istanbul ignore next */
202
278
  if (typeof table !== 'string') {
203
- throw new errors.TableMustBeAStringError(table)
279
+ throw new TableMustBeAStringError(table)
204
280
  }
205
281
  // If include is being used and a table is not explicitly included add it to the ignore object
206
282
  if (Object.keys(include).length && !include[table]) {
@@ -209,7 +285,21 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize, inclu
209
285
  if (ignore[table] === true) {
210
286
  continue
211
287
  }
212
- const entity = buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit, schemaList, columns, constraints)
288
+ const entity = buildEntity(
289
+ db,
290
+ sql,
291
+ log,
292
+ table,
293
+ queries,
294
+ autoTimestamp,
295
+ schema,
296
+ useSchema,
297
+ ignore[table] || {},
298
+ limit,
299
+ schemaList,
300
+ columns,
301
+ constraints
302
+ )
213
303
  // Check for primary key of all entities
214
304
  if (entity.primaryKeys.size === 0) {
215
305
  log.warn({ table }, 'Cannot find any primary keys for table')
@@ -231,7 +321,7 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize, inclu
231
321
  entities,
232
322
  cleanUpAllEntities: buildCleanUp(db, sql, log, entities, queries),
233
323
  addEntityHooks,
234
- dbschema,
324
+ dbschema
235
325
  }
236
326
 
237
327
  if (cache) {
@@ -247,7 +337,7 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize, inclu
247
337
  function addEntityHooks (entityName, hooks) {
248
338
  const entity = entities[entityName]
249
339
  if (!entity) {
250
- throw new errors.CannotFindEntityError(entityName)
340
+ throw new CannotFindEntityError(entityName)
251
341
  }
252
342
  for (const key of Object.keys(hooks)) {
253
343
  if (hooks[key] && entity[key]) {
@@ -260,7 +350,7 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize, inclu
260
350
  async function sqlMapper (app, opts) {
261
351
  const mapper = await connect({
262
352
  log: app.log,
263
- ...opts,
353
+ ...opts
264
354
  })
265
355
 
266
356
  app.onClose(() => mapper.db.dispose())
@@ -276,7 +366,7 @@ async function sqlMapper (app, opts) {
276
366
  app.addHook('onRequest', function (req, reply, done) {
277
367
  req.platformaticContext = {
278
368
  app: this, // uses the encapsulated fastify instance of the route
279
- reply,
369
+ reply
280
370
  }
281
371
  done()
282
372
  })
@@ -300,7 +390,7 @@ async function dropTable (db, sql, table) {
300
390
  }
301
391
  }
302
392
 
303
- async function dropAllTables (db, sql, schemas) {
393
+ export async function dropAllTables (db, sql, schemas) {
304
394
  let queries
305
395
  /* istanbul ignore next */
306
396
  if (db.isPg) {
@@ -313,13 +403,15 @@ async function dropAllTables (db, sql, schemas) {
313
403
  queries = queriesFactory.sqlite
314
404
  }
315
405
 
316
- const tables = new Set((await queries.listTables(db, sql, schemas)).map((t) => {
317
- /* istanbul ignore next */
318
- if (t.schema) {
319
- return `${t.schema}.${t.table}`
320
- }
321
- return t.table
322
- }))
406
+ const tables = new Set(
407
+ (await queries.listTables(db, sql, schemas)).map(t => {
408
+ /* istanbul ignore next */
409
+ if (t.schema) {
410
+ return `${t.schema}.${t.table}`
411
+ }
412
+ return t.table
413
+ })
414
+ )
323
415
  let count = 0
324
416
 
325
417
  while (tables.size > 0) {
@@ -341,10 +433,7 @@ async function dropAllTables (db, sql, schemas) {
341
433
  }
342
434
  }
343
435
 
344
- module.exports = fp(sqlMapper)
345
- module.exports.connect = connect
346
- module.exports.createConnectionPool = createConnectionPool
347
- module.exports.plugin = module.exports
348
- module.exports.utils = require('./lib/utils')
349
- module.exports.errors = errors
350
- module.exports.dropAllTables = dropAllTables
436
+ export const plugin = fp(sqlMapper)
437
+ export default plugin
438
+ export * as errors from './lib/errors.js'
439
+ export * as utils from './lib/utils.js'
package/lib/cache.js CHANGED
@@ -1,7 +1,6 @@
1
- 'use strict'
2
- const { createCache } = require('async-cache-dedupe')
1
+ import { createCache } from 'async-cache-dedupe'
3
2
 
4
- function setupCache (res, opts) {
3
+ export function setupCache (res, opts) {
5
4
  // TODO validate opts
6
5
  if (opts === true) {
7
6
  opts = { ttl: 0 }
@@ -17,18 +16,22 @@ function setupCache (res, opts) {
17
16
  const fnName = `${entity.name}Find`
18
17
  const originalFn = entity.find
19
18
 
20
- cache.define(fnName, {
21
- serialize (query) {
22
- const serialized = {
23
- ...query,
24
- ctx: undefined,
19
+ cache.define(
20
+ fnName,
21
+ {
22
+ serialize (query) {
23
+ const serialized = {
24
+ ...query,
25
+ ctx: undefined
26
+ }
27
+ return serialized
25
28
  }
26
- return serialized
27
29
  },
28
- }, async function (query) {
29
- const res = await originalFn.call(entity, query)
30
- return res
31
- })
30
+ async function (query) {
31
+ const res = await originalFn.call(entity, query)
32
+ return res
33
+ }
34
+ )
32
35
 
33
36
  addEntityHooks(entity.singularName, {
34
37
  find (originalFn, query) {
@@ -36,11 +39,9 @@ function setupCache (res, opts) {
36
39
  return originalFn(query)
37
40
  }
38
41
  return cache[fnName](query)
39
- },
42
+ }
40
43
  })
41
44
  }
42
45
 
43
46
  return cache
44
47
  }
45
-
46
- module.exports = setupCache
package/lib/clean-up.js CHANGED
@@ -1,9 +1,7 @@
1
- 'use strict'
1
+ import { Sorter } from '@hapi/topo'
2
+ import { tableName } from './utils.js'
2
3
 
3
- const { tableName } = require('./utils')
4
- const { Sorter } = require('@hapi/topo')
5
-
6
- function buildCleanUp (db, sql, logger, entities) {
4
+ export function buildCleanUp (db, sql, logger, entities) {
7
5
  return async function cleanUp () {
8
6
  logger.trace('cleaning up')
9
7
  await db.tx(async tx => {
@@ -32,5 +30,3 @@ function buildCleanUp (db, sql, logger, entities) {
32
30
  })
33
31
  }
34
32
  }
35
-
36
- module.exports = buildCleanUp
@@ -1,7 +1,7 @@
1
1
  // The most general way to get the connection info is through the driver.
2
2
  // In this way, we don't need to do any assumptions about the connection string
3
3
  // (with the exception of SQLite)
4
- const getConnectionInfo = async (db, connectionString) => {
4
+ export async function getConnectionInfo (db, connectionString) {
5
5
  let database, host, port, user
6
6
  let dbSystem = 'unknown'
7
7
  if (db.isPg) {
@@ -35,8 +35,6 @@ const getConnectionInfo = async (db, connectionString) => {
35
35
  isPg: !!db.isPg,
36
36
  isMySql: !!db.isMySql,
37
37
  isSQLite: !!db.isSQLite,
38
- dbSystem,
38
+ dbSystem
39
39
  }
40
40
  }
41
-
42
- module.exports = { getConnectionInfo }