@jcbuisson/express-x 1.3.3 → 1.3.5
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 +2 -7
- package/src/server.mjs +36 -49
- package/test/index.test.js +46 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jcbuisson/express-x",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"private": false,
|
|
14
14
|
"scripts": {
|
|
15
|
-
"test": "
|
|
15
|
+
"test": "node --test",
|
|
16
16
|
"generate": "npx prisma generate",
|
|
17
17
|
"migrate": "npx prisma migrate dev --name init"
|
|
18
18
|
},
|
|
@@ -23,12 +23,7 @@
|
|
|
23
23
|
"axios": "^1.4.0",
|
|
24
24
|
"bcrypt": "^5.1.0",
|
|
25
25
|
"config": "^3.3.9",
|
|
26
|
-
"esm": "^3.2.25",
|
|
27
26
|
"express": "^4.18.2",
|
|
28
27
|
"socket.io": "^4.6.0"
|
|
29
|
-
},
|
|
30
|
-
"devDependencies": {
|
|
31
|
-
"chai": "^4.3.7",
|
|
32
|
-
"mocha": "^10.2.0"
|
|
33
28
|
}
|
|
34
29
|
}
|
package/src/server.mjs
CHANGED
|
@@ -19,9 +19,9 @@ export function expressX(options = {}) {
|
|
|
19
19
|
let lastConnectionId = 1
|
|
20
20
|
|
|
21
21
|
// logging function - a winston logger must be configured first
|
|
22
|
-
app.log = (severity,
|
|
22
|
+
app.log = (severity, message) => {
|
|
23
23
|
const logger = app.get('logger')
|
|
24
|
-
if (logger) logger.log(severity,
|
|
24
|
+
if (logger) logger.log(severity, message)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/*
|
|
@@ -74,8 +74,8 @@ export function expressX(options = {}) {
|
|
|
74
74
|
|
|
75
75
|
// call method
|
|
76
76
|
const result = await method(...context.args)
|
|
77
|
-
app.log('debug',
|
|
78
|
-
|
|
77
|
+
app.log('debug', `result ${result}`)
|
|
78
|
+
|
|
79
79
|
// call 'after' hooks
|
|
80
80
|
const afterMethodHooks = service?.hooks?.after && service.hooks.after[methodName] || []
|
|
81
81
|
const afterAllHooks = service?.hooks?.after?.all || []
|
|
@@ -127,81 +127,68 @@ export function expressX(options = {}) {
|
|
|
127
127
|
http: { name: service.name }
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
// introspect
|
|
131
|
-
async function
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
fieldTypes[column.name] = column.type.toLowerCase()
|
|
139
|
-
})
|
|
140
|
-
} else if (service.prisma._activeProvider === 'postgresql') {
|
|
141
|
-
const fieldInfo = await service.prisma.$queryRawUnsafe(`
|
|
142
|
-
SELECT column_name, data_type
|
|
143
|
-
FROM information_schema.columns
|
|
144
|
-
WHERE table_name = '${service.entity}';
|
|
145
|
-
`)
|
|
146
|
-
fieldInfo.forEach(column => {
|
|
147
|
-
fieldTypes[column.column_name] = column.data_type
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
return fieldTypes
|
|
130
|
+
// introspect schema
|
|
131
|
+
async function getTypesMap() {
|
|
132
|
+
const dmmf = await service.prisma._getDmmf()
|
|
133
|
+
const fieldDescriptions = dmmf.modelMap[service.name].fields
|
|
134
|
+
return fieldDescriptions.reduce((accu, descr) => {
|
|
135
|
+
accu[descr.name] = descr.type
|
|
136
|
+
return accu
|
|
137
|
+
}, {})
|
|
151
138
|
}
|
|
152
139
|
|
|
153
140
|
|
|
154
141
|
app.post(path, async (req, res) => {
|
|
155
|
-
app.log('verbose',
|
|
142
|
+
app.log('verbose', `http request POST ${req.url}`)
|
|
156
143
|
context.http.req = req
|
|
157
144
|
try {
|
|
158
145
|
const value = await service.__create(context, { data: req.body })
|
|
159
146
|
publish(service, 'create', value)
|
|
160
147
|
res.json(value)
|
|
161
148
|
} catch(err) {
|
|
162
|
-
|
|
149
|
+
app.log('error', err)
|
|
163
150
|
res.status(500).send(err.toString())
|
|
164
151
|
}
|
|
165
152
|
})
|
|
166
153
|
|
|
167
154
|
app.get(path, async (req, res) => {
|
|
168
|
-
app.log('verbose',
|
|
155
|
+
app.log('verbose', `http request GET ${req.url}`)
|
|
169
156
|
context.http.req = req
|
|
170
157
|
const query = { ...req.query }
|
|
171
158
|
try {
|
|
172
159
|
// the values in `req.query` are all strings, but Prisma need proper types
|
|
173
160
|
// we need to introspect column types and do the proper transtyping
|
|
174
161
|
for (const fieldName in query) {
|
|
175
|
-
|
|
176
|
-
const fieldType =
|
|
162
|
+
const typesDict = await getTypesMap()
|
|
163
|
+
const fieldType = typesDict[fieldName]
|
|
177
164
|
|
|
178
|
-
if (fieldType === '
|
|
165
|
+
if (fieldType === 'Int') {
|
|
179
166
|
query[fieldName] = parseInt(query[fieldName])
|
|
180
|
-
} else if (fieldType === '
|
|
167
|
+
} else if (fieldType === 'Float') {
|
|
181
168
|
query[fieldName] = parseFloat(query[fieldName])
|
|
182
|
-
} else if (fieldType === '
|
|
169
|
+
} else if (fieldType === 'Boolean') {
|
|
183
170
|
query[fieldName] = (query[fieldName] === 't') ? true : false
|
|
184
|
-
} else if (fieldType === '
|
|
171
|
+
} else if (fieldType === 'String') {
|
|
185
172
|
query[fieldName] = query[fieldName]
|
|
186
173
|
} else {
|
|
187
174
|
// ?
|
|
188
175
|
query[fieldName] = query[fieldName]
|
|
189
176
|
}
|
|
190
177
|
}
|
|
191
|
-
|
|
178
|
+
// call __findMany
|
|
192
179
|
const values = await service.__findMany(context, {
|
|
193
180
|
where: query,
|
|
194
181
|
})
|
|
195
182
|
publish(service, 'findMany', values)
|
|
196
183
|
res.json(values)
|
|
197
184
|
} catch(err) {
|
|
198
|
-
|
|
185
|
+
app.log('error', err)
|
|
199
186
|
res.status(500).send(err.toString())
|
|
200
187
|
}
|
|
201
188
|
})
|
|
202
189
|
|
|
203
190
|
app.get(`${path}/:id`, async (req, res) => {
|
|
204
|
-
app.log('verbose',
|
|
191
|
+
app.log('verbose', `http request GET ${req.url}`)
|
|
205
192
|
context.http.req = req
|
|
206
193
|
try {
|
|
207
194
|
const value = await service.__findUnique(context, {
|
|
@@ -212,13 +199,13 @@ export function expressX(options = {}) {
|
|
|
212
199
|
publish(service, 'findUnique', value)
|
|
213
200
|
res.json(value)
|
|
214
201
|
} catch(err) {
|
|
215
|
-
|
|
202
|
+
app.log('error', err)
|
|
216
203
|
res.status(500).send(err.toString())
|
|
217
204
|
}
|
|
218
205
|
})
|
|
219
206
|
|
|
220
207
|
app.patch(`${path}/:id`, async (req, res) => {
|
|
221
|
-
app.log('verbose',
|
|
208
|
+
app.log('verbose', `http request PATCH ${req.url}`)
|
|
222
209
|
context.http.req = req
|
|
223
210
|
try {
|
|
224
211
|
const value = await service.__update(context, {
|
|
@@ -230,13 +217,13 @@ export function expressX(options = {}) {
|
|
|
230
217
|
publish(service, 'update', value)
|
|
231
218
|
res.json(value)
|
|
232
219
|
} catch(err) {
|
|
233
|
-
|
|
220
|
+
app.log('error', err)
|
|
234
221
|
res.status(500).send(err.toString())
|
|
235
222
|
}
|
|
236
223
|
})
|
|
237
224
|
|
|
238
225
|
app.delete(`${path}/:id`, async (req, res) => {
|
|
239
|
-
app.log('verbose',
|
|
226
|
+
app.log('verbose', `http request DELETE ${req.url}`)
|
|
240
227
|
context.http.req = req
|
|
241
228
|
try {
|
|
242
229
|
const value = await service.__delete(context, {
|
|
@@ -247,7 +234,7 @@ export function expressX(options = {}) {
|
|
|
247
234
|
publish(service, 'delete', value)
|
|
248
235
|
res.json(value)
|
|
249
236
|
} catch(err) {
|
|
250
|
-
|
|
237
|
+
app.log('error', err)
|
|
251
238
|
res.status(500).send(err.toString())
|
|
252
239
|
}
|
|
253
240
|
})
|
|
@@ -275,7 +262,7 @@ export function expressX(options = {}) {
|
|
|
275
262
|
}
|
|
276
263
|
// store connection in cache
|
|
277
264
|
connections[connection.id] = connection
|
|
278
|
-
app.log('verbose',
|
|
265
|
+
app.log('verbose', `active connections ${Object.keys(connections)}`)
|
|
279
266
|
|
|
280
267
|
// emit 'connection' event for app (expressjs extends EventEmitter)
|
|
281
268
|
app.emit('connection', connection)
|
|
@@ -284,7 +271,7 @@ export function expressX(options = {}) {
|
|
|
284
271
|
socket.emit('connected', connection.id)
|
|
285
272
|
|
|
286
273
|
socket.on('disconnect', () => {
|
|
287
|
-
app.log('verbose',
|
|
274
|
+
app.log('verbose', `Client disconnected ${connection.id}`)
|
|
288
275
|
delete connections[connection.id]
|
|
289
276
|
})
|
|
290
277
|
|
|
@@ -294,7 +281,7 @@ export function expressX(options = {}) {
|
|
|
294
281
|
* Emit in return a 'client-response' message
|
|
295
282
|
*/
|
|
296
283
|
socket.on('client-request', async ({ uid, name, action, args }) => {
|
|
297
|
-
app.log('verbose',
|
|
284
|
+
app.log('verbose', `client-request ${uid} ${name} ${action} ${args}`)
|
|
298
285
|
if (name in services) {
|
|
299
286
|
const service = services[name]
|
|
300
287
|
try {
|
|
@@ -314,7 +301,7 @@ export function expressX(options = {}) {
|
|
|
314
301
|
// pub/sub: send event on associated channels
|
|
315
302
|
publish(service, action, result)
|
|
316
303
|
} catch(err) {
|
|
317
|
-
|
|
304
|
+
app.log('error', err)
|
|
318
305
|
io.emit('client-response', {
|
|
319
306
|
uid,
|
|
320
307
|
error: err.toString(),
|
|
@@ -347,12 +334,12 @@ export function expressX(options = {}) {
|
|
|
347
334
|
const publishFunc = service.publishCallback
|
|
348
335
|
if (publishFunc) {
|
|
349
336
|
const channelNames = await publishFunc(result, app)
|
|
350
|
-
app.log('verbose',
|
|
337
|
+
app.log('verbose', `publish channels ${service.name} ${action} ${channelNames}`)
|
|
351
338
|
for (const channelName of channelNames) {
|
|
352
|
-
app.log('verbose',
|
|
339
|
+
app.log('verbose', `service-event ${service.name} ${action} ${channelName}`)
|
|
353
340
|
const connectionList = Object.values(connections).filter(cnx => cnx.channelNames.has(channelName))
|
|
354
341
|
for (const connection of connectionList) {
|
|
355
|
-
app.log('verbose',
|
|
342
|
+
app.log('verbose', `emit to ${connection.id} ${service.name} ${action} ${result}`)
|
|
356
343
|
connection.socket.emit('service-event', {
|
|
357
344
|
name: service.name,
|
|
358
345
|
action,
|
package/test/index.test.js
CHANGED
|
@@ -4,7 +4,9 @@ import axios from 'axios'
|
|
|
4
4
|
import io from 'socket.io-client'
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
import { assert } from 'chai'
|
|
7
|
+
// import { assert } from 'chai'
|
|
8
|
+
import { describe, it, before, after, beforeEach, afterEach } from 'node:test'
|
|
9
|
+
import { strict as assert } from 'node:assert'
|
|
8
10
|
|
|
9
11
|
import { expressX, expressXClient } from '../src/index.mjs'
|
|
10
12
|
|
|
@@ -12,13 +14,18 @@ import { expressX, expressXClient } from '../src/index.mjs'
|
|
|
12
14
|
// `app` is a regular express application, enhanced with services and real-time features
|
|
13
15
|
const app = expressX()
|
|
14
16
|
|
|
15
|
-
app.createDatabaseService('User')
|
|
16
|
-
app.createDatabaseService('Post')
|
|
17
|
-
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
describe('ExpressX API (no running server)', () => {
|
|
21
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
|
+
|
|
22
29
|
it("can delete all users", async () => {
|
|
23
30
|
const res = await app.service('User').deleteMany()
|
|
24
31
|
console.log('res delete', res)
|
|
@@ -61,24 +68,41 @@ describe('HTTP/REST API', () => {
|
|
|
61
68
|
|
|
62
69
|
let chris
|
|
63
70
|
|
|
64
|
-
before(() => {
|
|
71
|
+
before(async () => {
|
|
72
|
+
console.log("before")
|
|
65
73
|
// add body parsers for http requests
|
|
66
74
|
app.use(bodyParser.json())
|
|
67
75
|
app.use(bodyParser.urlencoded({ extended: false }))
|
|
68
76
|
|
|
77
|
+
app.createDatabaseService('User')
|
|
78
|
+
app.createDatabaseService('Post')
|
|
79
|
+
|
|
80
|
+
await app.service('User').deleteMany()
|
|
81
|
+
await app.service('Post').deleteMany()
|
|
82
|
+
|
|
69
83
|
// add http/rest endpoints
|
|
70
84
|
app.addHttpRest('/api/user', app.service('User'))
|
|
71
85
|
app.addHttpRest('/api/post', app.service('Post'))
|
|
72
86
|
|
|
73
|
-
|
|
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()
|
|
74
98
|
})
|
|
75
99
|
|
|
76
100
|
it("can create a user", async () => {
|
|
77
101
|
const res = await axios.post('http://localhost:8008/api/user', {
|
|
78
|
-
name: "
|
|
79
|
-
email: '
|
|
102
|
+
name: "chris",
|
|
103
|
+
email: 'chris@mail.fr'
|
|
80
104
|
})
|
|
81
|
-
assert(res?.data?.name === '
|
|
105
|
+
assert(res?.data?.name === 'chris')
|
|
82
106
|
})
|
|
83
107
|
|
|
84
108
|
it("can find users", async () => {
|
|
@@ -110,23 +134,32 @@ describe('HTTP/REST API', () => {
|
|
|
110
134
|
})
|
|
111
135
|
|
|
112
136
|
after(async () => {
|
|
113
|
-
|
|
137
|
+
app.server.close()
|
|
114
138
|
})
|
|
115
139
|
})
|
|
116
140
|
|
|
117
141
|
|
|
118
|
-
// test compatibility with
|
|
142
|
+
// test compatibility with client API
|
|
119
143
|
describe('Client API', () => {
|
|
120
144
|
|
|
121
145
|
let clientApp, socket
|
|
122
146
|
|
|
123
|
-
before(() => {
|
|
124
|
-
|
|
147
|
+
before(async () => {
|
|
148
|
+
await new Promise((resolve) => {
|
|
149
|
+
app.server.listen(8008, () => {
|
|
150
|
+
console.log(`App listening at http://localhost:8008`)
|
|
151
|
+
resolve()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
125
154
|
|
|
126
155
|
socket = io('http://localhost:8008', { transports: ["websocket"] })
|
|
127
156
|
clientApp = expressXClient(socket)
|
|
128
157
|
})
|
|
129
158
|
|
|
159
|
+
after(async () => {
|
|
160
|
+
app.server.close()
|
|
161
|
+
})
|
|
162
|
+
|
|
130
163
|
it("can create a user", async () => {
|
|
131
164
|
const user = await clientApp.service('User').create({
|
|
132
165
|
data: {
|