expediate 1.0.4 → 1.0.5
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 +16 -16
- package/README.md +417 -30
- package/dist/apis.d.ts +138 -21
- package/dist/apis.d.ts.map +1 -1
- package/dist/apis.js +172 -79
- package/dist/apis.js.map +1 -1
- package/dist/cjs/apis.js +327 -0
- package/dist/cjs/git.js +293 -0
- package/dist/cjs/index.js +2583 -0
- package/dist/cjs/jwt-auth.js +532 -0
- package/dist/cjs/middleware.js +511 -0
- package/dist/cjs/mimetypes.json +1 -0
- package/dist/cjs/misc.js +787 -0
- package/dist/cjs/openapi.js +485 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/router.js +898 -0
- package/dist/cjs/static.js +669 -0
- package/dist/git.d.ts +71 -8
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +127 -72
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +17 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -24
- package/dist/index.js.map +1 -1
- package/dist/jwt-auth.d.ts +147 -57
- package/dist/jwt-auth.d.ts.map +1 -1
- package/dist/jwt-auth.js +445 -205
- package/dist/jwt-auth.js.map +1 -1
- package/dist/middleware.d.ts +476 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +647 -0
- package/dist/middleware.js.map +1 -0
- package/dist/mimetypes.json +1 -1
- package/dist/misc.d.ts +112 -5
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +235 -102
- package/dist/misc.js.map +1 -1
- package/dist/openapi.d.ts +290 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +481 -0
- package/dist/openapi.js.map +1 -0
- package/dist/router.d.ts +405 -46
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +658 -153
- package/dist/router.js.map +1 -1
- package/dist/static.d.ts +1 -1
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +88 -84
- package/dist/static.js.map +1 -1
- package/package.json +21 -4
- package/.npmignore +0 -16
package/dist/apis.d.ts
CHANGED
|
@@ -1,16 +1,68 @@
|
|
|
1
|
-
import type { RouterRequest, Router } from './router.js';
|
|
1
|
+
import type { RouterRequest, RouterResponse, Router } from './router.js';
|
|
2
|
+
import './openapi.js';
|
|
3
|
+
import type { SpecOptions, SpecFormat, OpenApiDocument } from './openapi.js';
|
|
4
|
+
/**
|
|
5
|
+
* Context object passed as the first argument to every service method handler.
|
|
6
|
+
*
|
|
7
|
+
* Replaces the old flat `params` map: route parameters, URL query parameters,
|
|
8
|
+
* and other request-scoped data are now namespaced to avoid collisions and to
|
|
9
|
+
* make each data source explicit.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* GET: {
|
|
14
|
+
* '/items/:id': function (ctx: ApiContext) {
|
|
15
|
+
* const id = ctx.query.route.id; // ':id' from the path pattern
|
|
16
|
+
* const fmt = ctx.query.url.format; // '?format=json' from the query string
|
|
17
|
+
* return getItem(id, fmt);
|
|
18
|
+
* },
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export interface ApiContext {
|
|
23
|
+
/** Route and URL query parameters, separated by origin. */
|
|
24
|
+
query: {
|
|
25
|
+
/**
|
|
26
|
+
* Named parameters captured from the route pattern.
|
|
27
|
+
*
|
|
28
|
+
* For example, a route registered as `/items/:id` matched against
|
|
29
|
+
* `/items/42` produces `{ id: '42' }`.
|
|
30
|
+
*/
|
|
31
|
+
route: Record<string, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Parameters decoded from the URL query string.
|
|
34
|
+
*
|
|
35
|
+
* Repeated keys are preserved as string arrays.
|
|
36
|
+
* For example, `?q=hello&tag=a&tag=b` → `{ q: 'hello', tag: ['a', 'b'] }`.
|
|
37
|
+
*/
|
|
38
|
+
url: Record<string, string | string[]>;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The request path as seen by this API router, after any prefix stripping
|
|
42
|
+
* performed by a parent `use()` mount.
|
|
43
|
+
*/
|
|
44
|
+
path: string;
|
|
45
|
+
/**
|
|
46
|
+
* Authentication data attached to the request, if present.
|
|
47
|
+
*
|
|
48
|
+
* Populated when a middleware (e.g. a JWT `authenticate` middleware) sets
|
|
49
|
+
* `req.user` before the service method is called. `undefined` when no
|
|
50
|
+
* authentication data has been attached.
|
|
51
|
+
*/
|
|
52
|
+
user?: any;
|
|
53
|
+
}
|
|
2
54
|
/**
|
|
3
55
|
* An API error thrown (or rejected) by a service method.
|
|
4
56
|
*
|
|
5
57
|
* When a service method throws or rejects with an object of this shape, the
|
|
6
58
|
* framework translates it into an HTTP error response automatically:
|
|
7
|
-
* - `
|
|
59
|
+
* - `status` → HTTP status code (defaults to `500`).
|
|
8
60
|
* - `data` → JSON-serialised response body (takes precedence over `message`).
|
|
9
61
|
* - `message` → Plain-text response body.
|
|
10
62
|
*/
|
|
11
63
|
export interface ApiError {
|
|
12
64
|
/** HTTP status code to send (e.g. `404`, `503`). Defaults to `500`. */
|
|
13
|
-
|
|
65
|
+
status?: number;
|
|
14
66
|
/** Structured error payload; serialised to JSON when present. */
|
|
15
67
|
data?: unknown;
|
|
16
68
|
/** Human-readable error message used when `data` is absent. */
|
|
@@ -21,21 +73,23 @@ export interface ApiError {
|
|
|
21
73
|
*
|
|
22
74
|
* Called with `this` bound to the current service instance.
|
|
23
75
|
*
|
|
24
|
-
* @param
|
|
25
|
-
*
|
|
26
|
-
* @
|
|
27
|
-
*
|
|
76
|
+
* @param ctx - Request context containing route parameters, URL query
|
|
77
|
+
* parameters, the request path, and optional auth data.
|
|
78
|
+
* See {@link ApiContext}.
|
|
79
|
+
* @param body - Parsed request body (populated by a body-parsing middleware
|
|
80
|
+
* such as `json()`).
|
|
28
81
|
* @returns The value to send as the JSON response body, a `Promise` of the
|
|
29
82
|
* same, or `undefined` / `null` / any falsy value to send **201 No
|
|
30
83
|
* Content** (useful for mutations that produce no response body).
|
|
31
84
|
*/
|
|
32
|
-
export type ServiceMethod<TInstance = ServiceInstance> = (this: TInstance,
|
|
85
|
+
export type ServiceMethod<TInstance = ServiceInstance, TResponse = any, TBody = any> = (this: TInstance, ctx: ApiContext, body?: TBody) => TResponse | Promise<TResponse>;
|
|
33
86
|
/**
|
|
34
87
|
* The runtime state object that backs a service instance.
|
|
35
88
|
*
|
|
36
|
-
* Produced by `service.data()`
|
|
37
|
-
* `service.methods` before `service.setup()`
|
|
38
|
-
* property carries the scope key when `data()` is
|
|
89
|
+
* Produced by `service.data()` (which returns `Partial<TInstance>`) and
|
|
90
|
+
* extended with the methods from `service.methods` before `service.setup()`
|
|
91
|
+
* is called. A hidden `$key` property carries the scope key when `data()` is
|
|
92
|
+
* not supplied.
|
|
39
93
|
*/
|
|
40
94
|
export type ServiceInstance = Record<string, unknown> & {
|
|
41
95
|
/** The scope key used to identify this instance (set by the framework). */
|
|
@@ -57,6 +111,46 @@ export type ServiceMethods<TInstance extends ServiceInstance = ServiceInstance>
|
|
|
57
111
|
export type RouteMap<TInstance extends ServiceInstance = ServiceInstance> = {
|
|
58
112
|
[path: string]: ServiceMethod<TInstance>;
|
|
59
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Extra methods attached to the router returned by {@link apiBuilder}.
|
|
116
|
+
*
|
|
117
|
+
* These allow the service's route definitions to be introspected and
|
|
118
|
+
* serialised as an OpenAPI 3.1.0 document.
|
|
119
|
+
*/
|
|
120
|
+
export interface ApiRouterExtensions {
|
|
121
|
+
/**
|
|
122
|
+
* Generate an OpenAPI 3.1.0 document from the service definition.
|
|
123
|
+
*
|
|
124
|
+
* @param opts - Top-level spec options (title, version, basePath, …).
|
|
125
|
+
* @returns A plain-object OpenAPI document ready to be serialised with
|
|
126
|
+
* `JSON.stringify`.
|
|
127
|
+
*/
|
|
128
|
+
spec(opts: SpecOptions): OpenApiDocument;
|
|
129
|
+
/**
|
|
130
|
+
* Return a request handler that serves the OpenAPI spec on `GET`.
|
|
131
|
+
*
|
|
132
|
+
* The spec is generated once and cached as a serialised string on the first
|
|
133
|
+
* call to the returned handler.
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* // JSON (default)
|
|
137
|
+
* app.get('/openapi.json', itemsApi.specHandler({ title: 'Items API', version: '1.0.0' }));
|
|
138
|
+
*
|
|
139
|
+
* // YAML
|
|
140
|
+
* app.get('/openapi.yaml', itemsApi.specHandler({ title: 'Items API', version: '1.0.0' }, 'yaml'));
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @param opts - Top-level spec options (title, version, basePath, …).
|
|
144
|
+
* @param format - Output format: `'json'` (default) or `'yaml'`.
|
|
145
|
+
* @returns An Express-compatible middleware handler.
|
|
146
|
+
*/
|
|
147
|
+
specHandler(opts: SpecOptions, format?: SpecFormat): (req: RouterRequest, res: RouterResponse) => void;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The return type of {@link apiBuilder}: a standard {@link Router} augmented
|
|
151
|
+
* with OpenAPI introspection helpers ({@link ApiRouterExtensions}).
|
|
152
|
+
*/
|
|
153
|
+
export type ApiRouter = Router & ApiRouterExtensions;
|
|
60
154
|
/**
|
|
61
155
|
* A service definition object — the single argument to {@link apiBuilder}.
|
|
62
156
|
*
|
|
@@ -90,7 +184,12 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
|
|
|
90
184
|
*/
|
|
91
185
|
scope?: (req: RouterRequest) => string | null;
|
|
92
186
|
/**
|
|
93
|
-
* Factory that returns the initial
|
|
187
|
+
* Factory that returns the **initial data** portion of a new instance.
|
|
188
|
+
*
|
|
189
|
+
* The return type is `Partial<TInstance>` because the methods declared in
|
|
190
|
+
* {@link ServiceDefinition.methods} are mixed in *after* `data()` is called,
|
|
191
|
+
* completing the instance shape. By the time any route handler or
|
|
192
|
+
* `setup()` runs the instance is fully initialised.
|
|
94
193
|
*
|
|
95
194
|
* When omitted, the instance is initialised as `{ $key: key }`.
|
|
96
195
|
*
|
|
@@ -98,14 +197,18 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
|
|
|
98
197
|
* global instances, `null` for ephemeral ones, or the string
|
|
99
198
|
* returned by `scope()`).
|
|
100
199
|
*/
|
|
101
|
-
data?: (key: string | null) => TInstance
|
|
200
|
+
data?: (key: string | null) => Partial<TInstance>;
|
|
102
201
|
/**
|
|
103
202
|
* Lifecycle hook called once after an instance is created and its methods
|
|
104
203
|
* are mixed in.
|
|
105
204
|
*
|
|
106
|
-
* May be synchronous or asynchronous
|
|
107
|
-
* `Promise`
|
|
108
|
-
*
|
|
205
|
+
* May be synchronous or **asynchronous**. When asynchronous, the framework
|
|
206
|
+
* now awaits the returned `Promise` before marking the instance as ready:
|
|
207
|
+
* - For **singleton** services, route handlers are only registered (and
|
|
208
|
+
* requests served) once setup resolves. Requests that arrive while setup
|
|
209
|
+
* is in progress receive **503 Service not ready**.
|
|
210
|
+
* - For **keyed / ephemeral** services, the route handler awaits instance
|
|
211
|
+
* creation (including setup) per request before invoking the method.
|
|
109
212
|
*/
|
|
110
213
|
setup?: (this: TInstance) => void | Promise<void>;
|
|
111
214
|
/**
|
|
@@ -138,11 +241,25 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
|
|
|
138
241
|
* app.use('/api', apiBuilder(myService));
|
|
139
242
|
* ```
|
|
140
243
|
*
|
|
244
|
+
* **Singleton setup lifecycle:**
|
|
245
|
+
* When `service.scope` is absent the service is a singleton. The framework
|
|
246
|
+
* pre-builds the instance eagerly and registers a **503 Service not ready**
|
|
247
|
+
* guard middleware on the router immediately. Route handlers are registered
|
|
248
|
+
* only once `setup()` resolves (or immediately, for sync setup). Any request
|
|
249
|
+
* that arrives before setup completes receives a 503 response.
|
|
250
|
+
*
|
|
251
|
+
* **Keyed / ephemeral setup lifecycle:**
|
|
252
|
+
* Route handlers are registered immediately. For each request,
|
|
253
|
+
* `resolveInstance()` is awaited inside the handler, so `setup()` is always
|
|
254
|
+
* complete before the service method is invoked.
|
|
255
|
+
*
|
|
141
256
|
* **Route handlers** declared in `service.GET`, `service.POST`, etc. are
|
|
142
257
|
* called with `this` bound to the service instance. They receive two
|
|
143
258
|
* arguments:
|
|
144
|
-
* 1. `
|
|
145
|
-
*
|
|
259
|
+
* 1. `ctx` — an {@link ApiContext} object containing `ctx.query.route`
|
|
260
|
+
* (named route parameters), `ctx.query.url` (URL query-string parameters),
|
|
261
|
+
* `ctx.path` (the request path), and `ctx.user` (optional auth data).
|
|
262
|
+
* 2. `body` — the parsed request body from `req.body` (requires a
|
|
146
263
|
* body-parsing middleware such as `json()` to run first).
|
|
147
264
|
*
|
|
148
265
|
* **Return values:**
|
|
@@ -152,15 +269,15 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
|
|
|
152
269
|
* `Promise` resolving to one → `201 No Content` (useful for mutations).
|
|
153
270
|
*
|
|
154
271
|
* **Error handling:**
|
|
155
|
-
* - Throwing or rejecting with `{
|
|
272
|
+
* - Throwing or rejecting with `{ status, message }` sends the
|
|
156
273
|
* corresponding HTTP error.
|
|
157
|
-
* - Throwing or rejecting with `{
|
|
274
|
+
* - Throwing or rejecting with `{ status, data }` sends the `data` object
|
|
158
275
|
* as a JSON body.
|
|
159
276
|
* - Any other thrown value produces `500 Internal Server Error`.
|
|
160
277
|
*
|
|
161
278
|
* @param service - The service definition (see {@link ServiceDefinition}).
|
|
162
279
|
* @returns A router instance pre-configured with all declared routes.
|
|
163
280
|
*/
|
|
164
|
-
export declare function apiBuilder<TInstance extends ServiceInstance = ServiceInstance>(service: ServiceDefinition<TInstance>):
|
|
281
|
+
export declare function apiBuilder<TInstance extends ServiceInstance = ServiceInstance>(service: ServiceDefinition<TInstance>): ApiRouter;
|
|
165
282
|
export default apiBuilder;
|
|
166
283
|
//# sourceMappingURL=apis.d.ts.map
|
package/dist/apis.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apis.d.ts","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"apis.d.ts","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"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"}
|
package/dist/apis.js
CHANGED
|
@@ -19,40 +19,38 @@
|
|
|
19
19
|
* DEALINGS IN THE SOFTWARE.
|
|
20
20
|
*/
|
|
21
21
|
'use strict';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.apiBuilder = apiBuilder;
|
|
27
|
-
const router_js_1 = __importDefault(require("./router.js"));
|
|
22
|
+
import createRouter from './router.js';
|
|
23
|
+
import { openApiSpec, serializeSpec } from './openapi.js';
|
|
28
24
|
// ---------------------------------------------------------------------------
|
|
29
25
|
// Internal helpers
|
|
30
26
|
// ---------------------------------------------------------------------------
|
|
31
27
|
/**
|
|
32
|
-
* Instantiate a new service module for the given scope `key
|
|
28
|
+
* Instantiate a new service module for the given scope `key` and await its
|
|
29
|
+
* setup lifecycle hook.
|
|
33
30
|
*
|
|
34
31
|
* The lifecycle is:
|
|
35
|
-
* 1. **`data(key)`** — create the initial
|
|
36
|
-
*
|
|
32
|
+
* 1. **`data(key)`** — create the initial data object (`Partial<TInstance>`);
|
|
33
|
+
* methods are mixed in next to complete the shape.
|
|
37
34
|
* 2. **Mix in methods** — each entry in `service.methods` is copied onto the
|
|
38
|
-
* instance as a regular function bound to `this`.
|
|
39
|
-
* 3. **`setup()`** —
|
|
35
|
+
* instance as a regular function bound to `this = instance`.
|
|
36
|
+
* 3. **`await setup()`** — if `setup` returns a `Promise`, it is fully
|
|
37
|
+
* awaited before the instance is returned. Any rejection propagates to
|
|
38
|
+
* the caller.
|
|
40
39
|
*
|
|
41
40
|
* @param service - The service definition.
|
|
42
41
|
* @param key - The scope key (`'singleton'`, `null`, or a session ID).
|
|
43
|
-
* @returns A fully initialised service instance.
|
|
42
|
+
* @returns A Promise resolving to a fully initialised service instance.
|
|
44
43
|
*/
|
|
45
|
-
function buildModule(service, key) {
|
|
44
|
+
async function buildModule(service, key) {
|
|
45
|
+
// `data()` returns Partial<TInstance>; methods are mixed in below to complete
|
|
46
|
+
// the instance shape. The cast is intentional and safe — by the time this
|
|
47
|
+
// function returns the instance IS a full TInstance.
|
|
46
48
|
const instance = service.data
|
|
47
49
|
? service.data(key)
|
|
48
50
|
: { $key: key };
|
|
49
51
|
// Mix service methods into the instance, bound to `this = instance`.
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// from the enclosing `buildModule` scope (which holds `(service, key)`).
|
|
53
|
-
// Any arguments forwarded to the method were therefore silently dropped.
|
|
54
|
-
// Corrected to a regular function expression that captures its own `arguments`
|
|
55
|
-
// via a rest parameter spread.
|
|
52
|
+
// Regular function expressions are used (not arrow functions) so that each
|
|
53
|
+
// method has its own `arguments` object and `this` binding works correctly.
|
|
56
54
|
if (service.methods) {
|
|
57
55
|
for (const methodName of Object.keys(service.methods)) {
|
|
58
56
|
const method = service.methods[methodName];
|
|
@@ -61,8 +59,10 @@ function buildModule(service, key) {
|
|
|
61
59
|
};
|
|
62
60
|
}
|
|
63
61
|
}
|
|
62
|
+
// Await setup so that async initialisation (DB connections, config fetches,
|
|
63
|
+
// etc.) completes before the instance is considered ready.
|
|
64
64
|
if (service.setup)
|
|
65
|
-
service.setup.apply(instance, []);
|
|
65
|
+
await service.setup.apply(instance, []);
|
|
66
66
|
return instance;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
@@ -70,32 +70,42 @@ function buildModule(service, key) {
|
|
|
70
70
|
*
|
|
71
71
|
* Instance lifecycle by scope:
|
|
72
72
|
* - **Singleton** (`scope` absent): always returns `modules['singleton']`.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* - **
|
|
76
|
-
*
|
|
73
|
+
* The singleton is guaranteed to be fully initialised before routes run,
|
|
74
|
+
* so this path is synchronous within the async wrapper.
|
|
75
|
+
* - **Keyed** (`scope` returns a string): look up `modules[key]`; on first
|
|
76
|
+
* access, build and cache the instance (in-flight builds for the same key
|
|
77
|
+
* are deduplicated via `building` to avoid concurrent double-builds).
|
|
78
|
+
* - **Ephemeral** (`scope` returns `null`): create a fresh, uncached instance
|
|
79
|
+
* for every request.
|
|
77
80
|
*
|
|
78
|
-
* @param service
|
|
79
|
-
* @param modules
|
|
80
|
-
* @param
|
|
81
|
-
*
|
|
81
|
+
* @param service - The service definition.
|
|
82
|
+
* @param modules - The resolved-instance cache (mutated on first keyed access).
|
|
83
|
+
* @param building - In-flight build-promise cache; prevents duplicate builds
|
|
84
|
+
* for the same key under concurrent requests.
|
|
85
|
+
* @param req - The incoming request.
|
|
86
|
+
* @returns A Promise resolving to the service instance for this request.
|
|
82
87
|
*/
|
|
83
|
-
function resolveInstance(service, modules, req) {
|
|
88
|
+
async function resolveInstance(service, modules, building, req) {
|
|
84
89
|
if (typeof service.scope !== 'function') {
|
|
85
|
-
// Singleton —
|
|
90
|
+
// Singleton — routes only run after setup is complete, so this is safe.
|
|
86
91
|
return modules['singleton'];
|
|
87
92
|
}
|
|
88
|
-
// BUG FIX: the original called `service.key(req)` which does not exist on
|
|
89
|
-
// the service definition. The correct method is `service.scope(req)`.
|
|
90
93
|
const key = service.scope(req);
|
|
91
94
|
if (key === null) {
|
|
92
95
|
// Ephemeral — create a fresh, uncached instance for every request.
|
|
93
96
|
return buildModule(service, null);
|
|
94
97
|
}
|
|
95
|
-
// Keyed — retrieve from cache or
|
|
96
|
-
if (
|
|
97
|
-
modules[key]
|
|
98
|
-
|
|
98
|
+
// Keyed — retrieve from resolved cache or initiate (and deduplicate) a build.
|
|
99
|
+
if (modules[key])
|
|
100
|
+
return modules[key];
|
|
101
|
+
if (!building[key]) {
|
|
102
|
+
building[key] = buildModule(service, key).then(instance => {
|
|
103
|
+
modules[key] = instance;
|
|
104
|
+
delete building[key];
|
|
105
|
+
return instance;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return building[key];
|
|
99
109
|
}
|
|
100
110
|
// ---------------------------------------------------------------------------
|
|
101
111
|
// Response helpers
|
|
@@ -107,9 +117,6 @@ function resolveInstance(service, modules, req) {
|
|
|
107
117
|
* @param data - Any JSON-serialisable value.
|
|
108
118
|
*/
|
|
109
119
|
function sendJson(res, data) {
|
|
110
|
-
// BUG FIX: the original called `res.send(JSON.stringify(val))` without
|
|
111
|
-
// setting a `Content-Type` header. Clients had no way to detect that the
|
|
112
|
-
// response body was JSON. Corrected by setting the header explicitly.
|
|
113
120
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
114
121
|
res.send(JSON.stringify(data));
|
|
115
122
|
}
|
|
@@ -117,15 +124,16 @@ function sendJson(res, data) {
|
|
|
117
124
|
* Translate a caught error (thrown or rejected by a service method) into an
|
|
118
125
|
* HTTP error response.
|
|
119
126
|
*
|
|
120
|
-
* Expected shape: `{
|
|
127
|
+
* Expected shape: `{ status?, data?, message? }` (see {@link ApiError}).
|
|
121
128
|
* Any other thrown value is treated as an opaque 500 Internal Server Error.
|
|
122
129
|
*
|
|
123
130
|
* @param res - The outgoing response.
|
|
124
131
|
* @param err - The caught value.
|
|
125
132
|
*/
|
|
126
133
|
function sendError(res, err) {
|
|
134
|
+
// console.error('Api Err', err)
|
|
127
135
|
const e = err;
|
|
128
|
-
const status = e?.
|
|
136
|
+
const status = e?.status ?? 500;
|
|
129
137
|
if (e?.data !== undefined) {
|
|
130
138
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
131
139
|
res.status(status).send(JSON.stringify(e.data));
|
|
@@ -148,11 +156,25 @@ function sendError(res, err) {
|
|
|
148
156
|
* app.use('/api', apiBuilder(myService));
|
|
149
157
|
* ```
|
|
150
158
|
*
|
|
159
|
+
* **Singleton setup lifecycle:**
|
|
160
|
+
* When `service.scope` is absent the service is a singleton. The framework
|
|
161
|
+
* pre-builds the instance eagerly and registers a **503 Service not ready**
|
|
162
|
+
* guard middleware on the router immediately. Route handlers are registered
|
|
163
|
+
* only once `setup()` resolves (or immediately, for sync setup). Any request
|
|
164
|
+
* that arrives before setup completes receives a 503 response.
|
|
165
|
+
*
|
|
166
|
+
* **Keyed / ephemeral setup lifecycle:**
|
|
167
|
+
* Route handlers are registered immediately. For each request,
|
|
168
|
+
* `resolveInstance()` is awaited inside the handler, so `setup()` is always
|
|
169
|
+
* complete before the service method is invoked.
|
|
170
|
+
*
|
|
151
171
|
* **Route handlers** declared in `service.GET`, `service.POST`, etc. are
|
|
152
172
|
* called with `this` bound to the service instance. They receive two
|
|
153
173
|
* arguments:
|
|
154
|
-
* 1. `
|
|
155
|
-
*
|
|
174
|
+
* 1. `ctx` — an {@link ApiContext} object containing `ctx.query.route`
|
|
175
|
+
* (named route parameters), `ctx.query.url` (URL query-string parameters),
|
|
176
|
+
* `ctx.path` (the request path), and `ctx.user` (optional auth data).
|
|
177
|
+
* 2. `body` — the parsed request body from `req.body` (requires a
|
|
156
178
|
* body-parsing middleware such as `json()` to run first).
|
|
157
179
|
*
|
|
158
180
|
* **Return values:**
|
|
@@ -162,32 +184,30 @@ function sendError(res, err) {
|
|
|
162
184
|
* `Promise` resolving to one → `201 No Content` (useful for mutations).
|
|
163
185
|
*
|
|
164
186
|
* **Error handling:**
|
|
165
|
-
* - Throwing or rejecting with `{
|
|
187
|
+
* - Throwing or rejecting with `{ status, message }` sends the
|
|
166
188
|
* corresponding HTTP error.
|
|
167
|
-
* - Throwing or rejecting with `{
|
|
189
|
+
* - Throwing or rejecting with `{ status, data }` sends the `data` object
|
|
168
190
|
* as a JSON body.
|
|
169
191
|
* - Any other thrown value produces `500 Internal Server Error`.
|
|
170
192
|
*
|
|
171
193
|
* @param service - The service definition (see {@link ServiceDefinition}).
|
|
172
194
|
* @returns A router instance pre-configured with all declared routes.
|
|
173
195
|
*/
|
|
174
|
-
function apiBuilder(service) {
|
|
175
|
-
const api = (
|
|
196
|
+
export function apiBuilder(service) {
|
|
197
|
+
const api = createRouter();
|
|
198
|
+
/** Resolved instance cache: populated once setup completes for a given key. */
|
|
176
199
|
const modules = {};
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// BUG FIX: the original called `buildModule(service)` without a key,
|
|
180
|
-
// passing `undefined` to `data()` and leaving `$key` as `undefined`.
|
|
181
|
-
// Corrected to pass `'singleton'` as the canonical key.
|
|
182
|
-
modules['singleton'] = buildModule(service, 'singleton');
|
|
200
|
+
/** In-flight build promises: deduplicates concurrent keyed instance builds. */
|
|
201
|
+
const building = {};
|
|
183
202
|
/**
|
|
184
203
|
* Register all route handlers from a route map for a given HTTP method.
|
|
185
204
|
*
|
|
186
205
|
* Each handler:
|
|
187
|
-
* 1. Resolves the correct service instance (
|
|
188
|
-
* 2.
|
|
189
|
-
* 3.
|
|
190
|
-
* 4.
|
|
206
|
+
* 1. Resolves the correct service instance (awaiting setup when needed).
|
|
207
|
+
* 2. Builds an {@link ApiContext} from the incoming request.
|
|
208
|
+
* 3. Invokes the service method with `(ctx, body)`.
|
|
209
|
+
* 4. Sends the return value as JSON (or 201 if falsy).
|
|
210
|
+
* 5. Catches thrown / rejected {@link ApiError} objects and translates them
|
|
191
211
|
* into the appropriate HTTP error response.
|
|
192
212
|
*
|
|
193
213
|
* @param routeMap - Map of path patterns to service methods (`undefined` = skip).
|
|
@@ -211,40 +231,113 @@ function apiBuilder(service) {
|
|
|
211
231
|
for (const path of sortedPaths) {
|
|
212
232
|
const method = routeMap[path];
|
|
213
233
|
register(path, (req, res) => {
|
|
214
|
-
const
|
|
234
|
+
const ctx = {
|
|
235
|
+
query: {
|
|
236
|
+
route: req.queries?.route ?? {},
|
|
237
|
+
url: req.queries?.url ?? {},
|
|
238
|
+
},
|
|
239
|
+
path: req.path,
|
|
240
|
+
user: req.user,
|
|
241
|
+
};
|
|
215
242
|
const body = req.body;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
243
|
+
// Await instance resolution (no-op microtask for singletons; may
|
|
244
|
+
// trigger async buildModule for keyed / ephemeral instances).
|
|
245
|
+
resolveInstance(service, modules, building, req)
|
|
246
|
+
.then(instance => {
|
|
247
|
+
const ret = method.apply(instance, [ctx, body]);
|
|
219
248
|
if (ret instanceof Promise) {
|
|
220
|
-
ret
|
|
221
|
-
.then(
|
|
249
|
+
return ret
|
|
250
|
+
.then(val => {
|
|
222
251
|
if (val !== undefined && val !== null && val !== false && val !== 0 && val !== '')
|
|
223
252
|
sendJson(res, val);
|
|
224
253
|
else
|
|
225
254
|
res.status(201).end();
|
|
226
255
|
})
|
|
227
|
-
.catch(
|
|
256
|
+
.catch(err => {
|
|
257
|
+
// console.error(err)
|
|
258
|
+
sendError(res, err);
|
|
259
|
+
});
|
|
228
260
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
catch (err) {
|
|
261
|
+
if (ret !== undefined && ret !== null && ret !== false && ret !== 0 && ret !== '')
|
|
262
|
+
sendJson(res, ret);
|
|
263
|
+
else
|
|
264
|
+
res.status(201).end();
|
|
265
|
+
})
|
|
266
|
+
.catch(err => {
|
|
267
|
+
// console.error(err)
|
|
237
268
|
sendError(res, err);
|
|
238
|
-
}
|
|
269
|
+
});
|
|
239
270
|
});
|
|
240
271
|
}
|
|
241
272
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
273
|
+
/** Convenience wrapper to register routes for all five HTTP verbs. */
|
|
274
|
+
function registerAllRoutes() {
|
|
275
|
+
buildRoutes(service.GET, (path, h) => api.get(path, h));
|
|
276
|
+
buildRoutes(service.POST, (path, h) => api.post(path, h));
|
|
277
|
+
buildRoutes(service.PUT, (path, h) => api.put(path, h));
|
|
278
|
+
buildRoutes(service.DELETE, (path, h) => api.delete(path, h));
|
|
279
|
+
buildRoutes(service.PATCH, (path, h) => api.patch(path, h));
|
|
280
|
+
}
|
|
281
|
+
if (typeof service.scope !== 'function') {
|
|
282
|
+
// ── Singleton ─────────────────────────────────────────────────────────
|
|
283
|
+
// Register a "not ready" guard first so that requests arriving while
|
|
284
|
+
// setup is in progress receive 503 rather than 404.
|
|
285
|
+
let ready = false;
|
|
286
|
+
api.use('/', (_req, res, next) => {
|
|
287
|
+
if (!ready) {
|
|
288
|
+
res.statusCode = 503;
|
|
289
|
+
res.end('Service not ready');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
next();
|
|
293
|
+
});
|
|
294
|
+
// Build the singleton asynchronously, then flip the guard and register
|
|
295
|
+
// routes. Even for synchronous setup() functions, buildModule() is async
|
|
296
|
+
// (it uses await internally), so route registration happens in a microtask
|
|
297
|
+
// that runs before any I/O callbacks — routes are always in place by the
|
|
298
|
+
// time the first HTTP request can be processed.
|
|
299
|
+
buildModule(service, 'singleton')
|
|
300
|
+
.then(instance => {
|
|
301
|
+
modules['singleton'] = instance;
|
|
302
|
+
ready = true;
|
|
303
|
+
registerAllRoutes();
|
|
304
|
+
})
|
|
305
|
+
.catch(err => {
|
|
306
|
+
// setup() rejected — log the error; the guard permanently returns 503.
|
|
307
|
+
console.error('[apiBuilder] singleton setup() rejected:', err);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// ── Keyed / ephemeral ─────────────────────────────────────────────────
|
|
312
|
+
// Register routes immediately. Each handler awaits resolveInstance(),
|
|
313
|
+
// which in turn awaits buildModule() for first-time keyed instances.
|
|
314
|
+
registerAllRoutes();
|
|
315
|
+
}
|
|
316
|
+
// ── OpenAPI introspection ──────────────────────────────────────────────────
|
|
317
|
+
/**
|
|
318
|
+
* Generate an OpenAPI 3.1.0 document from the service definition.
|
|
319
|
+
* Delegates to {@link openApiSpec} from `openapi.ts`.
|
|
320
|
+
*/
|
|
321
|
+
api.spec = function (opts) {
|
|
322
|
+
return openApiSpec(service, opts);
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* Return a middleware handler that serves the OpenAPI spec as JSON or YAML.
|
|
326
|
+
* The spec is generated once and cached on the first call to the returned handler.
|
|
327
|
+
*/
|
|
328
|
+
api.specHandler = function (opts, format = 'json') {
|
|
329
|
+
let cached = null;
|
|
330
|
+
const contentType = format === 'yaml'
|
|
331
|
+
? 'application/yaml; charset=utf-8'
|
|
332
|
+
: 'application/json; charset=utf-8';
|
|
333
|
+
return function (_req, res) {
|
|
334
|
+
if (!cached)
|
|
335
|
+
cached = serializeSpec(openApiSpec(service, opts), format);
|
|
336
|
+
res.setHeader('Content-Type', contentType);
|
|
337
|
+
res.end(cached);
|
|
338
|
+
};
|
|
339
|
+
};
|
|
247
340
|
return api;
|
|
248
341
|
}
|
|
249
|
-
|
|
342
|
+
export default apiBuilder;
|
|
250
343
|
//# sourceMappingURL=apis.js.map
|
package/dist/apis.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apis.js","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,YAAY,CAAC
|
|
1
|
+
{"version":3,"file":"apis.js","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"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"}
|