@controller-contract/server 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/LICENCE +21 -0
- package/dist/index-CgtO_soW.d.mts +40 -0
- package/dist/index.d.mts +67 -0
- package/dist/index.mjs +73 -0
- package/dist/utils/index.d.mts +2 -0
- package/dist/utils/index.mjs +60 -0
- package/package.json +37 -0
- package/src/index.ts +23 -0
- package/src/steps/endpoint.ts +15 -0
- package/src/steps/input.ts +16 -0
- package/src/steps/output.ts +30 -0
- package/src/types/adapter.ts +25 -0
- package/src/types/builder-step.ts +59 -0
- package/src/utils/cast.ts +7 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/output.ts +55 -0
package/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kenny G Perez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OutputExit, OutputFail, OutputNext, OutputPass, ParamsFrom } from "@controller-contract/core";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/cast.d.ts
|
|
4
|
+
declare namespace cast {
|
|
5
|
+
function params<T>(rawParams: T): ParamsFrom<string>;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/utils/output.d.ts
|
|
9
|
+
declare namespace output {
|
|
10
|
+
function next<T>(data: T, code?: number): OutputNext<T>;
|
|
11
|
+
function pass<T>(data: T, code?: number): OutputPass<T>;
|
|
12
|
+
function fail<const T extends string>(data: OutputFail<T>['data'], code?: number): OutputFail<T>;
|
|
13
|
+
/**
|
|
14
|
+
* Used to escape the controller contract state machine, useful for redirects.
|
|
15
|
+
*/
|
|
16
|
+
function exit(): OutputExit;
|
|
17
|
+
}
|
|
18
|
+
declare function rootOnly(message: string): OutputFail<'root'>['data'];
|
|
19
|
+
declare const StandardOutput: {
|
|
20
|
+
OK: OutputPass<{
|
|
21
|
+
key: "root";
|
|
22
|
+
message: string;
|
|
23
|
+
}[]>;
|
|
24
|
+
Created: OutputPass<{
|
|
25
|
+
key: "root";
|
|
26
|
+
message: string;
|
|
27
|
+
}[]>;
|
|
28
|
+
Accepted: OutputPass<{
|
|
29
|
+
key: "root";
|
|
30
|
+
message: string;
|
|
31
|
+
}[]>;
|
|
32
|
+
BadRequest: OutputFail<"root">;
|
|
33
|
+
Unauthorized: OutputFail<"root">;
|
|
34
|
+
Forbidden: OutputFail<"root">;
|
|
35
|
+
NotFound: OutputFail<"root">;
|
|
36
|
+
InternalServerError: OutputFail<"root">;
|
|
37
|
+
BadGateway: OutputFail<"root">;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { cast as i, output as n, rootOnly as r, StandardOutput as t };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { n as output } from "./index-CgtO_soW.mjs";
|
|
2
|
+
import { ControllerContract, InputChannel, Method, NoInput, OutputFail, OutputMonads, ParamsFrom, Prettify } from "@controller-contract/core";
|
|
3
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
4
|
+
|
|
5
|
+
//#region src/types/builder-step.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* All the possible steps are defined below.
|
|
8
|
+
*/
|
|
9
|
+
declare namespace BuilderStep {
|
|
10
|
+
/**
|
|
11
|
+
* Always the first step
|
|
12
|
+
*/
|
|
13
|
+
interface Endpoint<TContext> {
|
|
14
|
+
endpoint<const TMethod extends Method, const TPath extends string>(method: TMethod, path: TPath): BuilderStep.Output<TContext, TMethod, TPath, NoInput, never> & BuilderStep.Input<TContext, TMethod, TPath>;
|
|
15
|
+
}
|
|
16
|
+
interface Input<TContext, TMethod extends Method, TPath extends string> {
|
|
17
|
+
input<TInput>(channel: InputChannel, schema: StandardSchemaV1<TInput>): BuilderStep.Output<TContext, TMethod, TPath, TInput, OutputFail<Extract<keyof TInput, string>>>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Always the last step
|
|
21
|
+
*/
|
|
22
|
+
interface Output<TContext, TMethod extends Method, TPath extends string, TInput, TOutput extends OutputMonads> {
|
|
23
|
+
output<TFnOutput extends OutputMonads>(handler: OutputHandler<TContext, TPath, TInput, TFnOutput>): ControllerContract<TMethod, TPath, TInput, TOutput | TFnOutput>;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
type OutputHandler<TContext, TPath extends string, TInput, TOutput extends OutputMonads> = (context: Prettify<TContext & {
|
|
27
|
+
params: ParamsFrom<TPath>;
|
|
28
|
+
input: TInput;
|
|
29
|
+
output: typeof output;
|
|
30
|
+
}>) => TOutput | Promise<TOutput>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/types/adapter.d.ts
|
|
33
|
+
type AdaptorFn<TContext> = (blueprint: AdapterBlueprint<TContext> | AdapterWithSchemaBlueprint<TContext>) => void;
|
|
34
|
+
declare class AdapterBlueprint<TContext> {
|
|
35
|
+
readonly method: Method;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly handler: OutputHandler<TContext, string, any, OutputMonads>;
|
|
38
|
+
constructor(method: Method, path: string, handler: OutputHandler<TContext, string, any, OutputMonads>);
|
|
39
|
+
}
|
|
40
|
+
declare class AdapterWithSchemaBlueprint<TContext> {
|
|
41
|
+
readonly method: Method;
|
|
42
|
+
readonly path: string;
|
|
43
|
+
readonly channel: InputChannel;
|
|
44
|
+
readonly schema: StandardSchemaV1;
|
|
45
|
+
readonly handler: OutputHandler<TContext, string, any, OutputMonads>;
|
|
46
|
+
constructor(method: Method, path: string, channel: InputChannel, schema: StandardSchemaV1, handler: OutputHandler<TContext, string, any, OutputMonads>);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/index.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* This factory function provides the inteface for you to write the integration between
|
|
52
|
+
* `contract-controller` and your http library/framework of choice.
|
|
53
|
+
*
|
|
54
|
+
* When youre done writing your adaptor you can use the product in your code base.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // lib/cc.ts
|
|
58
|
+
* export const cc = controllerContractFactory(() => {
|
|
59
|
+
* // your code
|
|
60
|
+
* })
|
|
61
|
+
*
|
|
62
|
+
* // api/users.ts
|
|
63
|
+
* export const getUsers = cc.endpoint().input('...', ...).output(() => { ... });
|
|
64
|
+
*/
|
|
65
|
+
declare function controllerContractBuilderFactory<TContext>(adaptor: AdaptorFn<TContext>): BuilderStep.Endpoint<TContext>;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { AdapterBlueprint, AdapterWithSchemaBlueprint, AdaptorFn, controllerContractBuilderFactory };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/types/adapter.ts
|
|
2
|
+
var AdapterBlueprint = class {
|
|
3
|
+
method;
|
|
4
|
+
path;
|
|
5
|
+
handler;
|
|
6
|
+
constructor(method, path, handler) {
|
|
7
|
+
this.method = method;
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.handler = handler;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var AdapterWithSchemaBlueprint = class {
|
|
13
|
+
method;
|
|
14
|
+
path;
|
|
15
|
+
channel;
|
|
16
|
+
schema;
|
|
17
|
+
handler;
|
|
18
|
+
constructor(method, path, channel, schema, handler) {
|
|
19
|
+
this.method = method;
|
|
20
|
+
this.path = path;
|
|
21
|
+
this.channel = channel;
|
|
22
|
+
this.schema = schema;
|
|
23
|
+
this.handler = handler;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/steps/output.ts
|
|
28
|
+
function outputStep(adaptor, method, path, channel, schema) {
|
|
29
|
+
return { output(handler) {
|
|
30
|
+
if (channel && schema) adaptor(new AdapterWithSchemaBlueprint(method, path, channel, schema, handler));
|
|
31
|
+
else adaptor(new AdapterBlueprint(method, path, handler));
|
|
32
|
+
return {};
|
|
33
|
+
} };
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/steps/input.ts
|
|
37
|
+
function inputStep(adaptor, method, path) {
|
|
38
|
+
return { input(channel, schema) {
|
|
39
|
+
return outputStep(adaptor, method, path, channel, schema);
|
|
40
|
+
} };
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/steps/endpoint.ts
|
|
44
|
+
function endpointStep(adaptor) {
|
|
45
|
+
return { endpoint(method, path) {
|
|
46
|
+
return {
|
|
47
|
+
...outputStep(adaptor, method, path),
|
|
48
|
+
...inputStep(adaptor, method, path)
|
|
49
|
+
};
|
|
50
|
+
} };
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/index.ts
|
|
54
|
+
/**
|
|
55
|
+
* This factory function provides the inteface for you to write the integration between
|
|
56
|
+
* `contract-controller` and your http library/framework of choice.
|
|
57
|
+
*
|
|
58
|
+
* When youre done writing your adaptor you can use the product in your code base.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* // lib/cc.ts
|
|
62
|
+
* export const cc = controllerContractFactory(() => {
|
|
63
|
+
* // your code
|
|
64
|
+
* })
|
|
65
|
+
*
|
|
66
|
+
* // api/users.ts
|
|
67
|
+
* export const getUsers = cc.endpoint().input('...', ...).output(() => { ... });
|
|
68
|
+
*/
|
|
69
|
+
function controllerContractBuilderFactory(adaptor) {
|
|
70
|
+
return endpointStep(adaptor);
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { AdapterBlueprint, AdapterWithSchemaBlueprint, controllerContractBuilderFactory };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/utils/cast.ts
|
|
2
|
+
let cast;
|
|
3
|
+
(function(_cast) {
|
|
4
|
+
function params(rawParams) {
|
|
5
|
+
return rawParams;
|
|
6
|
+
}
|
|
7
|
+
_cast.params = params;
|
|
8
|
+
})(cast || (cast = {}));
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/utils/output.ts
|
|
11
|
+
let output;
|
|
12
|
+
(function(_output) {
|
|
13
|
+
function next(data, code = 100) {
|
|
14
|
+
return {
|
|
15
|
+
type: "next",
|
|
16
|
+
code,
|
|
17
|
+
data
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
_output.next = next;
|
|
21
|
+
function pass(data, code = 200) {
|
|
22
|
+
return {
|
|
23
|
+
type: "pass",
|
|
24
|
+
code,
|
|
25
|
+
data
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
_output.pass = pass;
|
|
29
|
+
function fail(data, code = 400) {
|
|
30
|
+
return {
|
|
31
|
+
type: "fail",
|
|
32
|
+
code,
|
|
33
|
+
data
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
_output.fail = fail;
|
|
37
|
+
function exit() {
|
|
38
|
+
return { type: "exit" };
|
|
39
|
+
}
|
|
40
|
+
_output.exit = exit;
|
|
41
|
+
})(output || (output = {}));
|
|
42
|
+
function rootOnly(message) {
|
|
43
|
+
return [{
|
|
44
|
+
key: "root",
|
|
45
|
+
message
|
|
46
|
+
}];
|
|
47
|
+
}
|
|
48
|
+
const StandardOutput = {
|
|
49
|
+
OK: output.pass(rootOnly("ok"), 200),
|
|
50
|
+
Created: output.pass(rootOnly("Created"), 201),
|
|
51
|
+
Accepted: output.pass(rootOnly("Accepted"), 202),
|
|
52
|
+
BadRequest: output.fail(rootOnly("Bad Request"), 400),
|
|
53
|
+
Unauthorized: output.fail(rootOnly("Unauthorized"), 401),
|
|
54
|
+
Forbidden: output.fail(rootOnly("Forbidden"), 403),
|
|
55
|
+
NotFound: output.fail(rootOnly("Not Found"), 404),
|
|
56
|
+
InternalServerError: output.fail(rootOnly("Internal Server Error"), 500),
|
|
57
|
+
BadGateway: output.fail(rootOnly("Bad Gateway"), 502)
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
export { StandardOutput, cast, output, rootOnly };
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@controller-contract/server",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"author": "kennychulo",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.mjs",
|
|
18
|
+
"./utils": "./dist/utils/index.mjs",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "22.19.19",
|
|
26
|
+
"tsdown": "0.22.0",
|
|
27
|
+
"typescript": "6.0.3"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@controller-contract/core": "0.1.1",
|
|
31
|
+
"@standard-schema/spec": "1.1.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsdown",
|
|
35
|
+
"release": "pnpm publish"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { endpointStep } from './steps/endpoint';
|
|
2
|
+
import type { AdaptorFn } from './types/adapter';
|
|
3
|
+
|
|
4
|
+
export * from './types/adapter';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This factory function provides the inteface for you to write the integration between
|
|
8
|
+
* `contract-controller` and your http library/framework of choice.
|
|
9
|
+
*
|
|
10
|
+
* When youre done writing your adaptor you can use the product in your code base.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // lib/cc.ts
|
|
14
|
+
* export const cc = controllerContractFactory(() => {
|
|
15
|
+
* // your code
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // api/users.ts
|
|
19
|
+
* export const getUsers = cc.endpoint().input('...', ...).output(() => { ... });
|
|
20
|
+
*/
|
|
21
|
+
export function controllerContractBuilderFactory<TContext>(adaptor: AdaptorFn<TContext>) {
|
|
22
|
+
return endpointStep<TContext>(adaptor);
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AdaptorFn } from '../types/adapter';
|
|
2
|
+
import type { BuilderStep } from '../types/builder-step';
|
|
3
|
+
import { inputStep } from './input';
|
|
4
|
+
import { outputStep } from './output';
|
|
5
|
+
|
|
6
|
+
export function endpointStep<TContext>(adaptor: AdaptorFn<TContext>): BuilderStep.Endpoint<TContext> {
|
|
7
|
+
return {
|
|
8
|
+
endpoint(method, path) {
|
|
9
|
+
return {
|
|
10
|
+
...outputStep(adaptor, method, path),
|
|
11
|
+
...inputStep(adaptor, method, path),
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Method } from '@controller-contract/core';
|
|
2
|
+
import type { AdaptorFn } from '../types/adapter';
|
|
3
|
+
import type { BuilderStep } from '../types/builder-step';
|
|
4
|
+
import { outputStep } from './output';
|
|
5
|
+
|
|
6
|
+
export function inputStep<TContext, const TMethod extends Method, const TPath extends string>(
|
|
7
|
+
adaptor: AdaptorFn<TContext>,
|
|
8
|
+
method: TMethod,
|
|
9
|
+
path: TPath,
|
|
10
|
+
): BuilderStep.Input<TContext, TMethod, TPath> {
|
|
11
|
+
return {
|
|
12
|
+
input(channel, schema) {
|
|
13
|
+
return outputStep(adaptor, method, path, channel, schema);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { InputChannel, Method, OutputMonads } from '@controller-contract/core';
|
|
2
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
import { AdapterBlueprint, AdapterWithSchemaBlueprint, type AdaptorFn } from '../types/adapter';
|
|
4
|
+
import type { BuilderStep } from '../types/builder-step';
|
|
5
|
+
|
|
6
|
+
export function outputStep<
|
|
7
|
+
TContext,
|
|
8
|
+
const TMethod extends Method,
|
|
9
|
+
const TPath extends string,
|
|
10
|
+
TInput,
|
|
11
|
+
TOutput extends OutputMonads,
|
|
12
|
+
>(
|
|
13
|
+
adaptor: AdaptorFn<TContext>,
|
|
14
|
+
method: TMethod,
|
|
15
|
+
path: TPath,
|
|
16
|
+
channel?: InputChannel,
|
|
17
|
+
schema?: StandardSchemaV1<TInput>,
|
|
18
|
+
): BuilderStep.Output<TContext, TMethod, TPath, TInput, TOutput> {
|
|
19
|
+
return {
|
|
20
|
+
output(handler) {
|
|
21
|
+
if (channel && schema) {
|
|
22
|
+
adaptor(new AdapterWithSchemaBlueprint(method, path, channel, schema, handler));
|
|
23
|
+
} else {
|
|
24
|
+
adaptor(new AdapterBlueprint(method, path, handler));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {};
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InputChannel, Method, OutputMonads } from '@controller-contract/core';
|
|
2
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
import type { OutputHandler } from './builder-step';
|
|
4
|
+
|
|
5
|
+
export type AdaptorFn<TContext> = (
|
|
6
|
+
blueprint: AdapterBlueprint<TContext> | AdapterWithSchemaBlueprint<TContext>,
|
|
7
|
+
) => void;
|
|
8
|
+
|
|
9
|
+
export class AdapterBlueprint<TContext> {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly method: Method,
|
|
12
|
+
readonly path: string,
|
|
13
|
+
readonly handler: OutputHandler<TContext, string, any, OutputMonads>,
|
|
14
|
+
) {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AdapterWithSchemaBlueprint<TContext> {
|
|
18
|
+
constructor(
|
|
19
|
+
readonly method: Method,
|
|
20
|
+
readonly path: string,
|
|
21
|
+
readonly channel: InputChannel,
|
|
22
|
+
readonly schema: StandardSchemaV1,
|
|
23
|
+
readonly handler: OutputHandler<TContext, string, any, OutputMonads>,
|
|
24
|
+
) {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ControllerContract,
|
|
3
|
+
InputChannel,
|
|
4
|
+
Method,
|
|
5
|
+
NoInput,
|
|
6
|
+
OutputFail,
|
|
7
|
+
OutputMonads,
|
|
8
|
+
ParamsFrom,
|
|
9
|
+
Prettify,
|
|
10
|
+
} from '@controller-contract/core';
|
|
11
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
12
|
+
import type { output } from '../utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* All the possible steps are defined below.
|
|
16
|
+
*/
|
|
17
|
+
export namespace BuilderStep {
|
|
18
|
+
/**
|
|
19
|
+
* Always the first step
|
|
20
|
+
*/
|
|
21
|
+
export interface Endpoint<TContext> {
|
|
22
|
+
endpoint<const TMethod extends Method, const TPath extends string>(
|
|
23
|
+
method: TMethod,
|
|
24
|
+
path: TPath,
|
|
25
|
+
): BuilderStep.Output<TContext, TMethod, TPath, NoInput, never> & BuilderStep.Input<TContext, TMethod, TPath>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Input<TContext, TMethod extends Method, TPath extends string> {
|
|
29
|
+
input<TInput>(
|
|
30
|
+
channel: InputChannel,
|
|
31
|
+
schema: StandardSchemaV1<TInput>,
|
|
32
|
+
): BuilderStep.Output<TContext, TMethod, TPath, TInput, OutputFail<Extract<keyof TInput, string>>>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Always the last step
|
|
37
|
+
*/
|
|
38
|
+
export interface Output<
|
|
39
|
+
TContext,
|
|
40
|
+
TMethod extends Method,
|
|
41
|
+
TPath extends string,
|
|
42
|
+
TInput,
|
|
43
|
+
TOutput extends OutputMonads,
|
|
44
|
+
> {
|
|
45
|
+
output<TFnOutput extends OutputMonads>(
|
|
46
|
+
handler: OutputHandler<TContext, TPath, TInput, TFnOutput>,
|
|
47
|
+
): ControllerContract<TMethod, TPath, TInput, TOutput | TFnOutput>;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type OutputHandler<TContext, TPath extends string, TInput, TOutput extends OutputMonads> = (
|
|
52
|
+
context: Prettify<
|
|
53
|
+
TContext & {
|
|
54
|
+
params: ParamsFrom<TPath>;
|
|
55
|
+
input: TInput;
|
|
56
|
+
output: typeof output;
|
|
57
|
+
}
|
|
58
|
+
>,
|
|
59
|
+
) => TOutput | Promise<TOutput>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { OutputExit, OutputFail, OutputNext, OutputPass } from '@controller-contract/core';
|
|
2
|
+
|
|
3
|
+
export namespace output {
|
|
4
|
+
export function next<T>(data: T, code = 100): OutputNext<T> {
|
|
5
|
+
return {
|
|
6
|
+
type: 'next',
|
|
7
|
+
code,
|
|
8
|
+
data,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function pass<T>(data: T, code = 200): OutputPass<T> {
|
|
13
|
+
return {
|
|
14
|
+
type: 'pass',
|
|
15
|
+
code,
|
|
16
|
+
data,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function fail<const T extends string>(data: OutputFail<T>['data'], code = 400): OutputFail<T> {
|
|
21
|
+
return {
|
|
22
|
+
type: 'fail',
|
|
23
|
+
code,
|
|
24
|
+
data,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Used to escape the controller contract state machine, useful for redirects.
|
|
30
|
+
*/
|
|
31
|
+
export function exit(): OutputExit {
|
|
32
|
+
return {
|
|
33
|
+
type: 'exit',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function rootOnly(message: string): OutputFail<'root'>['data'] {
|
|
39
|
+
return [{ key: 'root', message }];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const StandardOutput = {
|
|
43
|
+
// 200
|
|
44
|
+
OK: output.pass(rootOnly('ok'), 200),
|
|
45
|
+
Created: output.pass(rootOnly('Created'), 201),
|
|
46
|
+
Accepted: output.pass(rootOnly('Accepted'), 202),
|
|
47
|
+
// 400
|
|
48
|
+
BadRequest: output.fail(rootOnly('Bad Request'), 400),
|
|
49
|
+
Unauthorized: output.fail(rootOnly('Unauthorized'), 401),
|
|
50
|
+
Forbidden: output.fail(rootOnly('Forbidden'), 403),
|
|
51
|
+
NotFound: output.fail(rootOnly('Not Found'), 404),
|
|
52
|
+
// 500
|
|
53
|
+
InternalServerError: output.fail(rootOnly('Internal Server Error'), 500),
|
|
54
|
+
BadGateway: output.fail(rootOnly('Bad Gateway'), 502),
|
|
55
|
+
};
|