@jcbuisson/express-x 1.7.10 → 1.7.12

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.10",
3
+ "version": "1.7.12",
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 || []
@@ -125,7 +126,7 @@ export function expressX(prisma, config) {
125
126
 
126
127
  // publish event (websocket transport)
127
128
  if (config.WS_TRANSPORT && service.publishFunction) {
128
- const channelNames = await service.publishFunction(result, methodName)
129
+ const channelNames = await service.publishFunction(context)
129
130
  app.log('verbose', `publish channels ${service.name} ${methodName} ${channelNames}`)
130
131
  const connections = await app.prisma.Connection.findMany({})
131
132
  for (const channelName of channelNames) {
@@ -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
- })