| /** | |
| * Derives RPC and HTTP API surfaces from clustered entities. | |
| * | |
| * The generated APIs let callers use ordinary RPC clients or HTTP routes while | |
| * the cluster runtime still locates and delivers messages to the entity | |
| * instance identified by `entityId`. Each generated operation keeps the | |
| * original payload and success schema, adds cluster client errors, and creates | |
| * a discard variant for fire-and-forget delivery. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Schema from "../../Schema.ts" | |
| import * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.ts" | |
| import * as HttpApiGroup from "../httpapi/HttpApiGroup.ts" | |
| import * as Rpc from "../rpc/Rpc.ts" | |
| import * as RpcGroup from "../rpc/RpcGroup.ts" | |
| import { AlreadyProcessingMessage, MailboxFull, PersistenceError } from "./ClusterError.ts" | |
| import type * as Entity from "./Entity.ts" | |
| import type { EntityId } from "./EntityId.ts" | |
| const clientErrors = [ | |
| MailboxFull, | |
| AlreadyProcessingMessage, | |
| PersistenceError | |
| ] as const | |
| /** | |
| * Derives an `RpcGroup` from an `Entity`. | |
| * | |
| * **Example** (Deriving RPC endpoints from an entity) | |
| * | |
| * ```ts | |
| * import { Layer, Schema } from "effect" | |
| * import { ClusterSchema, Entity, EntityProxy, EntityProxyServer } from "effect/unstable/cluster" | |
| * import { Rpc, RpcServer } from "effect/unstable/rpc" | |
| * | |
| * export const Counter = Entity.make("Counter", [ | |
| * Rpc.make("Increment", { | |
| * payload: { id: Schema.String, amount: Schema.Number }, | |
| * primaryKey: ({ id }) => id, | |
| * success: Schema.Number | |
| * }) | |
| * ]).annotateRpcs(ClusterSchema.Persisted, true) | |
| * | |
| * // Use EntityProxy.toRpcGroup to create a `RpcGroup` from the Counter entity | |
| * export class MyRpcs extends EntityProxy.toRpcGroup(Counter) {} | |
| * | |
| * // Use EntityProxyServer.layerRpcHandlers to create a layer that implements | |
| * // the rpc handlers | |
| * const RpcServerLayer = RpcServer.layer(MyRpcs).pipe( | |
| * Layer.provide(EntityProxyServer.layerRpcHandlers(Counter)) | |
| * ) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const toRpcGroup = <Type extends string, Rpcs extends Rpc.Any>( | |
| entity: Entity.Entity<Type, Rpcs> | |
| ): RpcGroup.RpcGroup<ConvertRpcs<Rpcs, Type>> => { | |
| const rpcs: Array<Rpc.Any> = [] | |
| for (const parentRpc_ of entity.protocol.requests.values()) { | |
| const parentRpc = parentRpc_ as any as Rpc.AnyWithProps | |
| const payloadSchema = Schema.Struct({ | |
| entityId: Schema.String, | |
| payload: parentRpc.payloadSchema | |
| }) | |
| const oldMake = payloadSchema.make | |
| payloadSchema.make = (input: any, options?: Schema.MakeOptions) => { | |
| return oldMake({ | |
| entityId: input.entityId, | |
| payload: parentRpc.payloadSchema.make(input.payload, options) | |
| }, options) | |
| } | |
| const rpc = Rpc.make(`${entity.type}.${parentRpc._tag}`, { | |
| payload: payloadSchema, | |
| error: Schema.Union([parentRpc.errorSchema, ...clientErrors]), | |
| success: parentRpc.successSchema | |
| }).annotateMerge(parentRpc.annotations) | |
| const rpcDiscard = Rpc.make(`${entity.type}.${parentRpc._tag}Discard`, { | |
| payload: payloadSchema, | |
| error: Schema.Union(clientErrors) | |
| }).annotateMerge(parentRpc.annotations) | |
| rpcs.push(rpc, rpcDiscard) | |
| } | |
| return RpcGroup.make(...rpcs) as any as RpcGroup.RpcGroup<ConvertRpcs<Rpcs, Type>> | |
| } | |
| /** | |
| * Type-level conversion used by `toRpcGroup`. | |
| * | |
| * **Details** | |
| * | |
| * For each entity RPC, this creates a prefixed request RPC and a discard RPC | |
| * whose payload includes `entityId`, and whose errors include cluster client | |
| * errors. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export type ConvertRpcs<Rpcs extends Rpc.Any, Prefix extends string> = Rpcs extends Rpc.Rpc< | |
| infer _Tag, | |
| infer _Payload, | |
| infer _Success, | |
| infer _Error, | |
| infer _Middleware, | |
| infer _Requires | |
| > ? | |
| | Rpc.Rpc< | |
| `${Prefix}.${_Tag}`, | |
| Schema.Struct<{ | |
| entityId: typeof Schema.String | |
| payload: _Payload | |
| }>, | |
| _Success, | |
| Schema.Codec< | |
| _Error["Type"] | MailboxFull | AlreadyProcessingMessage | PersistenceError, | |
| | _Error["Encoded"] | |
| | typeof MailboxFull["Encoded"] | |
| | typeof AlreadyProcessingMessage["Encoded"] | |
| | typeof PersistenceError["Encoded"], | |
| _Error["DecodingServices"], | |
| _Error["EncodingServices"] | |
| > | |
| > | |
| | Rpc.Rpc< | |
| `${Prefix}.${_Tag}Discard`, | |
| Schema.Struct<{ | |
| entityId: typeof Schema.String | |
| payload: _Payload | |
| }>, | |
| typeof Schema.Void, | |
| Schema.Union<[ | |
| typeof MailboxFull, | |
| typeof AlreadyProcessingMessage, | |
| typeof PersistenceError | |
| ]> | |
| > | |
| : never | |
| const entityIdPath = { | |
| entityId: Schema.String | |
| } | |
| /** | |
| * Derives an `HttpApiGroup` from an `Entity`. | |
| * | |
| * **Example** (Deriving HTTP API endpoints from an entity) | |
| * | |
| * ```ts | |
| * import { Layer, Schema } from "effect" | |
| * import { ClusterSchema, Entity, EntityProxy, EntityProxyServer } from "effect/unstable/cluster" | |
| * import { HttpApi, HttpApiBuilder } from "effect/unstable/httpapi" | |
| * import { Rpc } from "effect/unstable/rpc" | |
| * | |
| * export const Counter = Entity.make("Counter", [ | |
| * Rpc.make("Increment", { | |
| * payload: { id: Schema.String, amount: Schema.Number }, | |
| * primaryKey: ({ id }) => id, | |
| * success: Schema.Number | |
| * }) | |
| * ]).annotateRpcs(ClusterSchema.Persisted, true) | |
| * | |
| * // Use EntityProxy.toHttpApiGroup to create a `HttpApiGroup` from the | |
| * // Counter entity | |
| * export class MyApi extends HttpApi.make("api") | |
| * .add( | |
| * EntityProxy.toHttpApiGroup("counter", Counter) | |
| * .prefix("/counter") | |
| * ) | |
| * {} | |
| * | |
| * // Use EntityProxyServer.layerHttpApi to create a layer that implements | |
| * // the handlers for the HttpApiGroup | |
| * const ApiLayer = HttpApiBuilder.layer(MyApi).pipe( | |
| * Layer.provide(EntityProxyServer.layerHttpApi(MyApi, "counter", Counter)) | |
| * ) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const toHttpApiGroup = <const Name extends string, Type extends string, Rpcs extends Rpc.Any>( | |
| name: Name, | |
| entity: Entity.Entity<Type, Rpcs> | |
| ): HttpApiGroup.HttpApiGroup<Name, ConvertHttpApi<Rpcs>> => { | |
| let group = HttpApiGroup.make(name) | |
| for (const parentRpc_ of entity.protocol.requests.values()) { | |
| const parentRpc = parentRpc_ as any as Rpc.AnyWithProps | |
| const endpoint = HttpApiEndpoint.post(parentRpc._tag, `/${tagToPath(parentRpc._tag)}/:entityId`, { | |
| params: entityIdPath, | |
| payload: parentRpc.payloadSchema, | |
| success: parentRpc.successSchema, | |
| error: [parentRpc.errorSchema, ...clientErrors] | |
| }).annotateMerge(parentRpc.annotations) | |
| const endpointDiscard = HttpApiEndpoint.post( | |
| `${parentRpc._tag}Discard`, | |
| `/${tagToPath(parentRpc._tag)}/:entityId/discard`, | |
| { | |
| params: entityIdPath, | |
| payload: parentRpc.payloadSchema, | |
| error: clientErrors | |
| } | |
| ).annotateMerge(parentRpc.annotations) | |
| group = group.add(endpoint).add(endpointDiscard) as any | |
| } | |
| return group as any as HttpApiGroup.HttpApiGroup<Name, ConvertHttpApi<Rpcs>> | |
| } | |
| // TODO: type level equivalent | |
| const tagToPath = (tag: string): string => | |
| tag | |
| // .replace(/[^a-zA-Z0-9]+/g, "-") // Replace non-alphanumeric characters with hyphen | |
| // .replace(/([a-z])([A-Z])/g, "$1-$2") // Insert hyphen before uppercase letters | |
| .toLowerCase() | |
| /** | |
| * Type-level conversion used by `toHttpApiGroup`. | |
| * | |
| * **Details** | |
| * | |
| * For each entity RPC, this creates a POST endpoint at `/<tag>/:entityId` and a | |
| * discard endpoint at `/<tag>/:entityId/discard`, including cluster client | |
| * errors. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export type ConvertHttpApi<Rpcs extends Rpc.Any> = Rpcs extends Rpc.Rpc< | |
| infer _Tag, | |
| infer _Payload, | |
| infer _Success, | |
| infer _Error, | |
| infer _Middleware, | |
| infer _Requires | |
| > ? | |
| | HttpApiEndpoint.HttpApiEndpoint< | |
| _Tag, | |
| "POST", | |
| `/${Lowercase<_Tag>}/:entityId`, | |
| Schema.Struct<{ entityId: typeof EntityId }>, | |
| never, | |
| _Payload, | |
| never, | |
| _Success, | |
| | _Error | |
| | typeof MailboxFull | |
| | typeof AlreadyProcessingMessage | |
| | typeof PersistenceError | |
| > | |
| | HttpApiEndpoint.HttpApiEndpoint< | |
| `${_Tag}Discard`, | |
| "POST", | |
| `/${Lowercase<_Tag>}/:entityId/discard`, | |
| Schema.Struct<{ entityId: typeof EntityId }>, | |
| never, | |
| _Payload, | |
| never, | |
| Schema.Void, | |
| typeof MailboxFull | typeof AlreadyProcessingMessage | typeof PersistenceError | |
| > | |
| : never | |
Xet Storage Details
- Size:
- 8.39 kB
- Xet hash:
- c8176aedf72292b7f2b012ef861fe0945fd5cf93e8bfc85252459abc77cbdd2a
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.