EdgeAIG's picture
download
raw
11.3 kB
/**
* Defines endpoint declarations used inside an HTTP API group.
*
* An endpoint records a stable name, HTTP method, router path, request schemas,
* response schemas, declared errors, middleware, and annotations. Endpoint
* values are declarations, not handlers: builders use them to decode requests,
* type handler input, encode responses, generate OpenAPI metadata, and derive
* generated-client call signatures. This module also includes HTTP method
* constructors, payload and response schema helpers, and type utilities used by
* builders and generated clients.
*
* @since 4.0.0
*/
import * as Arr from "../../Array.js";
import * as Context from "../../Context.js";
import { identity } from "../../Function.js";
import { pipeArguments } from "../../Pipeable.js";
import * as Predicate from "../../Predicate.js";
import * as Schema from "../../Schema.js";
import * as AST from "../../SchemaAST.js";
import * as HttpRouter from "../http/HttpRouter.js";
import * as HttpApiSchema from "./HttpApiSchema.js";
const TypeId = "~effect/httpapi/HttpApiEndpoint";
/**
* Returns `true` when a value is an `HttpApiEndpoint`, narrowing the value to the
* endpoint interface.
*
* @category guards
* @since 4.0.0
*/
export const isHttpApiEndpoint = u => Predicate.hasProperty(u, TypeId);
/** @internal */
export function getPayloadSchemas(endpoint) {
const result = [];
for (const {
schemas
} of endpoint.payload.values()) {
result.push(...schemas);
}
return result;
}
/** @internal */
export function getSuccessSchemas(endpoint) {
const schemas = Array.from(endpoint.success);
return Arr.isArrayNonEmpty(schemas) ? schemas : [HttpApiSchema.NoContent];
}
/** @internal */
export function getErrorSchemas(endpoint) {
const schemas = new Set(endpoint.error);
for (const middleware of endpoint.middlewares) {
const key = middleware;
for (const schema of key.error) {
schemas.add(schema);
}
}
return Array.from(schemas);
}
const Proto = {
[TypeId]: TypeId,
pipe() {
return pipeArguments(this, arguments);
},
prefix(prefix) {
return makeProto({
...this,
path: HttpRouter.prefixPath(this.path, prefix)
});
},
middleware(middleware) {
return makeProto({
...this,
middlewares: new Set([...this.middlewares, middleware])
});
},
annotate(key, value) {
return makeProto({
...this,
annotations: Context.add(this.annotations, key, value)
});
},
annotateMerge(annotations) {
return makeProto({
...this,
annotations: Context.merge(this.annotations, annotations)
});
}
};
function makeProto(options) {
return Object.assign(Object.create(Proto), options);
}
/**
* Creates endpoint constructors for a specific HTTP method. The resulting
* constructor builds an `HttpApiEndpoint` from a name, path, and optional request
* and response schemas, applying automatic JSON or string-tree codecs unless
* `disableCodecs` is enabled.
*
* @category constructors
* @since 4.0.0
*/
export const make = method => (name, path, options) => {
const disableCodecs = options?.disableCodecs ?? false;
const transformStringTree = disableCodecs ? identity : Schema.toCodecStringTree;
return makeProto({
name,
path,
method,
params: ensureStruct(options?.params, transformStringTree),
query: ensureStruct(options?.query, transformStringTree),
headers: ensureStruct(options?.headers, transformStringTree),
payload: getPayload(options?.payload, method, disableCodecs),
success: getSuccessResponse(options?.success, method, disableCodecs),
error: getErrorResponse(options?.error, disableCodecs),
annotations: Context.empty(),
middlewares: new Set()
});
};
function ensureStruct(params, transform) {
if (params === undefined) return undefined;
if (Schema.isSchema(params)) return transform(params);
return transform(Schema.Struct(params));
}
function getPayload(payload, method, disableCodecs) {
const result = new Map();
if (payload === undefined) return result;
const schemas = Array.isArray(payload) ? payload : Schema.isSchema(payload) ? [payload] : [Schema.Struct(payload).pipe(HttpApiSchema.asFormUrlEncoded())];
const transform = disableCodecs ? identity : transformPayload;
for (const schema of schemas) {
const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method);
const existing = result.get(encoding.contentType);
if (existing) {
if (existing.encoding._tag !== encoding._tag) {
throw new Error(`Multiple payload encodings for content-type: ${encoding.contentType}`);
}
if (existing.encoding._tag === "Multipart") {
throw new Error(`Multiple multipart payloads for content-type: ${encoding.contentType}`);
}
existing.schemas.push(transform(schema, method));
} else {
result.set(encoding.contentType, {
encoding,
schemas: [transform(schema, method)]
});
}
}
return result;
}
const reservedStreamFailureEvent = "effect/httpapi/stream/failure";
function getSuccessResponse(success, method, disableCodecs) {
if (success === undefined) return new Set();
const schemas = Arr.ensure(success);
validateSuccessResponse(schemas, method);
return new Set(disableCodecs ? schemas : schemas.map(schema => HttpApiSchema.isStreamSchema(schema) ? schema : transformResponse(schema)));
}
function getErrorResponse(error, disableCodecs) {
if (error === undefined) return new Set();
const schemas = Arr.ensure(error);
for (const schema of schemas) {
if (HttpApiSchema.isStreamSchema(schema)) {
throw new Error("Streaming schemas are not supported in error responses");
}
}
return new Set(disableCodecs ? schemas : schemas.map(transformResponse));
}
function validateSuccessResponse(schemas, method) {
const statuses = new Map();
for (const schema of schemas) {
if (HttpApiSchema.isStreamSchema(schema)) {
validateStreamSuccess(schema, method);
const status = HttpApiSchema.getStatusStream(schema);
const entry = getStatusEntry(statuses, status);
if (entry.stream !== undefined) {
throw new Error(`Multiple streaming success responses for status: ${status}`);
}
if (entry.noContent) {
throw new Error(`Cannot combine no-content and streaming success responses for status: ${status}`);
}
if (entry.bufferedContentTypes.has(normalizeResponseContentType(schema.contentType))) {
throw new Error(`Cannot combine buffered and streaming success responses for status ${status} and content-type: ${schema.contentType}`);
}
statuses.set(status, {
...entry,
stream: schema
});
} else {
const status = HttpApiSchema.getStatusSuccess(schema.ast);
const entry = getStatusEntry(statuses, status);
const noContent = HttpApiSchema.isNoContent(schema.ast);
if (entry.stream !== undefined) {
if (noContent) {
throw new Error(`Cannot combine no-content and streaming success responses for status: ${status}`);
}
const encoding = HttpApiSchema.getResponseEncoding(schema.ast);
if (normalizeResponseContentType(encoding.contentType) === normalizeResponseContentType(entry.stream.contentType)) {
throw new Error(`Cannot combine buffered and streaming success responses for status ${status} and content-type: ${encoding.contentType}`);
}
}
if (!noContent) {
entry.bufferedContentTypes.add(normalizeResponseContentType(HttpApiSchema.getResponseEncoding(schema.ast).contentType));
}
entry.noContent = entry.noContent || noContent;
}
}
}
function normalizeResponseContentType(contentType) {
const normalized = contentType.toLowerCase().trim();
const index = normalized.indexOf(";");
return index === -1 ? normalized : normalized.slice(0, index).trim();
}
function getStatusEntry(statuses, status) {
let entry = statuses.get(status);
if (entry === undefined) {
entry = {
bufferedContentTypes: new Set(),
noContent: false
};
statuses.set(status, entry);
}
return entry;
}
function validateStreamSuccess(schema, method) {
if (method === "HEAD") {
throw new Error("HEAD endpoints cannot declare streaming success responses");
}
if (HttpApiSchema.isStreamSse(schema) && hasReservedSseEventName(schema.events.ast)) {
throw new Error(`SSE event name is reserved: ${reservedStreamFailureEvent}`);
}
}
function hasReservedSseEventName(ast) {
return hasReservedEventName(AST.toEncoded(ast), new Set());
}
function hasReservedEventName(ast, seen) {
if (seen.has(ast)) return false;
seen.add(ast);
if (AST.isUnion(ast)) {
return ast.types.some(type => hasReservedEventName(type, seen));
}
if (AST.isSuspend(ast)) {
return hasReservedEventName(ast.thunk(), seen);
}
if (!AST.isObjects(ast)) return false;
const event = ast.propertySignatures.find(ps => ps.name === "event");
return event !== undefined && hasReservedEventLiteral(event.type, seen);
}
function hasReservedEventLiteral(ast, seen) {
if (seen.has(ast)) return false;
seen.add(ast);
const encoded = AST.toEncoded(ast);
if (encoded !== ast) {
return hasReservedEventLiteral(encoded, seen);
}
if (AST.isLiteral(ast)) {
return ast.literal === reservedStreamFailureEvent;
}
if (AST.isUnion(ast)) {
return ast.types.some(type => hasReservedEventLiteral(type, seen));
}
if (AST.isSuspend(ast)) {
return hasReservedEventLiteral(ast.thunk(), seen);
}
return false;
}
function transformResponse(schema) {
const encoding = HttpApiSchema.getResponseEncoding(schema.ast);
switch (encoding._tag) {
case "Json":
return Schema.toCodecJson(schema);
case "FormUrlEncoded":
return Schema.toCodecStringTree(schema);
case "Text":
case "Uint8Array":
return schema;
}
}
function transformPayload(schema, method) {
const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method);
switch (encoding._tag) {
case "Json":
return Schema.toCodecJson(schema);
case "FormUrlEncoded":
return Schema.toCodecStringTree(schema);
case "Text":
case "Uint8Array":
case "Multipart":
return schema;
}
}
/**
* Creates a `GET` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
export const get = /*#__PURE__*/make("GET");
/**
* Creates a `POST` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
export const post = /*#__PURE__*/make("POST");
/**
* Creates a `PUT` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
export const put = /*#__PURE__*/make("PUT");
/**
* Creates a `PATCH` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
export const patch = /*#__PURE__*/make("PATCH");
const del = /*#__PURE__*/make("DELETE");
export {
/**
* Creates a `DELETE` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
del as delete };
/**
* Creates a `HEAD` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
export const head = /*#__PURE__*/make("HEAD");
/**
* Creates an `OPTIONS` endpoint declaration.
*
* @category constructors
* @since 4.0.0
*/
export const options = /*#__PURE__*/make("OPTIONS");
//# sourceMappingURL=HttpApiEndpoint.js.map

Xet Storage Details

Size:
11.3 kB
·
Xet hash:
6d55c3dc1eb915521c1e66e7539ef0ecab17412edf402833a05954ceeb382de1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.