@srfnstack/spliffy 1.2.5 → 1.2.6

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/src/routes.mjs CHANGED
@@ -1,205 +1,205 @@
1
- import { validateMiddleware, mergeMiddleware } from './middleware.mjs'
2
- import { createStaticHandler } from './staticHandler.mjs'
3
- import { getContentTypeByExtension } from './content.mjs'
4
- import fs from 'fs'
5
- import path from 'path'
6
- import { HTTP_METHODS } from './handler.mjs'
7
- import util from 'util'
8
-
9
- const { promisify } = util
10
-
11
- const readdir = promisify(fs.readdir)
12
-
13
- const isVariable = part => part.startsWith('$')
14
- const getVariableName = part => part.substr(1)
15
- const getPathPart = name => {
16
- if (name === 'index') {
17
- return ''
18
- }
19
- if (name.startsWith('$')) {
20
- return `:${name.substr(1)}`
21
- } else if (name.endsWith('+')) {
22
- return `${name.substr(0, name.length - 1)}/*`
23
- } else {
24
- return name
25
- }
26
- }
27
- const filterTestFiles = config => f => (!f.name.endsWith('.test.js') && !f.name.endsWith('.test.mjs')) || config.allowTestFileRoutes
28
- const filterIgnoredFiles = config => f => !config.ignoreFilesMatching.filter(p => p).find(pattern => f.name.match(pattern))
29
- const ignoreHandlerFields = { middleware: true, streamRequestBody: true }
30
-
31
- const isRouteFile = name => name.endsWith('.rt.js') || name.endsWith('.rt.mjs') || name.endsWith('.rt.cjs')
32
- const isMiddlewareFile = name => name.endsWith('.mw.js') || name.endsWith('.mw.mjs') || name.endsWith('.mw.cjs')
33
-
34
- const doFindRoutes = async (config, currentFile, filePath, urlPath, pathParameters, inheritedMiddleware) => {
35
- const routes = []
36
- const name = currentFile.name
37
- if (currentFile.isDirectory()) {
38
- routes.push(...(await findRoutesInDir(name, filePath, urlPath, inheritedMiddleware, pathParameters, config)))
39
- } else if (!config.staticMode && isRouteFile(name)) {
40
- routes.push(await buildJSHandlerRoute(name, filePath, urlPath, inheritedMiddleware, pathParameters))
41
- } else {
42
- routes.push(...buildStaticRoutes(name, filePath, urlPath, inheritedMiddleware, pathParameters, config))
43
- }
44
- return routes
45
- }
46
-
47
- const wrapSyntaxError = (e, path) => {
48
- // Hack to workaround https://github.com/nodejs/modules/issues/471
49
- if (e instanceof SyntaxError) {
50
- const newError = new SyntaxError(`${e.message}. In file: ${path}`)
51
- newError.stack += `\nCaused By: ${e.stack}`
52
- throw newError
53
- }
54
- throw e
55
- }
56
-
57
- const importModules = async (config, dirPath, files) => Promise.all(
58
- files
59
- .filter(filterTestFiles(config))
60
- .filter(filterIgnoredFiles(config))
61
- .map(f => path.join(dirPath, f.name))
62
- .map(mwPath => import(`file://${mwPath}`)
63
- .then(module => ({ module, mwPath }))
64
- .catch(e => wrapSyntaxError(e, mwPath))
65
- ))
66
-
67
- const findRoutesInDir = async (name, filePath, urlPath, inheritedMiddleware, pathParameters, config) => {
68
- if (isVariable(name)) {
69
- pathParameters = pathParameters.concat(getVariableName(name))
70
- }
71
- const files = await readdir(filePath, { withFileTypes: true })
72
-
73
- const middlewareModules = await importModules(config, filePath, files.filter(f => isMiddlewareFile(f.name)))
74
- const dirMiddleware = middlewareModules.map(({ module, mwPath }) => {
75
- const middleware = module.middleware || module.default?.middleware
76
- if (!middleware) {
77
- throw new Error(`${mwPath} must export a middleware property or have a middleware property on the default export`)
78
- }
79
- try {
80
- validateMiddleware(middleware)
81
- } catch (e) {
82
- throw new Error('Failed to load middleware in file ' + mwPath + '\n' + e.message + '\n' + e.stack)
83
- }
84
- return middleware
85
- })
86
- .reduce((result, incoming) => mergeMiddleware(incoming, result), inheritedMiddleware)
87
-
88
- return Promise.all(files
89
- .filter(f => !isMiddlewareFile(f.name))
90
- .filter(filterTestFiles(config))
91
- .filter(filterIgnoredFiles(config))
92
- .map(
93
- (f) => doFindRoutes(
94
- config,
95
- f,
96
- path.join(filePath, f.name),
97
- urlPath + '/' + getPathPart(name),
98
- pathParameters,
99
- dirMiddleware
100
- )
101
- ))
102
- .then(routes => routes.flat())
103
- }
104
-
105
- const buildJSHandlerRoute = async (name, filePath, urlPath, inheritedMiddleware, pathParameters) => {
106
- if (name.endsWith('.mjs') || name.endsWith('.cjs')) {
107
- name = name.substr(0, name.length - '.rt.mjs'.length)
108
- } else {
109
- name = name.substr(0, name.length - '.rt.js'.length)
110
- }
111
- if (isVariable(name)) {
112
- pathParameters = pathParameters.concat(getVariableName(name))
113
- }
114
- const route = {
115
- pathParameters,
116
- urlPath: `${urlPath}/${getPathPart(name)}`,
117
- filePath,
118
- handlers: {}
119
- }
120
- let module
121
- try {
122
- module = await import(`file://${filePath}`)
123
- } catch (e) {
124
- wrapSyntaxError(e, filePath)
125
- }
126
- const handlers = module.default
127
- try {
128
- route.middleware = mergeMiddleware(handlers.middleware || [], inheritedMiddleware)
129
- } catch (e) {
130
- const err = new Error(`Failed to load middleware for route: ${filePath}`)
131
- err.stack += `\nCaused By: ${e.stack}`
132
- throw err
133
- }
134
- for (const method of Object.keys(handlers).filter(k => !ignoreHandlerFields[k])) {
135
- if (HTTP_METHODS.indexOf(method) === -1) {
136
- throw new Error(`Method: ${method} in file ${filePath} is not a valid http method. It must be one of: ${HTTP_METHODS}. Method names must be all uppercase.`)
137
- }
138
- const loadedHandler = handlers[method]
139
- let handler = loadedHandler
140
- if (typeof loadedHandler.handler === 'function') {
141
- handler = loadedHandler.handler
142
- }
143
- if (typeof handler !== 'function' && method !== 'WEBSOCKET') {
144
- throw new Error(`Request method ${method} in file ${filePath} must be a function. Got: ${typeof handlers[method]}`)
145
- } else if (method === 'WEBSOCKET' && typeof handler !== 'object') {
146
- throw new Error(`Websocket in file ${filePath} must be an object. Got: ${typeof handlers[method]}`)
147
- }
148
- if (!('streamRequestBody' in loadedHandler)) {
149
- handler.streamRequestBody = handlers.streamRequestBody
150
- } else {
151
- handler.streamRequestBody = loadedHandler.streamRequestBody
152
- }
153
- route.handlers[method] = handler
154
- }
155
- return route
156
- }
157
-
158
- const buildStaticRoutes = (name, filePath, urlPath, inheritedMiddleware, pathParameters, config) => {
159
- const routes = []
160
- if (isVariable(name)) {
161
- pathParameters = pathParameters.concat(getVariableName(name))
162
- }
163
- const contentType = getContentTypeByExtension(name, config.staticContentTypes)
164
- const route = {
165
- pathParameters,
166
- urlPath: `${urlPath}/${getPathPart(name)}`,
167
- filePath,
168
- handlers: createStaticHandler(filePath, contentType, config.cacheStatic, config.staticCacheControl),
169
- middleware: inheritedMiddleware
170
- }
171
-
172
- routes.push(route)
173
-
174
- for (const ext of config.resolveWithoutExtension) {
175
- if (name.endsWith(ext)) {
176
- const strippedName = name.substr(0, name.length - ext.length)
177
- // in the index case we need to add both the stripped and an empty path so it will resolve the parent
178
- if (strippedName === 'index') {
179
- const noExtRoute = Object.assign({}, route)
180
- noExtRoute.urlPath = `${urlPath}/${strippedName}`
181
- routes.push(noExtRoute)
182
- }
183
- const noExtRoute = Object.assign({}, route)
184
- noExtRoute.urlPath = `${urlPath}/${getPathPart(strippedName)}`
185
- routes.push(noExtRoute)
186
- }
187
- }
188
- return routes
189
- }
190
-
191
- export async function findRoutes (config) {
192
- const fullRouteDir = path.resolve(config.routeDir)
193
- if (!fs.existsSync(fullRouteDir)) {
194
- throw new Error(`can't find route directory: ${fullRouteDir}`)
195
- }
196
- const appMiddleware = mergeMiddleware(config.middleware || [], {})
197
- const files = await readdir(fullRouteDir, { withFileTypes: true })
198
- return Promise.all(files
199
- .filter(filterTestFiles(config))
200
- .filter(filterIgnoredFiles(config))
201
- .map(
202
- f => doFindRoutes(config, f, path.join(fullRouteDir, f.name), '', [], appMiddleware)
203
- ))
204
- .then(routes => routes.flat())
205
- }
1
+ import { validateMiddleware, mergeMiddleware } from './middleware.mjs'
2
+ import { createStaticHandler } from './staticHandler.mjs'
3
+ import { getContentTypeByExtension } from './content.mjs'
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+ import { HTTP_METHODS } from './handler.mjs'
7
+ import util from 'util'
8
+
9
+ const { promisify } = util
10
+
11
+ const readdir = promisify(fs.readdir)
12
+
13
+ const isVariable = part => part.startsWith('$')
14
+ const getVariableName = part => part.substr(1)
15
+ const getPathPart = name => {
16
+ if (name === 'index') {
17
+ return ''
18
+ }
19
+ if (name.startsWith('$')) {
20
+ return `:${name.substr(1)}`
21
+ } else if (name.endsWith('+')) {
22
+ return `${name.substr(0, name.length - 1)}/*`
23
+ } else {
24
+ return name
25
+ }
26
+ }
27
+ const filterTestFiles = config => f => (!f.name.endsWith('.test.js') && !f.name.endsWith('.test.mjs')) || config.allowTestFileRoutes
28
+ const filterIgnoredFiles = config => f => !config.ignoreFilesMatching.filter(p => p).find(pattern => f.name.match(pattern))
29
+ const ignoreHandlerFields = { middleware: true, streamRequestBody: true }
30
+
31
+ const isRouteFile = name => name.endsWith('.rt.js') || name.endsWith('.rt.mjs') || name.endsWith('.rt.cjs')
32
+ const isMiddlewareFile = name => name.endsWith('.mw.js') || name.endsWith('.mw.mjs') || name.endsWith('.mw.cjs')
33
+
34
+ const doFindRoutes = async (config, currentFile, filePath, urlPath, pathParameters, inheritedMiddleware) => {
35
+ const routes = []
36
+ const name = currentFile.name
37
+ if (currentFile.isDirectory()) {
38
+ routes.push(...(await findRoutesInDir(name, filePath, urlPath, inheritedMiddleware, pathParameters, config)))
39
+ } else if (!config.staticMode && isRouteFile(name)) {
40
+ routes.push(await buildJSHandlerRoute(name, filePath, urlPath, inheritedMiddleware, pathParameters))
41
+ } else {
42
+ routes.push(...buildStaticRoutes(name, filePath, urlPath, inheritedMiddleware, pathParameters, config))
43
+ }
44
+ return routes
45
+ }
46
+
47
+ const wrapSyntaxError = (e, path) => {
48
+ // Hack to workaround https://github.com/nodejs/modules/issues/471
49
+ if (e instanceof SyntaxError) {
50
+ const newError = new SyntaxError(`${e.message}. In file: ${path}`)
51
+ newError.stack += `\nCaused By: ${e.stack}`
52
+ throw newError
53
+ }
54
+ throw e
55
+ }
56
+
57
+ const importModules = async (config, dirPath, files) => Promise.all(
58
+ files
59
+ .filter(filterTestFiles(config))
60
+ .filter(filterIgnoredFiles(config))
61
+ .map(f => path.join(dirPath, f.name))
62
+ .map(mwPath => import(`file://${mwPath}`)
63
+ .then(module => ({ module, mwPath }))
64
+ .catch(e => wrapSyntaxError(e, mwPath))
65
+ ))
66
+
67
+ const findRoutesInDir = async (name, filePath, urlPath, inheritedMiddleware, pathParameters, config) => {
68
+ if (isVariable(name)) {
69
+ pathParameters = pathParameters.concat(getVariableName(name))
70
+ }
71
+ const files = await readdir(filePath, { withFileTypes: true })
72
+
73
+ const middlewareModules = await importModules(config, filePath, files.filter(f => isMiddlewareFile(f.name)))
74
+ const dirMiddleware = middlewareModules.map(({ module, mwPath }) => {
75
+ const middleware = module.middleware || module.default?.middleware
76
+ if (!middleware) {
77
+ throw new Error(`${mwPath} must export a middleware property or have a middleware property on the default export`)
78
+ }
79
+ try {
80
+ validateMiddleware(middleware)
81
+ } catch (e) {
82
+ throw new Error('Failed to load middleware in file ' + mwPath + '\n' + e.message + '\n' + e.stack)
83
+ }
84
+ return middleware
85
+ })
86
+ .reduce((result, incoming) => mergeMiddleware(incoming, result), inheritedMiddleware)
87
+
88
+ return Promise.all(files
89
+ .filter(f => !isMiddlewareFile(f.name))
90
+ .filter(filterTestFiles(config))
91
+ .filter(filterIgnoredFiles(config))
92
+ .map(
93
+ (f) => doFindRoutes(
94
+ config,
95
+ f,
96
+ path.join(filePath, f.name),
97
+ urlPath + '/' + getPathPart(name),
98
+ pathParameters,
99
+ dirMiddleware
100
+ )
101
+ ))
102
+ .then(routes => routes.flat())
103
+ }
104
+
105
+ const buildJSHandlerRoute = async (name, filePath, urlPath, inheritedMiddleware, pathParameters) => {
106
+ if (name.endsWith('.mjs') || name.endsWith('.cjs')) {
107
+ name = name.substr(0, name.length - '.rt.mjs'.length)
108
+ } else {
109
+ name = name.substr(0, name.length - '.rt.js'.length)
110
+ }
111
+ if (isVariable(name)) {
112
+ pathParameters = pathParameters.concat(getVariableName(name))
113
+ }
114
+ const route = {
115
+ pathParameters,
116
+ urlPath: `${urlPath}/${getPathPart(name)}`,
117
+ filePath,
118
+ handlers: {}
119
+ }
120
+ let module
121
+ try {
122
+ module = await import(`file://${filePath}`)
123
+ } catch (e) {
124
+ wrapSyntaxError(e, filePath)
125
+ }
126
+ const handlers = module.default
127
+ try {
128
+ route.middleware = mergeMiddleware(handlers.middleware || [], inheritedMiddleware)
129
+ } catch (e) {
130
+ const err = new Error(`Failed to load middleware for route: ${filePath}`)
131
+ err.stack += `\nCaused By: ${e.stack}`
132
+ throw err
133
+ }
134
+ for (const method of Object.keys(handlers).filter(k => !ignoreHandlerFields[k])) {
135
+ if (HTTP_METHODS.indexOf(method) === -1) {
136
+ throw new Error(`Method: ${method} in file ${filePath} is not a valid http method. It must be one of: ${HTTP_METHODS}. Method names must be all uppercase.`)
137
+ }
138
+ const loadedHandler = handlers[method]
139
+ let handler = loadedHandler
140
+ if (typeof loadedHandler.handler === 'function') {
141
+ handler = loadedHandler.handler
142
+ }
143
+ if (typeof handler !== 'function' && method !== 'WEBSOCKET') {
144
+ throw new Error(`Request method ${method} in file ${filePath} must be a function. Got: ${typeof handlers[method]}`)
145
+ } else if (method === 'WEBSOCKET' && typeof handler !== 'object') {
146
+ throw new Error(`Websocket in file ${filePath} must be an object. Got: ${typeof handlers[method]}`)
147
+ }
148
+ if (!('streamRequestBody' in loadedHandler)) {
149
+ handler.streamRequestBody = handlers.streamRequestBody
150
+ } else {
151
+ handler.streamRequestBody = loadedHandler.streamRequestBody
152
+ }
153
+ route.handlers[method] = handler
154
+ }
155
+ return route
156
+ }
157
+
158
+ const buildStaticRoutes = (name, filePath, urlPath, inheritedMiddleware, pathParameters, config) => {
159
+ const routes = []
160
+ if (isVariable(name)) {
161
+ pathParameters = pathParameters.concat(getVariableName(name))
162
+ }
163
+ const contentType = getContentTypeByExtension(name, config.staticContentTypes)
164
+ const route = {
165
+ pathParameters,
166
+ urlPath: `${urlPath}/${getPathPart(name)}`,
167
+ filePath,
168
+ handlers: createStaticHandler(filePath, contentType, config.cacheStatic, config.staticCacheControl),
169
+ middleware: inheritedMiddleware
170
+ }
171
+
172
+ routes.push(route)
173
+
174
+ for (const ext of config.resolveWithoutExtension) {
175
+ if (name.endsWith(ext)) {
176
+ const strippedName = name.substr(0, name.length - ext.length)
177
+ // in the index case we need to add both the stripped and an empty path so it will resolve the parent
178
+ if (strippedName === 'index') {
179
+ const noExtRoute = Object.assign({}, route)
180
+ noExtRoute.urlPath = `${urlPath}/${strippedName}`
181
+ routes.push(noExtRoute)
182
+ }
183
+ const noExtRoute = Object.assign({}, route)
184
+ noExtRoute.urlPath = `${urlPath}/${getPathPart(strippedName)}`
185
+ routes.push(noExtRoute)
186
+ }
187
+ }
188
+ return routes
189
+ }
190
+
191
+ export async function findRoutes (config) {
192
+ const fullRouteDir = path.resolve(config.routeDir)
193
+ if (!fs.existsSync(fullRouteDir)) {
194
+ throw new Error(`can't find route directory: ${fullRouteDir}`)
195
+ }
196
+ const appMiddleware = mergeMiddleware(config.middleware || [], {})
197
+ const files = await readdir(fullRouteDir, { withFileTypes: true })
198
+ return Promise.all(files
199
+ .filter(filterTestFiles(config))
200
+ .filter(filterIgnoredFiles(config))
201
+ .map(
202
+ f => doFindRoutes(config, f, path.join(fullRouteDir, f.name), '', [], appMiddleware)
203
+ ))
204
+ .then(routes => routes.flat())
205
+ }