@jcbuisson/express-x 1.3.6 → 1.4.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/package.json +1 -2
- package/src/index.mjs +0 -2
- package/src/server.mjs +16 -13
- package/test/index.test.js +9 -2
- package/src/client.mjs +0 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jcbuisson/express-x",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
},
|
|
19
19
|
"keywords": [],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@jcbuisson/express-x-client": "^1.0.10",
|
|
22
21
|
"@prisma/client": "^4.10.1",
|
|
23
22
|
"axios": "^1.4.0",
|
|
24
23
|
"bcryptjs": "^2.4.3",
|
package/src/index.mjs
CHANGED
package/src/server.mjs
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import http from 'http'
|
|
3
3
|
import { Server } from "socket.io"
|
|
4
4
|
import express from 'express'
|
|
5
|
-
import { PrismaClient } from '@prisma/client'
|
|
6
5
|
|
|
7
6
|
/*
|
|
8
7
|
* Enhance `app` express application with services and real-time features
|
|
9
8
|
*/
|
|
10
|
-
export function expressX(options = {}) {
|
|
9
|
+
export function expressX(prisma, options = {}) {
|
|
11
10
|
|
|
12
11
|
const app = express()
|
|
12
|
+
app.set('prisma', prisma)
|
|
13
13
|
|
|
14
14
|
if (options.ws === undefined) options.ws = { ws_prefix: "expressx" }
|
|
15
15
|
|
|
@@ -28,13 +28,6 @@ export function expressX(options = {}) {
|
|
|
28
28
|
* create a service `name` based on Prisma table `entity`
|
|
29
29
|
*/
|
|
30
30
|
function createDatabaseService(name, prismaOptions = { entity: name }) {
|
|
31
|
-
|
|
32
|
-
let prisma = app.get('prisma')
|
|
33
|
-
if (!prisma) {
|
|
34
|
-
prisma = new PrismaClient()
|
|
35
|
-
app.set('prisma', prisma)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
31
|
// take all prisma methods on `entity` table
|
|
39
32
|
const methods = prisma[prismaOptions.entity]
|
|
40
33
|
|
|
@@ -74,7 +67,7 @@ export function expressX(options = {}) {
|
|
|
74
67
|
|
|
75
68
|
// call method
|
|
76
69
|
const result = await method(...context.args)
|
|
77
|
-
app.log('debug',
|
|
70
|
+
app.log('debug', 'result ' + JSON.stringify(result))
|
|
78
71
|
|
|
79
72
|
// call 'after' hooks
|
|
80
73
|
const afterMethodHooks = service?.hooks?.after && service.hooks.after[methodName] || []
|
|
@@ -127,7 +120,7 @@ export function expressX(options = {}) {
|
|
|
127
120
|
http: { name: service.name }
|
|
128
121
|
}
|
|
129
122
|
|
|
130
|
-
// introspect schema
|
|
123
|
+
// introspect schema and return a map: field name => prisma type
|
|
131
124
|
async function getTypesMap() {
|
|
132
125
|
const dmmf = await service.prisma._getDmmf()
|
|
133
126
|
const fieldDescriptions = dmmf.modelMap[service.name].fields
|
|
@@ -259,10 +252,11 @@ export function expressX(options = {}) {
|
|
|
259
252
|
id: lastConnectionId++,
|
|
260
253
|
socket,
|
|
261
254
|
channelNames: new Set(),
|
|
255
|
+
data: {},
|
|
262
256
|
}
|
|
263
257
|
// store connection in cache
|
|
264
258
|
connections[connection.id] = connection
|
|
265
|
-
app.log('verbose', `active
|
|
259
|
+
app.log('verbose', `active connection ids: ${Object.keys(connections)}`)
|
|
266
260
|
|
|
267
261
|
// emit 'connection' event for app (expressjs extends EventEmitter)
|
|
268
262
|
app.emit('connection', connection)
|
|
@@ -272,7 +266,16 @@ export function expressX(options = {}) {
|
|
|
272
266
|
|
|
273
267
|
socket.on('disconnect', () => {
|
|
274
268
|
app.log('verbose', `Client disconnected ${connection.id}`)
|
|
275
|
-
|
|
269
|
+
|
|
270
|
+
// TODO: wait for 1mn before cleaning (a reconnection will use this data)
|
|
271
|
+
// delete connections[connection.id]
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// handle connection data transfer caused by a disconnection (page reload, network issue, etc.)
|
|
275
|
+
socket.on('cnx-transfer', async ({ from, to }) => {
|
|
276
|
+
app.log('verbose', `cnx-transfer from ${from} to ${to}`)
|
|
277
|
+
connections[to] = connections[from]
|
|
278
|
+
delete connections[from]
|
|
276
279
|
})
|
|
277
280
|
|
|
278
281
|
|
package/test/index.test.js
CHANGED
|
@@ -2,17 +2,24 @@
|
|
|
2
2
|
import bodyParser from 'body-parser'
|
|
3
3
|
import axios from 'axios'
|
|
4
4
|
import io from 'socket.io-client'
|
|
5
|
+
import { PrismaClient } from '@prisma/client'
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
// import { assert } from 'chai'
|
|
8
8
|
import { describe, it, before, after, beforeEach, afterEach } from 'node:test'
|
|
9
9
|
import { strict as assert } from 'node:assert'
|
|
10
10
|
|
|
11
11
|
import { expressX, expressXClient } from '../src/index.mjs'
|
|
12
12
|
|
|
13
|
+
const prisma = new PrismaClient({
|
|
14
|
+
datasources: {
|
|
15
|
+
db: {
|
|
16
|
+
url: "file:./dev.db",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
})
|
|
13
20
|
|
|
14
21
|
// `app` is a regular express application, enhanced with services and real-time features
|
|
15
|
-
const app = expressX()
|
|
22
|
+
const app = expressX(prisma)
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
|
package/src/client.mjs
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { v4 } from 'uuid'
|
|
3
|
-
|
|
4
|
-
export function expressXClient(socket, options={}) {
|
|
5
|
-
if (options.debug === undefined) options.debug = false
|
|
6
|
-
|
|
7
|
-
const waitingPromisesByUid = {}
|
|
8
|
-
const action2service2handlers = {}
|
|
9
|
-
let onConnectionCallback = null
|
|
10
|
-
let onDisconnectionCallback = null
|
|
11
|
-
|
|
12
|
-
const setConnectionCallback = (callback) => {
|
|
13
|
-
onConnectionCallback = callback
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const setDisconnectionCallback = (callback) => {
|
|
17
|
-
onDisconnectionCallback = callback
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// on connection
|
|
21
|
-
socket.on("connected", async (connectionId) => {
|
|
22
|
-
if (options.debug) console.log('connected', connectionId)
|
|
23
|
-
if (onConnectionCallback) onConnectionCallback(connectionId)
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// on receiving response from service request
|
|
27
|
-
socket.on('client-response', ({ uid, error, result }) => {
|
|
28
|
-
if (options.debug) console.log('client-response', uid, error, result)
|
|
29
|
-
if (!waitingPromisesByUid[uid]) return // may not exist because a timeout removed it
|
|
30
|
-
const [resolve, reject] = waitingPromisesByUid[uid]
|
|
31
|
-
if (error) {
|
|
32
|
-
reject(error)
|
|
33
|
-
} else {
|
|
34
|
-
resolve(result)
|
|
35
|
-
}
|
|
36
|
-
delete waitingPromisesByUid[uid]
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// on receiving events from pub/sub
|
|
40
|
-
socket.on('service-event', ({ name, action, result }) => {
|
|
41
|
-
if (!action2service2handlers[action]) action2service2handlers[action] = {}
|
|
42
|
-
const serviceHandlers = action2service2handlers[action]
|
|
43
|
-
const handler = serviceHandlers[name]
|
|
44
|
-
if (handler) handler(result)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
async function serviceMethodRequest(name, action, ...args) {
|
|
48
|
-
const uid = v4()
|
|
49
|
-
// create a promise which will resolve or reject by an event 'client-response'
|
|
50
|
-
const promise = new Promise((resolve, reject) => {
|
|
51
|
-
waitingPromisesByUid[uid] = [resolve, reject]
|
|
52
|
-
// a 5s timeout may also reject the promise
|
|
53
|
-
setTimeout(() => {
|
|
54
|
-
delete waitingPromisesByUid[uid]
|
|
55
|
-
reject(`Error: timeout on service '${name}', action '${action}', args: ${args}`)
|
|
56
|
-
}, 5000)
|
|
57
|
-
})
|
|
58
|
-
// send request to server through websocket
|
|
59
|
-
socket.emit('client-request', {
|
|
60
|
-
uid,
|
|
61
|
-
name,
|
|
62
|
-
action,
|
|
63
|
-
args,
|
|
64
|
-
})
|
|
65
|
-
return promise
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function service(name) {
|
|
69
|
-
const service = {
|
|
70
|
-
// associate a handler to a pub/sub event for this service
|
|
71
|
-
on: (action, handler) => {
|
|
72
|
-
if (!action2service2handlers[action]) action2service2handlers[action] = {}
|
|
73
|
-
const serviceHandlers = action2service2handlers[action]
|
|
74
|
-
serviceHandlers[name] = handler
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
// use a Proxy to allow for any method name for a service
|
|
78
|
-
const handler = {
|
|
79
|
-
get(service, action) {
|
|
80
|
-
if (!(action in service)) {
|
|
81
|
-
// newly used property `action`: define it as a service method request function
|
|
82
|
-
service[action] = (...args) => serviceMethodRequest(name, action, ...args)
|
|
83
|
-
}
|
|
84
|
-
return service[action]
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return new Proxy(service, handler)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
setConnectionCallback,
|
|
92
|
-
setDisconnectionCallback,
|
|
93
|
-
service,
|
|
94
|
-
}
|
|
95
|
-
}
|