@jcbuisson/express-x 1.1.0 → 1.1.2

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.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -12,15 +12,21 @@
12
12
  "license": "MIT",
13
13
  "private": false,
14
14
  "scripts": {
15
- },
15
+ "test": "mocha --require esm 'test/**/*.test.js'",
16
+ "generate": "npx prisma generate",
17
+ "migrate": "npx prisma migrate dev --name init"
18
+ },
16
19
  "keywords": [],
17
20
  "dependencies": {
18
21
  "@prisma/client": "^4.10.1",
22
+ "axios": "^1.4.0",
19
23
  "config": "^3.3.9",
24
+ "esm": "^3.2.25",
20
25
  "express": "^4.18.2",
21
26
  "socket.io": "^4.6.0"
22
27
  },
23
28
  "devDependencies": {
24
- "@types/express": "^4.17.17"
29
+ "chai": "^4.3.7",
30
+ "mocha": "^10.2.0"
25
31
  }
26
32
  }
package/prisma/dev.db ADDED
Binary file
@@ -0,0 +1,22 @@
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
+ }
package/src/index.mjs CHANGED
@@ -8,7 +8,7 @@ import { PrismaClient } from '@prisma/client'
8
8
  */
9
9
  function expressX(app, options={}) {
10
10
 
11
- if (options.debug === undefined) options.debug = true
11
+ if (options.debug === undefined) options.debug = false
12
12
  if (options.ws === undefined) options.ws = { ws_prefix: "expressx" }
13
13
 
14
14
  const services = {}
@@ -19,62 +19,23 @@ function expressX(app, options={}) {
19
19
  /*
20
20
  * create a service `name` based on Prisma table `entity`
21
21
  */
22
- function createDatabaseService(name, { entity=name }) {
22
+ function createDatabaseService(name, prismaOptions = { entity: name }) {
23
+
23
24
  let prisma = app.get('prisma')
24
25
  if (!prisma) {
25
26
  prisma = new PrismaClient()
26
27
  app.set('prisma', prisma)
27
28
  }
28
-
29
- const service = createService(name, {
30
- create: (data) => {
31
- if (options.debug) console.log('create', name, data)
32
- return prisma[entity].create({
33
- data,
34
- })
35
- },
36
-
37
- get: (id) => {
38
- if (options.debug) console.log('get', name, id)
39
- return prisma[entity].findUnique({
40
- where: {
41
- id,
42
- },
43
- })
44
- },
45
-
46
- patch: (id, data) => {
47
- if (options.debug) console.log('patch', name, id, data)
48
- return prisma[entity].update({
49
- where: {
50
- id,
51
- },
52
- data,
53
- })
54
- },
55
-
56
- remove: (id) => {
57
- if (options.debug) console.log('remove', name, id)
58
- return prisma[entity].delete({
59
- where: {
60
- id,
61
- },
62
- })},
63
29
 
64
- find: (options) => {
65
- if (options.debug) console.log('find', name, options)
66
- return prisma[entity].findMany(options)
67
- },
30
+ // take all prisma methods on `entity` table
31
+ const methods = prisma[prismaOptions.entity]
68
32
 
69
- upsert: (options) => {
70
- if (options.debug) console.log('upsert', name, options)
71
- return prisma[entity].upsert(options)
72
- },
73
- })
33
+ const service = createService(name, methods)
34
+
74
35
  service.prisma = prisma
75
- service.entity = entity
36
+ service.entity = prismaOptions.entity
76
37
 
77
- if (options.debug) console.log(`created service '${name}' over table '${entity}'`)
38
+ if (options.debug) console.log(`created service '${name}' over table '${prismaOptions.entity}'`)
78
39
  return service
79
40
  }
80
41
 
@@ -86,6 +47,7 @@ function expressX(app, options={}) {
86
47
 
87
48
  for (const methodName in methods) {
88
49
  const method = methods[methodName]
50
+ if (! method instanceof Function) continue
89
51
 
90
52
  // `context` is the context of execution (transport type, connection, app)
91
53
  // `args` is the list of arguments of the method
@@ -151,16 +113,40 @@ function expressX(app, options={}) {
151
113
  /*
152
114
  * add an HTTP REST endpoint at `path`, based on `service`
153
115
  */
154
- function addHttpRest(path, service) {
116
+ async function addHttpRest(path, service) {
155
117
  const context = {
156
118
  app,
157
119
  http: { name: service.name }
158
120
  }
159
121
 
122
+ // introspect table schema
123
+ async function getFieldTypes() {
124
+ const fieldTypes = {}
125
+ if (service.prisma._activeProvider === 'sqlite') {
126
+ const fieldInfo = await service.prisma.$queryRawUnsafe(`
127
+ PRAGMA table_info(${service.entity})
128
+ `)
129
+ fieldInfo.forEach(column => {
130
+ fieldTypes[column.name] = column.type.toLowerCase()
131
+ })
132
+ } else if (service.prisma._activeProvider === 'postgresql') {
133
+ const fieldInfo = await service.prisma.$queryRawUnsafe(`
134
+ SELECT column_name, data_type
135
+ FROM information_schema.columns
136
+ WHERE table_name = '${service.entity}';
137
+ `)
138
+ fieldInfo.forEach(column => {
139
+ fieldTypes[column.column_name] = column.data_type
140
+ })
141
+ }
142
+ return fieldTypes
143
+ }
144
+
145
+
160
146
  app.post(path, async (req, res) => {
161
147
  context.http.req = req
162
148
  try {
163
- const value = await service.__create(context, req.body)
149
+ const value = await service.__create(context, { data: req.body })
164
150
  res.json(value)
165
151
  } catch(err) {
166
152
  console.log('callErr', err)
@@ -171,28 +157,26 @@ function expressX(app, options={}) {
171
157
  app.get(path, async (req, res) => {
172
158
  context.http.req = req
173
159
  const query = { ...req.query }
174
- for (const fieldName in query) {
175
- const fieldInfo = await service.prisma.$queryRawUnsafe(`
176
- SELECT column_name, data_type
177
- FROM information_schema.columns
178
- WHERE table_name = '${service.entity}' AND column_name = '${fieldName}';
179
- `)
180
- const fieldType = fieldInfo[0].data_type
181
- if (fieldType === 'integer') {
182
- query[fieldName] = parseInt(query[fieldName])
183
- } else if (fieldType === 'numeric') {
184
- query[fieldName] = parseFloat(query[fieldName])
185
- } else if (fieldType === 'boolean') {
186
- query[fieldName] = (query[fieldName] === 't') ? true : false
187
- } else if (fieldType === 'text' || fieldType === 'character varying') {
188
- query[fieldName] = query[fieldName]
189
- } else {
190
- // ?
191
- query[fieldName] = query[fieldName]
192
- }
193
- }
194
160
  try {
195
- const values = await service.__find(context, {
161
+ for (const fieldName in query) {
162
+ if (!service.fieldTypes) service.fieldTypes = await getFieldTypes()
163
+ const fieldType = service.fieldTypes[fieldName]
164
+
165
+ if (fieldType === 'integer') {
166
+ query[fieldName] = parseInt(query[fieldName])
167
+ } else if (fieldType === 'numeric') {
168
+ query[fieldName] = parseFloat(query[fieldName])
169
+ } else if (fieldType === 'boolean') {
170
+ query[fieldName] = (query[fieldName] === 't') ? true : false
171
+ } else if (fieldType === 'text' || fieldType === 'character varying') {
172
+ query[fieldName] = query[fieldName]
173
+ } else {
174
+ // ?
175
+ query[fieldName] = query[fieldName]
176
+ }
177
+ }
178
+
179
+ const values = await service.__findMany(context, {
196
180
  where: query,
197
181
  })
198
182
  res.json(values)
@@ -205,7 +189,11 @@ function expressX(app, options={}) {
205
189
  app.get(`${path}/:id`, async (req, res) => {
206
190
  context.http.req = req
207
191
  try {
208
- const value = await service.__get(context, parseInt(req.params.id))
192
+ const value = await service.__findUnique(context, {
193
+ where: {
194
+ id: parseInt(req.params.id)
195
+ }
196
+ })
209
197
  res.json(value)
210
198
  } catch(err) {
211
199
  console.log('callErr', err)
@@ -216,7 +204,12 @@ function expressX(app, options={}) {
216
204
  app.patch(`${path}/:id`, async (req, res) => {
217
205
  context.http.req = req
218
206
  try {
219
- const value = await service.__patch(context, parseInt(req.params.id), req.body)
207
+ const value = await service.__update(context, {
208
+ where: {
209
+ id: parseInt(req.params.id),
210
+ },
211
+ data: req.body,
212
+ })
220
213
  res.json(value)
221
214
  } catch(err) {
222
215
  console.log('callErr', err)
@@ -227,7 +220,11 @@ function expressX(app, options={}) {
227
220
  app.delete(`${path}/:id`, async (req, res) => {
228
221
  context.http.req = req
229
222
  try {
230
- const value = await service.__remove(context, parseInt(req.params.id))
223
+ const value = await service.__delete(context, {
224
+ where: {
225
+ id: parseInt(req.params.id)
226
+ }
227
+ })
231
228
  res.json(value)
232
229
  } catch(err) {
233
230
  console.log('callErr', err)
@@ -0,0 +1,110 @@
1
+
2
+ import expressX from '../src/index.mjs'
3
+ import express from 'express'
4
+ import bodyParser from 'body-parser'
5
+ import axios from 'axios'
6
+
7
+ import { expect, assert } from 'chai'
8
+
9
+
10
+ // `app` is a regular express application, enhanced with express-x features
11
+ const app = expressX(express(), { debug: false })
12
+
13
+ app.createDatabaseService('User')
14
+ app.createDatabaseService('Post')
15
+
16
+
17
+
18
+ describe('ExpressX API', () => {
19
+
20
+ it("can delete all users", async () => {
21
+ const res = await app.service('User').deleteMany()
22
+ assert('chris' === 'chris')
23
+ })
24
+
25
+ it("can create a user", async () => {
26
+ const user = await app.service('User').create({
27
+ data: {
28
+ name: "chris",
29
+ email: 'chris@mail.fr'
30
+ },
31
+ })
32
+ assert(user.name === 'chris')
33
+ })
34
+
35
+ it("can find a user by name", async () => {
36
+ const users = await app.service('User').findMany({
37
+ where: {
38
+ name: {
39
+ startsWith: "ch"
40
+ }
41
+ }
42
+ })
43
+ assert(users.length > 0)
44
+ })
45
+
46
+ it("can find a unique user by email", async () => {
47
+ const chris = await app.service('User').findUnique({
48
+ where: {
49
+ email: "chris@mail.fr"
50
+ }
51
+ })
52
+ assert(chris.name === 'chris')
53
+ })
54
+ })
55
+
56
+
57
+ describe('HTTP/REST API', () => {
58
+
59
+ // add body parsers for http requests
60
+ app.use(bodyParser.json())
61
+ app.use(bodyParser.urlencoded({ extended: false }))
62
+
63
+ // add http/rest endpoints
64
+ app.addHttpRest('/api/user', app.service('User'))
65
+ app.addHttpRest('/api/post', app.service('Post'))
66
+
67
+ app.server.listen(8008, () => console.log(`App listening at http://localhost:8008`))
68
+
69
+ let chris
70
+
71
+ it("can create a user", async () => {
72
+ const res = await axios.post('http://localhost:8008/api/user', {
73
+ name: "carole",
74
+ email: 'carole@mail.fr'
75
+ })
76
+ assert(res?.data?.name === 'carole')
77
+ })
78
+
79
+ it("can find users", async () => {
80
+ const res = await axios.get('http://localhost:8008/api/user')
81
+ assert(res?.data?.length > 0)
82
+ })
83
+
84
+ it("can find a user by name", async () => {
85
+ const res = await axios.get('http://localhost:8008/api/user?name=chris')
86
+ assert(res?.data?.length > 0)
87
+ chris = res.data[0]
88
+ })
89
+
90
+ it("can find a user by id", async () => {
91
+ const res = await axios.get(`http://localhost:8008/api/user/${chris.id}`)
92
+ assert(res?.data?.id === chris.id)
93
+ })
94
+
95
+ it("can patch a user", async () => {
96
+ const res = await axios.patch(`http://localhost:8008/api/user/${chris.id}`, {
97
+ name: "Christophe",
98
+ })
99
+ assert(res?.data?.name === "Christophe")
100
+ })
101
+
102
+ it("can delete a user", async () => {
103
+ const res = await axios.delete(`http://localhost:8008/api/user/${chris.id}`)
104
+ assert(res?.data?.name === "Christophe")
105
+ })
106
+
107
+ it("can stop server", () => {
108
+ app.server.close()
109
+ })
110
+ })