@molecule/api-server-default-express 1.0.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/LICENSE +115 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +18 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/server.d.ts +148 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +294 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work.
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
42
|
+
represent, as a whole, an original work of authorship.
|
|
43
|
+
|
|
44
|
+
"Contribution" shall mean any work of authorship, including the
|
|
45
|
+
original version of the Work and any modifications or additions
|
|
46
|
+
to that Work, that is intentionally submitted to the Licensor for
|
|
47
|
+
inclusion in the Work by the copyright owner or by an individual or
|
|
48
|
+
Legal Entity authorized to submit on behalf of the copyright owner.
|
|
49
|
+
|
|
50
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
51
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
52
|
+
subsequently incorporated within the Work.
|
|
53
|
+
|
|
54
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
55
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
56
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
57
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
58
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
59
|
+
Work and such Derivative Works in Source or Object form.
|
|
60
|
+
|
|
61
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
62
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
63
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
64
|
+
patent license to make, have made, use, offer to sell, sell, import,
|
|
65
|
+
and otherwise transfer the Work.
|
|
66
|
+
|
|
67
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
68
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
69
|
+
modifications, and in Source or Object form, provided that You
|
|
70
|
+
meet the following conditions:
|
|
71
|
+
|
|
72
|
+
(a) You must give any other recipients of the Work or
|
|
73
|
+
Derivative Works a copy of this License; and
|
|
74
|
+
|
|
75
|
+
(b) You must cause any modified files to carry prominent notices
|
|
76
|
+
stating that You changed the files; and
|
|
77
|
+
|
|
78
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
79
|
+
that You distribute, all copyright, patent, trademark, and
|
|
80
|
+
attribution notices from the Source form of the Work,
|
|
81
|
+
excluding those notices that do not pertain to any part of
|
|
82
|
+
the Derivative Works; and
|
|
83
|
+
|
|
84
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
85
|
+
distribution, then any Derivative Works that You distribute must
|
|
86
|
+
include a readable copy of the attribution notices contained
|
|
87
|
+
within such NOTICE file.
|
|
88
|
+
|
|
89
|
+
5. Submission of Contributions.
|
|
90
|
+
|
|
91
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
92
|
+
names, trademarks, service marks, or product names of the Licensor.
|
|
93
|
+
|
|
94
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
95
|
+
agreed to in writing, Licensor provides the Work on an "AS IS" BASIS,
|
|
96
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
|
|
97
|
+
|
|
98
|
+
8. Limitation of Liability. In no event and under no legal theory shall
|
|
99
|
+
any Contributor be liable to You for damages.
|
|
100
|
+
|
|
101
|
+
9. Accepting Warranty or Additional Liability.
|
|
102
|
+
|
|
103
|
+
Copyright 2026 Molecule Dev, Inc.
|
|
104
|
+
|
|
105
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
106
|
+
you may not use this file except in compliance with the License.
|
|
107
|
+
You may obtain a copy of the License at
|
|
108
|
+
|
|
109
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
110
|
+
|
|
111
|
+
Unless required by applicable law or agreed to in writing, software
|
|
112
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
113
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
114
|
+
See the License for the specific language governing permissions and
|
|
115
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-guard.d.ts","sourceRoot":"","sources":["../src/browser-guard.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser guard — `@molecule/api-server-default-express` is SERVER-ONLY.
|
|
3
|
+
*
|
|
4
|
+
* Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
|
|
5
|
+
* Evaluating a server package in a browser bundle is always an import-graph mistake
|
|
6
|
+
* (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
|
|
7
|
+
* ("Buffer is not defined") far from the culprit. Throwing here names the package and
|
|
8
|
+
* the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
|
|
9
|
+
* unaffected: the throw requires browser globals AND the absence of a node runtime.
|
|
10
|
+
*/
|
|
11
|
+
const g = globalThis;
|
|
12
|
+
if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
|
|
13
|
+
throw new Error('@molecule/api-server-default-express is SERVER-ONLY: it was bundled into browser/client code. Import it only ' +
|
|
14
|
+
'from server code (a server route/function or your API), or dynamic-import it inside ' +
|
|
15
|
+
'the server handler — never from components or shared client modules, and never ' +
|
|
16
|
+
'polyfill Buffer/process to silence this.');
|
|
17
|
+
}
|
|
18
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@molecule/api-server-default-express` — drop-in Express server
|
|
3
|
+
* factory used by the molecule fleet's `api/src/server.ts`.
|
|
4
|
+
*
|
|
5
|
+
* `createServerFactory({ setupBonds, runMigrations, getRouter })`
|
|
6
|
+
* returns a `(port?) => Promise<server>` function that runs
|
|
7
|
+
* migrations, wires bonds, mounts router + middleware, and starts
|
|
8
|
+
* an HTTP (or self-signed HTTPS for local dev) listener.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createServerFactory } from '@molecule/api-server-default-express'
|
|
13
|
+
*
|
|
14
|
+
* // In your app's api/src/server.ts these come from the scaffolded files:
|
|
15
|
+
* // import { setupBonds } from './bonds/index.js'
|
|
16
|
+
* // import { runMigrations } from './scripts/migrate.js'
|
|
17
|
+
* const create = createServerFactory({
|
|
18
|
+
* setupBonds,
|
|
19
|
+
* runMigrations,
|
|
20
|
+
* // Router loads lazily AFTER setupBonds() so bond-conditional route
|
|
21
|
+
* // maps see fully-registered providers. The module must export
|
|
22
|
+
* // `router`. In your app: getRouter: () => import('./App/router.js')
|
|
23
|
+
* getRouter: async () => ({ router }),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // Runs migrations → wires bonds → mounts middleware + router at /api →
|
|
27
|
+
* // listens on PORT (default 4000). The scaffolded server.ts exports
|
|
28
|
+
* // `create` and invokes it when the file is run directly.
|
|
29
|
+
* await create()
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
export * from './browser-guard.js';
|
|
35
|
+
export * from './server.js';
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@molecule/api-server-default-express` — drop-in Express server
|
|
3
|
+
* factory used by the molecule fleet's `api/src/server.ts`.
|
|
4
|
+
*
|
|
5
|
+
* `createServerFactory({ setupBonds, runMigrations, getRouter })`
|
|
6
|
+
* returns a `(port?) => Promise<server>` function that runs
|
|
7
|
+
* migrations, wires bonds, mounts router + middleware, and starts
|
|
8
|
+
* an HTTP (or self-signed HTTPS for local dev) listener.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createServerFactory } from '@molecule/api-server-default-express'
|
|
13
|
+
*
|
|
14
|
+
* // In your app's api/src/server.ts these come from the scaffolded files:
|
|
15
|
+
* // import { setupBonds } from './bonds/index.js'
|
|
16
|
+
* // import { runMigrations } from './scripts/migrate.js'
|
|
17
|
+
* const create = createServerFactory({
|
|
18
|
+
* setupBonds,
|
|
19
|
+
* runMigrations,
|
|
20
|
+
* // Router loads lazily AFTER setupBonds() so bond-conditional route
|
|
21
|
+
* // maps see fully-registered providers. The module must export
|
|
22
|
+
* // `router`. In your app: getRouter: () => import('./App/router.js')
|
|
23
|
+
* getRouter: async () => ({ router }),
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // Runs migrations → wires bonds → mounts middleware + router at /api →
|
|
27
|
+
* // listens on PORT (default 4000). The scaffolded server.ts exports
|
|
28
|
+
* // `create` and invokes it when the file is run directly.
|
|
29
|
+
* await create()
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
export * from './browser-guard.js';
|
|
35
|
+
export * from './server.js';
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import express from 'express';
|
|
4
|
+
/** A deliberately-tagged molecule error mapped to a real HTTP status by the API. */
|
|
5
|
+
export interface TaggedError {
|
|
6
|
+
/** HTTP status to return (e.g. 503 for a missing provider config). */
|
|
7
|
+
statusCode: number;
|
|
8
|
+
/** Machine-readable key the app/IDE maps to a friendly message. */
|
|
9
|
+
errorKey: string;
|
|
10
|
+
/** Human-readable message. */
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Classify a thrown value for the API error middleware. Returns a {@link TaggedError}
|
|
15
|
+
* ONLY for values deliberately tagged by molecule with BOTH a numeric `statusCode`
|
|
16
|
+
* AND a string `errorKey` — e.g. a provider's config-missing throw (`statusCode: 503`,
|
|
17
|
+
* `errorKey: 'config.notConfigured'`). These are expected, actionable conditions a
|
|
18
|
+
* user must resolve (a missing `STRIPE_SECRET_KEY` is theirs to set, not a server bug),
|
|
19
|
+
* so the middleware surfaces the real status + errorKey instead of an opaque 500 — the
|
|
20
|
+
* app/IDE can then show "configure X to enable this feature".
|
|
21
|
+
*
|
|
22
|
+
* Requiring BOTH fields is deliberate: it keeps arbitrary library errors that merely
|
|
23
|
+
* carry a `.statusCode` (e.g. an AWS SDK error) from being silently surfaced with a
|
|
24
|
+
* status molecule never chose. Returns `null` for everything else (→ default 500 path).
|
|
25
|
+
*
|
|
26
|
+
* @param error - The thrown value caught by the error middleware.
|
|
27
|
+
* @returns The classified tagged error, or `null` if it isn't a molecule-tagged error.
|
|
28
|
+
*/
|
|
29
|
+
export declare function classifyTaggedError(error: unknown): TaggedError | null;
|
|
30
|
+
/**
|
|
31
|
+
* Terminal Express error middleware for the canonical molecule fleet server.
|
|
32
|
+
*
|
|
33
|
+
* Resolves a thrown value to exactly one of three sanitized responses and NEVER
|
|
34
|
+
* delegates to Express's built-in `finalhandler`:
|
|
35
|
+
*
|
|
36
|
+
* 1. Bare-string `Unauthorized` / `Unauthorized.` → `401` with the string body
|
|
37
|
+
* (so authSelf-style middleware routes to 401 instead of a 500 page).
|
|
38
|
+
* 2. A deliberately-tagged molecule error ({@link classifyTaggedError}) → its real
|
|
39
|
+
* `statusCode` + `{ error, errorKey }` JSON (expected, user-actionable config
|
|
40
|
+
* conditions, e.g. a missing `STRIPE_SECRET_KEY` → 503 `config.notConfigured`).
|
|
41
|
+
* 3. EVERYTHING else (untagged library throws, null derefs, driver errors) → a
|
|
42
|
+
* generic `500 { error: 'Internal Server Error' }`, logged server-side AND
|
|
43
|
+
* reported to the bonded error tracker (`@molecule/api-error-tracking`'s
|
|
44
|
+
* `captureException`, a documented no-op when no tracker is bonded).
|
|
45
|
+
*
|
|
46
|
+
* Only case 3 is captured: cases 1–2 (401s, tagged config-missing 503s, and any
|
|
47
|
+
* other tagged 4xx/5xx) are expected, user-actionable conditions — not defects —
|
|
48
|
+
* so reporting them would drown real faults in noise.
|
|
49
|
+
*
|
|
50
|
+
* Case 3 is the security-critical branch: it is safe-by-construction and does NOT
|
|
51
|
+
* depend on `NODE_ENV`. Calling `next(error)` here would fall through to Express's
|
|
52
|
+
* `finalhandler`, which embeds `err.stack` in the HTTP response body whenever
|
|
53
|
+
* `app.get('env') !== 'production'` (the default when `NODE_ENV` is unset or
|
|
54
|
+
* `development`), disclosing absolute server paths, module layout, dependency
|
|
55
|
+
* versions, and query/data fragments. Returning the opaque 500 unconditionally
|
|
56
|
+
* removes that leak for every flagship app regardless of how it is deployed.
|
|
57
|
+
*
|
|
58
|
+
* @param error - The thrown value caught by Express.
|
|
59
|
+
* @param req - The request (used only as capture context for error tracking).
|
|
60
|
+
* @param res - The response to write the sanitized error to.
|
|
61
|
+
* @param _next - The next function (intentionally never called for untagged errors).
|
|
62
|
+
*/
|
|
63
|
+
export declare const errorMiddleware: express.ErrorRequestHandler;
|
|
64
|
+
/** Options for `createServerFactory`. */
|
|
65
|
+
export interface CreateServerOptions {
|
|
66
|
+
/** App-specific bond wiring (resolves secrets + wires providers). */
|
|
67
|
+
setupBonds: () => Promise<void>;
|
|
68
|
+
/** DB migration runner (typically the `createMigrator()`-bound function). */
|
|
69
|
+
runMigrations: () => Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Lazy router import. Loaded AFTER `setupBonds()` so bond-conditional
|
|
72
|
+
* route maps see fully-registered providers at module-evaluation time.
|
|
73
|
+
*/
|
|
74
|
+
getRouter: () => Promise<{
|
|
75
|
+
router: express.Router;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Optional hook to mount middleware AFTER cors+cookieParser but
|
|
79
|
+
* BEFORE the body parser. Use this for routes that need their own
|
|
80
|
+
* multipart streaming (file uploads via busboy) — the body parser's
|
|
81
|
+
* `files: 0` config would silently consume the multipart stream.
|
|
82
|
+
*/
|
|
83
|
+
preBodyParser?: (app: express.Express) => Promise<void> | void;
|
|
84
|
+
/**
|
|
85
|
+
* Optional hook called after `setupBonds()` but before the router
|
|
86
|
+
* import. Use for additional one-shot setup (e.g. entitlements
|
|
87
|
+
* tier-registry registration that runs after the bonds are wired).
|
|
88
|
+
*/
|
|
89
|
+
postBondsSetup?: () => Promise<void> | void;
|
|
90
|
+
/**
|
|
91
|
+
* Optional hook to mount middleware on `/api` BEFORE the canonical
|
|
92
|
+
* `app.use('/api', router)` mount. Use for app-specific authed
|
|
93
|
+
* content handlers (`/api`-prefixed) that need to run before the
|
|
94
|
+
* resource router.
|
|
95
|
+
*/
|
|
96
|
+
preApiRouter?: (app: express.Express) => Promise<void> | void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Register a hook to run with the HTTP(S) server right before it listens.
|
|
100
|
+
* Typically called from a bond's setup (e.g. `setupRealtimeSocketio`) during
|
|
101
|
+
* `setupBonds()`, which runs earlier in `create()` than server construction.
|
|
102
|
+
*
|
|
103
|
+
* @param hook - Receives the real `http.Server`/`https.Server`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function registerServerCreatedHook(hook: (server: http.Server | https.Server) => void | Promise<void>): void;
|
|
106
|
+
/**
|
|
107
|
+
* Global browser-security headers applied to EVERY response (mounted before the
|
|
108
|
+
* routers in `createServerFactory`, mirroring the molecule.dev platform server).
|
|
109
|
+
*
|
|
110
|
+
* Defaults are conservative and framework-agnostic — no app-specific CSP source
|
|
111
|
+
* lists, just the clickjacking / MIME-sniffing / referrer baseline a JSON API
|
|
112
|
+
* should always ship:
|
|
113
|
+
*
|
|
114
|
+
* - `X-Content-Type-Options: nosniff` — stop MIME-type sniffing.
|
|
115
|
+
* - `X-Frame-Options: DENY` + `Content-Security-Policy: frame-ancestors 'none'`
|
|
116
|
+
* — anti-clickjacking. A generated app that intends to be embedded (iframe)
|
|
117
|
+
* can override these in its own middleware.
|
|
118
|
+
* - `X-XSS-Protection: 0` — disable the legacy, buggy XSS auditor (modern
|
|
119
|
+
* correct value; CSP is the real defense).
|
|
120
|
+
* - `Referrer-Policy: strict-origin-when-cross-origin` — don't leak full URLs
|
|
121
|
+
* cross-origin.
|
|
122
|
+
* - `Strict-Transport-Security` — production only (mirrors the platform server's
|
|
123
|
+
* `NODE_ENV` check) so local plain-HTTP dev isn't force-upgraded to HTTPS.
|
|
124
|
+
*
|
|
125
|
+
* @param _req - The request (unused).
|
|
126
|
+
* @param res - The response to set headers on.
|
|
127
|
+
* @param next - Express next.
|
|
128
|
+
*/
|
|
129
|
+
export declare const securityHeadersMiddleware: express.RequestHandler;
|
|
130
|
+
/**
|
|
131
|
+
* Returns an Express server-creation function bound to the given
|
|
132
|
+
* setupBonds / runMigrations / router loaders. The returned `create`
|
|
133
|
+
* builds the canonical molecule fleet server:
|
|
134
|
+
*
|
|
135
|
+
* - Migrations run first, then bonds, then router import.
|
|
136
|
+
* - Global browser-security headers ({@link securityHeadersMiddleware})
|
|
137
|
+
* applied before any router (anti-clickjacking + nosniff + referrer baseline).
|
|
138
|
+
* - `bodyParser` / `cookieParser` / `cors` middleware applied.
|
|
139
|
+
* - Router mounted at `/api`.
|
|
140
|
+
* - `/health` endpoint with `{ status: 'ok', timestamp }`.
|
|
141
|
+
* - Bare-string `Unauthorized` / `Unauthorized.` errors normalized to 401.
|
|
142
|
+
* - HTTPS in dev if `process.env.HTTPS` is set, using self-signed
|
|
143
|
+
* certs from optional dependency `pem`.
|
|
144
|
+
* - `process.on('uncaughtException')` + `unhandledRejection` registered
|
|
145
|
+
* on first call (idempotent across multiple `create()` invocations).
|
|
146
|
+
*/
|
|
147
|
+
export declare function createServerFactory(opts: CreateServerOptions): (port?: number) => Promise<express.Express | https.Server>;
|
|
148
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,KAAK,MAAM,YAAY,CAAA;AAG9B,OAAO,OAAO,MAAM,SAAS,CAAA;AAS7B,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAetE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,mBA6CrC,CAAA;AAED,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,6EAA6E;IAC7E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAA;KAAE,CAAC,CAAA;IACpD;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9D;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3C;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC9D;AAYD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACjE,IAAI,CAEN;AAYD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,yBAAyB,EAAE,OAAO,CAAC,cAU/C,CAAA;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,mBAAmB,GACxB,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAgH5D"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import { captureException } from '@molecule/api-error-tracking';
|
|
6
|
+
import { logger } from '@molecule/api-logger';
|
|
7
|
+
import { bodyParser as bodyParserMiddleware } from '@molecule/api-middleware-body-parser';
|
|
8
|
+
import { cookieParser as cookieParserMiddleware } from '@molecule/api-middleware-cookie-parser';
|
|
9
|
+
import { cors as corsMiddleware } from '@molecule/api-middleware-cors';
|
|
10
|
+
import { buildConfigReport, logConfigReport } from '@molecule/api-secrets';
|
|
11
|
+
/**
|
|
12
|
+
* Classify a thrown value for the API error middleware. Returns a {@link TaggedError}
|
|
13
|
+
* ONLY for values deliberately tagged by molecule with BOTH a numeric `statusCode`
|
|
14
|
+
* AND a string `errorKey` — e.g. a provider's config-missing throw (`statusCode: 503`,
|
|
15
|
+
* `errorKey: 'config.notConfigured'`). These are expected, actionable conditions a
|
|
16
|
+
* user must resolve (a missing `STRIPE_SECRET_KEY` is theirs to set, not a server bug),
|
|
17
|
+
* so the middleware surfaces the real status + errorKey instead of an opaque 500 — the
|
|
18
|
+
* app/IDE can then show "configure X to enable this feature".
|
|
19
|
+
*
|
|
20
|
+
* Requiring BOTH fields is deliberate: it keeps arbitrary library errors that merely
|
|
21
|
+
* carry a `.statusCode` (e.g. an AWS SDK error) from being silently surfaced with a
|
|
22
|
+
* status molecule never chose. Returns `null` for everything else (→ default 500 path).
|
|
23
|
+
*
|
|
24
|
+
* @param error - The thrown value caught by the error middleware.
|
|
25
|
+
* @returns The classified tagged error, or `null` if it isn't a molecule-tagged error.
|
|
26
|
+
*/
|
|
27
|
+
export function classifyTaggedError(error) {
|
|
28
|
+
if (typeof error === 'object' &&
|
|
29
|
+
error !== null &&
|
|
30
|
+
typeof error.statusCode === 'number' &&
|
|
31
|
+
typeof error.errorKey === 'string') {
|
|
32
|
+
const e = error;
|
|
33
|
+
return {
|
|
34
|
+
statusCode: e.statusCode,
|
|
35
|
+
errorKey: e.errorKey,
|
|
36
|
+
message: typeof e.message === 'string' ? e.message : 'Error',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Terminal Express error middleware for the canonical molecule fleet server.
|
|
43
|
+
*
|
|
44
|
+
* Resolves a thrown value to exactly one of three sanitized responses and NEVER
|
|
45
|
+
* delegates to Express's built-in `finalhandler`:
|
|
46
|
+
*
|
|
47
|
+
* 1. Bare-string `Unauthorized` / `Unauthorized.` → `401` with the string body
|
|
48
|
+
* (so authSelf-style middleware routes to 401 instead of a 500 page).
|
|
49
|
+
* 2. A deliberately-tagged molecule error ({@link classifyTaggedError}) → its real
|
|
50
|
+
* `statusCode` + `{ error, errorKey }` JSON (expected, user-actionable config
|
|
51
|
+
* conditions, e.g. a missing `STRIPE_SECRET_KEY` → 503 `config.notConfigured`).
|
|
52
|
+
* 3. EVERYTHING else (untagged library throws, null derefs, driver errors) → a
|
|
53
|
+
* generic `500 { error: 'Internal Server Error' }`, logged server-side AND
|
|
54
|
+
* reported to the bonded error tracker (`@molecule/api-error-tracking`'s
|
|
55
|
+
* `captureException`, a documented no-op when no tracker is bonded).
|
|
56
|
+
*
|
|
57
|
+
* Only case 3 is captured: cases 1–2 (401s, tagged config-missing 503s, and any
|
|
58
|
+
* other tagged 4xx/5xx) are expected, user-actionable conditions — not defects —
|
|
59
|
+
* so reporting them would drown real faults in noise.
|
|
60
|
+
*
|
|
61
|
+
* Case 3 is the security-critical branch: it is safe-by-construction and does NOT
|
|
62
|
+
* depend on `NODE_ENV`. Calling `next(error)` here would fall through to Express's
|
|
63
|
+
* `finalhandler`, which embeds `err.stack` in the HTTP response body whenever
|
|
64
|
+
* `app.get('env') !== 'production'` (the default when `NODE_ENV` is unset or
|
|
65
|
+
* `development`), disclosing absolute server paths, module layout, dependency
|
|
66
|
+
* versions, and query/data fragments. Returning the opaque 500 unconditionally
|
|
67
|
+
* removes that leak for every flagship app regardless of how it is deployed.
|
|
68
|
+
*
|
|
69
|
+
* @param error - The thrown value caught by Express.
|
|
70
|
+
* @param req - The request (used only as capture context for error tracking).
|
|
71
|
+
* @param res - The response to write the sanitized error to.
|
|
72
|
+
* @param _next - The next function (intentionally never called for untagged errors).
|
|
73
|
+
*/
|
|
74
|
+
export const errorMiddleware = (error, req, res, _next) => {
|
|
75
|
+
// Match both the bare `Unauthorized` string and the i18n-resolved
|
|
76
|
+
// `Unauthorized.` (with trailing period from the resource locale
|
|
77
|
+
// bond) so authSelf-style middleware reliably routes to 401 instead
|
|
78
|
+
// of the default Express 500 error page.
|
|
79
|
+
if (typeof error === 'string' && /^Unauthorized\.?$/.test(error)) {
|
|
80
|
+
if (!res.headersSent) {
|
|
81
|
+
res.status(401);
|
|
82
|
+
res.send(error);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Deliberately-tagged molecule errors (e.g. a provider config-missing throw:
|
|
87
|
+
// 503 + 'config.notConfigured') are expected, user-actionable conditions — not
|
|
88
|
+
// internal bugs. Surface the real status + errorKey so the app/IDE can render a
|
|
89
|
+
// clean "configure X to enable this feature" instead of an opaque 500.
|
|
90
|
+
const tagged = classifyTaggedError(error);
|
|
91
|
+
if (tagged) {
|
|
92
|
+
// 5xx here means a dependency/config isn't ready, not a server fault — warn,
|
|
93
|
+
// don't error (an error-level log would falsely read as an app defect).
|
|
94
|
+
if (tagged.statusCode >= 500) {
|
|
95
|
+
logger.warn(tagged.message, { errorKey: tagged.errorKey });
|
|
96
|
+
}
|
|
97
|
+
if (!res.headersSent) {
|
|
98
|
+
res.status(tagged.statusCode).json({ error: tagged.message, errorKey: tagged.errorKey });
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Untagged error: a real internal fault. Log the full detail SERVER-SIDE only,
|
|
103
|
+
// then respond with a generic 500. We deliberately do NOT call `next(error)` —
|
|
104
|
+
// that would hand control to Express's finalhandler, which leaks `err.stack`
|
|
105
|
+
// into the response body unless NODE_ENV is exactly 'production'. Safe-by-
|
|
106
|
+
// construction: no stack, no paths, no NODE_ENV dependency.
|
|
107
|
+
logger.error(error);
|
|
108
|
+
// Report the genuine unexpected error to the bonded error tracker. A no-op
|
|
109
|
+
// when no tracker is bonded, and never throws — safe inside this terminal
|
|
110
|
+
// handler. The 401/tagged branches above are deliberately NOT captured.
|
|
111
|
+
// Method/URL only: headers and bodies can carry credentials.
|
|
112
|
+
captureException(error, {
|
|
113
|
+
tags: { source: 'express' },
|
|
114
|
+
request: { method: req.method, url: req.originalUrl },
|
|
115
|
+
});
|
|
116
|
+
if (!res.headersSent) {
|
|
117
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Generic server-lifecycle hooks invoked with the real HTTP(S) server once it
|
|
122
|
+
* has been created in `createServerFactory`, before it starts listening. Lets a
|
|
123
|
+
* bond that must bind to the HTTP server itself — e.g. a Socket.IO realtime
|
|
124
|
+
* provider sharing the API's port at `/socket.io/` — attach at the right moment,
|
|
125
|
+
* without the factory hardcoding any specific bond. Drained (run once then
|
|
126
|
+
* cleared) per `create()` so repeat calls (tests) don't accumulate stale hooks.
|
|
127
|
+
*/
|
|
128
|
+
const serverCreatedHooks = [];
|
|
129
|
+
/**
|
|
130
|
+
* Register a hook to run with the HTTP(S) server right before it listens.
|
|
131
|
+
* Typically called from a bond's setup (e.g. `setupRealtimeSocketio`) during
|
|
132
|
+
* `setupBonds()`, which runs earlier in `create()` than server construction.
|
|
133
|
+
*
|
|
134
|
+
* @param hook - Receives the real `http.Server`/`https.Server`.
|
|
135
|
+
*/
|
|
136
|
+
export function registerServerCreatedHook(hook) {
|
|
137
|
+
serverCreatedHooks.push(hook);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Run and drain all registered server-created hooks.
|
|
141
|
+
*
|
|
142
|
+
* @param server - The HTTP(S) server to hand to each hook.
|
|
143
|
+
*/
|
|
144
|
+
async function runServerCreatedHooks(server) {
|
|
145
|
+
const hooks = serverCreatedHooks.splice(0);
|
|
146
|
+
for (const hook of hooks)
|
|
147
|
+
await hook(server);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Global browser-security headers applied to EVERY response (mounted before the
|
|
151
|
+
* routers in `createServerFactory`, mirroring the molecule.dev platform server).
|
|
152
|
+
*
|
|
153
|
+
* Defaults are conservative and framework-agnostic — no app-specific CSP source
|
|
154
|
+
* lists, just the clickjacking / MIME-sniffing / referrer baseline a JSON API
|
|
155
|
+
* should always ship:
|
|
156
|
+
*
|
|
157
|
+
* - `X-Content-Type-Options: nosniff` — stop MIME-type sniffing.
|
|
158
|
+
* - `X-Frame-Options: DENY` + `Content-Security-Policy: frame-ancestors 'none'`
|
|
159
|
+
* — anti-clickjacking. A generated app that intends to be embedded (iframe)
|
|
160
|
+
* can override these in its own middleware.
|
|
161
|
+
* - `X-XSS-Protection: 0` — disable the legacy, buggy XSS auditor (modern
|
|
162
|
+
* correct value; CSP is the real defense).
|
|
163
|
+
* - `Referrer-Policy: strict-origin-when-cross-origin` — don't leak full URLs
|
|
164
|
+
* cross-origin.
|
|
165
|
+
* - `Strict-Transport-Security` — production only (mirrors the platform server's
|
|
166
|
+
* `NODE_ENV` check) so local plain-HTTP dev isn't force-upgraded to HTTPS.
|
|
167
|
+
*
|
|
168
|
+
* @param _req - The request (unused).
|
|
169
|
+
* @param res - The response to set headers on.
|
|
170
|
+
* @param next - Express next.
|
|
171
|
+
*/
|
|
172
|
+
export const securityHeadersMiddleware = (_req, res, next) => {
|
|
173
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
174
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
175
|
+
res.setHeader('X-XSS-Protection', '0');
|
|
176
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
177
|
+
res.setHeader('Content-Security-Policy', "frame-ancestors 'none'");
|
|
178
|
+
if (process.env.NODE_ENV === 'production') {
|
|
179
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
|
|
180
|
+
}
|
|
181
|
+
next();
|
|
182
|
+
};
|
|
183
|
+
let processHandlersRegistered = false;
|
|
184
|
+
/**
|
|
185
|
+
* Returns an Express server-creation function bound to the given
|
|
186
|
+
* setupBonds / runMigrations / router loaders. The returned `create`
|
|
187
|
+
* builds the canonical molecule fleet server:
|
|
188
|
+
*
|
|
189
|
+
* - Migrations run first, then bonds, then router import.
|
|
190
|
+
* - Global browser-security headers ({@link securityHeadersMiddleware})
|
|
191
|
+
* applied before any router (anti-clickjacking + nosniff + referrer baseline).
|
|
192
|
+
* - `bodyParser` / `cookieParser` / `cors` middleware applied.
|
|
193
|
+
* - Router mounted at `/api`.
|
|
194
|
+
* - `/health` endpoint with `{ status: 'ok', timestamp }`.
|
|
195
|
+
* - Bare-string `Unauthorized` / `Unauthorized.` errors normalized to 401.
|
|
196
|
+
* - HTTPS in dev if `process.env.HTTPS` is set, using self-signed
|
|
197
|
+
* certs from optional dependency `pem`.
|
|
198
|
+
* - `process.on('uncaughtException')` + `unhandledRejection` registered
|
|
199
|
+
* on first call (idempotent across multiple `create()` invocations).
|
|
200
|
+
*/
|
|
201
|
+
export function createServerFactory(opts) {
|
|
202
|
+
return async (port = Number(process.env.PORT) || 4000) => {
|
|
203
|
+
if (!processHandlersRegistered) {
|
|
204
|
+
processHandlersRegistered = true;
|
|
205
|
+
// Genuine unexpected errors — log AND report to the bonded error tracker
|
|
206
|
+
// (captureException no-ops when no tracker is bonded and never throws).
|
|
207
|
+
process.on('uncaughtException', (error) => {
|
|
208
|
+
logger.error(error);
|
|
209
|
+
captureException(error, { tags: { source: 'uncaughtException' } });
|
|
210
|
+
});
|
|
211
|
+
process.on('unhandledRejection', (error) => {
|
|
212
|
+
logger.error(error);
|
|
213
|
+
captureException(error, { tags: { source: 'unhandledRejection' } });
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
await opts.runMigrations();
|
|
217
|
+
await opts.setupBonds();
|
|
218
|
+
if (opts.postBondsSetup)
|
|
219
|
+
await opts.postBondsSetup();
|
|
220
|
+
// Import router after bonds are set up — handler maps are built at
|
|
221
|
+
// module-evaluation time, so bond-conditional handlers need providers
|
|
222
|
+
// registered first.
|
|
223
|
+
const { router } = await opts.getRouter();
|
|
224
|
+
const app = express();
|
|
225
|
+
app.disable('x-powered-by');
|
|
226
|
+
// Browser-security headers on every response — mounted before any router so
|
|
227
|
+
// /api, /health and error responses all carry them (mirrors the platform
|
|
228
|
+
// server). Anti-clickjacking + nosniff + referrer baseline; HSTS in prod only.
|
|
229
|
+
app.use(securityHeadersMiddleware);
|
|
230
|
+
if (opts.preBodyParser) {
|
|
231
|
+
// Custom-ordering path: mount cors+cookie before custom hooks
|
|
232
|
+
// (e.g. multipart uploads) which run BEFORE the body parser
|
|
233
|
+
// would otherwise swallow their streams.
|
|
234
|
+
app.use(corsMiddleware);
|
|
235
|
+
app.use(cookieParserMiddleware);
|
|
236
|
+
await opts.preBodyParser(app);
|
|
237
|
+
app.use(bodyParserMiddleware);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Canonical ordering — body/cookie/cors stacked in that order.
|
|
241
|
+
app.use(bodyParserMiddleware);
|
|
242
|
+
app.use(cookieParserMiddleware);
|
|
243
|
+
app.use(corsMiddleware);
|
|
244
|
+
}
|
|
245
|
+
if (opts.preApiRouter)
|
|
246
|
+
await opts.preApiRouter(app);
|
|
247
|
+
app.use('/api', router);
|
|
248
|
+
app.get('/health', (_req, res) => {
|
|
249
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
250
|
+
});
|
|
251
|
+
// Terminal sanitizing error handler — safe-by-construction. Untagged errors
|
|
252
|
+
// get a generic 500 here and never reach Express's stack-leaking finalhandler.
|
|
253
|
+
app.use(errorMiddleware);
|
|
254
|
+
let server = app;
|
|
255
|
+
if (process.env.HTTPS) {
|
|
256
|
+
// Self-signed cert for local HTTPS dev. `pem` is an optional dep.
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
258
|
+
const pem = require('pem');
|
|
259
|
+
server = await new Promise((resolve, reject) => {
|
|
260
|
+
pem.createCertificate({ days: 1, selfSigned: true }, (error, keys) => {
|
|
261
|
+
if (error)
|
|
262
|
+
reject(error);
|
|
263
|
+
else
|
|
264
|
+
resolve(https.createServer({ key: keys.serviceKey, cert: keys.certificate }, app));
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// The real HTTP(S) server that lifecycle-coupled bonds (e.g. a Socket.IO
|
|
269
|
+
// realtime provider) must bind to. In HTTP mode `server` is the Express app,
|
|
270
|
+
// whose underlying http.Server is otherwise created+discarded by
|
|
271
|
+
// `app.listen()`; create it explicitly so server-created hooks attach to the
|
|
272
|
+
// SAME server the API listens on (Socket.IO then serves `/socket.io/` on the
|
|
273
|
+
// API port). Hooks run before `listen()` so the upgrade handler is ready.
|
|
274
|
+
const httpServer = process.env.HTTPS
|
|
275
|
+
? server
|
|
276
|
+
: http.createServer(app);
|
|
277
|
+
await runServerCreatedHooks(httpServer);
|
|
278
|
+
httpServer.listen(port, () => {
|
|
279
|
+
logger.info(`Started ${process.env.NODE_ENV} ${process.env.HTTPS ? 'HTTPS' : 'HTTP'} server on port ${port}.`);
|
|
280
|
+
// Boot-time configuration report: bonds self-register their secret
|
|
281
|
+
// definitions at import; log which are configured vs missing (each
|
|
282
|
+
// missing REQUIRED key warns with its description + setup URL) so a
|
|
283
|
+
// degraded integration is loud and actionable at startup — instead of
|
|
284
|
+
// surfacing later as a lazy 503 on the first request that needs it.
|
|
285
|
+
// Fire-and-forget: reporting must never delay or fail the boot.
|
|
286
|
+
buildConfigReport()
|
|
287
|
+
.then(logConfigReport)
|
|
288
|
+
.catch((error) => {
|
|
289
|
+
logger.debug('boot config report failed', { error });
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
return server;
|
|
293
|
+
};
|
|
294
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@molecule/api-server-default-express",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Default Express server factory: bonds setup, DB migrations, body/cookie/cors middleware, /api router mount, /health endpoint, 401 normalization, optional HTTPS via self-signed pem certs. Extracts 80-line server.ts shipped by 79 fleet apps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"molecule",
|
|
24
|
+
"server",
|
|
25
|
+
"default",
|
|
26
|
+
"express"
|
|
27
|
+
],
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "26.1.2",
|
|
31
|
+
"typescript": "6.0.3",
|
|
32
|
+
"vitest": "4.1.10",
|
|
33
|
+
"@molecule/api-error-tracking": "1.0.0",
|
|
34
|
+
"@molecule/api-logger": "1.0.0",
|
|
35
|
+
"@molecule/api-middleware-body-parser": "1.0.0",
|
|
36
|
+
"@molecule/api-middleware-cookie-parser": "1.0.0",
|
|
37
|
+
"@molecule/api-middleware-cors": "1.0.0",
|
|
38
|
+
"express": "5.2.1",
|
|
39
|
+
"@types/express": "5.0.6"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@molecule/api-error-tracking": "^1.0.0",
|
|
43
|
+
"@molecule/api-logger": "^1.0.0",
|
|
44
|
+
"@molecule/api-middleware-body-parser": "^1.0.0",
|
|
45
|
+
"@molecule/api-middleware-cookie-parser": "^1.0.0",
|
|
46
|
+
"@molecule/api-middleware-cors": "^1.0.0",
|
|
47
|
+
"@molecule/api-secrets": "^1.0.0",
|
|
48
|
+
"express": "^4.0.0 || ^5.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|