@loutrejs/loutre 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/openapi.js +2 -1
- package/dist/core/context-key.d.ts +3 -1
- package/dist/core/context-key.js +3 -0
- package/dist/core/contract-internal.d.ts +22 -0
- package/dist/core/contract-internal.js +61 -0
- package/dist/core/contract.d.ts +34 -32
- package/dist/core/contract.js +35 -26
- package/dist/core/implementation.d.ts +25 -15
- package/dist/core/implementation.js +8 -3
- package/dist/core/layer.d.ts +30 -2
- package/dist/core/layer.js +29 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/graph.d.ts +2 -1
- package/dist/graph/graph.js +39 -24
- package/dist/http/application.js +10 -1
- package/dist/http/client.d.ts +4 -4
- package/dist/http/client.js +7 -2
- package/dist/http/definitions.d.ts +155 -17
- package/dist/http/definitions.js +244 -3
- package/dist/http/internal.d.ts +8 -0
- package/dist/http/internal.js +1 -0
- package/dist/http/path.d.ts +4 -0
- package/dist/http/path.js +6 -0
- package/package.json +1 -1
package/dist/graph/graph.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { argumentsProvider, asModuleInstance, childPipelineOf, contextKeyName, defineModule, isArgsClass, isEnvClass, layerDefinitionOf, normalizeProvider, queueRuntimeToken, tokenName, } from '../core/index.js';
|
|
2
|
+
import { contractOfBinding, contractRootOf, resolveContractProcedureIdentity, } from '../core/contract-internal.js';
|
|
2
3
|
import { Container, Logger } from '../runtime/index.js';
|
|
3
4
|
export class StaticValidationError extends Error {
|
|
4
5
|
diagnostics;
|
|
@@ -38,6 +39,7 @@ export function compileApplication(input) {
|
|
|
38
39
|
validateNamedDescriptors(queues, 'LUTRE_QUEUE_DUPLICATE', diagnostics);
|
|
39
40
|
validateCronTriggers(triggers, diagnostics);
|
|
40
41
|
diagnostics.push(...validateQueueDrivers(input.modules, queueTriggers));
|
|
42
|
+
const applicationContract = input.contract === undefined ? undefined : contractOfBinding(input.contract);
|
|
41
43
|
const contractIds = new Map();
|
|
42
44
|
const idContract = (contract) => {
|
|
43
45
|
const current = contractIds.get(contract);
|
|
@@ -56,29 +58,36 @@ export function compileApplication(input) {
|
|
|
56
58
|
implementationIds.set(implementation, id);
|
|
57
59
|
return id;
|
|
58
60
|
};
|
|
61
|
+
if (applicationContract)
|
|
62
|
+
idContract(applicationContract);
|
|
59
63
|
const descriptors = modules.flatMap((module) => module.definition.implementations ?? []);
|
|
60
64
|
for (const implementation of new Set(descriptors)) {
|
|
61
|
-
|
|
65
|
+
const firstProcedure = implementation.procedures[0];
|
|
66
|
+
const identity = firstProcedure === undefined
|
|
67
|
+
? { contract: contractRootOf(implementation.contract) }
|
|
68
|
+
: resolveContractProcedureIdentity(implementation.contract, firstProcedure, applicationContract);
|
|
69
|
+
idContract(identity.contract);
|
|
62
70
|
idImplementation(implementation);
|
|
63
71
|
}
|
|
64
72
|
const targets = [];
|
|
65
73
|
for (const implementation of descriptors) {
|
|
66
74
|
for (const procedureName of implementation.procedures) {
|
|
75
|
+
const identity = resolveContractProcedureIdentity(implementation.contract, procedureName, applicationContract);
|
|
67
76
|
const procedure = implementation.contract.procedures[procedureName];
|
|
68
77
|
const protocol = procedure?.protocols[implementation.protocol];
|
|
69
78
|
if (!procedure || !protocol) {
|
|
70
79
|
diagnostics.push({
|
|
71
80
|
code: 'LUTRE_IMPL_003',
|
|
72
81
|
message: `${procedureName} is not declared for protocol ${implementation.protocol}`,
|
|
73
|
-
path: `${idContract(
|
|
82
|
+
path: `${idContract(identity.contract)}.${identity.procedure}.${implementation.protocol}`,
|
|
74
83
|
});
|
|
75
84
|
continue;
|
|
76
85
|
}
|
|
77
86
|
targets.push({
|
|
78
87
|
implementation,
|
|
79
|
-
contractId: idContract(
|
|
88
|
+
contractId: idContract(identity.contract),
|
|
80
89
|
implementationId: idImplementation(implementation),
|
|
81
|
-
procedure:
|
|
90
|
+
procedure: identity.procedure,
|
|
82
91
|
protocol: implementation.protocol,
|
|
83
92
|
dispatchKey: protocol.dispatchKey,
|
|
84
93
|
pipeline: protocol.pipeline ?? protocol.definition?.pipeline ?? [],
|
|
@@ -90,7 +99,7 @@ export function compileApplication(input) {
|
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
validateDispatchKeys(targets, diagnostics);
|
|
93
|
-
validateCoverage(descriptors, contractIds, diagnostics);
|
|
102
|
+
validateCoverage(descriptors, targets, contractIds, diagnostics, applicationContract);
|
|
94
103
|
validateDuplicateProviders(modules, diagnostics);
|
|
95
104
|
const tokensById = collectCustomTokens(providers, targets, diagnostics);
|
|
96
105
|
const contextKeysByName = collectContextKeys(targets, diagnostics);
|
|
@@ -104,13 +113,17 @@ export function compileApplication(input) {
|
|
|
104
113
|
layers: target.pipeline.map(toLayerIR),
|
|
105
114
|
});
|
|
106
115
|
}
|
|
107
|
-
const implementations = [...new Set(descriptors)].map((implementation) =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
const implementations = [...new Set(descriptors)].map((implementation) => {
|
|
117
|
+
const implementationTargets = targets.filter((target) => target.implementation === implementation);
|
|
118
|
+
return {
|
|
119
|
+
id: idImplementation(implementation),
|
|
120
|
+
name: implementation.name,
|
|
121
|
+
contract: implementationTargets[0]?.contractId ??
|
|
122
|
+
idContract(contractRootOf(implementation.contract)),
|
|
123
|
+
protocol: implementation.protocol,
|
|
124
|
+
procedures: implementationTargets.map((target) => target.procedure),
|
|
125
|
+
};
|
|
126
|
+
});
|
|
114
127
|
const dependencyGraph = buildDependencyGraph(modules, descriptors, targets, tasks, diagnostics);
|
|
115
128
|
const probedTokenIds = dependencyGraph.nodes
|
|
116
129
|
.filter((node) => node.kind === 'token')
|
|
@@ -272,7 +285,7 @@ function validateDispatchKeys(targets, diagnostics) {
|
|
|
272
285
|
const path = `${target.contractId}.${target.procedure}.${target.protocol}`;
|
|
273
286
|
const existing = targetsByKey.get(target.dispatchKey);
|
|
274
287
|
if (existing) {
|
|
275
|
-
if (existing.
|
|
288
|
+
if (existing.contractId === target.contractId &&
|
|
276
289
|
existing.procedure === target.procedure &&
|
|
277
290
|
existing.protocol === target.protocol) {
|
|
278
291
|
continue;
|
|
@@ -914,18 +927,20 @@ function collectModules(roots) {
|
|
|
914
927
|
visit(root);
|
|
915
928
|
return modules;
|
|
916
929
|
}
|
|
917
|
-
function validateCoverage(implementations, contractIds, diagnostics) {
|
|
918
|
-
const contracts =
|
|
930
|
+
function validateCoverage(implementations, targets, contractIds, diagnostics, applicationContract) {
|
|
931
|
+
const contracts = applicationContract
|
|
932
|
+
? [applicationContract]
|
|
933
|
+
: [
|
|
934
|
+
...new Set(implementations.map((implementation) => contractRootOf(implementation.contract))),
|
|
935
|
+
];
|
|
919
936
|
for (const contract of contracts) {
|
|
920
|
-
const
|
|
921
|
-
for (const
|
|
922
|
-
for (const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
implementation.procedures.includes(procedureName));
|
|
928
|
-
const path = `${contractIds.get(contract) ?? 'contract:unknown'}.${procedureName}.${protocol}`;
|
|
937
|
+
const contractId = contractIds.get(contract) ?? 'contract:unknown';
|
|
938
|
+
for (const [procedureName, procedure] of Object.entries(contract.procedures)) {
|
|
939
|
+
for (const protocol of Object.keys(procedure.protocols)) {
|
|
940
|
+
const covering = targets.filter((target) => target.contractId === contractId &&
|
|
941
|
+
target.protocol === protocol &&
|
|
942
|
+
target.procedure === procedureName);
|
|
943
|
+
const path = `${contractId}.${procedureName}.${protocol}`;
|
|
929
944
|
if (covering.length === 0) {
|
|
930
945
|
diagnostics.push({
|
|
931
946
|
code: 'LUTRE_IMPL_001',
|
package/dist/http/application.js
CHANGED
|
@@ -363,7 +363,16 @@ async function finalizeResponse(definition, result, signal) {
|
|
|
363
363
|
});
|
|
364
364
|
}
|
|
365
365
|
const body = await validateSchema(response.body, result.body);
|
|
366
|
-
|
|
366
|
+
const headers = mergeResponseHeaders(response.staticHeaders, responseHeaders);
|
|
367
|
+
if (response.status === 204 ||
|
|
368
|
+
response.status === 205 ||
|
|
369
|
+
response.status === 304) {
|
|
370
|
+
if (body !== undefined) {
|
|
371
|
+
throw new Error(`HTTP ${response.status} response body must be undefined`);
|
|
372
|
+
}
|
|
373
|
+
return new Response(undefined, { status: response.status, headers });
|
|
374
|
+
}
|
|
375
|
+
return jsonResponse(response.status, body, headers);
|
|
367
376
|
}
|
|
368
377
|
async function validateResponseHeaders(schema, headers) {
|
|
369
378
|
if (!schema) {
|
package/dist/http/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ContractDefinition, type ProcedureDefinition, type SchemaInput, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
|
|
1
|
+
import { type ContractBinding, type ContractDefinition, type ContractOfBinding, type ProcedureDefinition, type SchemaInput, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
|
|
2
2
|
import type { HttpHeaders, HttpParamsSchemas, HttpProtocol, HttpProtocolDefinition, HttpRequestBodyDefinition, HttpResponseDefinition } from './definitions.js';
|
|
3
3
|
import { type PathParamNames, type RawPathParams } from './path.js';
|
|
4
4
|
export interface HttpClientTransportRequest {
|
|
@@ -52,8 +52,8 @@ export type HttpClientResponse<TDefinition extends HttpProtocolDefinition> = {
|
|
|
52
52
|
} : never;
|
|
53
53
|
}[keyof TDefinition['responses'] & string];
|
|
54
54
|
type HttpClientMethod<TDefinition extends HttpProtocolDefinition> = keyof HttpClientRequest<TDefinition> extends never ? () => Promise<HttpClientResponse<TDefinition>> : (request: HttpClientRequest<TDefinition>) => Promise<HttpClientResponse<TDefinition>>;
|
|
55
|
-
export type HttpClient<
|
|
56
|
-
readonly [TProcedure in HttpProcedureNames<
|
|
55
|
+
export type HttpClient<TBinding extends ContractBinding> = {
|
|
56
|
+
readonly [TProcedure in HttpProcedureNames<ContractOfBinding<TBinding>>]: HttpClientMethod<HttpDefinitionOf<ContractOfBinding<TBinding>, TProcedure>>;
|
|
57
57
|
};
|
|
58
58
|
export declare class HttpClientResponseError extends Error {
|
|
59
59
|
readonly status: number;
|
|
@@ -62,7 +62,7 @@ export declare class HttpClientResponseError extends Error {
|
|
|
62
62
|
readonly procedure: string;
|
|
63
63
|
constructor(status: number, method: string, path: string, procedure: string, message: string, options?: ErrorOptions);
|
|
64
64
|
}
|
|
65
|
-
export declare function createHttpClient<const
|
|
65
|
+
export declare function createHttpClient<const TBinding extends ContractBinding>(binding: TBinding, transport: HttpClientTransport): HttpClient<TBinding>;
|
|
66
66
|
export interface FetchHttpTransportOptions {
|
|
67
67
|
readonly baseUrl: string | URL;
|
|
68
68
|
readonly fetch?: typeof globalThis.fetch;
|
package/dist/http/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SchemaValidationError, validateSchema, } from '../core/index.js';
|
|
2
|
+
import { contractOfBinding } from '../core/contract-internal.js';
|
|
2
3
|
import { parseHttpPath, } from './path.js';
|
|
3
4
|
export class HttpClientResponseError extends Error {
|
|
4
5
|
status;
|
|
@@ -14,7 +15,8 @@ export class HttpClientResponseError extends Error {
|
|
|
14
15
|
this.name = 'HttpClientResponseError';
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
|
-
export function createHttpClient(
|
|
18
|
+
export function createHttpClient(binding, transport) {
|
|
19
|
+
const contract = contractOfBinding(binding);
|
|
18
20
|
const client = {};
|
|
19
21
|
for (const [procedureName, procedure] of Object.entries(contract.procedures)) {
|
|
20
22
|
const definition = httpDefinitionOf(procedure);
|
|
@@ -189,8 +191,11 @@ function encodeBody(body, contentType) {
|
|
|
189
191
|
return body;
|
|
190
192
|
}
|
|
191
193
|
async function decodeFetchBody(response) {
|
|
192
|
-
if (response.status === 204 ||
|
|
194
|
+
if (response.status === 204 ||
|
|
195
|
+
response.status === 205 ||
|
|
196
|
+
response.status === 304) {
|
|
193
197
|
return undefined;
|
|
198
|
+
}
|
|
194
199
|
const mediaType = normalizeMediaType(response.headers.get('content-type'));
|
|
195
200
|
if (mediaType === 'text/event-stream') {
|
|
196
201
|
if (!response.body) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { ContractDefinition, ContextProvidedBeforeTerminal, HasValidationBeforeTerminal, IsValidProtocolPipeline, PipelineItem, ProtocolDescriptor, ProtocolFactory, ProtocolGroup, SchemaInput, SchemaOutput, ShortCircuitDeclarationsOf, ShortCircuitResultOf, StandardSchemaV1, TerminalLayerDescriptor, ValidationLayerDescriptor } from '../core/index.js';
|
|
1
|
+
import type { ContractBinding, ContractDefinition, ContractOfBinding, ResolvedContractNode, ContextProvidedBeforeTerminal, HasValidationBeforeTerminal, IsValidProtocolPipeline, PipelineItem, ProcedureDefinition, ProtocolDescriptor, ProtocolFactory, ProtocolGroup, SchemaInput, SchemaOutput, ShortCircuitDeclarationsOf, ShortCircuitResultOf, StandardSchemaV1, TerminalLayerDescriptor, ValidationLayerDescriptor } from '../core/index.js';
|
|
2
|
+
import { protocolNamespaceType } from '../core/contract-internal.js';
|
|
3
|
+
import { httpNodeMetadata } from './internal.js';
|
|
2
4
|
import type { Logger } from '../runtime/index.js';
|
|
3
|
-
import { type HttpDispatchKey, type IsValidHttpPath, type PathParamNames, type RawPathParams } from './path.js';
|
|
5
|
+
import { type HttpDispatchKey, type IsValidHttpMethod, type IsValidHttpPath, type PathParamNames, type RawPathParams } from './path.js';
|
|
4
6
|
export type HttpParamsSchemas = Readonly<Record<string, StandardSchemaV1>>;
|
|
5
7
|
export interface HttpRequestBodyDefinition<TSchema extends StandardSchemaV1 = StandardSchemaV1> {
|
|
6
8
|
readonly contentType: string;
|
|
@@ -49,8 +51,110 @@ export interface HttpProtocolDefinition {
|
|
|
49
51
|
readonly pipeline: readonly PipelineItem[];
|
|
50
52
|
readonly interaction?: 'unary' | 'server-stream';
|
|
51
53
|
}
|
|
52
|
-
export interface
|
|
54
|
+
export interface HttpBranchDefinition {
|
|
55
|
+
readonly path?: string;
|
|
56
|
+
readonly pipeline?: readonly PipelineItem[];
|
|
57
|
+
readonly responses?: Readonly<Record<string, HttpResponseDefinition>>;
|
|
58
|
+
readonly routes: HttpRouteTree;
|
|
59
|
+
}
|
|
60
|
+
export type HttpRouteTree = Readonly<Record<string, HttpProtocolDefinition | HttpBranchDefinition | HttpResolvedNode>>;
|
|
61
|
+
interface HttpResolvedNodeMetadata<TSource extends HttpProtocolDefinition | HttpBranchDefinition = HttpProtocolDefinition | HttpBranchDefinition, THandlerName extends string | never = string | never> {
|
|
62
|
+
readonly kind: 'leaf' | 'branch';
|
|
63
|
+
readonly source: TSource;
|
|
64
|
+
readonly handlerName: THandlerName;
|
|
65
|
+
}
|
|
66
|
+
export type HttpResolvedLeaf<THandlerName extends string = string, TSource extends HttpProtocolDefinition = HttpProtocolDefinition, TEffective extends HttpProtocolDefinition = HttpProtocolDefinition> = ResolvedContractNode<ContractDefinition<{
|
|
67
|
+
readonly [K in THandlerName]: {
|
|
68
|
+
readonly kind: 'procedure';
|
|
69
|
+
readonly protocols: {
|
|
70
|
+
readonly http: HttpProtocol<TEffective>;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
}>, 'leaf'> & {
|
|
74
|
+
readonly [httpNodeMetadata]: HttpResolvedNodeMetadata<TSource, THandlerName> & {
|
|
75
|
+
readonly kind: 'leaf';
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
type ResolvedContractOf<TNode> = TNode extends ResolvedContractNode<infer TContract> ? TContract : never;
|
|
79
|
+
type HttpProtocolOfResolvedLeaf<TNode> = ResolvedContractOf<TNode> extends ContractDefinition<infer TProcedures> ? TProcedures[keyof TProcedures & string]['protocols'] extends {
|
|
80
|
+
readonly http: infer TProtocol extends ProtocolDescriptor<'http'>;
|
|
81
|
+
} ? TProtocol : never : never;
|
|
82
|
+
type FlatResolvedHttpProcedures<TChildren extends Readonly<Record<string, unknown>>, TPrefix extends string = ''> = UnionToIntersection<{
|
|
83
|
+
[K in keyof TChildren & string]: TChildren[K] extends HttpResolvedLeaf<any, any, any> ? {
|
|
84
|
+
readonly [P in JoinProcedurePath<TPrefix, K>]: ProcedureDefinition<{
|
|
85
|
+
readonly http: HttpProtocolOfResolvedLeaf<TChildren[K]>;
|
|
86
|
+
}>;
|
|
87
|
+
} : TChildren[K] extends HttpResolvedBranch<any, infer TGrandchildren> ? FlatResolvedHttpProcedures<TGrandchildren, JoinProcedurePath<TPrefix, K>> : never;
|
|
88
|
+
}[keyof TChildren & string]> extends infer TProcedures extends Record<string, ProcedureDefinition> ? TProcedures : never;
|
|
89
|
+
export type HttpResolvedBranch<TSource extends HttpBranchDefinition = HttpBranchDefinition, TChildren extends Readonly<Record<string, HttpResolvedNode>> = Readonly<Record<string, HttpResolvedNode>>> = ResolvedContractNode<ContractDefinition<FlatResolvedHttpProcedures<TChildren>>, 'branch'> & TChildren & {
|
|
90
|
+
readonly [httpNodeMetadata]: HttpResolvedNodeMetadata<TSource, never> & {
|
|
91
|
+
readonly kind: 'branch';
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
export type HttpResolvedNode = ResolvedContractNode<ContractDefinition, 'leaf' | 'branch'> & {
|
|
95
|
+
readonly [httpNodeMetadata]: HttpResolvedNodeMetadata;
|
|
96
|
+
};
|
|
97
|
+
type SourceOfHttpNode<TNode> = TNode extends {
|
|
98
|
+
readonly [httpNodeMetadata]: HttpResolvedNodeMetadata<infer TSource, any>;
|
|
99
|
+
} ? TSource : TNode;
|
|
100
|
+
type HandlerNameOfHttpNode<TName extends string, TNode> = TNode extends {
|
|
101
|
+
readonly [httpNodeMetadata]: HttpResolvedNodeMetadata<any, infer THandlerName>;
|
|
102
|
+
} ? [THandlerName] extends [never] ? TName : THandlerName & string : TName;
|
|
103
|
+
type BranchPathOf<TBranch extends HttpBranchDefinition> = TBranch extends {
|
|
104
|
+
readonly path: infer TPath extends string;
|
|
105
|
+
} ? TPath : '';
|
|
106
|
+
type BranchPipelineOf<TBranch extends HttpBranchDefinition> = TBranch extends {
|
|
107
|
+
readonly pipeline: infer TPipeline extends readonly PipelineItem[];
|
|
108
|
+
} ? TPipeline : readonly [];
|
|
109
|
+
type BranchResponsesOf<TBranch extends HttpBranchDefinition> = TBranch extends {
|
|
110
|
+
readonly responses: infer TResponses extends Readonly<Record<string, HttpResponseDefinition>>;
|
|
111
|
+
} ? TResponses : {};
|
|
112
|
+
type JoinHttpPath<TParent extends string, TChild extends string> = TParent extends '' ? TChild : TChild extends '/' ? TParent : `${TParent}${TChild}`;
|
|
113
|
+
type MergeHttpResponses<TParent extends Readonly<Record<string, HttpResponseDefinition>>, TLocal extends Readonly<Record<string, HttpResponseDefinition>>> = Extract<keyof TParent, keyof TLocal> extends never ? TParent & TLocal : never;
|
|
114
|
+
type EffectiveHttpDefinition<TDefinition extends HttpProtocolDefinition, TParentPath extends string, TParentPipeline extends readonly PipelineItem[], TParentResponses extends Readonly<Record<string, HttpResponseDefinition>>> = {
|
|
115
|
+
readonly method: TDefinition['method'];
|
|
116
|
+
readonly path: JoinHttpPath<TParentPath, TDefinition['path']>;
|
|
117
|
+
readonly responses: MergeHttpResponses<TParentResponses, TDefinition['responses']>;
|
|
118
|
+
readonly pipeline: readonly [...TParentPipeline, ...TDefinition['pipeline']];
|
|
119
|
+
} & (TDefinition extends {
|
|
120
|
+
readonly summary: infer TValue extends string;
|
|
121
|
+
} ? {
|
|
122
|
+
readonly summary: TValue;
|
|
123
|
+
} : {}) & (TDefinition extends {
|
|
124
|
+
readonly description: infer TValue extends string;
|
|
125
|
+
} ? {
|
|
126
|
+
readonly description: TValue;
|
|
127
|
+
} : {}) & (TDefinition extends {
|
|
128
|
+
readonly tags: infer TValue extends readonly string[];
|
|
129
|
+
} ? {
|
|
130
|
+
readonly tags: TValue;
|
|
131
|
+
} : {}) & (TDefinition extends {
|
|
132
|
+
readonly deprecated: infer TValue extends boolean;
|
|
133
|
+
} ? {
|
|
134
|
+
readonly deprecated: TValue;
|
|
135
|
+
} : {}) & (TDefinition extends {
|
|
136
|
+
readonly request: infer TValue extends HttpRequestDefinition;
|
|
137
|
+
} ? {
|
|
138
|
+
readonly request: TValue;
|
|
139
|
+
} : {}) & (TDefinition extends {
|
|
140
|
+
readonly interaction: infer TValue extends 'unary' | 'server-stream';
|
|
141
|
+
} ? {
|
|
142
|
+
readonly interaction: TValue;
|
|
143
|
+
} : {});
|
|
144
|
+
type ResolvedHttpTree<TTree extends Readonly<Record<string, unknown>>, TParentPath extends string = '', TParentPipeline extends readonly PipelineItem[] = readonly [], TParentResponses extends Readonly<Record<string, HttpResponseDefinition>> = {}> = {
|
|
145
|
+
readonly [K in keyof TTree]: ResolveHttpNode<K & string, TTree[K], TParentPath, TParentPipeline, TParentResponses>;
|
|
146
|
+
};
|
|
147
|
+
type ResolveHttpNode<TName extends string, TNode, TParentPath extends string, TParentPipeline extends readonly PipelineItem[], TParentResponses extends Readonly<Record<string, HttpResponseDefinition>>> = SourceOfHttpNode<TNode> extends infer TSource ? TSource extends HttpProtocolDefinition ? HttpResolvedLeaf<HandlerNameOfHttpNode<TName, TNode>, TSource, EffectiveHttpDefinition<TSource, TParentPath, TParentPipeline, TParentResponses>> : TSource extends HttpBranchDefinition ? HttpResolvedBranch<TSource, ResolvedHttpTree<TSource['routes'], JoinHttpPath<TParentPath, BranchPathOf<TSource>>, readonly [...TParentPipeline, ...BranchPipelineOf<TSource>], MergeHttpResponses<TParentResponses, BranchResponsesOf<TSource>>>> : never : never;
|
|
148
|
+
type JoinProcedurePath<TPrefix extends string, TName extends string> = TPrefix extends '' ? TName : `${TPrefix}.${TName}`;
|
|
149
|
+
type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends (value: infer TIntersection) => void ? TIntersection : never;
|
|
150
|
+
type FlatHttpProtocols<TTree extends Readonly<Record<string, unknown>>, TParentPath extends string = '', TParentPipeline extends readonly PipelineItem[] = readonly [], TParentResponses extends Readonly<Record<string, HttpResponseDefinition>> = {}, TProcedurePrefix extends string = '', TRootTree extends Readonly<Record<string, unknown>> = TTree> = UnionToIntersection<{
|
|
151
|
+
[K in keyof TTree & string]: SourceOfHttpNode<TTree[K]> extends infer TSource ? TSource extends HttpProtocolDefinition ? {
|
|
152
|
+
readonly [P in JoinProcedurePath<TProcedurePrefix, K>]: HttpProtocol<EffectiveHttpDefinition<TSource, TParentPath, TParentPipeline, TParentResponses>, ResolvedHttpTree<TRootTree>>;
|
|
153
|
+
} : TSource extends HttpBranchDefinition ? FlatHttpProtocols<TSource['routes'], JoinHttpPath<TParentPath, BranchPathOf<TSource>>, readonly [...TParentPipeline, ...BranchPipelineOf<TSource>], MergeHttpResponses<TParentResponses, BranchResponsesOf<TSource>>, JoinProcedurePath<TProcedurePrefix, K>, TRootTree> : never : never;
|
|
154
|
+
}[keyof TTree & string]>;
|
|
155
|
+
export interface HttpProtocol<TDefinition extends HttpProtocolDefinition = HttpProtocolDefinition, TNamespace = unknown> extends ProtocolDescriptor<'http', HttpControllerContextDefinition<TDefinition>, DeclaredHttpResults<TDefinition['responses']>, HttpDispatchKey<TDefinition['method'], TDefinition['path']>> {
|
|
53
156
|
readonly definition: TDefinition;
|
|
157
|
+
readonly [protocolNamespaceType]: TNamespace;
|
|
54
158
|
readonly interaction: TDefinition extends {
|
|
55
159
|
readonly interaction: infer TInteraction;
|
|
56
160
|
} ? TInteraction & ('unary' | 'server-stream') : 'unary';
|
|
@@ -88,6 +192,14 @@ type HttpPipelineConstraint<TDefinition extends HttpProtocolDefinition> = IsVali
|
|
|
88
192
|
} : {
|
|
89
193
|
readonly pipeline: never;
|
|
90
194
|
};
|
|
195
|
+
type HttpStatusDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
196
|
+
type HttpStatusHundreds = '2' | '3' | '4' | '5';
|
|
197
|
+
type IsValidHttpResponseStatus<TStatus extends number> = number extends TStatus ? false : `${TStatus}` extends `${HttpStatusHundreds}${HttpStatusDigit}${HttpStatusDigit}` ? true : false;
|
|
198
|
+
type IsBodylessHttpStatus<TStatus extends number> = TStatus extends 204 | 205 | 304 ? true : false;
|
|
199
|
+
type IsResponseStatusCompatible<TResponse> = TResponse extends HttpResponseDefinition ? IsValidHttpResponseStatus<TResponse['status']> extends true ? IsBodylessHttpStatus<TResponse['status']> extends true ? [SchemaOutput<TResponse['body']>] extends [undefined] ? true : false : true : false : false;
|
|
200
|
+
type AreResponseStatusesCompatible<TResponses extends HttpProtocolDefinition['responses']> = false extends {
|
|
201
|
+
[TVariant in keyof TResponses]: IsResponseStatusCompatible<TResponses[TVariant]>;
|
|
202
|
+
}[keyof TResponses] ? false : true;
|
|
91
203
|
type IsResponseHeadersSchemaCompatible<TResponse> = TResponse extends HttpResponseDefinition ? TResponse extends {
|
|
92
204
|
readonly headers: infer THeaders extends StandardSchemaV1;
|
|
93
205
|
} ? SchemaOutput<THeaders> extends HttpHeaders | undefined ? true : false : true : false;
|
|
@@ -96,7 +208,9 @@ type AreResponseHeadersSchemasCompatible<TResponses extends HttpProtocolDefiniti
|
|
|
96
208
|
}[keyof TResponses] ? false : true;
|
|
97
209
|
type HttpResponseConstraint<TDefinition extends HttpProtocolDefinition> = string extends keyof TDefinition['responses'] ? {
|
|
98
210
|
readonly responses: never;
|
|
99
|
-
} : AreResponseHeadersSchemasCompatible<TDefinition['responses']> extends true ? AreErrorMappingsCompatible<TDefinition['responses']> extends true ? unknown : {
|
|
211
|
+
} : AreResponseStatusesCompatible<TDefinition['responses']> extends true ? AreResponseHeadersSchemasCompatible<TDefinition['responses']> extends true ? AreErrorMappingsCompatible<TDefinition['responses']> extends true ? unknown : {
|
|
212
|
+
readonly responses: never;
|
|
213
|
+
} : {
|
|
100
214
|
readonly responses: never;
|
|
101
215
|
} : {
|
|
102
216
|
readonly responses: never;
|
|
@@ -110,6 +224,8 @@ type IsUnion<TValue, TCandidate = TValue> = TValue extends unknown ? [TCandidate
|
|
|
110
224
|
type IsSingleStringLiteral<TValue extends string> = string extends TValue ? false : true extends IsUnion<TValue> ? false : true;
|
|
111
225
|
type HttpPathConstraint<TDefinition extends HttpProtocolDefinition> = IsSingleStringLiteral<TDefinition['method']> extends false ? {
|
|
112
226
|
readonly method: never;
|
|
227
|
+
} : IsValidHttpMethod<TDefinition['method']> extends false ? {
|
|
228
|
+
readonly method: never;
|
|
113
229
|
} : IsSingleStringLiteral<TDefinition['path']> extends false ? {
|
|
114
230
|
readonly path: never;
|
|
115
231
|
} : IsValidHttpPath<TDefinition['path']> extends true ? TDefinition['request'] extends {
|
|
@@ -132,15 +248,39 @@ type IsErrorMappingCompatible<TResponse> = TResponse extends HttpResponseDefinit
|
|
|
132
248
|
type AreErrorMappingsCompatible<TResponses extends HttpProtocolDefinition['responses']> = false extends {
|
|
133
249
|
[TVariant in keyof TResponses]: IsErrorMappingCompatible<TResponses[TVariant]>;
|
|
134
250
|
}[keyof TResponses] ? false : true;
|
|
135
|
-
|
|
136
|
-
type
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
type
|
|
140
|
-
|
|
251
|
+
type IsConstraintSatisfied<TConstraint> = keyof TConstraint extends never ? true : false;
|
|
252
|
+
type IsValidHttpRouteName<TName extends string> = TName extends '' ? false : TName extends '__proto__' ? false : TName extends `${string}.${string}` ? false : TName extends `${number}` ? false : true;
|
|
253
|
+
type HasOnlyHttpNodeProperties<TNode, TAllowed extends PropertyKey> = Exclude<keyof TNode, TAllowed> extends never ? true : false;
|
|
254
|
+
type IsExactHttpLeaf<TNode> = HasOnlyHttpNodeProperties<TNode, keyof HttpProtocolDefinition>;
|
|
255
|
+
type IsExactHttpBranch<TNode> = HasOnlyHttpNodeProperties<TNode, keyof HttpBranchDefinition>;
|
|
256
|
+
type IsBranchPipelineTerminalFree<TPipeline extends readonly PipelineItem[]> = number extends TPipeline['length'] ? false : TPipeline extends readonly [infer THead, ...infer TTail] ? THead extends TerminalLayerDescriptor ? false : THead extends {
|
|
257
|
+
readonly kind: 'layer';
|
|
258
|
+
readonly pipeline: infer TChild extends readonly PipelineItem[];
|
|
259
|
+
} ? IsBranchPipelineTerminalFree<TChild> extends true ? IsBranchPipelineTerminalFree<Extract<TTail, readonly PipelineItem[]>> : false : IsBranchPipelineTerminalFree<Extract<TTail, readonly PipelineItem[]>> : true;
|
|
260
|
+
type IsHttpBranchPathValid<TBranch extends HttpBranchDefinition> = TBranch extends {
|
|
261
|
+
readonly path: infer TPath extends string;
|
|
262
|
+
} ? string extends TPath ? false : TPath extends '/' ? true : IsValidHttpPath<TPath> : true;
|
|
263
|
+
type IsHttpBranchPipelineValid<TBranch extends HttpBranchDefinition> = TBranch extends {
|
|
264
|
+
readonly pipeline: infer TPipeline extends readonly PipelineItem[];
|
|
265
|
+
} ? IsBranchPipelineTerminalFree<TPipeline> : true;
|
|
266
|
+
type IsHttpBranchResponsesValid<TBranch extends HttpBranchDefinition> = TBranch extends {
|
|
267
|
+
readonly responses: infer TResponses extends Readonly<Record<string, HttpResponseDefinition>>;
|
|
268
|
+
} ? string extends keyof TResponses ? false : AreResponseHeadersSchemasCompatible<TResponses> extends true ? AreErrorMappingsCompatible<TResponses> : false : true;
|
|
269
|
+
type HasHttpResponseCollision<TParent extends Readonly<Record<string, HttpResponseDefinition>>, TLocal extends Readonly<Record<string, HttpResponseDefinition>>> = Extract<keyof TParent, keyof TLocal> extends never ? false : true;
|
|
270
|
+
type IsHttpResolvedLeafValid<TDefinition extends HttpProtocolDefinition, TParentPath extends string, TParentPipeline extends readonly PipelineItem[], TParentResponses extends Readonly<Record<string, HttpResponseDefinition>>, TEffective extends HttpProtocolDefinition = EffectiveHttpDefinition<TDefinition, TParentPath, TParentPipeline, TParentResponses>> = IsConstraintSatisfied<HttpPipelineConstraint<TEffective> & HttpResponseConstraint<TEffective> & HttpPathConstraint<TEffective>>;
|
|
271
|
+
type IsHttpTreeValid<TTree extends Readonly<Record<string, unknown>>, TParentPath extends string = '', TParentPipeline extends readonly PipelineItem[] = readonly [], TParentResponses extends Readonly<Record<string, HttpResponseDefinition>> = {}> = Extract<keyof TTree, number> extends never ? false extends {
|
|
272
|
+
[K in keyof TTree & string]: IsValidHttpRouteName<K> extends true ? SourceOfHttpNode<TTree[K]> extends infer TSource ? TSource extends HttpProtocolDefinition ? IsExactHttpLeaf<TSource> extends true ? IsHttpResolvedLeafValid<TSource, TParentPath, TParentPipeline, TParentResponses> : false : TSource extends HttpBranchDefinition ? IsExactHttpBranch<TSource> extends true ? IsHttpBranchPathValid<TSource> extends true ? IsHttpBranchPipelineValid<TSource> extends true ? IsHttpBranchResponsesValid<TSource> extends true ? HasHttpResponseCollision<TParentResponses, BranchResponsesOf<TSource>> extends false ? IsHttpTreeValid<TSource['routes'], JoinHttpPath<TParentPath, BranchPathOf<TSource>>, readonly [
|
|
273
|
+
...TParentPipeline,
|
|
274
|
+
...BranchPipelineOf<TSource>
|
|
275
|
+
], MergeHttpResponses<TParentResponses, BranchResponsesOf<TSource>>> : false : false : false : false : false : false : false : false;
|
|
276
|
+
}[keyof TTree & string] ? false : true : false;
|
|
277
|
+
type HttpTreeConstraint<TDefinitions extends HttpRouteTree> = IsHttpTreeValid<TDefinitions> extends true ? unknown : {
|
|
278
|
+
readonly __invalidHttpRouteTree__: never;
|
|
141
279
|
};
|
|
280
|
+
declare function defineHttp<const TDefinition extends HttpProtocolDefinition>(definition: TDefinition & HttpPipelineConstraint<TDefinition> & HttpResponseConstraint<TDefinition> & HttpPathConstraint<TDefinition>): HttpProtocol<TDefinition>;
|
|
281
|
+
type HttpProtocolGroup<TDefinitions extends HttpRouteTree> = ProtocolGroup<'http', FlatHttpProtocols<TDefinitions> extends infer TProtocols extends Record<string, ProtocolDescriptor<'http'>> ? TProtocols : never>;
|
|
142
282
|
export interface HttpProtocolFactory extends ProtocolFactory<'http'> {
|
|
143
|
-
<const TDefinitions extends
|
|
283
|
+
<const TDefinitions extends HttpRouteTree>(definitions: TDefinitions & HttpTreeConstraint<TDefinitions>): HttpProtocolGroup<TDefinitions>;
|
|
144
284
|
readonly route: typeof defineHttp;
|
|
145
285
|
readonly controller: TerminalLayerDescriptor<'http'>;
|
|
146
286
|
readonly error: typeof httpError;
|
|
@@ -164,12 +304,10 @@ export declare const validate: Readonly<{
|
|
|
164
304
|
readonly name: "validate.body";
|
|
165
305
|
};
|
|
166
306
|
}>;
|
|
167
|
-
type ProceduresForHttp<TContract extends ContractDefinition
|
|
307
|
+
type ProceduresForHttp<TBinding extends ContractBinding, TContract extends ContractDefinition = ContractOfBinding<TBinding>> = {
|
|
168
308
|
[K in keyof TContract['procedures']]: 'http' extends keyof TContract['procedures'][K]['protocols'] ? K : never;
|
|
169
309
|
}[keyof TContract['procedures']] & string;
|
|
170
|
-
type HttpProtocolAt<
|
|
171
|
-
readonly definition: infer TDefinition;
|
|
172
|
-
} ? TDefinition extends HttpProtocolDefinition ? HttpProtocol<TDefinition> : never : never : never;
|
|
310
|
+
type HttpProtocolAt<TBinding extends ContractBinding, TProcedure extends keyof ContractOfBinding<TBinding>['procedures'], TContract extends ContractDefinition = ContractOfBinding<TBinding>> = TContract['procedures'][TProcedure]['protocols']['http' & keyof TContract['procedures'][TProcedure]['protocols']] extends infer TProtocol ? TProtocol extends HttpProtocol<any, any> ? TProtocol : never : never;
|
|
173
311
|
type RequestBodySchema<TBody> = TBody extends HttpRequestBodyDefinition<infer TSchema> ? TSchema : never;
|
|
174
312
|
type PartOutput<TDefinition extends HttpProtocolDefinition, TPart extends keyof HttpRequestDefinition> = TPart extends 'params' ? HasValidationBeforeTerminal<TDefinition['pipeline'], 'params'> extends true ? TDefinition['request'] extends {
|
|
175
313
|
readonly params: infer TSchemas extends HttpParamsSchemas;
|
|
@@ -218,8 +356,8 @@ type HttpControllerContextDefinition<TDefinition extends HttpProtocolDefinition>
|
|
|
218
356
|
readonly signal: AbortSignal;
|
|
219
357
|
} & ContextProvidedBeforeTerminal<TDefinition['pipeline']>;
|
|
220
358
|
export type HttpControllerContext<TProtocol extends HttpProtocol<any>> = HttpControllerContextDefinition<TProtocol['definition']>;
|
|
221
|
-
export type ControllerOf<
|
|
222
|
-
[K in TProcedures]: (context: HttpControllerContext<HttpProtocolAt<
|
|
359
|
+
export type ControllerOf<TBinding extends ContractBinding, TProtocol extends 'http', TProcedures extends ProceduresForHttp<TBinding> = ProceduresForHttp<TBinding>> = TProtocol extends 'http' ? {
|
|
360
|
+
[K in TProcedures]: (context: HttpControllerContext<HttpProtocolAt<TBinding, K>>) => DeclaredHttpResults<HttpProtocolAt<TBinding, K>['definition']['responses']> | Promise<DeclaredHttpResults<HttpProtocolAt<TBinding, K>['definition']['responses']>>;
|
|
223
361
|
} : never;
|
|
224
362
|
export type ContextOf<TController, TProcedure extends keyof TController> = TController[TProcedure] extends (context: infer TContext, ...args: any[]) => any ? TContext : never;
|
|
225
363
|
export {};
|