effect-actors 0.0.1

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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +467 -0
  3. package/dist/actor/client.d.ts +11 -0
  4. package/dist/actor/client.js +68 -0
  5. package/dist/actor/definition.d.ts +77 -0
  6. package/dist/actor/definition.js +110 -0
  7. package/dist/actor/state/codec.d.ts +10 -0
  8. package/dist/actor/state/codec.js +117 -0
  9. package/dist/actor/state/compiled.d.ts +17 -0
  10. package/dist/actor/state/compiled.js +1 -0
  11. package/dist/actor/state/layer.d.ts +7 -0
  12. package/dist/actor/state/layer.js +167 -0
  13. package/dist/client/http/errors.d.ts +30 -0
  14. package/dist/client/http/errors.js +72 -0
  15. package/dist/client/http/events.d.ts +10 -0
  16. package/dist/client/http/events.js +30 -0
  17. package/dist/client/http/layer.d.ts +5 -0
  18. package/dist/client/http/layer.js +133 -0
  19. package/dist/client/http.d.ts +1 -0
  20. package/dist/client/http.js +1 -0
  21. package/dist/client/outcome.d.ts +4 -0
  22. package/dist/client/outcome.js +10 -0
  23. package/dist/client/service.d.ts +28 -0
  24. package/dist/client/service.js +4 -0
  25. package/dist/errors/index.d.ts +93 -0
  26. package/dist/errors/index.js +93 -0
  27. package/dist/gateway/access.d.ts +11 -0
  28. package/dist/gateway/access.js +38 -0
  29. package/dist/gateway/dispatch.d.ts +13 -0
  30. package/dist/gateway/dispatch.js +45 -0
  31. package/dist/gateway/fault.d.ts +32 -0
  32. package/dist/gateway/fault.js +85 -0
  33. package/dist/gateway/http/events.d.ts +8 -0
  34. package/dist/gateway/http/events.js +32 -0
  35. package/dist/gateway/http/response.d.ts +11 -0
  36. package/dist/gateway/http/response.js +57 -0
  37. package/dist/gateway/http/routes.d.ts +6 -0
  38. package/dist/gateway/http/routes.js +184 -0
  39. package/dist/gateway/http/validation.d.ts +19 -0
  40. package/dist/gateway/http/validation.js +92 -0
  41. package/dist/gateway/options.d.ts +31 -0
  42. package/dist/gateway/options.js +107 -0
  43. package/dist/gateway/security.d.ts +25 -0
  44. package/dist/gateway/security.js +4 -0
  45. package/dist/gateway/service.d.ts +113 -0
  46. package/dist/gateway/service.js +5 -0
  47. package/dist/gateway/websocket/operations.d.ts +20 -0
  48. package/dist/gateway/websocket/operations.js +96 -0
  49. package/dist/gateway/websocket.d.ts +8 -0
  50. package/dist/gateway/websocket.js +96 -0
  51. package/dist/gateway/wire.d.ts +172 -0
  52. package/dist/gateway/wire.js +102 -0
  53. package/dist/host/client.d.ts +9 -0
  54. package/dist/host/client.js +46 -0
  55. package/dist/host/definition.d.ts +71 -0
  56. package/dist/host/definition.js +68 -0
  57. package/dist/host/entity.d.ts +9 -0
  58. package/dist/host/entity.js +302 -0
  59. package/dist/index.d.ts +9 -0
  60. package/dist/index.js +9 -0
  61. package/dist/protocol/encoding.d.ts +29 -0
  62. package/dist/protocol/encoding.js +165 -0
  63. package/dist/protocol/index.d.ts +90 -0
  64. package/dist/protocol/index.js +1 -0
  65. package/dist/protocol/schemas.d.ts +49 -0
  66. package/dist/protocol/schemas.js +30 -0
  67. package/dist/runtime/cluster/address.d.ts +3 -0
  68. package/dist/runtime/cluster/address.js +21 -0
  69. package/dist/runtime/cluster/client.d.ts +9 -0
  70. package/dist/runtime/cluster/client.js +131 -0
  71. package/dist/runtime/cluster/protocol.d.ts +104 -0
  72. package/dist/runtime/cluster/protocol.js +69 -0
  73. package/dist/runtime/configuration.d.ts +4 -0
  74. package/dist/runtime/configuration.js +34 -0
  75. package/dist/runtime/dispatchers/layer.d.ts +6 -0
  76. package/dist/runtime/dispatchers/layer.js +53 -0
  77. package/dist/runtime/dispatchers/outbox.d.ts +4 -0
  78. package/dist/runtime/dispatchers/outbox.js +57 -0
  79. package/dist/runtime/layer.d.ts +6 -0
  80. package/dist/runtime/layer.js +43 -0
  81. package/dist/runtime/service.d.ts +40 -0
  82. package/dist/runtime/service.js +5 -0
  83. package/dist/store/memory/background.d.ts +39 -0
  84. package/dist/store/memory/background.js +206 -0
  85. package/dist/store/memory/model.d.ts +72 -0
  86. package/dist/store/memory/model.js +149 -0
  87. package/dist/store/memory/ownership.d.ts +11 -0
  88. package/dist/store/memory/ownership.js +82 -0
  89. package/dist/store/memory/service.d.ts +3 -0
  90. package/dist/store/memory/service.js +32 -0
  91. package/dist/store/memory/state.d.ts +13 -0
  92. package/dist/store/memory/state.js +109 -0
  93. package/dist/store/service.d.ts +89 -0
  94. package/dist/store/service.js +9 -0
  95. package/dist/store/sql/events.d.ts +13 -0
  96. package/dist/store/sql/events.js +89 -0
  97. package/dist/store/sql/model.d.ts +39 -0
  98. package/dist/store/sql/model.js +126 -0
  99. package/dist/store/sql/outbox.d.ts +15 -0
  100. package/dist/store/sql/outbox.js +130 -0
  101. package/dist/store/sql/ownership.d.ts +44 -0
  102. package/dist/store/sql/ownership.js +130 -0
  103. package/dist/store/sql/receipts.d.ts +25 -0
  104. package/dist/store/sql/receipts.js +93 -0
  105. package/dist/store/sql/schema.d.ts +9 -0
  106. package/dist/store/sql/schema.js +124 -0
  107. package/dist/store/sql/service.d.ts +5 -0
  108. package/dist/store/sql/service.js +42 -0
  109. package/dist/store/sql/state.d.ts +13 -0
  110. package/dist/store/sql/state.js +109 -0
  111. package/dist/store/sql/timers.d.ts +10 -0
  112. package/dist/store/sql/timers.js +90 -0
  113. package/dist/store/sql/transaction.d.ts +6 -0
  114. package/dist/store/sql/transaction.js +57 -0
  115. package/dist/store/sql/transition.d.ts +15 -0
  116. package/dist/store/sql/transition.js +154 -0
  117. package/dist/testing/index.d.ts +6 -0
  118. package/dist/testing/index.js +27 -0
  119. package/package.json +80 -0
@@ -0,0 +1,165 @@
1
+ import canonicalizeImport from "canonicalize";
2
+ import { Effect, Encoding, Result, Schema } from "effect";
3
+ import { InvalidInput } from "../errors/index.js";
4
+ const canonicalize = canonicalizeImport;
5
+ const Segment = Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(256), Schema.makeFilter((value) => !/[\u0000-\u001f\u007f]/.test(value)));
6
+ export const KeySchema = Schema.Struct({ tenant: Segment, key: Schema.Array(Segment) });
7
+ export const AddressSchema = Schema.Struct({
8
+ namespace: Segment,
9
+ tenant: Segment,
10
+ actorType: Segment,
11
+ key: Schema.Array(Segment),
12
+ });
13
+ export const validateKey = (input) => {
14
+ let key;
15
+ try {
16
+ key = Schema.decodeUnknownSync(KeySchema)(input, { onExcessProperty: "error" });
17
+ }
18
+ catch {
19
+ throw new InvalidInput({ message: "Invalid actor identity" });
20
+ }
21
+ if (key.key.length === 0 || key.key.length > 32)
22
+ throw new InvalidInput({ message: "Actor keys require 1 to 32 segments" });
23
+ canonicalJson([key.tenant, key.key]);
24
+ return key;
25
+ };
26
+ export const addressId = (input) => {
27
+ let address;
28
+ try {
29
+ address = Schema.decodeUnknownSync(AddressSchema)(input, { onExcessProperty: "error" });
30
+ }
31
+ catch {
32
+ throw new InvalidInput({ message: "Invalid actor address" });
33
+ }
34
+ validateKey({ tenant: address.tenant, key: address.key });
35
+ const encoded = canonicalJson([address.namespace, address.tenant, address.actorType, address.key]);
36
+ if (new TextEncoder().encode(encoded).byteLength > 1024) {
37
+ throw new InvalidInput({
38
+ message: "Serialized actor identities cannot exceed 1024 UTF-8 bytes",
39
+ });
40
+ }
41
+ return encoded;
42
+ };
43
+ export const validateId = (value, maximumBytes = 256) => {
44
+ if (typeof value !== "string" ||
45
+ value.length === 0 ||
46
+ /[\u0000-\u001f\u007f]/.test(value) ||
47
+ new TextEncoder().encode(value).byteLength > maximumBytes) {
48
+ throw new InvalidInput({
49
+ message: `Operation identities require 1 to ${maximumBytes} UTF-8 bytes without control characters`,
50
+ });
51
+ }
52
+ canonicalJson(value);
53
+ return value;
54
+ };
55
+ const invalidSurrogate = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
56
+ export const canonicalJson = (input) => {
57
+ const active = new Set();
58
+ const pending = [
59
+ { value: input, depth: 0 },
60
+ ];
61
+ while (pending.length > 0) {
62
+ const current = pending.pop();
63
+ const value = current.value;
64
+ if (current.leave) {
65
+ active.delete(value);
66
+ continue;
67
+ }
68
+ if (current.depth > 64)
69
+ throw new InvalidInput({ message: "JSON exceeds the maximum nesting depth" });
70
+ if (value === null || typeof value === "boolean")
71
+ continue;
72
+ if (typeof value === "number") {
73
+ if (!Number.isFinite(value))
74
+ throw new InvalidInput({ message: "JSON numbers must be finite" });
75
+ continue;
76
+ }
77
+ if (typeof value === "string") {
78
+ if (invalidSurrogate.test(value))
79
+ throw new InvalidInput({ message: "JSON strings must contain valid Unicode" });
80
+ continue;
81
+ }
82
+ if (typeof value !== "object")
83
+ throw new InvalidInput({ message: "Value is not JSON" });
84
+ if (active.has(value))
85
+ throw new InvalidInput({ message: "JSON cannot contain cycles" });
86
+ const prototype = Object.getPrototypeOf(value);
87
+ if (Array.isArray(value) && (prototype !== Array.prototype || Object.hasOwn(value, "reduce"))) {
88
+ throw new InvalidInput({
89
+ message: "JSON arrays cannot override their serialization behavior",
90
+ });
91
+ }
92
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
93
+ throw new InvalidInput({ message: "JSON objects must be plain records" });
94
+ }
95
+ const descriptors = Object.getOwnPropertyDescriptors(value);
96
+ if (Object.values(descriptors).some((descriptor) => !("value" in descriptor)) ||
97
+ typeof descriptors.toJSON?.value === "function") {
98
+ throw new InvalidInput({ message: "JSON cannot contain accessors or custom serializers" });
99
+ }
100
+ active.add(value);
101
+ pending.push({ value, depth: current.depth, leave: true });
102
+ if (Array.isArray(value)) {
103
+ for (let index = 0; index < value.length; index++) {
104
+ if (!Object.hasOwn(value, index))
105
+ throw new InvalidInput({ message: "JSON arrays cannot contain holes" });
106
+ pending.push({ value: value[index], depth: current.depth + 1 });
107
+ }
108
+ }
109
+ else {
110
+ for (const [key, child] of Object.entries(value)) {
111
+ if (invalidSurrogate.test(key))
112
+ throw new InvalidInput({ message: "JSON keys must contain valid Unicode" });
113
+ pending.push({ value: child, depth: current.depth + 1 });
114
+ }
115
+ }
116
+ }
117
+ const encoded = canonicalize(input);
118
+ if (encoded === undefined)
119
+ throw new InvalidInput({ message: "Value is not JSON" });
120
+ return encoded;
121
+ };
122
+ export const fingerprint = (input) => canonicalJson({
123
+ command: input.command,
124
+ protocolVersion: input.protocolVersion,
125
+ payload: input.payload,
126
+ ...(input.expectedRevision === undefined ? {} : { expectedRevision: input.expectedRevision }),
127
+ });
128
+ const sequencePattern = /^(0|[1-9][0-9]{0,31})$/;
129
+ export const isSequence = (value) => value.trim() === value && sequencePattern.test(value);
130
+ export const makeCursor = (address, sequence) => {
131
+ if (!isSequence(sequence))
132
+ throw new InvalidInput({ message: "Invalid event sequence" });
133
+ return Encoding.encodeBase64Url(JSON.stringify([1, addressId(address), sequence]));
134
+ };
135
+ export const readCursor = (address, cursor) => {
136
+ if (cursor.length > 16_384)
137
+ throw new InvalidInput({ message: "Invalid event cursor" });
138
+ const decoded = Encoding.decodeBase64UrlString(cursor);
139
+ if (Result.isFailure(decoded))
140
+ throw new InvalidInput({ message: "Invalid event cursor" });
141
+ let value;
142
+ try {
143
+ value = JSON.parse(decoded.success);
144
+ }
145
+ catch {
146
+ throw new InvalidInput({ message: "Invalid event cursor" });
147
+ }
148
+ if (!Array.isArray(value) ||
149
+ value.length !== 3 ||
150
+ value[0] !== 1 ||
151
+ value[1] !== addressId(address) ||
152
+ typeof value[2] !== "string" ||
153
+ !isSequence(value[2])) {
154
+ throw new InvalidInput({ message: "Cursor does not belong to this actor" });
155
+ }
156
+ return value[2];
157
+ };
158
+ export const decode = (schema, input) => Schema.decodeUnknownEffect(schema)(input, { onExcessProperty: "error" }).pipe(Effect.mapError(() => new InvalidInput({ message: "Input does not match its schema" })));
159
+ export const encode = (schema, input) => Schema.encodeEffect(schema)(input).pipe(Effect.mapError(() => new InvalidInput({ message: "Output does not match its schema" })));
160
+ export const boundary = (evaluate) => Effect.try({
161
+ try: evaluate,
162
+ catch: (cause) => cause instanceof InvalidInput
163
+ ? cause
164
+ : new InvalidInput({ message: "Invalid protocol value" }),
165
+ });
@@ -0,0 +1,90 @@
1
+ import type { Schema } from "effect";
2
+ export { addressId, makeCursor, readCursor, fingerprint, canonicalJson, validateKey, validateId, decode, encode, boundary, } from "./encoding.js";
3
+ export type Json = Schema.Json;
4
+ export type JsonSchema = Schema.Codec<unknown, Json, never, never>;
5
+ export type Decoded<S extends JsonSchema> = S["Type"];
6
+ export interface Key {
7
+ readonly tenant: string;
8
+ readonly key: ReadonlyArray<string>;
9
+ }
10
+ export interface Address extends Key {
11
+ readonly namespace: string;
12
+ readonly actorType: string;
13
+ }
14
+ export interface CommandOptions {
15
+ readonly commandId: string;
16
+ readonly expectedRevision?: string;
17
+ }
18
+ export interface Receipt<A> {
19
+ readonly commandId: string;
20
+ readonly revision: string;
21
+ readonly cursor: string;
22
+ readonly committedAtMillis: number;
23
+ readonly value: A;
24
+ }
25
+ export interface QueryResult<A> {
26
+ readonly revision: string;
27
+ readonly cursor: string;
28
+ readonly value: A;
29
+ }
30
+ export interface Event<A> {
31
+ readonly cursor: string;
32
+ readonly commandId: string;
33
+ readonly committedAtMillis: number;
34
+ readonly value: A;
35
+ }
36
+ export interface SubscribeOptions {
37
+ readonly after: string;
38
+ }
39
+ export interface Procedure<P extends JsonSchema = JsonSchema, A extends JsonSchema = JsonSchema, E extends JsonSchema = JsonSchema> {
40
+ readonly payload: P;
41
+ readonly success: A;
42
+ readonly error: E;
43
+ }
44
+ export type Procedures = Readonly<Record<string, Procedure>>;
45
+ export interface OutgoingCommand {
46
+ readonly kind: "command" | "hosted";
47
+ readonly target: Address;
48
+ readonly operation: string;
49
+ readonly protocolVersion: number;
50
+ readonly payload: Json;
51
+ readonly commandId: string;
52
+ }
53
+ export type TimerChange = {
54
+ readonly _tag: "Set";
55
+ readonly timerId: string;
56
+ readonly dueAtMillis: number;
57
+ readonly command: string;
58
+ readonly payload: Json;
59
+ } | {
60
+ readonly _tag: "Cancel";
61
+ readonly timerId: string;
62
+ };
63
+ export type Outcome = {
64
+ readonly _tag: "Success";
65
+ readonly value: Json;
66
+ } | {
67
+ readonly _tag: "Failure";
68
+ readonly error: Json;
69
+ };
70
+ export type WireReceipt = Receipt<Outcome>;
71
+ export interface Invocation {
72
+ readonly address: Address;
73
+ readonly command: string;
74
+ readonly protocolVersion: number;
75
+ readonly payload: Json;
76
+ readonly commandId: string;
77
+ readonly fingerprint: string;
78
+ readonly expectedRevision?: string;
79
+ }
80
+ export interface Query {
81
+ readonly address: Address;
82
+ readonly query: string;
83
+ readonly protocolVersion: number;
84
+ readonly payload: Json;
85
+ }
86
+ export interface EventPage {
87
+ readonly events: ReadonlyArray<Event<Json>>;
88
+ readonly nextCursor: string;
89
+ readonly hasMore: boolean;
90
+ }
@@ -0,0 +1 @@
1
+ export { addressId, makeCursor, readCursor, fingerprint, canonicalJson, validateKey, validateId, decode, encode, boundary, } from "./encoding.js";
@@ -0,0 +1,49 @@
1
+ import { Schema } from "effect";
2
+ export declare const Sequence: Schema.String;
3
+ export declare const Outcome: Schema.Union<readonly [Schema.Struct<{
4
+ readonly _tag: Schema.tag<"Success">;
5
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
6
+ }>, Schema.Struct<{
7
+ readonly _tag: Schema.tag<"Failure">;
8
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
9
+ }>]>;
10
+ export declare const Receipt: Schema.Struct<{
11
+ readonly commandId: Schema.String;
12
+ readonly revision: Schema.String;
13
+ readonly cursor: Schema.String;
14
+ readonly committedAtMillis: Schema.Number;
15
+ readonly value: Schema.Union<readonly [Schema.Struct<{
16
+ readonly _tag: Schema.tag<"Success">;
17
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
18
+ }>, Schema.Struct<{
19
+ readonly _tag: Schema.tag<"Failure">;
20
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
21
+ }>]>;
22
+ }>;
23
+ export declare const QueryResult: Schema.Struct<{
24
+ readonly revision: Schema.String;
25
+ readonly cursor: Schema.String;
26
+ readonly value: Schema.Union<readonly [Schema.Struct<{
27
+ readonly _tag: Schema.tag<"Success">;
28
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
29
+ }>, Schema.Struct<{
30
+ readonly _tag: Schema.tag<"Failure">;
31
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
32
+ }>]>;
33
+ }>;
34
+ export declare const Event: Schema.Struct<{
35
+ readonly cursor: Schema.String;
36
+ readonly commandId: Schema.String;
37
+ readonly committedAtMillis: Schema.Number;
38
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
39
+ }>;
40
+ export declare const EventPage: Schema.Struct<{
41
+ readonly events: Schema.$Array<Schema.Struct<{
42
+ readonly cursor: Schema.String;
43
+ readonly commandId: Schema.String;
44
+ readonly committedAtMillis: Schema.Number;
45
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
46
+ }>>;
47
+ readonly nextCursor: Schema.String;
48
+ readonly hasMore: Schema.Boolean;
49
+ }>;
@@ -0,0 +1,30 @@
1
+ import { Schema } from "effect";
2
+ import { isSequence } from "./encoding.js";
3
+ export const Sequence = Schema.String.check(Schema.makeFilter(isSequence));
4
+ export const Outcome = Schema.Union([
5
+ Schema.Struct({ _tag: Schema.tag("Success"), value: Schema.Json }),
6
+ Schema.Struct({ _tag: Schema.tag("Failure"), error: Schema.Json }),
7
+ ]);
8
+ export const Receipt = Schema.Struct({
9
+ commandId: Schema.String,
10
+ revision: Sequence,
11
+ cursor: Schema.String,
12
+ committedAtMillis: Schema.Number,
13
+ value: Outcome,
14
+ });
15
+ export const QueryResult = Schema.Struct({
16
+ revision: Sequence,
17
+ cursor: Schema.String,
18
+ value: Outcome,
19
+ });
20
+ export const Event = Schema.Struct({
21
+ cursor: Schema.String,
22
+ commandId: Schema.String,
23
+ committedAtMillis: Schema.Number,
24
+ value: Schema.Json,
25
+ });
26
+ export const EventPage = Schema.Struct({
27
+ events: Schema.Array(Event),
28
+ nextCursor: Schema.String,
29
+ hasMore: Schema.Boolean,
30
+ });
@@ -0,0 +1,3 @@
1
+ import type { Address } from "../../protocol/index.js";
2
+ export declare const entityId: (address: Address) => string;
3
+ export declare const decodeAddress: (namespace: string, actorType: string, id: string) => Address;
@@ -0,0 +1,21 @@
1
+ import { InvalidInput } from "../../errors/index.js";
2
+ import { addressId, validateKey } from "../../protocol/encoding.js";
3
+ export const entityId = (address) => {
4
+ addressId(address);
5
+ return JSON.stringify([address.tenant, address.key]);
6
+ };
7
+ export const decodeAddress = (namespace, actorType, id) => {
8
+ let value;
9
+ try {
10
+ value = JSON.parse(id);
11
+ }
12
+ catch {
13
+ throw new InvalidInput({ message: "Invalid entity identity" });
14
+ }
15
+ if (!Array.isArray(value) || value.length !== 2)
16
+ throw new InvalidInput({ message: "Invalid entity identity" });
17
+ const key = validateKey({ tenant: value[0], key: value[1] });
18
+ const address = { namespace, actorType, ...key };
19
+ addressId(address);
20
+ return address;
21
+ };
@@ -0,0 +1,9 @@
1
+ import { Sharding } from "effect/unstable/cluster";
2
+ import type { Service } from "../../client/service.js";
3
+ import type { Invocation } from "../../protocol/index.js";
4
+ import type { Configuration, Runtime } from "../service.js";
5
+ export declare const validateInvocation: (input: Invocation, configuration: Configuration) => void;
6
+ export declare const make: (configuration: Configuration, sharding: Sharding.Sharding["Service"]) => {
7
+ readonly client: Service;
8
+ readonly wake: Runtime["Service"]["wake"];
9
+ };
@@ -0,0 +1,131 @@
1
+ import { Effect, Option, Result, Schema, Stream } from "effect";
2
+ import { Sharding } from "effect/unstable/cluster";
3
+ import { CommandErrorSchema, EventErrorSchema, InvalidInput, OutcomeUnknown, Overloaded, ReadErrorSchema, StorageUnavailable, } from "../../errors/index.js";
4
+ import { HostedErrorSchema } from "../../errors/index.js";
5
+ import { addressId, boundary, canonicalJson, fingerprint, readCursor, validateId, isSequence, } from "../../protocol/encoding.js";
6
+ import { entityId } from "./address.js";
7
+ import { makeEntity } from "./protocol.js";
8
+ const transportError = (error) => {
9
+ if (typeof error === "object" &&
10
+ error !== null &&
11
+ "_tag" in error &&
12
+ (error._tag === "MailboxFull" || error._tag === "AlreadyProcessingMessage")) {
13
+ return new Overloaded({ retryAfterMillis: 250 });
14
+ }
15
+ return new StorageUnavailable({ retryable: true });
16
+ };
17
+ const normalize = (schema, error) => {
18
+ const result = Schema.decodeUnknownResult(schema)(error);
19
+ return Result.isSuccess(result) ? result.success : transportError(error);
20
+ };
21
+ export const validateInvocation = (input, configuration) => {
22
+ addressId(input.address);
23
+ if (input.address.namespace !== configuration.namespace)
24
+ throw new InvalidInput({ message: "Actor namespace mismatch" });
25
+ validateId(input.commandId);
26
+ if (typeof input.commandId !== "string" ||
27
+ input.commandId.length === 0 ||
28
+ input.commandId.length > 256 ||
29
+ input.commandId.startsWith("~effect-actors/"))
30
+ throw new InvalidInput({ message: "Invalid or reserved command identity" });
31
+ if (!Number.isSafeInteger(input.protocolVersion) ||
32
+ input.protocolVersion < 1 ||
33
+ typeof input.command !== "string" ||
34
+ input.command.length < 1 ||
35
+ input.command.length > 128 ||
36
+ (input.expectedRevision !== undefined && !isSequence(input.expectedRevision))) {
37
+ throw new InvalidInput({ message: "Invalid command envelope" });
38
+ }
39
+ if (new TextEncoder().encode(canonicalJson(input.payload)).byteLength >
40
+ configuration.maxCommandBytes) {
41
+ throw new InvalidInput({ message: "Command payload exceeds the configured byte limit" });
42
+ }
43
+ if (fingerprint(input) !== input.fingerprint)
44
+ throw new InvalidInput({ message: "Command fingerprint mismatch" });
45
+ };
46
+ export const make = (configuration, sharding) => {
47
+ const clients = new Map();
48
+ const get = (address) => Effect.gen(function* () {
49
+ yield* boundary(() => {
50
+ addressId(address);
51
+ if (address.namespace !== configuration.namespace)
52
+ throw new InvalidInput({ message: "Actor namespace mismatch" });
53
+ });
54
+ let client = clients.get(address.actorType);
55
+ if (!client) {
56
+ client = yield* makeEntity(configuration.namespace, address.actorType).client.pipe(Effect.provideService(Sharding.Sharding, sharding));
57
+ clients.set(address.actorType, client);
58
+ }
59
+ return client(entityId(address));
60
+ });
61
+ const client = {
62
+ namespace: configuration.namespace,
63
+ command: (input) => Effect.gen(function* () {
64
+ yield* boundary(() => validateInvocation(input, configuration));
65
+ const client = yield* get(input.address);
66
+ return yield* client.Command(input).pipe(Effect.mapError((error) => normalize(CommandErrorSchema, error)), Effect.timeoutOrElse({
67
+ duration: configuration.maxTurnDuration,
68
+ orElse: () => Effect.fail(new OutcomeUnknown({ commandId: input.commandId })),
69
+ }));
70
+ }),
71
+ query: (input) => Effect.gen(function* () {
72
+ yield* boundary(() => {
73
+ if (new TextEncoder().encode(canonicalJson(input.payload)).byteLength >
74
+ configuration.maxCommandBytes) {
75
+ throw new InvalidInput({ message: "Query payload exceeds the configured byte limit" });
76
+ }
77
+ });
78
+ const client = yield* get(input.address);
79
+ return yield* client.Query(input).pipe(Effect.mapError((error) => normalize(ReadErrorSchema, error)), Effect.timeoutOrElse({
80
+ duration: configuration.maxTurnDuration,
81
+ orElse: () => Effect.fail(new StorageUnavailable({ retryable: true })),
82
+ }));
83
+ }),
84
+ receipt: (address, commandId) => Effect.gen(function* () {
85
+ yield* boundary(() => validateId(commandId, 1024));
86
+ const client = yield* get(address);
87
+ return yield* client.Receipt({ commandId }).pipe(Effect.map(Option.fromNullishOr), Effect.mapError((error) => normalize(ReadErrorSchema, error)), Effect.timeoutOrElse({
88
+ duration: configuration.maxTurnDuration,
89
+ orElse: () => Effect.fail(new StorageUnavailable({ retryable: true })),
90
+ }));
91
+ }),
92
+ events: (address, options) => Stream.unwrap(Effect.gen(function* () {
93
+ yield* boundary(() => readCursor(address, options.after));
94
+ const client = yield* get(address);
95
+ let after = options.after;
96
+ let waitBeforeNextPage = false;
97
+ const page = Effect.suspend(() => (waitBeforeNextPage ? Effect.sleep("250 millis") : Effect.void).pipe(Effect.andThen(client.Events({ after, limit: 128 })))).pipe(Effect.mapError((error) => normalize(EventErrorSchema, error)), Effect.timeoutOrElse({
98
+ duration: configuration.maxTurnDuration,
99
+ orElse: () => Effect.fail(new StorageUnavailable({ retryable: true })),
100
+ }), Effect.tap((page) => Effect.sync(() => {
101
+ after = page.nextCursor;
102
+ waitBeforeNextPage = !page.hasMore;
103
+ })));
104
+ return Stream.fromEffectRepeat(page).pipe(Stream.flatMap((page) => Stream.fromIterable(page.events)));
105
+ })),
106
+ hosted: (input) => Effect.gen(function* () {
107
+ yield* boundary(() => {
108
+ if (new TextEncoder().encode(canonicalJson(input.payload)).byteLength >
109
+ configuration.maxCommandBytes) {
110
+ throw new InvalidInput({ message: "Hosted payload exceeds the configured byte limit" });
111
+ }
112
+ });
113
+ const client = yield* get(input.address);
114
+ const request = input.requestKey === undefined ? client.Action(input) : client.DurableAction(input);
115
+ return yield* request.pipe(Effect.mapError((error) => normalize(HostedErrorSchema, error)), Effect.timeoutOrElse({
116
+ duration: configuration.maxTurnDuration,
117
+ orElse: () => Effect.fail(new OutcomeUnknown({ commandId: input.requestKey ?? "transient" })),
118
+ }));
119
+ }),
120
+ };
121
+ return {
122
+ client,
123
+ wake: (candidate) => Effect.gen(function* () {
124
+ const target = yield* get(candidate.address);
125
+ return yield* target.Wake(candidate).pipe(Effect.map(Option.fromNullishOr), Effect.mapError((error) => normalize(CommandErrorSchema, error)), Effect.timeoutOrElse({
126
+ duration: configuration.maxTurnDuration,
127
+ orElse: () => Effect.fail(new StorageUnavailable({ retryable: true })),
128
+ }));
129
+ }),
130
+ };
131
+ };
@@ -0,0 +1,104 @@
1
+ import { Schema } from "effect";
2
+ import { Entity } from "effect/unstable/cluster";
3
+ import { Rpc } from "effect/unstable/rpc";
4
+ export declare const makeEntity: (namespace: string, actorType: string) => Entity.Entity<`effect-actors/${string}`, Rpc.Rpc<"Action", Schema.Struct<{
5
+ action: Schema.String;
6
+ protocolVersion: Schema.Int;
7
+ payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
8
+ requestKey: Schema.optionalKey<Schema.String>;
9
+ fingerprint: Schema.String;
10
+ }>, Schema.Union<readonly [Schema.Struct<{
11
+ readonly _tag: Schema.tag<"Success">;
12
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
13
+ }>, Schema.Struct<{
14
+ readonly _tag: Schema.tag<"Failure">;
15
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
16
+ }>]>, Schema.Union<readonly [Schema.Union<readonly [Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").CommandConflict, typeof import("../../errors/index.js").RevisionConflict, typeof import("../../errors/index.js").OutcomeUnknown, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").HostUnavailable]>, never, never> | Rpc.Rpc<"Command", Schema.Struct<{
17
+ commandId: Schema.String;
18
+ fingerprint: Schema.String;
19
+ protocolVersion: Schema.Int;
20
+ command: Schema.String;
21
+ payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
22
+ expectedRevision: Schema.optionalKey<Schema.String>;
23
+ }>, Schema.Struct<{
24
+ readonly commandId: Schema.String;
25
+ readonly revision: Schema.String;
26
+ readonly cursor: Schema.String;
27
+ readonly committedAtMillis: Schema.Number;
28
+ readonly value: Schema.Union<readonly [Schema.Struct<{
29
+ readonly _tag: Schema.tag<"Success">;
30
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
31
+ }>, Schema.Struct<{
32
+ readonly _tag: Schema.tag<"Failure">;
33
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
34
+ }>]>;
35
+ }>, Schema.Union<readonly [Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").CommandConflict, typeof import("../../errors/index.js").RevisionConflict, typeof import("../../errors/index.js").OutcomeUnknown, typeof import("../../errors/index.js").MigrationRejected]>, never, never> | Rpc.Rpc<"DurableAction", Schema.Struct<{
36
+ action: Schema.String;
37
+ protocolVersion: Schema.Int;
38
+ payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
39
+ requestKey: Schema.optionalKey<Schema.String>;
40
+ fingerprint: Schema.String;
41
+ }>, Schema.Union<readonly [Schema.Struct<{
42
+ readonly _tag: Schema.tag<"Success">;
43
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
44
+ }>, Schema.Struct<{
45
+ readonly _tag: Schema.tag<"Failure">;
46
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
47
+ }>]>, Schema.Union<readonly [Schema.Union<readonly [Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").CommandConflict, typeof import("../../errors/index.js").RevisionConflict, typeof import("../../errors/index.js").OutcomeUnknown, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").HostUnavailable]>, never, never> | Rpc.Rpc<"Events", Schema.Struct<{
48
+ after: Schema.String;
49
+ limit: Schema.Int;
50
+ }>, Schema.Struct<{
51
+ readonly events: Schema.$Array<Schema.Struct<{
52
+ readonly cursor: Schema.String;
53
+ readonly commandId: Schema.String;
54
+ readonly committedAtMillis: Schema.Number;
55
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
56
+ }>>;
57
+ readonly nextCursor: Schema.String;
58
+ readonly hasMore: Schema.Boolean;
59
+ }>, Schema.Union<readonly [Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").CursorExpired]>, never, never> | Rpc.Rpc<"Query", Schema.Struct<{
60
+ query: Schema.String;
61
+ protocolVersion: Schema.Int;
62
+ payload: Schema.Codec<Schema.Json, Schema.Json, never, never>;
63
+ }>, Schema.Struct<{
64
+ readonly revision: Schema.String;
65
+ readonly cursor: Schema.String;
66
+ readonly value: Schema.Union<readonly [Schema.Struct<{
67
+ readonly _tag: Schema.tag<"Success">;
68
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
69
+ }>, Schema.Struct<{
70
+ readonly _tag: Schema.tag<"Failure">;
71
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
72
+ }>]>;
73
+ }>, Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, never, never> | Rpc.Rpc<"Receipt", Schema.Struct<{
74
+ commandId: Schema.String;
75
+ }>, Schema.NullOr<Schema.Struct<{
76
+ readonly commandId: Schema.String;
77
+ readonly revision: Schema.String;
78
+ readonly cursor: Schema.String;
79
+ readonly committedAtMillis: Schema.Number;
80
+ readonly value: Schema.Union<readonly [Schema.Struct<{
81
+ readonly _tag: Schema.tag<"Success">;
82
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
83
+ }>, Schema.Struct<{
84
+ readonly _tag: Schema.tag<"Failure">;
85
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
86
+ }>]>;
87
+ }>>, Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, never, never> | Rpc.Rpc<"Wake", Schema.Struct<{
88
+ timerId: Schema.String;
89
+ generation: Schema.String;
90
+ dueAtMillis: Schema.Number;
91
+ }>, Schema.NullOr<Schema.Struct<{
92
+ readonly commandId: Schema.String;
93
+ readonly revision: Schema.String;
94
+ readonly cursor: Schema.String;
95
+ readonly committedAtMillis: Schema.Number;
96
+ readonly value: Schema.Union<readonly [Schema.Struct<{
97
+ readonly _tag: Schema.tag<"Success">;
98
+ readonly value: Schema.Codec<Schema.Json, Schema.Json, never, never>;
99
+ }>, Schema.Struct<{
100
+ readonly _tag: Schema.tag<"Failure">;
101
+ readonly error: Schema.Codec<Schema.Json, Schema.Json, never, never>;
102
+ }>]>;
103
+ }>>, Schema.Union<readonly [Schema.Union<readonly [typeof import("../../errors/index.js").InvalidInput, typeof import("../../errors/index.js").StorageUnavailable, typeof import("../../errors/index.js").OwnershipLost, typeof import("../../errors/index.js").Overloaded, typeof import("../../errors/index.js").Unauthorized, typeof import("../../errors/index.js").UnsupportedVersion, typeof import("../../errors/index.js").MigrationRejected]>, typeof import("../../errors/index.js").CommandConflict, typeof import("../../errors/index.js").RevisionConflict, typeof import("../../errors/index.js").OutcomeUnknown, typeof import("../../errors/index.js").MigrationRejected]>, never, never>>;
104
+ export type ActorEntity = ReturnType<typeof makeEntity>;