@kensio/simnaril 0.0.0 → 0.1.1
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 +2 -0
- package/dist/api.d.ts +25 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +35 -31
- package/dist/api.js.map +1 -1
- package/dist/http/decode-form.d.ts +38 -0
- package/dist/http/decode-form.d.ts.map +1 -0
- package/dist/http/decode-form.js +119 -0
- package/dist/http/decode-form.js.map +1 -0
- package/dist/http/error-formatter.d.ts +13 -0
- package/dist/http/error-formatter.d.ts.map +1 -0
- package/dist/http/error-formatter.js +2 -0
- package/dist/http/error-formatter.js.map +1 -0
- package/dist/http/operation-router.d.ts +2 -0
- package/dist/http/operation-router.d.ts.map +1 -1
- package/dist/http/operation-router.js +16 -1
- package/dist/http/operation-router.js.map +1 -1
- package/dist/http/operation.d.ts +2 -1
- package/dist/http/operation.d.ts.map +1 -1
- package/dist/http/operation.js.map +1 -1
- package/dist/http/path-parameter.d.ts +3 -0
- package/dist/http/path-parameter.d.ts.map +1 -0
- package/dist/http/path-parameter.js +12 -0
- package/dist/http/path-parameter.js.map +1 -0
- package/dist/http/raw-operation.d.ts +16 -0
- package/dist/http/raw-operation.d.ts.map +1 -0
- package/dist/http/raw-operation.js +27 -0
- package/dist/http/raw-operation.js.map +1 -0
- package/dist/http/request-decoder.d.ts +30 -0
- package/dist/http/request-decoder.d.ts.map +1 -0
- package/dist/http/request-decoder.js +18 -0
- package/dist/http/request-decoder.js.map +1 -0
- package/dist/http/resource-operation-registry.d.ts +1 -1
- package/dist/http/resource-operation-registry.d.ts.map +1 -1
- package/dist/http/resource-operation-registry.js +6 -3
- package/dist/http/resource-operation-registry.js.map +1 -1
- package/dist/http/resource-operation.d.ts +2 -1
- package/dist/http/resource-operation.d.ts.map +1 -1
- package/dist/http/resource-operation.js +3 -3
- package/dist/http/resource-operation.js.map +1 -1
- package/dist/http/resource-path.d.ts +13 -0
- package/dist/http/resource-path.d.ts.map +1 -0
- package/dist/http/resource-path.js +23 -0
- package/dist/http/resource-path.js.map +1 -0
- package/dist/http/rest-resource-operation-defaults.d.ts +10 -0
- package/dist/http/rest-resource-operation-defaults.d.ts.map +1 -0
- package/dist/http/rest-resource-operation-defaults.js +8 -0
- package/dist/http/rest-resource-operation-defaults.js.map +1 -0
- package/dist/http/rest-resource-operations.d.ts +3 -3
- package/dist/http/rest-resource-operations.d.ts.map +1 -1
- package/dist/http/rest-resource-operations.js +37 -51
- package/dist/http/rest-resource-operations.js.map +1 -1
- package/dist/http/semantic-http-operation.d.ts +2 -1
- package/dist/http/semantic-http-operation.d.ts.map +1 -1
- package/dist/http/semantic-http-operation.js.map +1 -1
- package/dist/http/supplied-rest-resource-operation.d.ts +22 -0
- package/dist/http/supplied-rest-resource-operation.d.ts.map +1 -0
- package/dist/http/supplied-rest-resource-operation.js +27 -0
- package/dist/http/supplied-rest-resource-operation.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/resource.d.ts +1 -1
- package/dist/resource.d.ts.map +1 -1
- package/dist/resource.js +1 -1
- package/dist/resource.js.map +1 -1
- package/dist/rest-resource-operation.d.ts +20 -2
- package/dist/rest-resource-operation.d.ts.map +1 -1
- package/dist/rest-resource.d.ts +2 -0
- package/dist/rest-resource.d.ts.map +1 -1
- package/dist/rest-resource.js +9 -1
- package/dist/rest-resource.js.map +1 -1
- package/dist/unimplemented-route-error.d.ts +2 -1
- package/dist/unimplemented-route-error.d.ts.map +1 -1
- package/dist/unimplemented-route-error.js +4 -2
- package/dist/unimplemented-route-error.js.map +1 -1
- package/dist/webhooks.d.ts +98 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +129 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -32,6 +32,8 @@ Simnaril requires Node.js 24 or later.
|
|
|
32
32
|
- [Simulation environments](docs/simulation-environments/README.md)
|
|
33
33
|
- [Resource state](docs/resource-state/README.md)
|
|
34
34
|
- [REST resources](docs/rest-resources/README.md)
|
|
35
|
+
- [Request bodies](docs/request-bodies/README.md)
|
|
35
36
|
- [Custom operations](docs/custom-operations/README.md)
|
|
36
37
|
- [Middleware](docs/middleware/README.md)
|
|
38
|
+
- [Webhooks](docs/webhooks/README.md)
|
|
37
39
|
- [Composing a simulation](docs/composing-a-simulation/README.md)
|
package/dist/api.d.ts
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { HttpMiddleware, RawHttpOperation } from "./http/operation.js";
|
|
2
|
+
import type { ErrorFormatter } from "./http/error-formatter.js";
|
|
3
|
+
import { type RawHttpOperationHandler } from "./http/raw-operation.js";
|
|
4
|
+
import type { RequestDecoder } from "./http/request-decoder.js";
|
|
2
5
|
import { RestResource } from "./rest-resource.js";
|
|
3
6
|
import type { RestResourceProps } from "./rest-resource-operation.js";
|
|
4
7
|
import { SimResource, type SimResourceProps } from "./resource.js";
|
|
8
|
+
/** Configures behaviour shared by every resource on one simulated API. */
|
|
9
|
+
export interface SimApiProps {
|
|
10
|
+
/** The service name used in route errors. `SimApi` when none is given. */
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* How request bodies are read, for the resources that read one.
|
|
14
|
+
*
|
|
15
|
+
* JSON when none is given. A resource or an operation can name its own, and
|
|
16
|
+
* the closest one wins.
|
|
17
|
+
*/
|
|
18
|
+
decode?: RequestDecoder;
|
|
19
|
+
/**
|
|
20
|
+
* How a thrown error becomes a response.
|
|
21
|
+
*
|
|
22
|
+
* Runs before the supplied `EntityNotFoundError` and `DuplicateEntityError`
|
|
23
|
+
* mappings, and declining an error with `undefined` leaves it to them.
|
|
24
|
+
*/
|
|
25
|
+
formatError?: ErrorFormatter;
|
|
26
|
+
}
|
|
5
27
|
/** Configures conventional resource state and its HTTP operations. */
|
|
6
28
|
export interface SimApiResourceProps<T extends object> extends SimResourceProps<T>, RestResourceProps {
|
|
7
29
|
}
|
|
8
|
-
/** Handles a raw HTTP operation after Simnaril has matched its route. */
|
|
9
|
-
export type RawHttpOperationHandler = (context: HttpOperationContext) => Promise<Response> | Response;
|
|
10
30
|
/** Routes HTTP requests to stateful simulated resources. */
|
|
11
31
|
export declare class SimApi {
|
|
12
32
|
#private;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
constructor(props?: SimApiProps);
|
|
13
35
|
/** Adds middleware around every matched operation in this API. */
|
|
14
36
|
use(middleware: HttpMiddleware): this;
|
|
15
37
|
/** Creates resource state and exposes its conventional HTTP operations. */
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAsB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAQnE,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IAExB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,sEAAsE;AACtE,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,MAAM,CACnD,SAAQ,gBAAgB,CAAC,CAAC,CAAC,EAAE,iBAAiB;CAAG;AAEnD,4DAA4D;AAC5D,qBAAa,MAAM;;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAKtB,YAAY,KAAK,GAAE,WAAgB,EAIlC;IAED,kEAAkE;IAClE,GAAG,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAGpC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAUzE;IAED,4EAA4E;IAC5E,MAAM,CAAC,CAAC,SAAS,MAAM,EACrB,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,KAAK,EAAE,iBAAiB,GACvB,YAAY,CAAC,CAAC,CAAC,CAgBjB;IAED,6CAA6C;IAC7C,SAAS,CACP,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,uBAAuB,GAC9B,gBAAgB,CAKlB;IAED,yEAAyE;IACzE,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAE1C;CAUF"}
|
package/dist/api.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
import { createRawHttpOperation, operationMiddleware, } from "./http/operation.js";
|
|
2
1
|
import { OperationRouter } from "./http/operation-router.js";
|
|
3
|
-
import {
|
|
2
|
+
import { rawOperation, } from "./http/raw-operation.js";
|
|
3
|
+
import { validateResourcePath } from "./http/resource-path.js";
|
|
4
4
|
import { attachRestResource, RestResource } from "./rest-resource.js";
|
|
5
5
|
import { SimResource } from "./resource.js";
|
|
6
|
+
const resourcePathShape = (path) => path
|
|
7
|
+
.split("/")
|
|
8
|
+
.map((segment) => (segment.startsWith(":") ? ":" : segment))
|
|
9
|
+
.join("/");
|
|
6
10
|
/** Routes HTTP requests to stateful simulated resources. */
|
|
7
11
|
export class SimApi {
|
|
12
|
+
name;
|
|
8
13
|
#paths = new Set();
|
|
9
|
-
#router
|
|
14
|
+
#router;
|
|
15
|
+
#decode;
|
|
16
|
+
constructor(props = {}) {
|
|
17
|
+
this.name = props.name ?? "SimApi";
|
|
18
|
+
this.#decode = props.decode;
|
|
19
|
+
this.#router = new OperationRouter(this.name, props.formatError);
|
|
20
|
+
}
|
|
10
21
|
/** Adds middleware around every matched operation in this API. */
|
|
11
22
|
use(middleware) {
|
|
12
23
|
this.#router.use(middleware);
|
|
@@ -14,53 +25,46 @@ export class SimApi {
|
|
|
14
25
|
}
|
|
15
26
|
/** Creates resource state and exposes its conventional HTTP operations. */
|
|
16
27
|
resource(props) {
|
|
17
|
-
const { operations, path, ...stateProps } = props;
|
|
18
|
-
const restProps =
|
|
28
|
+
const { decode, itemPath, locate, operations, path, ...stateProps } = props;
|
|
29
|
+
const restProps = {
|
|
30
|
+
path,
|
|
31
|
+
...(decode === undefined ? {} : { decode }),
|
|
32
|
+
...(itemPath === undefined ? {} : { itemPath }),
|
|
33
|
+
...(locate === undefined ? {} : { locate }),
|
|
34
|
+
...(operations === undefined ? {} : { operations }),
|
|
35
|
+
};
|
|
19
36
|
return this.expose(new SimResource(stateProps), restProps);
|
|
20
37
|
}
|
|
21
38
|
/** Exposes existing resource state through conventional HTTP operations. */
|
|
22
39
|
expose(state, props) {
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
validateResourcePath(props.path);
|
|
41
|
+
const pathShape = resourcePathShape(props.path);
|
|
42
|
+
if (this.#paths.has(pathShape)) {
|
|
25
43
|
throw new TypeError(`A resource is already exposed at collection path "${props.path}".`);
|
|
26
44
|
}
|
|
27
|
-
const resource = new RestResource(state, props);
|
|
45
|
+
const resource = new RestResource(state, this.#withApiDecode(props));
|
|
28
46
|
attachRestResource(resource, (operation) => {
|
|
29
47
|
this.#router.register(operation);
|
|
30
48
|
});
|
|
31
|
-
this.#paths.add(
|
|
49
|
+
this.#paths.add(pathShape);
|
|
32
50
|
return resource;
|
|
33
51
|
}
|
|
34
52
|
/** Adds a raw HTTP operation to this API. */
|
|
35
53
|
operation(method, path, handle) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
...route,
|
|
40
|
-
decode: () => Promise.resolve(),
|
|
41
|
-
encode: (output) => output,
|
|
42
|
-
method: normalizeMethod(method),
|
|
43
|
-
middleware: operationMiddleware(operation),
|
|
44
|
-
operate: (_input, context) => handle(context),
|
|
45
|
-
resourceMiddleware: [],
|
|
46
|
-
transform: (decoded) => decoded,
|
|
47
|
-
});
|
|
48
|
-
return operation;
|
|
54
|
+
const built = rawOperation(method, path, handle);
|
|
55
|
+
this.#router.register(built.http);
|
|
56
|
+
return built.operation;
|
|
49
57
|
}
|
|
50
58
|
/** Handles one request through the matching simulated HTTP operation. */
|
|
51
59
|
handle(request) {
|
|
52
60
|
return this.#router.handle(request);
|
|
53
61
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
url.pathname !== path ||
|
|
59
|
-
url.search !== "" ||
|
|
60
|
-
url.hash !== "" ||
|
|
61
|
-
!/^\/[^/:]+(?:\/[^/:]+)*$/u.test(path)) {
|
|
62
|
-
throw new TypeError(`Expected a resource collection path such as "/widgets", received "${path}".`);
|
|
62
|
+
/** Applies the API's decoder to a resource that has not named its own. */
|
|
63
|
+
#withApiDecode(props) {
|
|
64
|
+
if (props.decode !== undefined || this.#decode === undefined) {
|
|
65
|
+
return props;
|
|
63
66
|
}
|
|
67
|
+
return { ...props, decode: this.#decode };
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RequestDecoder } from "./request-decoder.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reads an `application/x-www-form-urlencoded` body with bracketed nesting.
|
|
4
|
+
*
|
|
5
|
+
* A wire format, and not one service's dialect. Stripe, Rails and PHP
|
|
6
|
+
* applications all speak it, and all three write nesting the same way.
|
|
7
|
+
*
|
|
8
|
+
* ```text
|
|
9
|
+
* line_items[0][price_data][unit_amount]=250
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* becomes
|
|
13
|
+
*
|
|
14
|
+
* ```json
|
|
15
|
+
* { "line_items": [{ "price_data": { "unit_amount": "250" } }] }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Every leaf is a string. A form body carries no types, and guessing at them
|
|
19
|
+
* would make `quantity=1` and `postcode=01234` disagree about what a digit is.
|
|
20
|
+
* The resource's own creation behaviour converts what it needs.
|
|
21
|
+
*
|
|
22
|
+
* The header is ignored. What a body says it is and what it holds are two
|
|
23
|
+
* facts, and a decoder chosen by hand has already settled the first.
|
|
24
|
+
*
|
|
25
|
+
* ## What it refuses
|
|
26
|
+
*
|
|
27
|
+
* A real encoder emits well-formed keys. The cases below are hostile or
|
|
28
|
+
* mistaken input, and each throws a `SyntaxError`, the way a malformed JSON
|
|
29
|
+
* body already does.
|
|
30
|
+
*
|
|
31
|
+
* - A key given twice (`name=a&name=b`). Which one wins is a guess, and losing
|
|
32
|
+
* half a request quietly is worse than stopping.
|
|
33
|
+
* - A key needing one part to hold both a value and more keys (`a=1&a[b]=2`).
|
|
34
|
+
* - An empty bracket anywhere but the end (`a[][b]=1`).
|
|
35
|
+
* - A key brackets cannot be read out of (`a[b`, `[a]`).
|
|
36
|
+
*/
|
|
37
|
+
export declare const decodeForm: RequestDecoder;
|
|
38
|
+
//# sourceMappingURL=decode-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-form.d.ts","sourceRoot":"","sources":["../../src/http/decode-form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,UAAU,EAAE,cASxB,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads an `application/x-www-form-urlencoded` body with bracketed nesting.
|
|
3
|
+
*
|
|
4
|
+
* A wire format, and not one service's dialect. Stripe, Rails and PHP
|
|
5
|
+
* applications all speak it, and all three write nesting the same way.
|
|
6
|
+
*
|
|
7
|
+
* ```text
|
|
8
|
+
* line_items[0][price_data][unit_amount]=250
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* becomes
|
|
12
|
+
*
|
|
13
|
+
* ```json
|
|
14
|
+
* { "line_items": [{ "price_data": { "unit_amount": "250" } }] }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Every leaf is a string. A form body carries no types, and guessing at them
|
|
18
|
+
* would make `quantity=1` and `postcode=01234` disagree about what a digit is.
|
|
19
|
+
* The resource's own creation behaviour converts what it needs.
|
|
20
|
+
*
|
|
21
|
+
* The header is ignored. What a body says it is and what it holds are two
|
|
22
|
+
* facts, and a decoder chosen by hand has already settled the first.
|
|
23
|
+
*
|
|
24
|
+
* ## What it refuses
|
|
25
|
+
*
|
|
26
|
+
* A real encoder emits well-formed keys. The cases below are hostile or
|
|
27
|
+
* mistaken input, and each throws a `SyntaxError`, the way a malformed JSON
|
|
28
|
+
* body already does.
|
|
29
|
+
*
|
|
30
|
+
* - A key given twice (`name=a&name=b`). Which one wins is a guess, and losing
|
|
31
|
+
* half a request quietly is worse than stopping.
|
|
32
|
+
* - A key needing one part to hold both a value and more keys (`a=1&a[b]=2`).
|
|
33
|
+
* - An empty bracket anywhere but the end (`a[][b]=1`).
|
|
34
|
+
* - A key brackets cannot be read out of (`a[b`, `[a]`).
|
|
35
|
+
*/
|
|
36
|
+
export const decodeForm = async (request) => {
|
|
37
|
+
const body = await request.text();
|
|
38
|
+
const root = new Map();
|
|
39
|
+
for (const [key, value] of new URLSearchParams(body)) {
|
|
40
|
+
insert(root, key, value);
|
|
41
|
+
}
|
|
42
|
+
return materialise(root);
|
|
43
|
+
};
|
|
44
|
+
/** A branch of the tree, as opposed to a leaf holding one value. */
|
|
45
|
+
const isBranch = (held) => held instanceof Map;
|
|
46
|
+
/** A key is a name followed by any number of bracketed parts. */
|
|
47
|
+
const keyPattern = /^(?<name>[^[\]]+)(?<parts>(?:\[[^[\]]*\])*)$/u;
|
|
48
|
+
const partPattern = /\[(?<part>[^[\]]*)\]/gu;
|
|
49
|
+
const indexPattern = /^\d+$/u;
|
|
50
|
+
/** The parts of one form key, outermost first. */
|
|
51
|
+
function partsOf(key) {
|
|
52
|
+
const match = keyPattern.exec(key);
|
|
53
|
+
const name = match?.groups?.["name"];
|
|
54
|
+
if (name === undefined) {
|
|
55
|
+
throw new SyntaxError(`Form key "${key}" is not a name followed by bracketed parts.`);
|
|
56
|
+
}
|
|
57
|
+
const parts = [name];
|
|
58
|
+
for (const part of (match?.groups?.["parts"] ?? "").matchAll(partPattern)) {
|
|
59
|
+
parts.push(part.groups?.["part"] ?? "");
|
|
60
|
+
}
|
|
61
|
+
return parts;
|
|
62
|
+
}
|
|
63
|
+
/** Puts one key's value into the tree, growing branches on the way down. */
|
|
64
|
+
function insert(root, key, value) {
|
|
65
|
+
const parts = partsOf(key);
|
|
66
|
+
let node = root;
|
|
67
|
+
for (const [depth, part] of parts.entries()) {
|
|
68
|
+
const last = depth === parts.length - 1;
|
|
69
|
+
if (part === "" && !last) {
|
|
70
|
+
throw new SyntaxError(`Form key "${key}" has an empty bracket before its last part.`);
|
|
71
|
+
}
|
|
72
|
+
// An empty bracket appends, and the position it takes is the branch's size.
|
|
73
|
+
const name = part === "" ? String(node.size) : part;
|
|
74
|
+
if (last) {
|
|
75
|
+
if (node.has(name)) {
|
|
76
|
+
throw new SyntaxError(`Form key "${key}" is given twice.`);
|
|
77
|
+
}
|
|
78
|
+
node.set(name, value);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
node = branchAt(node, name, key);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** The branch below `name`, created when it is the first key to need one. */
|
|
85
|
+
function branchAt(node, name, key) {
|
|
86
|
+
const held = node.get(name);
|
|
87
|
+
if (isBranch(held)) {
|
|
88
|
+
return held;
|
|
89
|
+
}
|
|
90
|
+
if (held !== undefined) {
|
|
91
|
+
throw new SyntaxError(`Form key "${key}" needs "${name}" to hold a value and more keys at once.`);
|
|
92
|
+
}
|
|
93
|
+
const branch = new Map();
|
|
94
|
+
node.set(name, branch);
|
|
95
|
+
return branch;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Turns the tree into arrays and objects.
|
|
99
|
+
*
|
|
100
|
+
* A branch whose every part is a run of digits becomes an array. The digits
|
|
101
|
+
* order the entries and do not position them, so `a[0]`, `a[5]` and `a[9]`
|
|
102
|
+
* give three elements and never a sparse array of ten. A real encoder counts
|
|
103
|
+
* from zero, where the two readings agree.
|
|
104
|
+
*/
|
|
105
|
+
function materialise(node) {
|
|
106
|
+
const entries = [...node].map(([name, held]) => [name, isBranch(held) ? materialise(held) : held]);
|
|
107
|
+
if (entries.length > 0 &&
|
|
108
|
+
entries.every(([name]) => indexPattern.test(name))) {
|
|
109
|
+
return entries
|
|
110
|
+
.toSorted(([one], [other]) => Number(one) - Number(other))
|
|
111
|
+
.map(([, held]) => held);
|
|
112
|
+
}
|
|
113
|
+
/*
|
|
114
|
+
* `Object.fromEntries` defines own data properties, so a `__proto__` part of
|
|
115
|
+
* a hostile key lands beside the others and reaches no prototype.
|
|
116
|
+
*/
|
|
117
|
+
return Object.fromEntries(entries);
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=decode-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-form.js","sourceRoot":"","sources":["../../src/http/decode-form.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an error thrown by an operation into the response one API answers with.
|
|
3
|
+
*
|
|
4
|
+
* A real service has one error envelope across every endpoint. Stripe's is
|
|
5
|
+
* `{ error: { type, code, message, param } }`, GitHub's is
|
|
6
|
+
* `{ message, documentation_url }`, and simulating either takes describing the
|
|
7
|
+
* shape once.
|
|
8
|
+
*
|
|
9
|
+
* Returning `undefined` declines the error and leaves it to the supplied
|
|
10
|
+
* mappings, so a formatter can shape the errors it knows and ignore the rest.
|
|
11
|
+
*/
|
|
12
|
+
export type ErrorFormatter = (error: unknown) => Response | undefined;
|
|
13
|
+
//# sourceMappingURL=error-formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-formatter.d.ts","sourceRoot":"","sources":["../../src/http/error-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,QAAQ,GAAG,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-formatter.js","sourceRoot":"","sources":["../../src/http/error-formatter.ts"],"names":[],"mappings":""}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { ErrorFormatter } from "./error-formatter.js";
|
|
1
2
|
import type { HttpMiddleware, HttpOperation } from "./operation.js";
|
|
2
3
|
/** Selects and runs registered operations for one simulated API. */
|
|
3
4
|
export declare class OperationRouter {
|
|
4
5
|
#private;
|
|
6
|
+
constructor(apiName: string, formatError?: ErrorFormatter);
|
|
5
7
|
use(middleware: HttpMiddleware): void;
|
|
6
8
|
register(operation: HttpOperation): void;
|
|
7
9
|
handle(request: Request): Promise<Response>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-router.d.ts","sourceRoot":"","sources":["../../src/http/operation-router.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAEd,MAAM,gBAAgB,CAAC;AAIxB,oEAAoE;AACpE,qBAAa,eAAe;;
|
|
1
|
+
{"version":3,"file":"operation-router.d.ts","sourceRoot":"","sources":["../../src/http/operation-router.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAEd,MAAM,gBAAgB,CAAC;AAIxB,oEAAoE;AACpE,qBAAa,eAAe;;IAM1B,YAAY,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,cAAc,EAGxD;IAED,GAAG,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAEpC;IAED,QAAQ,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAEvC;IAEK,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CA0BhD;CAsDF"}
|
|
@@ -4,8 +4,14 @@ import { UnimplementedRouteError } from "../unimplemented-route-error.js";
|
|
|
4
4
|
import { runHttpOperation, runMiddleware } from "./operation-pipeline.js";
|
|
5
5
|
/** Selects and runs registered operations for one simulated API. */
|
|
6
6
|
export class OperationRouter {
|
|
7
|
+
#apiName;
|
|
7
8
|
#middleware = [];
|
|
8
9
|
#operations = [];
|
|
10
|
+
#formatError;
|
|
11
|
+
constructor(apiName, formatError) {
|
|
12
|
+
this.#apiName = apiName;
|
|
13
|
+
this.#formatError = formatError;
|
|
14
|
+
}
|
|
9
15
|
use(middleware) {
|
|
10
16
|
this.#middleware.push(middleware);
|
|
11
17
|
}
|
|
@@ -28,7 +34,7 @@ export class OperationRouter {
|
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
if (selected === undefined) {
|
|
31
|
-
throw new UnimplementedRouteError(request);
|
|
37
|
+
throw new UnimplementedRouteError(request, this.#apiName);
|
|
32
38
|
}
|
|
33
39
|
return this.#run(selected.operation, request, selected.match);
|
|
34
40
|
}
|
|
@@ -50,6 +56,15 @@ export class OperationRouter {
|
|
|
50
56
|
return await action();
|
|
51
57
|
}
|
|
52
58
|
catch (error) {
|
|
59
|
+
/*
|
|
60
|
+
* The API's own formatter goes first, so a service that shapes its errors
|
|
61
|
+
* shapes these two as well. Declining one leaves it to the mappings
|
|
62
|
+
* below.
|
|
63
|
+
*/
|
|
64
|
+
const formatted = this.#formatError?.(error);
|
|
65
|
+
if (formatted !== undefined) {
|
|
66
|
+
return formatted;
|
|
67
|
+
}
|
|
53
68
|
if (error instanceof EntityNotFoundError) {
|
|
54
69
|
return Response.json({ error: error.message }, { status: 404 });
|
|
55
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-router.js","sourceRoot":"","sources":["../../src/http/operation-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"operation-router.js","sourceRoot":"","sources":["../../src/http/operation-router.ts"],"names":[],"mappings":"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"}
|
package/dist/http/operation.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RequestDecoder } from "./request-decoder.js";
|
|
1
2
|
import type { CompiledRoute, RouteMatch } from "./route.js";
|
|
2
3
|
/** HTTP data shared by handlers and middleware for one matched operation. */
|
|
3
4
|
export interface HttpOperationContext {
|
|
@@ -17,7 +18,7 @@ export interface SemanticOperationOverride<TInput, TOutput, TResource> {
|
|
|
17
18
|
handle: (context: SemanticOperationContext<TInput, TResource>) => Promise<TOutput> | TOutput;
|
|
18
19
|
}
|
|
19
20
|
export interface HttpOperation extends CompiledRoute {
|
|
20
|
-
decode:
|
|
21
|
+
decode: RequestDecoder;
|
|
21
22
|
encode: (output: unknown) => Promise<Response> | Response;
|
|
22
23
|
method: string;
|
|
23
24
|
middleware: readonly HttpMiddleware[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/http/operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACzC,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,8DAA8D;AAC9D,MAAM,MAAM,cAAc,GAAG,CAC3B,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,KAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAElC,uEAAuE;AACvE,MAAM,WAAW,wBAAwB,CACvC,MAAM,EACN,SAAS,CACT,SAAQ,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS;IACnE,MAAM,EAAE,CACN,OAAO,EAAE,wBAAwB,CAAC,MAAM,EAAE,SAAS,CAAC,KACjD,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/http/operation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation.js","sourceRoot":"","sources":["../../src/http/operation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"operation.js","sourceRoot":"","sources":["../../src/http/operation.ts"],"names":[],"mappings":"AA0CA,MAAM,gBAAgB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;AACxD,MAAM,WAAW,GAAG,MAAM,CAAC,4BAA4B,CAAC,CAAC;AAEzD,gFAAgF;AAChF,MAAM,OAAO,iBAAiB;IACnB,WAAW,GAAqB,EAAE,CAAC;IAC5C,OAAO,CAEyB;IAEhC,YACE,MAE+B;QAE/B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,oDAAoD;IACpD,QAAQ,CACN,WAAkE;QAElE,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6CAA6C;IAC7C,GAAG,CAAC,UAA0B;QAC5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,CAAC,WAAW,CAAC,CACX,OAAoD;QAEpD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,CAAC,gBAAgB,CAAC;QAChB,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;CACF;AASD,MAAM,UAAU,sBAAsB;IACpC,MAAM,UAAU,GAAqB,EAAE,CAAC;IAExC,OAAO;QACL,GAAG,CAAC,KAAK;YACP,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,CAAC,gBAAgB,CAAC,EAAE,GAAG,EAAE,CAAC,UAAU;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,SAA2E;IAE3E,OAAO,SAAS,CAAC,gBAAgB,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,SAAwD,EACxD,OAAoD;IAEpD,OAAO,SAAS,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-parameter.d.ts","sourceRoot":"","sources":["../../src/http/path-parameter.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACxC,IAAI,EAAE,MAAM,GACX,MAAM,CAYR"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Returns one matched path parameter or throws when it is absent. */
|
|
2
|
+
export function requirePathParameter(params, name) {
|
|
3
|
+
if (!Object.hasOwn(params, name)) {
|
|
4
|
+
throw new TypeError(`Matched operation has no ":${name}" path parameter.`);
|
|
5
|
+
}
|
|
6
|
+
const value = params[name];
|
|
7
|
+
if (value === undefined) {
|
|
8
|
+
throw new TypeError(`Matched operation has no ":${name}" path parameter.`);
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=path-parameter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-parameter.js","sourceRoot":"","sources":["../../src/http/path-parameter.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAClC,MAAwC,EACxC,IAAY;IAEZ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,8BAA8B,IAAI,mBAAmB,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAE3B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,SAAS,CAAC,8BAA8B,IAAI,mBAAmB,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HttpOperationContext, RawHttpOperation } from "./operation.js";
|
|
2
|
+
import type { HttpOperation } from "./operation.js";
|
|
3
|
+
/** Handles a raw HTTP operation after Simnaril has matched its route. */
|
|
4
|
+
export type RawHttpOperationHandler = (context: HttpOperationContext) => Promise<Response> | Response;
|
|
5
|
+
/**
|
|
6
|
+
* Builds an operation whose handler owns the whole response.
|
|
7
|
+
*
|
|
8
|
+
* The router still supplies the matched path parameters and the query, so a
|
|
9
|
+
* handler down here reimplements no route matching. Everything above that is
|
|
10
|
+
* the handler's, including the status and the body.
|
|
11
|
+
*/
|
|
12
|
+
export declare function rawOperation(method: string, path: string, handle: RawHttpOperationHandler): {
|
|
13
|
+
http: HttpOperation;
|
|
14
|
+
operation: RawHttpOperation;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=raw-operation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-operation.d.ts","sourceRoot":"","sources":["../../src/http/raw-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,yEAAyE;AACzE,MAAM,MAAM,uBAAuB,GAAG,CACpC,OAAO,EAAE,oBAAoB,KAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAElC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,uBAAuB,GAC9B;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,gBAAgB,CAAA;CAAE,CAgBtD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createRawHttpOperation, operationMiddleware } from "./operation.js";
|
|
2
|
+
import { decodeNothing } from "./request-decoder.js";
|
|
3
|
+
import { compileRoute, normalizeMethod } from "./route.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builds an operation whose handler owns the whole response.
|
|
6
|
+
*
|
|
7
|
+
* The router still supplies the matched path parameters and the query, so a
|
|
8
|
+
* handler down here reimplements no route matching. Everything above that is
|
|
9
|
+
* the handler's, including the status and the body.
|
|
10
|
+
*/
|
|
11
|
+
export function rawOperation(method, path, handle) {
|
|
12
|
+
const operation = createRawHttpOperation();
|
|
13
|
+
return {
|
|
14
|
+
operation,
|
|
15
|
+
http: {
|
|
16
|
+
...compileRoute(path),
|
|
17
|
+
decode: decodeNothing,
|
|
18
|
+
encode: (output) => output,
|
|
19
|
+
method: normalizeMethod(method),
|
|
20
|
+
middleware: operationMiddleware(operation),
|
|
21
|
+
operate: (_input, context) => handle(context),
|
|
22
|
+
resourceMiddleware: [],
|
|
23
|
+
transform: (decoded) => decoded,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=raw-operation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-operation.js","sourceRoot":"","sources":["../../src/http/raw-operation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAO3D;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,IAAY,EACZ,MAA+B;IAE/B,MAAM,SAAS,GAAG,sBAAsB,EAAE,CAAC;IAE3C,OAAO;QACL,SAAS;QACT,IAAI,EAAE;YACJ,GAAG,YAAY,CAAC,IAAI,CAAC;YACrB,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAkB;YACtC,MAAM,EAAE,eAAe,CAAC,MAAM,CAAC;YAC/B,UAAU,EAAE,mBAAmB,CAAC,SAAS,CAAC;YAC1C,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;YAC7C,kBAAkB,EAAE,EAAE;YACtB,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO;SAChC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a request body into the input a semantic operation receives.
|
|
3
|
+
*
|
|
4
|
+
* JSON is the default and stays the default. A decoder is supplied for the
|
|
5
|
+
* services that speak something else on the way in. Stripe, Rails and PHP
|
|
6
|
+
* applications all take `application/x-www-form-urlencoded` and answer with
|
|
7
|
+
* JSON, and simulating one takes this hook.
|
|
8
|
+
*
|
|
9
|
+
* A decoder is only ever asked for a body by an operation that has one to read.
|
|
10
|
+
* `GET` and `DELETE` inherit none, whatever is configured above them.
|
|
11
|
+
*/
|
|
12
|
+
export type RequestDecoder = (request: Request) => unknown;
|
|
13
|
+
/** The default decoder, and what an operation uses when none is supplied. */
|
|
14
|
+
export declare const decodeJson: RequestDecoder;
|
|
15
|
+
/**
|
|
16
|
+
* Runs a decoder only when the request arrived with a body.
|
|
17
|
+
*
|
|
18
|
+
* An operation that may be called either way asks for this. Handing the
|
|
19
|
+
* decoder a request with no body is how `decodeJson` comes to answer
|
|
20
|
+
* `Unexpected end of JSON input` for a `DELETE` that was sent nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare const decodeWhenPresent: (decode: RequestDecoder) => RequestDecoder;
|
|
23
|
+
/**
|
|
24
|
+
* Reads nothing at all.
|
|
25
|
+
*
|
|
26
|
+
* What an operation with no body to read uses. Internal, and kept beside the
|
|
27
|
+
* default so the two live together.
|
|
28
|
+
*/
|
|
29
|
+
export declare const decodeNothing: () => undefined;
|
|
30
|
+
//# sourceMappingURL=request-decoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-decoder.d.ts","sourceRoot":"","sources":["../../src/http/request-decoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;AAE3D,6EAA6E;AAC7E,eAAO,MAAM,UAAU,EAAE,cAA4C,CAAC;AAEtE;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,WACnB,cAAc,KAAG,cAE2B,CAAC;AAExD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QAAO,SAAsB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The default decoder, and what an operation uses when none is supplied. */
|
|
2
|
+
export const decodeJson = (request) => request.json();
|
|
3
|
+
/**
|
|
4
|
+
* Runs a decoder only when the request arrived with a body.
|
|
5
|
+
*
|
|
6
|
+
* An operation that may be called either way asks for this. Handing the
|
|
7
|
+
* decoder a request with no body is how `decodeJson` comes to answer
|
|
8
|
+
* `Unexpected end of JSON input` for a `DELETE` that was sent nothing.
|
|
9
|
+
*/
|
|
10
|
+
export const decodeWhenPresent = (decode) => (request) => request.body === null ? undefined : decode(request);
|
|
11
|
+
/**
|
|
12
|
+
* Reads nothing at all.
|
|
13
|
+
*
|
|
14
|
+
* What an operation with no body to read uses. Internal, and kept beside the
|
|
15
|
+
* default so the two live together.
|
|
16
|
+
*/
|
|
17
|
+
export const decodeNothing = () => undefined;
|
|
18
|
+
//# sourceMappingURL=request-decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-decoder.js","sourceRoot":"","sources":["../../src/http/request-decoder.ts"],"names":[],"mappings":"AAaA,6EAA6E;AAC7E,MAAM,CAAC,MAAM,UAAU,GAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAC5B,CAAC,MAAsB,EAAkB,EAAE,CAC3C,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAc,EAAE,CAAC,SAAS,CAAC"}
|