@jcbuisson/express-x 1.0.22 → 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.0.22",
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)
@@ -170,8 +156,29 @@ function expressX(app, options={}) {
170
156
 
171
157
  app.get(path, async (req, res) => {
172
158
  context.http.req = req
159
+ const query = { ...req.query }
173
160
  try {
174
- const values = await service.__find(context, req.body)
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, {
180
+ where: query,
181
+ })
175
182
  res.json(values)
176
183
  } catch(err) {
177
184
  console.log('callErr', err)
@@ -182,7 +189,11 @@ function expressX(app, options={}) {
182
189
  app.get(`${path}/:id`, async (req, res) => {
183
190
  context.http.req = req
184
191
  try {
185
- 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
+ })
186
197
  res.json(value)
187
198
  } catch(err) {
188
199
  console.log('callErr', err)
@@ -193,7 +204,12 @@ function expressX(app, options={}) {
193
204
  app.patch(`${path}/:id`, async (req, res) => {
194
205
  context.http.req = req
195
206
  try {
196
- 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
+ })
197
213
  res.json(value)
198
214
  } catch(err) {
199
215
  console.log('callErr', err)
@@ -204,7 +220,11 @@ function expressX(app, options={}) {
204
220
  app.delete(`${path}/:id`, async (req, res) => {
205
221
  context.http.req = req
206
222
  try {
207
- 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
+ })
208
228
  res.json(value)
209
229
  } catch(err) {
210
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
+ })