@owlmeans/server-api 0.1.18-rc.50 → 0.1.18-rc.52
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 +20 -19
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-api/SKILL.md +9 -9
- package/build/server.js +3 -3
- package/build/server.js.map +1 -1
- package/build/utils/context/types.d.ts +13 -0
- package/build/utils/context/types.d.ts.map +1 -0
- package/build/utils/context/types.js +2 -0
- package/build/utils/context/types.js.map +1 -0
- package/build/utils/context.d.ts +3 -3
- package/build/utils/context.d.ts.map +1 -1
- package/build/utils/context.js +5 -2
- package/build/utils/context.js.map +1 -1
- package/build/utils/error/types.d.ts +52 -0
- package/build/utils/error/types.d.ts.map +1 -0
- package/build/utils/error/types.js +2 -0
- package/build/utils/error/types.js.map +1 -0
- package/build/utils/error.d.ts +4 -47
- package/build/utils/error.d.ts.map +1 -1
- package/build/utils/error.js +154 -187
- package/build/utils/error.js.map +1 -1
- package/build/utils/guards.d.ts.map +1 -1
- package/build/utils/guards.js +6 -6
- package/build/utils/guards.js.map +1 -1
- package/build/utils/index.d.ts +4 -1
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/payload/types.d.ts +18 -0
- package/build/utils/payload/types.d.ts.map +1 -0
- package/build/utils/payload/types.js +2 -0
- package/build/utils/payload/types.js.map +1 -0
- package/build/utils/payload.d.ts +3 -15
- package/build/utils/payload.d.ts.map +1 -1
- package/build/utils/payload.js +40 -60
- package/build/utils/payload.js.map +1 -1
- package/build/utils/schema.d.ts +7 -0
- package/build/utils/schema.d.ts.map +1 -0
- package/build/utils/schema.js +20 -0
- package/build/utils/schema.js.map +1 -0
- package/build/utils/server.js +6 -6
- package/build/utils/server.js.map +1 -1
- package/package.json +12 -12
- package/src/server.ts +3 -3
- package/src/utils/context/types.ts +13 -0
- package/src/utils/context.ts +9 -4
- package/src/utils/error/types.ts +52 -0
- package/src/utils/error.ts +156 -186
- package/src/utils/guards.ts +6 -6
- package/src/utils/index.ts +4 -1
- package/src/utils/payload/types.ts +18 -0
- package/src/utils/payload.ts +41 -63
- package/src/utils/schema.ts +23 -0
- package/src/utils/server.ts +6 -6
- package/tests/error-status.spec.ts +11 -11
package/build/utils/payload.js
CHANGED
|
@@ -1,68 +1,48 @@
|
|
|
1
1
|
import { EntrypointOutcome } from '@owlmeans/entrypoint';
|
|
2
2
|
import { ACCEPTED, CREATED, OK } from '@owlmeans/api';
|
|
3
|
-
import {
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
import { httpErrorHelper } from './error.js';
|
|
4
|
+
export const createPayloadHelper = () => {
|
|
5
|
+
const provideRequest = (alias, req, provision) => {
|
|
6
|
+
provision = provision ?? false;
|
|
7
|
+
return {
|
|
8
|
+
alias,
|
|
9
|
+
auth: req._auth ?? undefined,
|
|
10
|
+
// Resolved once by `authorize` and carried by reference, exactly like `_auth`, so a handler
|
|
11
|
+
// reached through any path sees the same entity the guard admitted.
|
|
12
|
+
entity: req._entity ?? undefined,
|
|
13
|
+
params: req.params,
|
|
14
|
+
body: req.body,
|
|
15
|
+
headers: req.headers,
|
|
16
|
+
query: req.query,
|
|
17
|
+
path: req.url,
|
|
18
|
+
original: provision ? req : undefined
|
|
19
|
+
};
|
|
18
20
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* callers don't rely on `reply.sent`, which in fastify v5 only flips once
|
|
24
|
-
* the raw socket finishes writing (asynchronously) and therefore reads
|
|
25
|
-
* `false` synchronously right after `reply.send()`.
|
|
26
|
-
*
|
|
27
|
-
* @throws {Error}
|
|
28
|
-
*/
|
|
29
|
-
export const executeResponse = (response, reply, throwOnError) => {
|
|
30
|
-
if (response.error != null) {
|
|
31
|
-
if (throwOnError ?? false) {
|
|
32
|
-
throw response.error;
|
|
33
|
-
}
|
|
34
|
-
handleError(response.error, reply);
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
else if (response.outcome != null) {
|
|
38
|
-
switch (response.outcome) {
|
|
39
|
-
case EntrypointOutcome.Accepted:
|
|
40
|
-
reply.code(ACCEPTED).send(response.value);
|
|
41
|
-
break;
|
|
42
|
-
case EntrypointOutcome.Created:
|
|
43
|
-
reply.code(CREATED).send(response.value);
|
|
44
|
-
break;
|
|
45
|
-
case EntrypointOutcome.Ok:
|
|
46
|
-
default:
|
|
47
|
-
reply.code(OK).send(response.value);
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
};
|
|
53
|
-
export const fixFormatDates = (schema) => {
|
|
54
|
-
if (schema.type === 'object') {
|
|
55
|
-
if (schema.format === 'date-time') {
|
|
56
|
-
schema.type = 'string';
|
|
57
|
-
schema.format = 'date-time';
|
|
58
|
-
if (schema.required != null) {
|
|
59
|
-
delete schema.required;
|
|
21
|
+
const executeResponse = (response, reply, throwOnError) => {
|
|
22
|
+
if (response.error != null) {
|
|
23
|
+
if (throwOnError ?? false) {
|
|
24
|
+
throw response.error;
|
|
60
25
|
}
|
|
26
|
+
httpErrorHelper.handleError(response.error, reply);
|
|
27
|
+
return true;
|
|
61
28
|
}
|
|
62
|
-
else if (
|
|
63
|
-
|
|
29
|
+
else if (response.outcome != null) {
|
|
30
|
+
switch (response.outcome) {
|
|
31
|
+
case EntrypointOutcome.Accepted:
|
|
32
|
+
reply.code(ACCEPTED).send(response.value);
|
|
33
|
+
break;
|
|
34
|
+
case EntrypointOutcome.Created:
|
|
35
|
+
reply.code(CREATED).send(response.value);
|
|
36
|
+
break;
|
|
37
|
+
case EntrypointOutcome.Ok:
|
|
38
|
+
default:
|
|
39
|
+
reply.code(OK).send(response.value);
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
64
42
|
}
|
|
65
|
-
|
|
66
|
-
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
return { provideRequest, executeResponse };
|
|
67
46
|
};
|
|
47
|
+
export const payloadHelper = createPayloadHelper();
|
|
68
48
|
//# sourceMappingURL=payload.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/utils/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAGxD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAG5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAkB,EAAE;IACrD,MAAM,cAAc,GAAG,CAAC,KAAa,EAAE,GAAY,EAAE,SAAmB,EAAmB,EAAE;QAC3F,SAAS,GAAG,SAAS,IAAI,KAAK,CAAA;QAC9B,OAAO;YACL,KAAK;YACL,IAAI,EAAG,GAAW,CAAC,KAAK,IAAI,SAAS;YACrC,4FAA4F;YAC5F,oEAAoE;YACpE,MAAM,EAAG,GAAW,CAAC,OAAO,IAAI,SAAS;YACzC,MAAM,EAAE,GAAG,CAAC,MAA4D;YACxE,IAAI,EAAE,GAAG,CAAC,IAA2B;YACrC,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAA2D;YACtE,IAAI,EAAE,GAAG,CAAC,GAAG;YACb,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;SACtC,CAAA;IACH,CAAC,CAAA;IAED,MAAM,eAAe,GAAG,CAAI,QAA6B,EAAE,KAAe,EAAE,YAAsB,EAAW,EAAE;QAC7G,IAAI,QAAQ,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;YAC3B,IAAI,YAAY,IAAI,KAAK,EAAE,CAAC;gBAC1B,MAAM,QAAQ,CAAC,KAAK,CAAA;YACtB,CAAC;YACD,eAAe,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;YAClD,OAAO,IAAI,CAAA;QACb,CAAC;aAAM,IAAI,QAAQ,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;YACpC,QAAQ,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACzB,KAAK,iBAAiB,CAAC,QAAQ;oBAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;oBACzC,MAAK;gBACP,KAAK,iBAAiB,CAAC,OAAO;oBAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;oBACxC,MAAK;gBACP,KAAK,iBAAiB,CAAC,EAAE,CAAC;gBAC1B;oBACE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YACvC,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC,CAAA;IAED,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,CAAA;AAC5C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,EAAE,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AnySchemaObject } from 'ajv';
|
|
2
|
+
/**
|
|
3
|
+
* A request schema Fastify can serve: a `date-time` object becomes a `date-time` string (and loses
|
|
4
|
+
* its `required` list), recursively through object properties. Mutates and returns `schema`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const fixFormatDates: (schema: AnySchemaObject) => AnySchemaObject;
|
|
7
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,KAAK,CAAA;AAE1C;;;GAGG;AACH,eAAO,MAAM,cAAc,WAAY,eAAe,KAAG,eAgBxD,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request schema Fastify can serve: a `date-time` object becomes a `date-time` string (and loses
|
|
3
|
+
* its `required` list), recursively through object properties. Mutates and returns `schema`.
|
|
4
|
+
*/
|
|
5
|
+
export const fixFormatDates = (schema) => {
|
|
6
|
+
if (schema.type === 'object') {
|
|
7
|
+
if (schema.format === 'date-time') {
|
|
8
|
+
schema.type = 'string';
|
|
9
|
+
schema.format = 'date-time';
|
|
10
|
+
if (schema.required != null) {
|
|
11
|
+
delete schema.required;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
else if (schema.properties != null) {
|
|
15
|
+
schema.properties = Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, fixFormatDates(value)]));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return schema;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/utils/schema.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAuB,EAAmB,EAAE;IACzE,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAA;YACtB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAA;YAC3B,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;gBAC5B,OAAO,MAAM,CAAC,QAAQ,CAAA;YACxB,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CACpC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,cAAc,CAAC,KAAwB,CAAC,CAAC,CAAC,CACzG,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
|
package/build/utils/server.js
CHANGED
|
@@ -3,8 +3,8 @@ import { provideResponse } from '@owlmeans/entrypoint';
|
|
|
3
3
|
import { ResilientError } from '@owlmeans/error';
|
|
4
4
|
import { OK } from '@owlmeans/api';
|
|
5
5
|
import { logger } from '@owlmeans/log';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { httpErrorHelper } from './error.js';
|
|
7
|
+
import { payloadHelper } from './payload.js';
|
|
8
8
|
import { authorize } from './guards.js';
|
|
9
9
|
import { RouteProtocols } from '@owlmeans/route';
|
|
10
10
|
export const canServeModule = (context, module) => {
|
|
@@ -29,12 +29,12 @@ export const createServerHandler = (module, location) => async (req, reply) => {
|
|
|
29
29
|
context = authorized[0];
|
|
30
30
|
module = authorized[1];
|
|
31
31
|
const response = provideResponse(reply);
|
|
32
|
-
const request = provideRequest(module.alias, req, true);
|
|
32
|
+
const request = payloadHelper.provideRequest(module.alias, req, true);
|
|
33
33
|
const gates = module.getGates();
|
|
34
34
|
for (const [srv, params] of gates) {
|
|
35
35
|
const gate = context.service(srv);
|
|
36
36
|
await gate.assert(request, response, params);
|
|
37
|
-
executeResponse(response, reply, true);
|
|
37
|
+
payloadHelper.executeResponse(response, reply, true);
|
|
38
38
|
}
|
|
39
39
|
await module.handle(request, response);
|
|
40
40
|
// Don't rely on `reply.sent` here: in fastify v5 it is backed by
|
|
@@ -43,7 +43,7 @@ export const createServerHandler = (module, location) => async (req, reply) => {
|
|
|
43
43
|
// synchronously right after `reply.send()`. Track the emitted state
|
|
44
44
|
// explicitly instead, and only fall back to a default response when
|
|
45
45
|
// neither `executeResponse` nor the handler itself (hijack) replied.
|
|
46
|
-
const responded = executeResponse(response, reply, true);
|
|
46
|
+
const responded = payloadHelper.executeResponse(response, reply, true);
|
|
47
47
|
if (!responded && !reply.sent) {
|
|
48
48
|
logger('http').warn(`SENDS DEFAULT RESPONSE: ${module.alias}`);
|
|
49
49
|
reply.code(OK).send(response.value);
|
|
@@ -55,7 +55,7 @@ export const createServerHandler = (module, location) => async (req, reply) => {
|
|
|
55
55
|
fixer.handle(reply, ResilientError.ensure(error));
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
handleError(error, reply, errorExposure(context.cfg));
|
|
58
|
+
httpErrorHelper.handleError(error, reply, httpErrorHelper.errorExposure(context.cfg));
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAG1D,OAAO,EAA2C,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/utils/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAG1D,OAAO,EAA2C,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC/F,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAuB,EAAE,MAAwB,EAAuC,EAAE;IACvH,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;QAChD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7F,OAAO,KAAK,CAAA;IACd,CAAC;IACD,6FAA6F;IAC7F,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI;WAClC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,KAAK,cAAc,CAAC,GAAG,EAAE,CAAC;QACxD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,gBAAgB,IAAI,MAAM,CAAC,KAAK,CAAA;AACzC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAwC,EAAE,QAAgB,EAAE,EAAE,CAChG,KAAK,EAAE,GAAmB,EAAE,KAAmB,EAAE,EAAE;IACjD,iDAAiD;IACjD,IAAI,OAAO,GAAG,aAAa,CAAiC,GAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACvF,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;QAC/D,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QACvB,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;QAEtB,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAA;QACvC,MAAM,OAAO,GAAG,aAAa,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;QAErE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAA;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YAClC,MAAM,IAAI,GAAgB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC9C,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;YAC5C,aAAa,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;QACtD,CAAC;QAED,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QAEtC,iEAAiE;QACjE,mEAAmE;QACnE,+DAA+D;QAC/D,oEAAoE;QACpE,oEAAoE;QACpE,qEAAqE;QACrE,MAAM,SAAS,GAAG,aAAa,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;QACtE,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,2BAA2B,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;YAC9D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACrC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;YACzB,MAAM,KAAK,GAAiB,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACzD,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,KAAc,CAAC,CAAC,CAAA;YAC1D,OAAM;QACR,CAAC;QACD,eAAe,CAAC,WAAW,CAAC,KAAc,EAAE,KAAK,EAAE,eAAe,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;IAChG,CAAC;AACH,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/server-api",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.52",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -33,16 +33,16 @@
|
|
|
33
33
|
"@fastify/helmet": "^13.1.1",
|
|
34
34
|
"@fastify/middie": "^9.3.4",
|
|
35
35
|
"@fastify/multipart": "^10.1.2",
|
|
36
|
-
"@owlmeans/api": "^0.1.18-rc.
|
|
37
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
38
|
-
"@owlmeans/auth-common": "^0.1.18-rc.
|
|
39
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
40
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
41
|
-
"@owlmeans/error": "^0.1.18-rc.
|
|
42
|
-
"@owlmeans/log": "^0.1.18-rc.
|
|
43
|
-
"@owlmeans/route": "^0.1.18-rc.
|
|
44
|
-
"@owlmeans/server-context": "^0.1.18-rc.
|
|
45
|
-
"@owlmeans/server-entrypoint": "^0.1.18-rc.
|
|
36
|
+
"@owlmeans/api": "^0.1.18-rc.48",
|
|
37
|
+
"@owlmeans/auth": "^0.1.18-rc.40",
|
|
38
|
+
"@owlmeans/auth-common": "^0.1.18-rc.48",
|
|
39
|
+
"@owlmeans/context": "^0.1.18-rc.34",
|
|
40
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.41",
|
|
41
|
+
"@owlmeans/error": "^0.1.18-rc.38",
|
|
42
|
+
"@owlmeans/log": "^0.1.18-rc.2",
|
|
43
|
+
"@owlmeans/route": "^0.1.18-rc.35",
|
|
44
|
+
"@owlmeans/server-context": "^0.1.18-rc.43",
|
|
45
|
+
"@owlmeans/server-entrypoint": "^0.1.18-rc.40",
|
|
46
46
|
"ajv-errors": "^3.0.0",
|
|
47
47
|
"fastify": "^5.12.5",
|
|
48
48
|
"fastify-raw-body": "^6.0.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@owlmeans/dep-config": "workspace:*",
|
|
57
|
-
"@owlmeans/resource": "^0.1.18-rc.
|
|
57
|
+
"@owlmeans/resource": "^0.1.18-rc.40",
|
|
58
58
|
"@types/bun": "^1.4.2",
|
|
59
59
|
"@types/node": "^26.6.4",
|
|
60
60
|
"nodemon": "^3.1.14",
|
package/src/server.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ServerContext } from '@owlmeans/server-context'
|
|
2
2
|
import type { ApiServer, ApiServerAppend } from './types.js'
|
|
3
3
|
import { assertContext, createService } from '@owlmeans/context'
|
|
4
|
-
import { canServeModule,
|
|
4
|
+
import { canServeModule, payloadHelper, createServerHandler, fixFormatDates } from './utils/index.js'
|
|
5
5
|
import { DEFAULT_ALIAS, CLOSED_HOST, PORT, OPENED_HOST } from './consts.js'
|
|
6
6
|
import type { ServerEntrypoint } from '@owlmeans/server-entrypoint'
|
|
7
7
|
import { RouteMethod } from '@owlmeans/route'
|
|
@@ -144,13 +144,13 @@ export const createApiServer = (alias: string): ApiServer => {
|
|
|
144
144
|
// An intermediate route can be created without a handler by the binding layer.
|
|
145
145
|
if (module.route.match(request, module.mount()) && module.handle != null) {
|
|
146
146
|
const response = provideResponse(reply)
|
|
147
|
-
const currentRequest = provideRequest(module.alias, request, true)
|
|
147
|
+
const currentRequest = payloadHelper.provideRequest(module.alias, request, true)
|
|
148
148
|
currentRequest.original._ctx = context
|
|
149
149
|
const result: Context = await module.handle(currentRequest, response)
|
|
150
150
|
if (result != null) {
|
|
151
151
|
context = result
|
|
152
152
|
}
|
|
153
|
-
responded = executeResponse(response, reply, true) || responded
|
|
153
|
+
responded = payloadHelper.executeResponse(response, reply, true) || responded
|
|
154
154
|
}
|
|
155
155
|
return context
|
|
156
156
|
}, Promise.resolve(context))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Config, Context } from '../../types.js'
|
|
2
|
+
|
|
3
|
+
/** Carries the serving context on one raw request. */
|
|
4
|
+
export interface RequestContextHelper {
|
|
5
|
+
/** Stores the context on the request. */
|
|
6
|
+
populateContext: <C extends Config, T extends Context<C>>(context: T) => void
|
|
7
|
+
/**
|
|
8
|
+
* The context stored on the request, else `ctx`.
|
|
9
|
+
*
|
|
10
|
+
* @throws {SyntaxError} when neither is there
|
|
11
|
+
*/
|
|
12
|
+
extractContext: <C extends Config, T extends Context<C>>(ctx?: T, location?: string) => T
|
|
13
|
+
}
|
package/src/utils/context.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { assertContext } from '@owlmeans/context'
|
|
2
2
|
import type { Config, Context, Request } from '../types.js'
|
|
3
3
|
import { DEFAULT_ALIAS } from '../consts.js'
|
|
4
|
+
import type { RequestContextHelper } from './context/types.js'
|
|
4
5
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
6
|
+
export const makeRequestContextHelper = (req: Request): RequestContextHelper => {
|
|
7
|
+
const populateContext = <C extends Config, T extends Context<C>>(context: T): void =>
|
|
8
|
+
void ((req as any)._ctx = context)
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
const extractContext = <C extends Config, T extends Context<C>>(ctx?: T, location?: string): T =>
|
|
11
|
+
assertContext<C, T>((req as any)._ctx ?? ctx, location ?? DEFAULT_ALIAS)
|
|
12
|
+
|
|
13
|
+
return { populateContext, extractContext }
|
|
14
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { FastifyReply } from 'fastify'
|
|
2
|
+
import type { Config, HttpErrorExposure } from '../../types.js'
|
|
3
|
+
import type { SerializedHttpError } from '../types.js'
|
|
4
|
+
|
|
5
|
+
/** How the HTTP boundary answers a thrown error: its status, its wire body, its headers and its log entry. */
|
|
6
|
+
export interface HttpErrorHelper {
|
|
7
|
+
/**
|
|
8
|
+
* The HTTP status an error's class declares, or `null` when it declares none that may be honoured.
|
|
9
|
+
*
|
|
10
|
+
* Read through the constructor chain — a static property is inherited, so a subclass answers what
|
|
11
|
+
* its nearest declaring ancestor says, and redeclares to change it. An integer 4xx is honoured
|
|
12
|
+
* directly. A 5xx is honoured only with `allowServerErrorStatus = true`, so a domain class must
|
|
13
|
+
* explicitly opt in to exposing a precise availability response. The read is structural, so a
|
|
14
|
+
* class from a duplicate module copy declares exactly what the imported one does.
|
|
15
|
+
*/
|
|
16
|
+
declaredErrorStatus: (error: unknown) => number | null
|
|
17
|
+
/**
|
|
18
|
+
* The HTTP status an error is answered with, read off the error exactly as given.
|
|
19
|
+
*
|
|
20
|
+
* Two families reach here and both must be recognised. `AuthFailedError` / `AccessError` are this
|
|
21
|
+
* package's own, raised by the request pipeline itself. `AuthorizationError` / `AuthForbidden`
|
|
22
|
+
* come from `@owlmeans/auth` and are what the guards and gates actually throw — an unrecognised
|
|
23
|
+
* one answers 500, which reports a refused request as a crashed server: the client cannot tell
|
|
24
|
+
* "sign in again" from "the service is broken", and a consumer watching statuses concludes the
|
|
25
|
+
* application fell over. A family member is recognised by class OR by registered type name, so a
|
|
26
|
+
* class from a duplicate module copy answers the same status.
|
|
27
|
+
*
|
|
28
|
+
* **Order matters.** `AuthForbidden extends AuthorizationError`, so the 403 branch has to be
|
|
29
|
+
* tested first or every refusal of a permission is reported as a failure to authenticate. Both
|
|
30
|
+
* auth branches come before a class's own declaration, so an auth refusal can never be restated
|
|
31
|
+
* as something else. Everything that is neither an auth error nor a class declaring an allowed
|
|
32
|
+
* {@link HttpErrorHelper.declaredErrorStatus} answers 500.
|
|
33
|
+
*/
|
|
34
|
+
errorStatus: (error: unknown) => number
|
|
35
|
+
/** Invalid or absent configuration always resolves to the production-safe policy. */
|
|
36
|
+
errorExposure: (config?: Pick<Config, 'http'>) => HttpErrorExposure
|
|
37
|
+
/** One central wire shape for every server error. */
|
|
38
|
+
serializeError: (error: Error, exposure?: HttpErrorExposure) => SerializedHttpError
|
|
39
|
+
/** Apply safe response metadata carried by an error without coupling its package to Fastify. */
|
|
40
|
+
applyErrorHeaders: (error: unknown, reply: FastifyReply) => void
|
|
41
|
+
/**
|
|
42
|
+
* Answer a thrown error: a status, and the marshalled `ResilientError` as the body.
|
|
43
|
+
*
|
|
44
|
+
* The status is resolved in two steps. The error AS THROWN comes first: it is the one object whose
|
|
45
|
+
* class is certainly the class that was raised, while `ResilientError.ensure` rebuilds through the
|
|
46
|
+
* registry of whichever `@owlmeans/error` copy this module loaded — under duplicate module copies
|
|
47
|
+
* that turns a perfectly recognisable auth refusal into a plain `ResilientError` answering 500.
|
|
48
|
+
* Only when the thrown error answers 500 is the ENSURED error asked, because the rebuild is what
|
|
49
|
+
* gives a status to a marshalled error that crossed a hop as a plain `Error`.
|
|
50
|
+
*/
|
|
51
|
+
handleError: (error: Error, reply: FastifyReply, exposure?: HttpErrorExposure) => void
|
|
52
|
+
}
|