EdgeAIG's picture
download
raw
5.31 kB
/**
* 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.js";
import * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.js";
import * as HttpApiGroup from "../httpapi/HttpApiGroup.js";
import * as Rpc from "../rpc/Rpc.js";
import * as RpcGroup from "../rpc/RpcGroup.js";
import { AlreadyProcessingMessage, MailboxFull, PersistenceError } from "./ClusterError.js";
const clientErrors = [MailboxFull, AlreadyProcessingMessage, PersistenceError];
/**
* 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 = entity => {
const rpcs = [];
for (const parentRpc_ of entity.protocol.requests.values()) {
const parentRpc = parentRpc_;
const payloadSchema = Schema.Struct({
entityId: Schema.String,
payload: parentRpc.payloadSchema
});
const oldMake = payloadSchema.make;
payloadSchema.make = (input, options) => {
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);
};
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 = (name, entity) => {
let group = HttpApiGroup.make(name);
for (const parentRpc_ of entity.protocol.requests.values()) {
const parentRpc = parentRpc_;
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);
}
return group;
};
// TODO: type level equivalent
const tagToPath = tag => 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();
//# sourceMappingURL=EntityProxy.js.map

Xet Storage Details

Size:
5.31 kB
·
Xet hash:
7d224b275465238478ad3df03a022945cb13e9362937925410d527a8adbdba16

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