effect-encore 0.1.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/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # effect-encore
2
+
3
+ Erlang gen_server semantics over `@effect/cluster`.
4
+
5
+ ## Why
6
+
7
+ `@effect/cluster` is powerful but low-level. Defining a single entity requires custom `Schema.Class` implementations, `Rpc.make`, `RpcGroup`, `Entity.make`, handler wiring via `entity.toLayer`, and a hand-rolled client service. A typical entity runs 100+ lines before any business logic.
8
+
9
+ effect-encore compresses this into a declarative DSL:
10
+
11
+ ```ts
12
+ import { Actor } from "effect-encore";
13
+ import { Schema } from "effect";
14
+
15
+ const Counter = Actor.make("Counter", {
16
+ Increment: {
17
+ payload: { amount: Schema.Number },
18
+ success: Schema.Number,
19
+ },
20
+ });
21
+ ```
22
+
23
+ Every operation supports `call` (block for reply), `cast` (fire-and-forget with receipt), and `peek`/`watch` (check or stream results). Delivery mode is the caller's choice, not the definition's — just like Erlang's `gen_server:call` vs `gen_server:cast`.
24
+
25
+ ## Core API
26
+
27
+ ### Define
28
+
29
+ ```ts
30
+ // Multi-operation actor
31
+ const OrderValidation = Actor.make("OrderValidation", {
32
+ Validate: {
33
+ payload: { orderId: Schema.String },
34
+ success: Schema.String,
35
+ error: ValidationError,
36
+ persisted: true,
37
+ primaryKey: (p) => p.orderId,
38
+ },
39
+ Cancel: {
40
+ payload: { orderId: Schema.String },
41
+ persisted: true,
42
+ primaryKey: (p) => p.orderId,
43
+ },
44
+ });
45
+
46
+ // Single-operation actor (no operation namespace on ref)
47
+ const VectorUpdate = Actor.single("VectorUpdate", {
48
+ payload: { locationId: Schema.String },
49
+ persisted: true,
50
+ primaryKey: (p) => p.locationId,
51
+ });
52
+
53
+ // Delayed delivery
54
+ const Scheduled = Actor.make("Scheduled", {
55
+ Process: {
56
+ payload: { id: Schema.String, deliverAt: Schema.DateTimeUtc },
57
+ primaryKey: (p) => p.id,
58
+ deliverAt: (p) => p.deliverAt,
59
+ persisted: true,
60
+ },
61
+ });
62
+
63
+ // Pre-built Schema.Class (escape hatch for custom symbol implementations)
64
+ const WithCustomPayload = Actor.make("Custom", {
65
+ Run: { payload: MySchemaClass, success: Schema.Void },
66
+ });
67
+ ```
68
+
69
+ ### Handle
70
+
71
+ ```ts
72
+ // Plain handlers
73
+ const handlers = Handlers.handlers(OrderValidation, {
74
+ Validate: (req) => validateOrder(req.payload.orderId),
75
+ Cancel: (req) => cancelOrder(req.payload.orderId),
76
+ });
77
+
78
+ // From Effect context
79
+ const handlers = Handlers.handlers(
80
+ OrderValidation,
81
+ Effect.gen(function* () {
82
+ const db = yield* Database;
83
+ return {
84
+ Validate: (req) => db.validate(req.payload.orderId),
85
+ Cancel: (req) => db.cancel(req.payload.orderId),
86
+ };
87
+ }),
88
+ );
89
+ ```
90
+
91
+ ### Call & Cast
92
+
93
+ ```ts
94
+ const makeRef = yield * Actor.client(OrderValidation);
95
+ const ref = makeRef("order-123");
96
+
97
+ // call — block for reply
98
+ const result = yield * ref.Validate.call({ orderId: "abc" });
99
+
100
+ // cast — fire-and-forget, get receipt
101
+ const receipt = yield * ref.Validate.cast({ orderId: "abc" });
102
+
103
+ // peek — one-shot status check via receipt
104
+ const status = yield * Peek.peek(OrderValidation, receipt);
105
+
106
+ // watch — polling stream of status changes
107
+ const stream = Peek.watch(OrderValidation, receipt);
108
+ ```
109
+
110
+ ### Test
111
+
112
+ ```ts
113
+ const makeRef = yield * Testing.testClient(Counter, handlerLayer);
114
+ const ref = yield * makeRef("counter-1");
115
+ const result = yield * ref.Increment.call({ amount: 5 });
116
+ ```
117
+
118
+ ## v3 Support
119
+
120
+ Import from `effect-encore/v3` for `@effect/cluster` v3 compatibility. Same API, different import paths under the hood.
121
+
122
+ ## Install
123
+
124
+ ```bash
125
+ bun add effect-encore
126
+ ```
127
+
128
+ Peer dependencies: `effect`, `@effect/cluster` (v3) or `effect` with `effect/unstable/cluster` (v4).
129
+
130
+ ## License
131
+
132
+ MIT
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll };
@@ -0,0 +1,113 @@
1
+ import { CastReceipt } from "./receipt.js";
2
+ import { Entity, Sharding, ShardingConfig } from "effect/unstable/cluster";
3
+ import { Rpc } from "effect/unstable/rpc";
4
+ import { Context, DateTime, Effect, Layer, Schema, Scope } from "effect";
5
+
6
+ //#region src/actor.d.ts
7
+ interface OperationDef {
8
+ readonly payload?: Schema.Top | Schema.Struct.Fields;
9
+ readonly success?: Schema.Top;
10
+ readonly error?: Schema.Top;
11
+ readonly persisted?: boolean;
12
+ readonly primaryKey?: (payload: never) => string;
13
+ readonly deliverAt?: (payload: never) => DateTime.DateTime;
14
+ }
15
+ type OperationDefs = Record<string, OperationDef>;
16
+ type ReservedKeys = "_tag" | "_meta" | "$is" | "Context";
17
+ type AssertNoReservedKeys<Defs extends OperationDefs> = Extract<keyof Defs, ReservedKeys> extends never ? Defs : never;
18
+ type PayloadOf<C extends OperationDef> = C extends {
19
+ readonly payload: infer P extends Schema.Top;
20
+ } ? P : C extends {
21
+ readonly payload: infer F extends Schema.Struct.Fields;
22
+ } ? Schema.Struct<F> : typeof Schema.Void;
23
+ type SuccessOf<C extends OperationDef> = C extends {
24
+ readonly success: infer S extends Schema.Top;
25
+ } ? S : typeof Schema.Void;
26
+ type ErrorOf<C extends OperationDef> = C extends {
27
+ readonly error: infer E extends Schema.Top;
28
+ } ? E : typeof Schema.Never;
29
+ type DefRpc<Tag extends string, C extends OperationDef> = Rpc.Rpc<Tag, PayloadOf<C>, SuccessOf<C>, ErrorOf<C>>;
30
+ type DefRpcs<Defs extends OperationDefs> = { readonly [Tag in keyof Defs & string]: DefRpc<Tag, Defs[Tag]> }[keyof Defs & string];
31
+ declare const OperationBrandId: unique symbol;
32
+ interface OperationBrand<Name extends string, Tag extends string, Output, Error> {
33
+ readonly [OperationBrandId]: {
34
+ readonly name: Name;
35
+ readonly tag: Tag;
36
+ readonly output: Output;
37
+ readonly error: Error;
38
+ };
39
+ }
40
+ type OperationOutput<V> = V extends {
41
+ readonly [OperationBrandId]: {
42
+ readonly output: infer A;
43
+ };
44
+ } ? A : never;
45
+ type OperationError<V> = V extends {
46
+ readonly [OperationBrandId]: {
47
+ readonly error: infer E;
48
+ };
49
+ } ? E : never;
50
+ type OperationValue<Name extends string, Tag extends string, C extends OperationDef> = {
51
+ readonly _tag: Tag;
52
+ } & PayloadFieldsType<C> & OperationBrand<Name, Tag, Schema.Schema.Type<SuccessOf<C>>, Schema.Schema.Type<ErrorOf<C>>>;
53
+ type PayloadFieldsType<C extends OperationDef> = C extends {
54
+ readonly payload: infer F extends Schema.Struct.Fields;
55
+ } ? { readonly [K in keyof F]: Schema.Schema.Type<F[K] extends Schema.Top ? F[K] : never> } : C extends {
56
+ readonly payload: infer P extends Schema.Top;
57
+ } ? Schema.Schema.Type<P> : {};
58
+ type OperationConstructorPayload<C extends OperationDef> = C extends {
59
+ readonly payload: infer F extends Schema.Struct.Fields;
60
+ } ? {} extends { [K in keyof F]: Schema.Schema.Type<F[K] extends Schema.Top ? F[K] : never> } ? [] : [payload: { readonly [K in keyof F]: Schema.Schema.Type<F[K] extends Schema.Top ? F[K] : never> }] : C extends {
61
+ readonly payload: infer _P extends Schema.Top;
62
+ } ? [payload: unknown] : [];
63
+ type OperationConstructor<Name extends string, Tag extends string, C extends OperationDef> = (...args: OperationConstructorPayload<C>) => OperationValue<Name, Tag, C>;
64
+ type OperationUnion<Name extends string, Defs extends OperationDefs> = { [Tag in keyof Defs & string]: OperationValue<Name, Tag, Defs[Tag]> }[keyof Defs & string];
65
+ interface ActorRef<Name extends string, Defs extends OperationDefs> {
66
+ readonly call: <V extends OperationUnion<Name, Defs>>(op: V) => Effect.Effect<OperationOutput<V>, OperationError<V>>;
67
+ readonly cast: <V extends OperationUnion<Name, Defs>>(op: V) => Effect.Effect<CastReceipt>;
68
+ }
69
+ type HandlerRequest<Tag extends string, C extends OperationDef> = {
70
+ readonly operation: {
71
+ readonly _tag: Tag;
72
+ } & PayloadFieldsType<C>;
73
+ readonly request: unknown;
74
+ };
75
+ type ActorHandlers<Defs extends OperationDefs> = { readonly [Tag in keyof Defs & string]: (req: HandlerRequest<Tag, Defs[Tag]>) => Effect.Effect<Schema.Schema.Type<SuccessOf<Defs[Tag]>>, Schema.Schema.Type<ErrorOf<Defs[Tag]>>, any> };
76
+ interface HandlerOptions {
77
+ readonly spanAttributes?: Record<string, string>;
78
+ readonly maxIdleTime?: number;
79
+ readonly concurrency?: number | "unbounded";
80
+ readonly mailboxCapacity?: number | "unbounded";
81
+ }
82
+ interface ActorMeta<Name extends string, Defs extends OperationDefs, Rpcs extends Rpc.Any = DefRpcs<Defs>> {
83
+ readonly name: Name;
84
+ readonly definitions: Defs;
85
+ readonly entity: Entity.Entity<Name, Rpcs>;
86
+ }
87
+ declare const ActorClientServiceId: unique symbol;
88
+ interface ActorClientService<Name extends string, Defs extends OperationDefs> {
89
+ readonly [ActorClientServiceId]: {
90
+ readonly name: Name;
91
+ readonly defs: Defs;
92
+ };
93
+ }
94
+ type ActorConstructors<Name extends string, Defs extends OperationDefs> = { readonly [Tag in keyof Defs & string]: OperationConstructor<Name, Tag, Defs[Tag]> };
95
+ type ActorObject<Name extends string, Defs extends OperationDefs, Rpcs extends Rpc.Any = DefRpcs<Defs>> = ActorConstructors<Name, Defs> & {
96
+ readonly _tag: "ActorObject";
97
+ readonly _meta: ActorMeta<Name, Defs, Rpcs>;
98
+ readonly Context: Context.Service<ActorClientService<Name, Defs>, (entityId: string) => ActorRef<Name, Defs>>;
99
+ readonly $is: <Tag extends keyof Defs & string>(tag: Tag) => (value: unknown) => value is OperationValue<Name, Tag, Defs[Tag]>;
100
+ };
101
+ declare const Actor: {
102
+ readonly make: <const Name extends string, const Defs extends OperationDefs>(name: Name, definitions: AssertNoReservedKeys<Defs>) => ActorObject<Name, Defs>;
103
+ readonly toLayer: <Name extends string, Defs extends OperationDefs, Rpcs extends Rpc.Any = DefRpcs<Defs>, RX = never>(actor: ActorObject<Name, Defs, Rpcs>, build: ActorHandlers<Defs> | Effect.Effect<ActorHandlers<Defs>, never, RX>, options?: HandlerOptions) => ReturnType<Entity.Entity<Name, Rpcs>["toLayer"]>;
104
+ readonly Live: <Name extends string, Defs extends OperationDefs, Rpcs extends Rpc.Any = DefRpcs<Defs>>(actor: ActorObject<Name, Defs, Rpcs>) => Layer.Layer<ActorClientService<Name, Defs>, never, Scope.Scope | Rpc.MiddlewareClient<Rpcs>>;
105
+ readonly Test: <Name extends string, Defs extends OperationDefs, Rpcs extends Rpc.Any = DefRpcs<Defs>, LA = never, LE = never, LR = never>(actor: ActorObject<Name, Defs, Rpcs>, handlerLayer: Layer.Layer<LA, LE, LR>) => Effect.Effect<(entityId: string) => Effect.Effect<ActorRef<Name, Defs>>, LE, Scope.Scope | ShardingConfig.ShardingConfig | Exclude<LR, Sharding.Sharding> | Rpc.MiddlewareClient<Rpcs>>;
106
+ };
107
+ declare const fromRpcs: <const Name extends string, const Rpcs extends ReadonlyArray<Rpc.Any>>(name: Name, rpcs: Rpcs) => {
108
+ readonly _tag: "RawActorDefinition";
109
+ readonly name: Name;
110
+ readonly entity: Entity.Entity<Name, Rpcs[number]>;
111
+ };
112
+ //#endregion
113
+ export { Actor, ActorMeta, ActorObject, ActorRef, HandlerOptions, OperationBrand, OperationDef, OperationDefs, OperationError, OperationOutput, fromRpcs };
package/dist/actor.js ADDED
@@ -0,0 +1,127 @@
1
+ import { makeCastReceipt } from "./receipt.js";
2
+ import { ClusterSchema, Entity } from "effect/unstable/cluster";
3
+ import * as DeliverAt from "effect/unstable/cluster/DeliverAt";
4
+ import { Rpc } from "effect/unstable/rpc";
5
+ import { Context, Effect, Layer, PrimaryKey, Schema } from "effect";
6
+ //#region src/actor.ts
7
+ const RESERVED_KEYS = new Set([
8
+ "_tag",
9
+ "_meta",
10
+ "$is",
11
+ "Context"
12
+ ]);
13
+ const compileRpc = (actorName, tag, def) => {
14
+ const options = {};
15
+ const payload = def["payload"];
16
+ const pkFn = def["primaryKey"];
17
+ const daFn = def["deliverAt"];
18
+ if (payload) if (Schema.isSchema(payload)) options["payload"] = payload;
19
+ else {
20
+ const fields = payload;
21
+ const Base = Schema.Class(`effect-encore/${actorName}/${tag}/Payload`)(fields);
22
+ class PayloadClass extends Base {}
23
+ const proto = PayloadClass.prototype;
24
+ if (pkFn) proto[PrimaryKey.symbol] = function() {
25
+ return pkFn(this);
26
+ };
27
+ if (daFn) proto[DeliverAt.symbol] = function() {
28
+ return daFn(this);
29
+ };
30
+ options["payload"] = PayloadClass;
31
+ }
32
+ if (def["success"]) options["success"] = def["success"];
33
+ if (def["error"]) options["error"] = def["error"];
34
+ let rpc = Rpc.make(tag, options);
35
+ if (def["persisted"]) rpc = rpc.annotate(ClusterSchema.Persisted, true);
36
+ return rpc;
37
+ };
38
+ const make = (name, definitions) => {
39
+ for (const tag of Object.keys(definitions)) if (RESERVED_KEYS.has(tag)) throw new Error(`effect-encore: operation "${tag}" collides with reserved property. Reserved: ${[...RESERVED_KEYS].join(", ")}`);
40
+ const rpcs = Object.entries(definitions).map(([tag, def]) => compileRpc(name, tag, def));
41
+ const entity = Entity.make(name, rpcs);
42
+ const constructors = {};
43
+ for (const tag of Object.keys(definitions)) constructors[tag] = (input) => ({
44
+ _tag: tag,
45
+ ...input != null && typeof input === "object" ? input : {}
46
+ });
47
+ const contextTag = Context.Service(`effect-encore/${name}/Client`);
48
+ const $is = (tag) => (value) => value != null && typeof value === "object" && "_tag" in value && value["_tag"] === tag;
49
+ return {
50
+ _tag: "ActorObject",
51
+ _meta: {
52
+ name,
53
+ definitions,
54
+ entity
55
+ },
56
+ Context: contextTag,
57
+ $is,
58
+ ...constructors
59
+ };
60
+ };
61
+ const toLayer = (actor, build, options) => {
62
+ const transformed = transformHandlers(build);
63
+ return actor._meta.entity.toLayer(transformed, {
64
+ spanAttributes: options?.spanAttributes,
65
+ maxIdleTime: options?.maxIdleTime,
66
+ concurrency: options?.concurrency,
67
+ mailboxCapacity: options?.mailboxCapacity
68
+ });
69
+ };
70
+ const Live = (actor) => Layer.effect(actor.Context, Effect.map(actor._meta.entity.client, (makeClient) => (entityId) => buildActorRef(actor._meta.name, entityId, actor._meta.definitions, makeClient(entityId))));
71
+ const Test = (actor, handlerLayer) => Effect.map(Entity.makeTestClient(actor._meta.entity, handlerLayer), (makeClient) => (entityId) => Effect.map(makeClient(entityId), (rpcClient) => buildActorRef(actor._meta.name, entityId, actor._meta.definitions, rpcClient)));
72
+ const transformHandlers = (build) => {
73
+ if (build != null && typeof build === "object" && !Effect.isEffect(build)) {
74
+ const handlers = build;
75
+ const transformed = {};
76
+ for (const tag of Object.keys(handlers)) {
77
+ const handler = handlers[tag];
78
+ if (!handler) continue;
79
+ transformed[tag] = (request) => {
80
+ return handler({
81
+ operation: {
82
+ _tag: tag,
83
+ ...request["payload"] ?? {}
84
+ },
85
+ request
86
+ });
87
+ };
88
+ }
89
+ return transformed;
90
+ }
91
+ return Effect.map(build, transformHandlers);
92
+ };
93
+ const buildActorRef = (actorName, entityId, definitions, rpcClient) => {
94
+ const client = rpcClient;
95
+ return {
96
+ call: (op) => {
97
+ const tag = op["_tag"];
98
+ const fn = client[tag];
99
+ return definitions[tag]?.["payload"] !== void 0 ? fn?.(op) : fn?.();
100
+ },
101
+ cast: (op) => {
102
+ const tag = op["_tag"];
103
+ const fn = client[tag];
104
+ const def = definitions[tag];
105
+ const discardCall = def?.["payload"] !== void 0 ? fn?.(op, { discard: true }) : fn?.(void 0, { discard: true });
106
+ return Effect.map(discardCall ?? Effect.void, () => makeCastReceipt({
107
+ actorType: actorName,
108
+ entityId,
109
+ operation: tag,
110
+ primaryKey: def?.["primaryKey"] ? def["primaryKey"](op) : void 0
111
+ }));
112
+ }
113
+ };
114
+ };
115
+ const Actor = {
116
+ make,
117
+ toLayer,
118
+ Live,
119
+ Test
120
+ };
121
+ const fromRpcs = (name, rpcs) => ({
122
+ _tag: "RawActorDefinition",
123
+ name,
124
+ entity: Entity.make(name, rpcs)
125
+ });
126
+ //#endregion
127
+ export { Actor, fromRpcs };
@@ -0,0 +1,2 @@
1
+ import { ActorRef, OperationError, OperationOutput } from "./actor.js";
2
+ export { type ActorRef, type OperationError, type OperationOutput };
package/dist/client.js ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ import { HandlerOptions } from "./actor.js";
2
+ export { type HandlerOptions };
File without changes
@@ -0,0 +1,6 @@
1
+ import { CastReceipt, Defect, Failure, Interrupted, PeekResult, Pending, Success, isFailure, isPending, isSuccess, isTerminal, makeCastReceipt } from "./receipt.js";
2
+ import { Actor, ActorMeta, ActorObject, ActorRef, HandlerOptions, OperationBrand, OperationDef, OperationDefs, OperationError, OperationOutput, fromRpcs } from "./actor.js";
3
+ import { NoPrimaryKeyError, peek, watch } from "./peek.js";
4
+ import { observability_d_exports } from "./observability.js";
5
+ import { workflow_d_exports } from "./workflow.js";
6
+ export { Actor, type ActorMeta, type ActorObject, type ActorRef, CastReceipt, Defect, Failure, type HandlerOptions, Interrupted, NoPrimaryKeyError, observability_d_exports as Observability, type OperationBrand, type OperationDef, type OperationDefs, type OperationError, type OperationOutput, type PeekResult, Pending, Success, workflow_d_exports as Workflow, fromRpcs, isFailure, isPending, isSuccess, isTerminal, makeCastReceipt, peek, watch };
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import { CastReceipt, Defect, Failure, Interrupted, Pending, Success, isFailure, isPending, isSuccess, isTerminal, makeCastReceipt } from "./receipt.js";
2
+ import { Actor, fromRpcs } from "./actor.js";
3
+ import { NoPrimaryKeyError, peek, watch } from "./peek.js";
4
+ import { observability_exports } from "./observability.js";
5
+ import { workflow_exports } from "./workflow.js";
6
+ export { Actor, CastReceipt, Defect, Failure, Interrupted, NoPrimaryKeyError, observability_exports as Observability, Pending, Success, workflow_exports as Workflow, fromRpcs, isFailure, isPending, isSuccess, isTerminal, makeCastReceipt, peek, watch };
@@ -0,0 +1,9 @@
1
+ import { CurrentAddress } from "effect/unstable/cluster/Entity";
2
+
3
+ //#region src/observability.d.ts
4
+ declare namespace observability_d_exports {
5
+ export { CurrentAddress, defaultSpanAttributes };
6
+ }
7
+ declare const defaultSpanAttributes: (actorName: string) => Record<string, string>;
8
+ //#endregion
9
+ export { CurrentAddress, defaultSpanAttributes, observability_d_exports };
@@ -0,0 +1,13 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { CurrentAddress } from "effect/unstable/cluster/Entity";
3
+ //#region src/observability.ts
4
+ var observability_exports = /* @__PURE__ */ __exportAll({
5
+ CurrentAddress: () => CurrentAddress,
6
+ defaultSpanAttributes: () => defaultSpanAttributes
7
+ });
8
+ const defaultSpanAttributes = (actorName) => ({
9
+ "actor.name": actorName,
10
+ "actor.library": "effect-encore"
11
+ });
12
+ //#endregion
13
+ export { CurrentAddress, defaultSpanAttributes, observability_exports };
package/dist/peek.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { CastReceipt, PeekResult } from "./receipt.js";
2
+ import { ActorObject, OperationDefs } from "./actor.js";
3
+ import { MessageStorage, Sharding } from "effect/unstable/cluster";
4
+ import { Duration, Effect, Stream } from "effect";
5
+ import { MalformedMessage, PersistenceError } from "effect/unstable/cluster/ClusterError";
6
+
7
+ //#region src/peek.d.ts
8
+ declare class NoPrimaryKeyError {
9
+ readonly receipt: CastReceipt;
10
+ readonly _tag = "NoPrimaryKeyError";
11
+ readonly message: string;
12
+ constructor(receipt: CastReceipt);
13
+ }
14
+ declare const peek: <Name extends string, Defs extends OperationDefs>(actor: ActorObject<Name, Defs>, receipt: CastReceipt) => Effect.Effect<PeekResult, PersistenceError | MalformedMessage | NoPrimaryKeyError, MessageStorage.MessageStorage | Sharding.Sharding>;
15
+ declare const watch: <Name extends string, Defs extends OperationDefs>(actor: ActorObject<Name, Defs>, receipt: CastReceipt, options?: {
16
+ readonly interval?: Duration.Input;
17
+ }) => Stream.Stream<PeekResult, PersistenceError | MalformedMessage | NoPrimaryKeyError, MessageStorage.MessageStorage | Sharding.Sharding>;
18
+ //#endregion
19
+ export { NoPrimaryKeyError, peek, watch };
package/dist/peek.js ADDED
@@ -0,0 +1,61 @@
1
+ import { Defect, Failure, Interrupted, Pending, Success, isTerminal } from "./receipt.js";
2
+ import { EntityAddress, EntityId, EntityType, MessageStorage, Sharding } from "effect/unstable/cluster";
3
+ import { Duration, Effect, Option, Schedule, Stream } from "effect";
4
+ //#region src/peek.ts
5
+ var NoPrimaryKeyError = class {
6
+ constructor(receipt) {
7
+ this.receipt = receipt;
8
+ this._tag = "NoPrimaryKeyError";
9
+ this.message = `Cannot peek receipt for ${receipt.actorType}.${receipt.operation}: no primaryKey defined on operation`;
10
+ }
11
+ };
12
+ const peek = (actor, receipt) => {
13
+ const op = actor._meta.definitions[receipt.operation];
14
+ if (!op || !op["primaryKey"] || !receipt.primaryKey) return Effect.fail(new NoPrimaryKeyError(receipt));
15
+ const primaryKey = receipt.primaryKey;
16
+ return Effect.gen(function* () {
17
+ const sharding = yield* Sharding.Sharding;
18
+ const storage = yield* MessageStorage.MessageStorage;
19
+ const entityId = EntityId.make(receipt.entityId);
20
+ const group = actor._meta.entity.getShardGroup(entityId);
21
+ const shardId = sharding.getShardId(entityId, group);
22
+ const address = EntityAddress.make({
23
+ entityType: EntityType.make(actor._meta.name),
24
+ entityId,
25
+ shardId
26
+ });
27
+ const maybeRequestId = yield* storage.requestIdForPrimaryKey({
28
+ address,
29
+ tag: receipt.operation,
30
+ id: primaryKey
31
+ });
32
+ if (Option.isNone(maybeRequestId)) return Pending;
33
+ const replies = yield* storage.repliesForUnfiltered([maybeRequestId.value]);
34
+ const last = replies[replies.length - 1];
35
+ if (!last || last._tag !== "WithExit") return Pending;
36
+ return mapExitToPeekResult(last.exit);
37
+ });
38
+ };
39
+ const watch = (actor, receipt, options) => {
40
+ const interval = options?.interval ?? Duration.millis(200);
41
+ return Stream.fromEffectSchedule(peek(actor, receipt), Schedule.spaced(interval)).pipe(Stream.changesWith(peekResultEquals), Stream.takeUntil(isTerminal));
42
+ };
43
+ const peekResultEquals = (a, b) => {
44
+ if (a._tag !== b._tag) return false;
45
+ if (a._tag === "Success" && b._tag === "Success") return a.value === b.value;
46
+ if (a._tag === "Failure" && b._tag === "Failure") return a.error === b.error;
47
+ if (a._tag === "Defect" && b._tag === "Defect") return a.cause === b.cause;
48
+ return true;
49
+ };
50
+ const mapExitToPeekResult = (exit) => {
51
+ if (exit._tag === "Success") return Success(exit.value);
52
+ const cause = exit.cause[0];
53
+ if (!cause) return Pending;
54
+ switch (cause._tag) {
55
+ case "Fail": return Failure(cause.error);
56
+ case "Die": return Defect(cause.defect);
57
+ case "Interrupt": return Interrupted;
58
+ }
59
+ };
60
+ //#endregion
61
+ export { NoPrimaryKeyError, peek, watch };
@@ -0,0 +1,50 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/receipt.d.ts
4
+ declare const CastReceipt_base: Schema.Class<CastReceipt, Schema.Struct<{
5
+ readonly _tag: Schema.Literal<"CastReceipt">;
6
+ readonly actorType: Schema.String;
7
+ readonly entityId: Schema.String;
8
+ readonly operation: Schema.String;
9
+ readonly primaryKey: Schema.optional<Schema.String>;
10
+ }>, {}>;
11
+ declare class CastReceipt extends CastReceipt_base {}
12
+ declare const makeCastReceipt: (options: {
13
+ readonly actorType: string;
14
+ readonly entityId: string;
15
+ readonly operation: string;
16
+ readonly primaryKey?: string | undefined;
17
+ }) => CastReceipt;
18
+ type PeekResult<A = unknown, E = unknown> = {
19
+ readonly _tag: "Pending";
20
+ } | {
21
+ readonly _tag: "Success";
22
+ readonly value: A;
23
+ } | {
24
+ readonly _tag: "Failure";
25
+ readonly error: E;
26
+ } | {
27
+ readonly _tag: "Interrupted";
28
+ } | {
29
+ readonly _tag: "Defect";
30
+ readonly cause: unknown;
31
+ };
32
+ declare const Pending: PeekResult;
33
+ declare const Success: <A>(value: A) => PeekResult<A, never>;
34
+ declare const Failure: <E>(error: E) => PeekResult<never, E>;
35
+ declare const Interrupted: PeekResult;
36
+ declare const Defect: (cause: unknown) => PeekResult;
37
+ declare const isPending: <A, E>(result: PeekResult<A, E>) => result is {
38
+ _tag: "Pending";
39
+ };
40
+ declare const isSuccess: <A, E>(result: PeekResult<A, E>) => result is {
41
+ _tag: "Success";
42
+ value: A;
43
+ };
44
+ declare const isFailure: <A, E>(result: PeekResult<A, E>) => result is {
45
+ _tag: "Failure";
46
+ error: E;
47
+ };
48
+ declare const isTerminal: <A, E>(result: PeekResult<A, E>) => boolean;
49
+ //#endregion
50
+ export { CastReceipt, Defect, Failure, Interrupted, PeekResult, Pending, Success, isFailure, isPending, isSuccess, isTerminal, makeCastReceipt };
@@ -0,0 +1,36 @@
1
+ import { Schema } from "effect";
2
+ //#region src/receipt.ts
3
+ var CastReceipt = class extends Schema.Class("effect-encore/CastReceipt")({
4
+ _tag: Schema.Literal("CastReceipt"),
5
+ actorType: Schema.String,
6
+ entityId: Schema.String,
7
+ operation: Schema.String,
8
+ primaryKey: Schema.optional(Schema.String)
9
+ }) {};
10
+ const makeCastReceipt = (options) => new CastReceipt({
11
+ _tag: "CastReceipt",
12
+ actorType: options.actorType,
13
+ entityId: options.entityId,
14
+ operation: options.operation,
15
+ primaryKey: options.primaryKey
16
+ });
17
+ const Pending = { _tag: "Pending" };
18
+ const Success = (value) => ({
19
+ _tag: "Success",
20
+ value
21
+ });
22
+ const Failure = (error) => ({
23
+ _tag: "Failure",
24
+ error
25
+ });
26
+ const Interrupted = { _tag: "Interrupted" };
27
+ const Defect = (cause) => ({
28
+ _tag: "Defect",
29
+ cause
30
+ });
31
+ const isPending = (result) => result._tag === "Pending";
32
+ const isSuccess = (result) => result._tag === "Success";
33
+ const isFailure = (result) => result._tag === "Failure";
34
+ const isTerminal = (result) => result._tag !== "Pending";
35
+ //#endregion
36
+ export { CastReceipt, Defect, Failure, Interrupted, Pending, Success, isFailure, isPending, isSuccess, isTerminal, makeCastReceipt };
@@ -0,0 +1,2 @@
1
+ import { ActorRef } from "./actor.js";
2
+ export { type ActorRef };
File without changes
@@ -0,0 +1,45 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { Activity, DurableDeferred, Workflow } from "effect/unstable/workflow";
3
+
4
+ //#region src/workflow.d.ts
5
+ declare namespace workflow_d_exports {
6
+ export { Activity, DurableDeferred, WorkflowDefinition, WorkflowReceipt, WorkflowRef, makeWorkflowReceipt, workflow, workflowClient, workflowHandlers, workflowPoll };
7
+ }
8
+ interface WorkflowDefinition<Name extends string = string> {
9
+ readonly _tag: "WorkflowDefinition";
10
+ readonly name: Name;
11
+ readonly workflow: Workflow.Any;
12
+ }
13
+ interface WorkflowReceipt<Type extends string = string> {
14
+ readonly _tag: "WorkflowReceipt";
15
+ readonly workflowName: Type;
16
+ readonly executionId: string;
17
+ }
18
+ declare const makeWorkflowReceipt: <Type extends string>(workflowName: Type, executionId: string) => WorkflowReceipt<Type>;
19
+ declare const workflow: <const Name extends string>(name: Name, options: {
20
+ readonly payload: Record<string, unknown>;
21
+ readonly idempotencyKey: (payload: Record<string, unknown>) => string;
22
+ readonly success?: unknown;
23
+ readonly error?: unknown;
24
+ }) => WorkflowDefinition<Name>;
25
+ type WorkflowRef<Name extends string = string> = {
26
+ readonly call: (payload: unknown) => Effect.Effect<unknown, unknown>;
27
+ readonly cast: (payload: unknown) => Effect.Effect<WorkflowReceipt<Name>, unknown>;
28
+ readonly interrupt: () => Effect.Effect<void>;
29
+ readonly resume: () => Effect.Effect<void>;
30
+ };
31
+ declare const workflowClient: <Name extends string>(def: WorkflowDefinition<Name>) => ((executionId: string) => WorkflowRef<Name>);
32
+ declare const workflowPoll: <Name extends string>(def: WorkflowDefinition<Name>, executionId: string) => Effect.Effect<{
33
+ _tag: "Pending";
34
+ } | {
35
+ _tag: "Success";
36
+ value: unknown;
37
+ } | {
38
+ _tag: "Failure";
39
+ error: unknown;
40
+ } | {
41
+ _tag: "Suspended";
42
+ }, never, never>;
43
+ declare const workflowHandlers: <Name extends string>(def: WorkflowDefinition<Name>, handler: Function) => Layer.Layer<never, never, never>;
44
+ //#endregion
45
+ export { Activity, DurableDeferred, WorkflowDefinition, WorkflowReceipt, WorkflowRef, makeWorkflowReceipt, workflow, workflowClient, workflowHandlers, workflowPoll, workflow_d_exports };