@jcbuisson/express-x 1.7.11 → 1.7.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcbuisson/express-x",
3
- "version": "1.7.11",
3
+ "version": "1.7.13",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -12,14 +12,12 @@
12
12
  "license": "MIT",
13
13
  "private": false,
14
14
  "scripts": {
15
- "test": "node --test",
16
15
  "generate": "npx prisma generate",
17
16
  "migrate": "npx prisma migrate dev --name init"
18
17
  },
19
18
  "keywords": [],
20
19
  "dependencies": {
21
20
  "@prisma/client": "^4.10.1",
22
- "axios": "^1.4.0",
23
21
  "bcryptjs": "^2.4.3",
24
22
  "config": "^3.3.9",
25
23
  "express": "^4.18.2",
@@ -4,14 +4,20 @@ import bcrypt from 'bcryptjs'
4
4
  import { getConnectionDataItem } from './context.mjs'
5
5
 
6
6
 
7
- // hash password of user record
7
+ /*
8
+ * Hash password of user record
9
+ * To be used as an 'after' hook for users service methods
10
+ */
8
11
  export const hashPassword = (passwordField) => async (context) => {
9
- // context.result is a user
10
- context.result[passwordField] = await bcrypt.hash(context.result[passwordField], 5)
12
+ const user = context.result
13
+ user[passwordField] = await bcrypt.hash(user[passwordField], 5)
11
14
  return context
12
15
  }
13
16
 
14
- // remove `field` from `result`
17
+
18
+ /*
19
+ * Remove `field` from `context.result`
20
+ */
15
21
  export function protect(field) {
16
22
  return async (context) => {
17
23
  if (Array.isArray(context.result)) {
@@ -25,13 +31,11 @@ export function protect(field) {
25
31
  }
26
32
  }
27
33
 
28
-
29
- export async function isAuthenticated(context) {
30
- // extract sessionId from connection data
31
- const sessionId = await getConnectionDataItem(context, 'sessionId')
32
- if (!sessionId) throw new Error('not-authenticated')
33
- }
34
-
34
+ /*
35
+ * Check if the 'expireAt' key in connection data is met
36
+ * If it is met, throw an error (which will be sent back to the calling server or client) and reset connection data
37
+ * If not, do nothing. If needed, an application-level hook may automatically extend the expiration data at each service call
38
+ */
35
39
  export const isNotExpired = async (context) => {
36
40
  const expireAt = await getConnectionDataItem(context, 'expireAt')
37
41
  if (expireAt) {
package/src/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { expressX } from './server.mjs'
3
- import { hashPassword, protect, isAuthenticated, isNotExpired } from './common-hooks.mjs'
3
+ import { hashPassword, protect, isNotExpired } from './common-hooks.mjs'
4
4
  import { getContextConnection, resetConnection, getConnectionDataItem, setConnectionDataItem, removeConnectionDataItem, sendServiceEventToClient } from './context.mjs'
5
5
 
6
6
  export {
@@ -17,6 +17,5 @@ export {
17
17
 
18
18
  hashPassword,
19
19
  protect,
20
- isAuthenticated,
21
20
  isNotExpired,
22
21
  }
package/src/server.mjs CHANGED
@@ -97,12 +97,13 @@ export function expressX(prisma, config) {
97
97
  // `context` is the context of execution (transport type, connection, app)
98
98
  // `args` is the list of arguments of the method
99
99
  service['__' + methodName] = async (context, ...args) => {
100
+ // put args into context
100
101
  context.args = args
101
102
 
102
103
  // if a hook or the method throws an error, it will be caught by `socket.on('client-request'` (ws)
103
104
  // or by express (http) and the client will get a rejected promise
104
105
 
105
- // call 'before' hooks, modifying `context`
106
+ // call 'before' hooks, possibly modifying `context`
106
107
  const beforeAppHooks = appHooks?.before || []
107
108
  const beforeMethodHooks = service?.hooks?.before && service.hooks.before[methodName] || []
108
109
  const beforeAllHooks = service?.hooks?.before?.all || []
@@ -115,7 +116,7 @@ export function expressX(prisma, config) {
115
116
  // put result into context
116
117
  context.result = result
117
118
 
118
- // call 'after' hooks, modifying `context`
119
+ // call 'after' hooks, possibly modifying `context`
119
120
  const afterMethodHooks = service?.hooks?.after && service.hooks.after[methodName] || []
120
121
  const afterAllHooks = service?.hooks?.after?.all || []
121
122
  const afterAppHooks = appHooks?.after || []
@@ -201,16 +202,17 @@ export function expressX(prisma, config) {
201
202
  }
202
203
 
203
204
  // introspect schema and return a map: field name => prisma type
204
- async function getTypesMap() {
205
- const dmmf = await service.prisma._getDmmf()
206
- const fieldDescriptions = dmmf.modelMap[service.name].fields
205
+ function getTypesMap() {
206
+ // const dmmf = await service.prisma._getDmmf()
207
+ // const fieldDescriptions = dmmf.modelMap[service.name].fields
208
+ const dmmf = service.prisma._runtimeDataModel
209
+ const fieldDescriptions = dmmf.models[service.name].fields
207
210
  return fieldDescriptions.reduce((accu, descr) => {
208
211
  accu[descr.name] = descr.type
209
212
  return accu
210
213
  }, {})
211
214
  }
212
215
 
213
-
214
216
  app.post(path, async (req, res) => {
215
217
  app.log('verbose', `http request POST ${req.url}`)
216
218
  context.params.req = req
@@ -231,7 +233,7 @@ export function expressX(prisma, config) {
231
233
  // the values in `req.query` are all strings, but Prisma need proper types
232
234
  // we need to introspect column types and do the proper transtyping
233
235
  for (const fieldName in query) {
234
- const typesDict = await getTypesMap()
236
+ const typesDict = getTypesMap()
235
237
  const fieldType = typesDict[fieldName]
236
238
 
237
239
  if (fieldType === 'Int') {
@@ -332,7 +334,6 @@ export function expressX(prisma, config) {
332
334
  setSocket(connection.id, socket)
333
335
 
334
336
  // emit 'connection' event for app (expressjs extends EventEmitter)
335
- console.log('EMIT CONNECTION')
336
337
  app.emit('connection', connection)
337
338
 
338
339
  // send 'connected' event to client
@@ -1,22 +0,0 @@
1
- generator client {
2
- provider = "prisma-client-js"
3
- }
4
-
5
- model User {
6
- id Int @default(autoincrement()) @id
7
- name String
8
- email String @unique
9
- posts Post[]
10
- }
11
-
12
- model Post {
13
- id Int @default(autoincrement()) @id
14
- text String
15
- author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
16
- authorId Int
17
- }
18
-
19
- datasource db {
20
- provider = "sqlite"
21
- url = "file:./dev.db"
22
- }
@@ -1,169 +0,0 @@
1
-
2
- import bodyParser from 'body-parser'
3
- import axios from 'axios'
4
- import { PrismaClient } from '@prisma/client'
5
-
6
-
7
- import { describe, it, before, after, beforeEach, afterEach } from 'node:test'
8
- import { strict as assert } from 'node:assert'
9
-
10
- import { expressX } from '../src/index.mjs'
11
-
12
- const prisma = new PrismaClient()
13
-
14
- // `app` is a regular express application, enhanced with services and real-time features
15
- const app = expressX(prisma)
16
-
17
-
18
-
19
- describe('ExpressX API (no running server)', () => {
20
-
21
- before(async () => {
22
- app.createDatabaseService('User')
23
- app.createDatabaseService('Post')
24
-
25
- await app.service('User').deleteMany()
26
- await app.service('Post').deleteMany()
27
- })
28
-
29
- it("can delete all users", async () => {
30
- const res = await app.service('User').deleteMany()
31
- console.log('res delete', res)
32
- assert(res.count >= 0)
33
- })
34
-
35
- it("can create a user", async () => {
36
- const user = await app.service('User').create({
37
- data: {
38
- name: "chris",
39
- email: 'chris@mail.fr'
40
- },
41
- })
42
- assert(user.name === 'chris')
43
- })
44
-
45
- it("can find a user by name", async () => {
46
- const users = await app.service('User').findMany({
47
- where: {
48
- name: {
49
- startsWith: "ch"
50
- }
51
- }
52
- })
53
- assert(users.length > 0)
54
- })
55
-
56
- it("can find a unique user by email", async () => {
57
- const chris = await app.service('User').findUnique({
58
- where: {
59
- email: "chris@mail.fr"
60
- }
61
- })
62
- assert(chris.name === 'chris')
63
- })
64
- })
65
-
66
-
67
- describe('HTTP/REST API', () => {
68
-
69
- let chris
70
-
71
- before(async () => {
72
- console.log("before")
73
- // add body parsers for http requests
74
- app.use(bodyParser.json())
75
- app.use(bodyParser.urlencoded({ extended: false }))
76
-
77
- app.createDatabaseService('User')
78
- app.createDatabaseService('Post')
79
-
80
- await app.service('User').deleteMany()
81
- await app.service('Post').deleteMany()
82
-
83
- // add http/rest endpoints
84
- app.addHttpRest('/api/user', app.service('User'))
85
- app.addHttpRest('/api/post', app.service('Post'))
86
-
87
- await new Promise((resolve) => {
88
- app.server.listen(8008, () => {
89
- console.log(`App listening at http://localhost:8008`)
90
- resolve()
91
- })
92
- })
93
- })
94
-
95
- after(() => {
96
- console.log("after")
97
- app.server.close()
98
- })
99
-
100
- it("can create a user", async () => {
101
- const res = await axios.post('http://localhost:8008/api/user', {
102
- name: "chris",
103
- email: 'chris@mail.fr'
104
- })
105
- assert(res?.data?.name === 'chris')
106
- })
107
-
108
- it("can find users", async () => {
109
- const res = await axios.get('http://localhost:8008/api/user')
110
- assert(res?.data?.length > 0)
111
- })
112
-
113
- it("can find a user by name", async () => {
114
- const res = await axios.get('http://localhost:8008/api/user?name=chris')
115
- assert(res?.data?.length > 0)
116
- chris = res.data[0]
117
- })
118
-
119
- it("can find a user by id", async () => {
120
- const res = await axios.get(`http://localhost:8008/api/user/${chris.id}`)
121
- assert(res?.data?.id === chris.id)
122
- })
123
-
124
- it("can patch a user", async () => {
125
- const res = await axios.patch(`http://localhost:8008/api/user/${chris.id}`, {
126
- name: "Christophe",
127
- })
128
- assert(res?.data?.name === "Christophe")
129
- })
130
-
131
- it("can delete a user", async () => {
132
- const res = await axios.delete(`http://localhost:8008/api/user/${chris.id}`)
133
- assert(res?.data?.name === "Christophe")
134
- })
135
-
136
- after(async () => {
137
- app.server.close()
138
- })
139
- })
140
-
141
-
142
- describe('Error codes', () => {
143
-
144
- before(async () => {
145
- app.createDatabaseService('User')
146
- app.createDatabaseService('Post')
147
-
148
- await app.service('User').deleteMany()
149
- await app.service('Post').deleteMany()
150
- })
151
-
152
- it("can detect 'missing-service' error", async () => {
153
- try {
154
- await app.service('users').findMany({})
155
- } catch(err) {
156
- assert(err.code === 'missing-service')
157
- }
158
- })
159
-
160
- // it("can detect 'missing-method' error", async () => {
161
- // try {
162
- // await app.service('User').dummyMethod({})
163
- // } catch(err) {
164
- // console.log('err', err)
165
- // assert(err.code === 'missing-method')
166
- // }
167
- // })
168
-
169
- })