@stonyx/rest-server 0.2.1-alpha.0 → 0.2.1-alpha.10

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/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![CI](https://github.com/abofs/stonyx-rest-server/actions/workflows/ci.yml/badge.svg)](https://github.com/abofs/stonyx-rest-server/actions/workflows/ci.yml)
2
+ [![npm version](https://img.shields.io/npm/v/@stonyx/rest-server.svg)](https://www.npmjs.com/package/@stonyx/rest-server)
3
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
+
1
5
  # @stonyx/rest-server
2
6
 
3
7
  REST server module for the [Stonyx framework](https://github.com/abofs/stonyx), providing dynamic route registration and built-in request handling with optional authentication hooks.
@@ -41,16 +45,13 @@ export default {
41
45
  };
42
46
  ```
43
47
 
44
- Then initialize the Stonyx framework, which auto-initializes all of its modules, including `@stonyx/rest-server`:
45
-
46
- ```js
47
- import Stonyx from 'stonyx';
48
- import config from './config/environment.js';
48
+ Then run the application via the Stonyx CLI, which auto-initializes all modules including the REST server:
49
49
 
50
- new Stonyx(config);
50
+ ```bash
51
+ stonyx serve
51
52
  ```
52
53
 
53
- For further framework initialization instructions, see the [Stonyx repository](https://github.com/abofs/stonyx).
54
+ For further framework instructions, see the [Stonyx repository](https://github.com/abofs/stonyx).
54
55
 
55
56
  ### Optional Direct Usage
56
57
 
@@ -72,12 +73,26 @@ Configuration is read from `stonyx/config` under `restServer`:
72
73
 
73
74
  | Option | Type | Default | Description |
74
75
  | :---------------: | :-----------------: | :---------- | :--------------------------------------------------------- |
75
- | `dir` | **String** | `undefined` | Directory containing request classes to mount as routes |
76
+ | `dir` | **String** | `'./requests'` | Directory containing request classes to mount as routes |
76
77
  | `camelCaseRoutes` | **Boolean** | `true` | Convert filenames to camelCase when generating route paths |
77
- | `port` | **Number** | `3000` | Port to listen on |
78
+ | `port` | **Number** | `2666` | Port to listen on |
78
79
  | `origin` | **String \| Array** | `'*'` | CORS origin(s) allowed |
80
+ | `methods` | **String** | `'GET,POST,PATCH,PUT,DELETE'` | CORS allowed methods |
81
+ | `enableHealthCheck` | **Boolean** | `true` | Register `GET /health` endpoint (disable via `REST_HEALTH_CHECK_DISABLE=true`) |
82
+ | `trustProxy` | **Boolean** | `false` | Trust reverse proxy headers (e.g. `X-Forwarded-Proto`). Enable via `REST_TRUST_PROXY=true` when running behind a load balancer such as AWS ALB/ELB to ensure correct protocol detection. |
79
83
  | `statusMap` | **Object** | `{}` | Optional mapping of HTTP status codes to custom messages |
80
- | `debug` | **Boolean** | `false` | Enable debug logging during route setup |
84
+
85
+ ### Running Behind a Load Balancer
86
+
87
+ When your application runs behind a reverse proxy or load balancer (e.g. AWS ALB/ELB), the load balancer terminates SSL and forwards requests to your server over HTTP internally. This means Express sees `http` as the protocol even though the original client request used `https`.
88
+
89
+ To fix this, enable the `trustProxy` option:
90
+
91
+ ```bash
92
+ REST_TRUST_PROXY=true
93
+ ```
94
+
95
+ This tells Express to trust the `X-Forwarded-Proto` header set by the load balancer, so `request.protocol` correctly returns `https`. This is important for any functionality that generates URLs based on the incoming request protocol, such as JSON:API relationship links.
81
96
 
82
97
  ## Request Class
83
98
 
@@ -135,10 +150,6 @@ project-root/
135
150
 
136
151
  The `RestServer` will automatically mount these routes using the filenames as paths (`/public` and `/private` by default, or camelCased if configured).
137
152
 
138
- Perfect! Here’s a self-contained **“Example Requests”** section with sample `curl` calls:
139
-
140
- ---
141
-
142
153
  ### Example Requests
143
154
 
144
155
  Assuming you have `public.js` and `private.js` routes mounted, you can test them like this:
@@ -3,8 +3,9 @@ const {
3
3
  REST_CORS_METHODS,
4
4
  REST_HEALTH_CHECK_DISABLE,
5
5
  REST_PORT,
6
- REST_REQUEST_PATH
7
- } = process;
6
+ REST_REQUEST_PATH,
7
+ REST_TRUST_PROXY
8
+ } = process.env;
8
9
 
9
10
  export default {
10
11
  enableHealthCheck: REST_HEALTH_CHECK_DISABLE !== 'true',
@@ -12,6 +13,7 @@ export default {
12
13
  methods: REST_CORS_METHODS ?? 'GET,POST,PATCH,PUT,DELETE',
13
14
  dir: REST_REQUEST_PATH ?? './requests',
14
15
  port: REST_PORT ?? 2666,
16
+ trustProxy: REST_TRUST_PROXY === 'true',
15
17
  logColor: 'yellow',
16
18
  logMethod: 'api'
17
19
  };
package/dist/main.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { type Express } from 'express';
2
+ import type { Server } from 'http';
3
+ export { default as Request } from './request.js';
4
+ export default class RestServer {
5
+ static instance: RestServer;
6
+ api: Express;
7
+ server: Server;
8
+ constructor();
9
+ static close(): void;
10
+ init(): Promise<void>;
11
+ setupRouter(): Promise<void>;
12
+ setupGlobalMiddleware(): void;
13
+ mountRoute(routeClassUntyped: unknown, { name, options }: {
14
+ name: string;
15
+ options?: unknown;
16
+ }): void;
17
+ }
package/dist/main.js ADDED
@@ -0,0 +1,81 @@
1
+ /*
2
+ * Copyright 2025 Stone Costa
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the 'License');
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import cors from 'cors';
17
+ import express from 'express';
18
+ import config from 'stonyx/config';
19
+ import log from 'stonyx/log';
20
+ import { forEachFileImport } from '@stonyx/utils/file';
21
+ export { default as Request } from './request.js';
22
+ export default class RestServer {
23
+ static instance;
24
+ api;
25
+ server;
26
+ constructor() {
27
+ if (RestServer.instance)
28
+ return RestServer.instance;
29
+ RestServer.instance = this;
30
+ this.api = express();
31
+ }
32
+ static close() {
33
+ if (!RestServer.instance)
34
+ throw new Error('RestServer has not been initialized yet');
35
+ const { server } = RestServer.instance;
36
+ server.closeAllConnections();
37
+ server.close();
38
+ }
39
+ async init() {
40
+ await this.setupRouter();
41
+ const { port } = config.restServer;
42
+ // start REST server
43
+ this.server = this.api.listen(port);
44
+ log.title(`API Server is listening on port ${port}`);
45
+ }
46
+ async setupRouter() {
47
+ const { camelCaseRoutes, dir, enableHealthCheck } = config.restServer;
48
+ this.setupGlobalMiddleware();
49
+ try {
50
+ await forEachFileImport(dir, this.mountRoute.bind(this), { rawName: !camelCaseRoutes, ignoreAccessFailure: true });
51
+ if (enableHealthCheck)
52
+ this.api.get('/health', (_req, res) => res.sendStatus(200));
53
+ }
54
+ catch (error) {
55
+ if (config.debug)
56
+ console.log(error);
57
+ log.error(`Unable to dynamically configure routes from files in ${dir}`);
58
+ throw new Error(`Unable to dynamically configure routes from files in ${dir}`);
59
+ }
60
+ }
61
+ setupGlobalMiddleware() {
62
+ const { origin, methods, trustProxy } = config.restServer;
63
+ if (trustProxy)
64
+ this.api.set('trust proxy', true);
65
+ this.api.use([
66
+ cors({ origin, methods }),
67
+ express.json()
68
+ ]);
69
+ }
70
+ mountRoute(routeClassUntyped, { name, options }) {
71
+ const routeClass = routeClassUntyped;
72
+ const { api } = this;
73
+ const classInstance = new routeClass(options);
74
+ const route = name === 'index' ? '/' : `/${name}`;
75
+ const { expressInstance } = classInstance;
76
+ classInstance.registerCalls();
77
+ expressInstance.mountpath = route;
78
+ // Mount handler to main api instance
79
+ api.use(route, expressInstance);
80
+ }
81
+ }
@@ -0,0 +1,15 @@
1
+ import { type Request as ExpressRequest, type Response as ExpressResponse, type Express } from 'express';
2
+ export type RequestState = Record<string, unknown>;
3
+ export type RequestHandler = (req: ExpressRequest, state: RequestState) => unknown | Promise<unknown>;
4
+ export type AuthHandler = (req: ExpressRequest, state: RequestState) => number | undefined;
5
+ export type RouteHandlers = Record<string, Record<string, RequestHandler | RequestHandler[]>>;
6
+ export default class Request {
7
+ static stateProp: string;
8
+ static getState(req: ExpressRequest): RequestState;
9
+ static sendStatusResponse(res: ExpressResponse, status: number): void;
10
+ expressInstance: Express;
11
+ handlers: RouteHandlers;
12
+ auth?: AuthHandler;
13
+ constructor();
14
+ registerCalls(): void;
15
+ }
@@ -0,0 +1,86 @@
1
+ import express from 'express';
2
+ import config from 'stonyx/config';
3
+ import { makeArray } from '@stonyx/utils/object';
4
+ const METHODS = new Set(['get', 'post', 'put', 'delete', 'patch']);
5
+ export default class Request {
6
+ static stateProp = '__stonyxState';
7
+ static getState(req) {
8
+ const { stateProp } = Request;
9
+ const record = req;
10
+ if (record[stateProp] !== undefined)
11
+ return record[stateProp];
12
+ record[stateProp] = {};
13
+ return record[stateProp];
14
+ }
15
+ static sendStatusResponse(res, status) {
16
+ const statusMap = config.restServer?.statusMap ?? {};
17
+ const message = statusMap[status] || '';
18
+ if (message) {
19
+ res.status(status).send(message);
20
+ }
21
+ else {
22
+ res.sendStatus(status);
23
+ }
24
+ }
25
+ expressInstance;
26
+ handlers;
27
+ constructor() {
28
+ const api = express();
29
+ api.disable('x-powered-by');
30
+ this.expressInstance = api;
31
+ }
32
+ registerCalls() {
33
+ const { expressInstance } = this;
34
+ const { getState, sendStatusResponse } = Request;
35
+ for (const [method, handlers] of Object.entries(this.handlers)) {
36
+ if (!METHODS.has(method)) {
37
+ console.warn(`Method "${method}" is not a valid HTTP method. Skipping...`);
38
+ continue;
39
+ }
40
+ for (const [route, handler] of Object.entries(handlers)) {
41
+ expressInstance[method](route, async (req, res) => {
42
+ // Run auth after route matching so request.params is populated
43
+ if (this.auth) {
44
+ const status = this.auth(req, getState(req));
45
+ if (status)
46
+ return sendStatusResponse(res, status);
47
+ }
48
+ const callStack = [...makeArray(handler)];
49
+ const mainCall = callStack.pop();
50
+ let response;
51
+ // Run middleware
52
+ while (callStack.length) {
53
+ response = await callStack.shift().bind(this)(req, getState(req));
54
+ if (response !== undefined)
55
+ break;
56
+ }
57
+ if (response === undefined)
58
+ response = await mainCall(req, getState(req));
59
+ if (Number.isInteger(response))
60
+ return sendStatusResponse(res, response);
61
+ // Handle redirect if set via call state object
62
+ const state = getState(req);
63
+ const { redirect } = state;
64
+ if (redirect)
65
+ return res.redirect(redirect);
66
+ // Handle pipe if set via call state object
67
+ const { pipe } = state;
68
+ if (pipe) {
69
+ const { headers, source } = pipe;
70
+ if (headers)
71
+ for (const [key, value] of Object.entries(headers))
72
+ res.set(key, value);
73
+ return source.pipe(res);
74
+ }
75
+ if (response === undefined) {
76
+ res.sendStatus(200);
77
+ return;
78
+ }
79
+ if (typeof response !== 'object')
80
+ return sendStatusResponse(res, 500);
81
+ res.send(response);
82
+ });
83
+ }
84
+ }
85
+ }
86
+ }
package/package.json CHANGED
@@ -4,22 +4,31 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.2.1-alpha.0",
7
+ "version": "0.2.1-alpha.10",
8
8
  "description": "Rest Server Module for Stonyx Framework",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "https://github.com/abofs/stonyx-rest-server"
12
12
  },
13
- "main": "src/main.js",
13
+ "main": "dist/main.js",
14
+ "types": "dist/main.d.ts",
14
15
  "type": "module",
15
16
  "exports": {
16
- ".": "./src/main.js"
17
+ ".": {
18
+ "types": "./dist/main.d.ts",
19
+ "default": "./dist/main.js"
20
+ }
17
21
  },
18
22
  "author": "Stone Costa",
19
23
  "license": "Apache-2.0",
20
24
  "contributors": [
21
25
  "Stone Costa <stone.costa@synamicd.com>"
22
26
  ],
27
+ "files": [
28
+ "dist",
29
+ "config",
30
+ "README.md"
31
+ ],
23
32
  "publishConfig": {
24
33
  "access": "public",
25
34
  "provenance": true
@@ -27,14 +36,22 @@
27
36
  "dependencies": {
28
37
  "cors": "^2.8.5",
29
38
  "express": "^5.1.0",
30
- "stonyx": "^0.2.2"
39
+ "stonyx": "0.2.3-beta.12"
31
40
  },
32
41
  "devDependencies": {
33
- "@stonyx/utils": "^0.2.2",
42
+ "@stonyx/utils": "0.2.3-beta.7",
43
+ "@types/cors": "^2.8.17",
44
+ "@types/express": "^5.0.6",
45
+ "@types/node": "^25.5.2",
46
+ "@types/qunit": "^2.19.13",
47
+ "@types/sinon": "^21.0.1",
34
48
  "qunit": "^2.24.1",
35
- "sinon": "^21.0.0"
49
+ "sinon": "^21.0.0",
50
+ "typescript": "^5.8.3"
36
51
  },
37
52
  "scripts": {
38
- "test": "qunit --require ./stonyx-bootstrap.cjs"
53
+ "build": "tsc",
54
+ "build:test": "tsc -p tsconfig.test.json",
55
+ "test": "pnpm build && pnpm build:test && stonyx test 'dist-test/test/**/*-test.js'"
39
56
  }
40
57
  }
@@ -1,36 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- pull_request:
5
- branches:
6
- - dev
7
- - main
8
-
9
- concurrency:
10
- group: ci-${{ github.head_ref || github.ref }}
11
- cancel-in-progress: true
12
-
13
- jobs:
14
- test:
15
- runs-on: ubuntu-latest
16
-
17
- steps:
18
- - name: Checkout code
19
- uses: actions/checkout@v3
20
-
21
- - name: Setup pnpm
22
- uses: pnpm/action-setup@v4
23
- with:
24
- version: 9
25
-
26
- - name: Set up Node.js
27
- uses: actions/setup-node@v3
28
- with:
29
- node-version: 24.13.0
30
- cache: 'pnpm'
31
-
32
- - name: Install dependencies
33
- run: pnpm install --frozen-lockfile
34
-
35
- - name: Run tests
36
- run: pnpm test
@@ -1,143 +0,0 @@
1
- name: Publish to NPM
2
-
3
- on:
4
- # Manual trigger (kept for flexibility)
5
- workflow_dispatch:
6
- inputs:
7
- version-type:
8
- description: 'Version type'
9
- required: true
10
- type: choice
11
- options:
12
- - alpha
13
- - patch
14
- - minor
15
- - major
16
- custom-version:
17
- description: 'Custom version (optional, overrides version-type)'
18
- required: false
19
- type: string
20
-
21
- # Auto-publish alpha on PR
22
- pull_request:
23
- types: [opened, synchronize, reopened]
24
- branches: [main, dev]
25
-
26
- # Auto-publish stable on merge to main
27
- push:
28
- branches: [main]
29
-
30
- permissions:
31
- contents: write
32
- id-token: write # Required for npm provenance
33
- pull-requests: write # For PR comments
34
-
35
- jobs:
36
- publish:
37
- runs-on: ubuntu-latest
38
-
39
- steps:
40
- - name: Checkout code
41
- uses: actions/checkout@v3
42
- with:
43
- fetch-depth: 0
44
- # For PR events, check out the PR branch
45
- ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
46
-
47
- - name: Setup pnpm
48
- uses: pnpm/action-setup@v4
49
- with:
50
- version: 9
51
-
52
- - name: Set up Node.js
53
- uses: actions/setup-node@v3
54
- with:
55
- node-version: 24.13.0
56
- cache: 'pnpm'
57
- registry-url: 'https://registry.npmjs.org'
58
-
59
- - name: Install dependencies
60
- run: pnpm install --frozen-lockfile
61
-
62
- - name: Run tests
63
- run: pnpm test
64
-
65
- - name: Configure git
66
- run: |
67
- git config user.name "github-actions[bot]"
68
- git config user.email "github-actions[bot]@users.noreply.github.com"
69
-
70
- # Determine version type based on trigger
71
- - name: Determine version bump type
72
- id: version-type
73
- run: |
74
- if [ "${{ github.event_name }}" = "pull_request" ]; then
75
- echo "type=alpha" >> $GITHUB_OUTPUT
76
- elif [ "${{ github.event_name }}" = "push" ]; then
77
- echo "type=patch" >> $GITHUB_OUTPUT
78
- elif [ "${{ github.event.inputs.custom-version }}" != "" ]; then
79
- echo "type=custom" >> $GITHUB_OUTPUT
80
- else
81
- echo "type=${{ github.event.inputs.version-type }}" >> $GITHUB_OUTPUT
82
- fi
83
-
84
- # Version bumping
85
- - name: Bump version (custom)
86
- if: steps.version-type.outputs.type == 'custom'
87
- run: pnpm version ${{ github.event.inputs.custom-version }} --no-git-tag-version
88
-
89
- - name: Bump version (alpha)
90
- if: steps.version-type.outputs.type == 'alpha'
91
- run: pnpm version prerelease --preid=alpha --no-git-tag-version
92
-
93
- - name: Bump version (patch/minor/major)
94
- if: steps.version-type.outputs.type == 'patch' || steps.version-type.outputs.type == 'minor' || steps.version-type.outputs.type == 'major'
95
- run: pnpm version ${{ steps.version-type.outputs.type }} --no-git-tag-version
96
-
97
- - name: Get package version
98
- id: package-version
99
- run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
100
-
101
- # Publishing
102
- - name: Publish to NPM (alpha)
103
- if: contains(steps.package-version.outputs.version, 'alpha')
104
- run: pnpm publish --tag alpha --access public --no-git-checks
105
-
106
- - name: Publish to NPM (stable)
107
- if: "!contains(steps.package-version.outputs.version, 'alpha')"
108
- run: pnpm publish --access public
109
-
110
- # Only commit and tag for stable releases (push to main or manual stable)
111
- - name: Commit version bump and create tag
112
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !contains(steps.package-version.outputs.version, 'alpha'))
113
- run: |
114
- git add package.json
115
- git commit -m "chore: release v${{ steps.package-version.outputs.version }}"
116
- git tag v${{ steps.package-version.outputs.version }}
117
- git push origin main --tags
118
-
119
- - name: Create GitHub Release
120
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && !contains(steps.package-version.outputs.version, 'alpha'))
121
- uses: actions/create-release@v1
122
- env:
123
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124
- with:
125
- tag_name: v${{ steps.package-version.outputs.version }}
126
- release_name: v${{ steps.package-version.outputs.version }}
127
- draft: false
128
- prerelease: false
129
-
130
- # Add PR comment with alpha version info
131
- - name: Comment on PR with alpha version
132
- if: github.event_name == 'pull_request'
133
- uses: actions/github-script@v6
134
- with:
135
- script: |
136
- const version = '${{ steps.package-version.outputs.version }}';
137
- const packageName = require('./package.json').name;
138
- github.rest.issues.createComment({
139
- issue_number: context.issue.number,
140
- owner: context.repo.owner,
141
- repo: context.repo.repo,
142
- body: `## 🚀 Alpha Version Published\n\n**Version:** \`${version}\`\n\n**Install:**\n\`\`\`bash\npnpm add ${packageName}@${version}\n# or\npnpm add ${packageName}@alpha # latest alpha\n\`\`\`\n\nThis alpha version is now available for testing!`
143
- });
package/src/main.js DELETED
@@ -1,89 +0,0 @@
1
- /*
2
- * Copyright 2025 Stone Costa
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the 'License');
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- import cors from 'cors';
18
- import express from 'express';
19
- import config from 'stonyx/config';
20
- import log from 'stonyx/log';
21
- import { forEachFileImport } from '@stonyx/utils/file';
22
-
23
- export { default as Request } from './request.js';
24
-
25
- export default class RestServer {
26
- constructor() {
27
- if (RestServer.instance) return RestServer.instance;
28
- RestServer.instance = this;
29
-
30
- this.api = new express();
31
- }
32
-
33
- static close() {
34
- if (!RestServer.instance) throw new Error('RestServer has not been initialized yet');
35
-
36
- RestServer.instance.server.close();
37
- }
38
-
39
- async init() {
40
- await this.setupRouter();
41
-
42
- const { port } = config.restServer;
43
-
44
- // start REST server
45
- this.server = this.api.listen(port);
46
- log.title(`API Server is listening on port ${port}`);
47
- }
48
-
49
- async setupRouter() {
50
- const { camelCaseRoutes, dir, enableHealthCheck } = config.restServer;
51
- this.setupGlobalMiddleware();
52
-
53
- try {
54
- await forEachFileImport(dir, this.mountRoute.bind(this), { rawName: !camelCaseRoutes, ignoreAccessFailure: true });
55
-
56
- if (enableHealthCheck) this.api.get('/health', (_req, res) => res.sendStatus(200));
57
- } catch (error) {
58
- if (config.debug) console.log(error);
59
- throw log.error(`Unable to dynamically configure routes from files in ${dir}`);
60
- }
61
- }
62
-
63
- async setupGlobalMiddleware() {
64
- const { origin, methods } = config.restServer;
65
-
66
- this.api.use([
67
- cors({ origin, methods }),
68
- express.json()
69
- ]);
70
- }
71
-
72
- async mountRoute(routeClass, { name, options }) {
73
- const { api } = this;
74
- const classInstance = new routeClass(options);
75
- const route = name === 'index' ? '/' : `/${name}`;
76
- const { expressInstance, authorization } = classInstance;
77
-
78
- const routeCalls = [ expressInstance ];
79
-
80
- // Assign auth callback if it exists
81
- if (authorization) routeCalls.unshift(authorization.bind(classInstance));
82
-
83
- classInstance.registerCalls();
84
- expressInstance.mountpath = route;
85
-
86
- // Mount handler to main api instance
87
- api.use(route, ...routeCalls);
88
- }
89
- }
package/src/request.js DELETED
@@ -1,85 +0,0 @@
1
- import express from 'express';
2
- import config from 'stonyx/config';
3
- import { makeArray } from '@stonyx/utils/object';
4
-
5
- const METHODS = new Set(['get', 'post', 'put', 'delete', 'patch']);
6
-
7
- export default class Request {
8
- static stateProp = '__stonyxState';
9
-
10
- static getState(req) {
11
- const { stateProp } = Request;
12
- if (req[stateProp] !== undefined) return req[stateProp];
13
-
14
- req[stateProp] = {};
15
- return req[stateProp];
16
- }
17
-
18
- static sendStatusResponse(res, status) {
19
- const statusMap = config.restServer?.statusMap || {};
20
- const message = statusMap[status] || '';
21
-
22
- return message ? res.status(status).send(message) : res.sendStatus(status);
23
- }
24
-
25
- constructor() {
26
- const api = express();
27
- api.disable('x-powered-by');
28
-
29
- this.expressInstance = api;
30
- }
31
-
32
- // auth hook wrapper
33
- authorization(req, res, next) {
34
- if (!this.auth) return next();
35
-
36
- const status = this.auth(req, Request.getState(req));
37
- if (status) return Request.sendStatusResponse(res, status);
38
-
39
- next();
40
- }
41
-
42
- registerCalls() {
43
- const { expressInstance } = this;
44
- const { getState } = Request;
45
-
46
- for (const [method, handlers] of Object.entries(this.handlers)) {
47
- if (!METHODS.has(method)) {
48
- console.warn(`Method "${method}" is not a valid HTTP method. Skipping...`);
49
- continue;
50
- }
51
-
52
- for (const [route, handler] of Object.entries(handlers)) {
53
- expressInstance[method](route, async (req, res) => {
54
- const callStack = [...makeArray(handler)];
55
- const mainCall = callStack.pop();
56
- const { sendStatusResponse } = Request;
57
- let response;
58
-
59
- // Run middleware
60
- while(callStack.length) {
61
- response = await callStack.shift().bind(this)(req, getState(req));
62
- if (response !== undefined) break;
63
- }
64
-
65
- if (response === undefined) response = await mainCall(req, getState(req));
66
- if (Number.isInteger(response)) return sendStatusResponse(res, response);
67
-
68
- // Handle pipe if set via call state object
69
- const { pipe } = getState(req);
70
- if (pipe) {
71
- const { headers, source } = pipe;
72
-
73
- if (headers) for (const [key, value] of Object.entries(headers)) res.set(key, value);
74
- return source.pipe(res);
75
- }
76
-
77
- if (response === undefined) return res.sendStatus(200);
78
- if (typeof response !== 'object') return sendStatusResponse(res, 500);
79
-
80
- res.send(response);
81
- });
82
- }
83
- }
84
- }
85
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * commonJS Bootstrap loading - Stonyx must be loaded first, prior to the rest of the application
3
- */
4
- const { default:Stonyx } = require('stonyx');
5
- const { default:config } = require('./config/environment.js');
6
-
7
- // Override dir for tests
8
- config.dir = './test/sample/requests';
9
-
10
- new Stonyx(config, __dirname);
11
-
12
- module.exports = Stonyx;