@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcbuisson/express-x",
3
- "version": "1.3.6",
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
@@ -1,11 +1,9 @@
1
1
 
2
2
  import { expressX } from './server.mjs'
3
- import { expressXClient } from './client.mjs'
4
3
  import { hashPassword, protect, setSessionJWT, } from './common-hooks.mjs'
5
4
 
6
5
  export {
7
6
  expressX,
8
- expressXClient,
9
7
 
10
8
  hashPassword,
11
9
  protect,
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', `result ${result}`)
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 connections ${Object.keys(connections)}`)
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
- delete connections[connection.id]
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
 
@@ -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
- }