| /** | |
| * 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.