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.
- package/LICENSE +21 -0
- package/README.md +467 -0
- package/dist/actor/client.d.ts +11 -0
- package/dist/actor/client.js +68 -0
- package/dist/actor/definition.d.ts +77 -0
- package/dist/actor/definition.js +110 -0
- package/dist/actor/state/codec.d.ts +10 -0
- package/dist/actor/state/codec.js +117 -0
- package/dist/actor/state/compiled.d.ts +17 -0
- package/dist/actor/state/compiled.js +1 -0
- package/dist/actor/state/layer.d.ts +7 -0
- package/dist/actor/state/layer.js +167 -0
- package/dist/client/http/errors.d.ts +30 -0
- package/dist/client/http/errors.js +72 -0
- package/dist/client/http/events.d.ts +10 -0
- package/dist/client/http/events.js +30 -0
- package/dist/client/http/layer.d.ts +5 -0
- package/dist/client/http/layer.js +133 -0
- package/dist/client/http.d.ts +1 -0
- package/dist/client/http.js +1 -0
- package/dist/client/outcome.d.ts +4 -0
- package/dist/client/outcome.js +10 -0
- package/dist/client/service.d.ts +28 -0
- package/dist/client/service.js +4 -0
- package/dist/errors/index.d.ts +93 -0
- package/dist/errors/index.js +93 -0
- package/dist/gateway/access.d.ts +11 -0
- package/dist/gateway/access.js +38 -0
- package/dist/gateway/dispatch.d.ts +13 -0
- package/dist/gateway/dispatch.js +45 -0
- package/dist/gateway/fault.d.ts +32 -0
- package/dist/gateway/fault.js +85 -0
- package/dist/gateway/http/events.d.ts +8 -0
- package/dist/gateway/http/events.js +32 -0
- package/dist/gateway/http/response.d.ts +11 -0
- package/dist/gateway/http/response.js +57 -0
- package/dist/gateway/http/routes.d.ts +6 -0
- package/dist/gateway/http/routes.js +184 -0
- package/dist/gateway/http/validation.d.ts +19 -0
- package/dist/gateway/http/validation.js +92 -0
- package/dist/gateway/options.d.ts +31 -0
- package/dist/gateway/options.js +107 -0
- package/dist/gateway/security.d.ts +25 -0
- package/dist/gateway/security.js +4 -0
- package/dist/gateway/service.d.ts +113 -0
- package/dist/gateway/service.js +5 -0
- package/dist/gateway/websocket/operations.d.ts +20 -0
- package/dist/gateway/websocket/operations.js +96 -0
- package/dist/gateway/websocket.d.ts +8 -0
- package/dist/gateway/websocket.js +96 -0
- package/dist/gateway/wire.d.ts +172 -0
- package/dist/gateway/wire.js +102 -0
- package/dist/host/client.d.ts +9 -0
- package/dist/host/client.js +46 -0
- package/dist/host/definition.d.ts +71 -0
- package/dist/host/definition.js +68 -0
- package/dist/host/entity.d.ts +9 -0
- package/dist/host/entity.js +302 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/protocol/encoding.d.ts +29 -0
- package/dist/protocol/encoding.js +165 -0
- package/dist/protocol/index.d.ts +90 -0
- package/dist/protocol/index.js +1 -0
- package/dist/protocol/schemas.d.ts +49 -0
- package/dist/protocol/schemas.js +30 -0
- package/dist/runtime/cluster/address.d.ts +3 -0
- package/dist/runtime/cluster/address.js +21 -0
- package/dist/runtime/cluster/client.d.ts +9 -0
- package/dist/runtime/cluster/client.js +131 -0
- package/dist/runtime/cluster/protocol.d.ts +104 -0
- package/dist/runtime/cluster/protocol.js +69 -0
- package/dist/runtime/configuration.d.ts +4 -0
- package/dist/runtime/configuration.js +34 -0
- package/dist/runtime/dispatchers/layer.d.ts +6 -0
- package/dist/runtime/dispatchers/layer.js +53 -0
- package/dist/runtime/dispatchers/outbox.d.ts +4 -0
- package/dist/runtime/dispatchers/outbox.js +57 -0
- package/dist/runtime/layer.d.ts +6 -0
- package/dist/runtime/layer.js +43 -0
- package/dist/runtime/service.d.ts +40 -0
- package/dist/runtime/service.js +5 -0
- package/dist/store/memory/background.d.ts +39 -0
- package/dist/store/memory/background.js +206 -0
- package/dist/store/memory/model.d.ts +72 -0
- package/dist/store/memory/model.js +149 -0
- package/dist/store/memory/ownership.d.ts +11 -0
- package/dist/store/memory/ownership.js +82 -0
- package/dist/store/memory/service.d.ts +3 -0
- package/dist/store/memory/service.js +32 -0
- package/dist/store/memory/state.d.ts +13 -0
- package/dist/store/memory/state.js +109 -0
- package/dist/store/service.d.ts +89 -0
- package/dist/store/service.js +9 -0
- package/dist/store/sql/events.d.ts +13 -0
- package/dist/store/sql/events.js +89 -0
- package/dist/store/sql/model.d.ts +39 -0
- package/dist/store/sql/model.js +126 -0
- package/dist/store/sql/outbox.d.ts +15 -0
- package/dist/store/sql/outbox.js +130 -0
- package/dist/store/sql/ownership.d.ts +44 -0
- package/dist/store/sql/ownership.js +130 -0
- package/dist/store/sql/receipts.d.ts +25 -0
- package/dist/store/sql/receipts.js +93 -0
- package/dist/store/sql/schema.d.ts +9 -0
- package/dist/store/sql/schema.js +124 -0
- package/dist/store/sql/service.d.ts +5 -0
- package/dist/store/sql/service.js +42 -0
- package/dist/store/sql/state.d.ts +13 -0
- package/dist/store/sql/state.js +109 -0
- package/dist/store/sql/timers.d.ts +10 -0
- package/dist/store/sql/timers.js +90 -0
- package/dist/store/sql/transaction.d.ts +6 -0
- package/dist/store/sql/transaction.js +57 -0
- package/dist/store/sql/transition.d.ts +15 -0
- package/dist/store/sql/transition.js +154 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +27 -0
- package/package.json +80 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Effect, Layer, Option, Stream } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
import { HostUnavailable, OutcomeUnknown } from "../../errors/index.js";
|
|
4
|
+
import { addressId, boundary, canonicalJson, fingerprint, validateId, } from "../../protocol/index.js";
|
|
5
|
+
import { isSequence } from "../../protocol/encoding.js";
|
|
6
|
+
import { Outcome as OutcomeSchema, QueryResult as QueryResultSchema, Receipt, } from "../../protocol/schemas.js";
|
|
7
|
+
import { ReceiptResponse } from "../../gateway/wire.js";
|
|
8
|
+
import { Client } from "../service.js";
|
|
9
|
+
import { isSuccess, invalid, parseCommandError, parseHostedError, parseReadError, parseReadOnlyError, responseJson, unavailable, } from "./errors.js";
|
|
10
|
+
import { decodeEventStream, eventsUrl, validateCursor } from "./events.js";
|
|
11
|
+
const normalizeBaseUrl = (baseUrl) => {
|
|
12
|
+
if (!/^https?:\/\//.test(baseUrl))
|
|
13
|
+
throw new TypeError("HTTP client baseUrl must be an absolute HTTP URL");
|
|
14
|
+
return baseUrl.replace(/\/+$/, "");
|
|
15
|
+
};
|
|
16
|
+
const segment = (value) => encodeURIComponent(value);
|
|
17
|
+
const commandUrl = (baseUrl, address, command) => `${baseUrl}/${segment(address.tenant)}/${segment(address.actorType)}/commands/${segment(command)}`;
|
|
18
|
+
const queryUrl = (baseUrl, address, query) => `${baseUrl}/${segment(address.tenant)}/${segment(address.actorType)}/queries/${segment(query)}`;
|
|
19
|
+
const receiptUrl = (baseUrl, address, commandId) => `${baseUrl}/${segment(address.tenant)}/${segment(address.actorType)}/receipts/${segment(commandId)}`;
|
|
20
|
+
const hostedUrl = (baseUrl, address, action) => `${baseUrl}/${segment(address.tenant)}/${segment(address.actorType)}/actions/${segment(action)}`;
|
|
21
|
+
const validateAddress = (namespace, address) => boundary(() => {
|
|
22
|
+
addressId(address);
|
|
23
|
+
if (address.namespace !== namespace)
|
|
24
|
+
throw invalid();
|
|
25
|
+
return address;
|
|
26
|
+
});
|
|
27
|
+
const validateInvocation = (namespace, input) => boundary(() => {
|
|
28
|
+
addressId(input.address);
|
|
29
|
+
if (input.address.namespace !== namespace)
|
|
30
|
+
throw invalid();
|
|
31
|
+
validateId(input.commandId);
|
|
32
|
+
if (input.commandId.startsWith("~effect-actors/"))
|
|
33
|
+
throw invalid();
|
|
34
|
+
if (input.expectedRevision !== undefined && !isSequence(input.expectedRevision))
|
|
35
|
+
throw invalid();
|
|
36
|
+
const expectedFingerprint = fingerprint({
|
|
37
|
+
command: input.command,
|
|
38
|
+
protocolVersion: input.protocolVersion,
|
|
39
|
+
payload: input.payload,
|
|
40
|
+
...(input.expectedRevision === undefined ? {} : { expectedRevision: input.expectedRevision }),
|
|
41
|
+
});
|
|
42
|
+
if (input.fingerprint !== expectedFingerprint)
|
|
43
|
+
throw invalid();
|
|
44
|
+
return input;
|
|
45
|
+
});
|
|
46
|
+
const validateReceiptCommandId = (commandId) => boundary(() => validateId(commandId, 1_024));
|
|
47
|
+
const validateHosted = (namespace, input) => boundary(() => {
|
|
48
|
+
addressId(input.address);
|
|
49
|
+
if (input.address.namespace !== namespace)
|
|
50
|
+
throw invalid();
|
|
51
|
+
if (input.fingerprint !==
|
|
52
|
+
fingerprint({
|
|
53
|
+
command: input.action,
|
|
54
|
+
protocolVersion: input.protocolVersion,
|
|
55
|
+
payload: input.payload,
|
|
56
|
+
}))
|
|
57
|
+
throw invalid();
|
|
58
|
+
return input;
|
|
59
|
+
});
|
|
60
|
+
const validateQuery = (namespace, input) => boundary(() => {
|
|
61
|
+
addressId(input.address);
|
|
62
|
+
if (input.address.namespace !== namespace)
|
|
63
|
+
throw invalid();
|
|
64
|
+
canonicalJson(input.payload);
|
|
65
|
+
return input;
|
|
66
|
+
});
|
|
67
|
+
const requestJson = (http, url, body) => HttpClientRequest.bodyJson(HttpClientRequest.post(url), body).pipe(Effect.mapError(unavailable), Effect.flatMap((request) => http.execute(request).pipe(Effect.mapError(unavailable))));
|
|
68
|
+
const requestEvents = (http, url) => http
|
|
69
|
+
.execute(HttpClientRequest.get(url, { headers: { accept: "text/event-stream" } }))
|
|
70
|
+
.pipe(Effect.mapError(unavailable));
|
|
71
|
+
export const layerHttp = (options) => {
|
|
72
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
73
|
+
return Layer.effect(Client, Effect.gen(function* () {
|
|
74
|
+
const http = yield* HttpClient.HttpClient;
|
|
75
|
+
const service = {
|
|
76
|
+
namespace: options.namespace,
|
|
77
|
+
command: (input) => validateInvocation(options.namespace, input).pipe(Effect.flatMap((checked) => requestJson(http, commandUrl(baseUrl, checked.address, checked.command), {
|
|
78
|
+
key: checked.address.key,
|
|
79
|
+
protocolVersion: checked.protocolVersion,
|
|
80
|
+
commandId: checked.commandId,
|
|
81
|
+
...(checked.expectedRevision === undefined
|
|
82
|
+
? {}
|
|
83
|
+
: { expectedRevision: checked.expectedRevision }),
|
|
84
|
+
payload: checked.payload,
|
|
85
|
+
}).pipe(Effect.mapError(() => new OutcomeUnknown({ commandId: checked.commandId })), Effect.flatMap((response) => isSuccess(response.status)
|
|
86
|
+
? responseJson(response, Receipt, new OutcomeUnknown({ commandId: checked.commandId })).pipe(Effect.flatMap((receipt) => validateCursor(checked.address, receipt.cursor).pipe(Effect.mapError(() => new OutcomeUnknown({ commandId: checked.commandId })), Effect.as(receipt))))
|
|
87
|
+
: parseCommandError(response, {
|
|
88
|
+
actorType: checked.address.actorType,
|
|
89
|
+
protocolVersion: checked.protocolVersion,
|
|
90
|
+
commandId: checked.commandId,
|
|
91
|
+
}, new OutcomeUnknown({ commandId: checked.commandId })))))),
|
|
92
|
+
query: (input) => validateQuery(options.namespace, input).pipe(Effect.flatMap((checkedInput) => requestJson(http, queryUrl(baseUrl, checkedInput.address, checkedInput.query), {
|
|
93
|
+
key: checkedInput.address.key,
|
|
94
|
+
protocolVersion: checkedInput.protocolVersion,
|
|
95
|
+
payload: checkedInput.payload,
|
|
96
|
+
}).pipe(Effect.flatMap((response) => isSuccess(response.status)
|
|
97
|
+
? responseJson(response, QueryResultSchema, unavailable()).pipe(Effect.flatMap((result) => validateCursor(checkedInput.address, result.cursor).pipe(Effect.mapError(unavailable), Effect.as(result))))
|
|
98
|
+
: parseReadOnlyError(response, {
|
|
99
|
+
actorType: checkedInput.address.actorType,
|
|
100
|
+
protocolVersion: checkedInput.protocolVersion,
|
|
101
|
+
}))))),
|
|
102
|
+
receipt: (address, commandId) => validateAddress(options.namespace, address).pipe(Effect.flatMap((checked) => validateReceiptCommandId(commandId).pipe(Effect.flatMap((checkedCommandId) => requestJson(http, receiptUrl(baseUrl, checked, checkedCommandId), {
|
|
103
|
+
key: checked.key,
|
|
104
|
+
}).pipe(Effect.flatMap((response) => {
|
|
105
|
+
if (response.status === 404)
|
|
106
|
+
return Effect.succeed(Option.none());
|
|
107
|
+
return isSuccess(response.status)
|
|
108
|
+
? responseJson(response, ReceiptResponse, unavailable()).pipe(Effect.flatMap((body) => body.receipt === null
|
|
109
|
+
? Effect.succeed(Option.none())
|
|
110
|
+
: validateCursor(checked, body.receipt.cursor).pipe(Effect.mapError(unavailable), Effect.as(Option.some(body.receipt)))))
|
|
111
|
+
: parseReadOnlyError(response, { actorType: checked.actorType });
|
|
112
|
+
})))))),
|
|
113
|
+
events: (address, subscribeOptions) => Stream.unwrap(validateAddress(options.namespace, address).pipe(Effect.flatMap((checked) => validateCursor(checked, subscribeOptions.after).pipe(Effect.as(checked))), Effect.mapError((error) => error), Effect.flatMap((checked) => requestEvents(http, eventsUrl(baseUrl, checked, subscribeOptions.after)).pipe(Effect.flatMap((response) => {
|
|
114
|
+
const context = { actorType: checked.actorType, address: checked };
|
|
115
|
+
return isSuccess(response.status)
|
|
116
|
+
? Effect.succeed(decodeEventStream(response.stream, context))
|
|
117
|
+
: parseReadError(response, context);
|
|
118
|
+
}))))),
|
|
119
|
+
hosted: (input) => validateHosted(options.namespace, input).pipe(Effect.flatMap((checked) => requestJson(http, hostedUrl(baseUrl, checked.address, checked.action), {
|
|
120
|
+
key: checked.address.key,
|
|
121
|
+
protocolVersion: checked.protocolVersion,
|
|
122
|
+
...(checked.requestKey === undefined ? {} : { requestKey: checked.requestKey }),
|
|
123
|
+
payload: checked.payload,
|
|
124
|
+
}).pipe(Effect.flatMap((response) => isSuccess(response.status)
|
|
125
|
+
? responseJson(response, OutcomeSchema, new HostUnavailable({ retryable: true }))
|
|
126
|
+
: parseHostedError(response, {
|
|
127
|
+
actorType: checked.address.actorType,
|
|
128
|
+
protocolVersion: checked.protocolVersion,
|
|
129
|
+
}))))),
|
|
130
|
+
};
|
|
131
|
+
return service;
|
|
132
|
+
}));
|
|
133
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { layerHttp } from "./http/layer.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { layerHttp } from "./http/layer.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { Decoded, JsonSchema, Outcome } from "../protocol/index.js";
|
|
3
|
+
import type { InvalidInput } from "../errors/index.js";
|
|
4
|
+
export declare const unwrap: <A extends JsonSchema, E extends JsonSchema, F extends JsonSchema>(success: A, error: E, framework: F, outcome: Outcome) => Effect.Effect<Decoded<A>, Decoded<E> | Decoded<F> | InvalidInput>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Effect, Result, Schema } from "effect";
|
|
2
|
+
import { decode } from "../protocol/encoding.js";
|
|
3
|
+
export const unwrap = (success, error, framework, outcome) => {
|
|
4
|
+
if (outcome._tag === "Success")
|
|
5
|
+
return decode(success, outcome.value);
|
|
6
|
+
const known = Schema.decodeUnknownResult(framework)(outcome.error);
|
|
7
|
+
if (Result.isSuccess(known))
|
|
8
|
+
return Effect.fail(known.success);
|
|
9
|
+
return Effect.flatMap(decode(error, outcome.error), Effect.fail);
|
|
10
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
import type { Effect, Option, Stream } from "effect";
|
|
3
|
+
import type { CommandError, CursorExpired, HostUnavailable, ReadError } from "../errors/index.js";
|
|
4
|
+
import type { Address, Event, Invocation, Json, Outcome, Query, QueryResult, SubscribeOptions, WireReceipt } from "../protocol/index.js";
|
|
5
|
+
export interface HostedInvocation {
|
|
6
|
+
readonly address: Address;
|
|
7
|
+
readonly action: string;
|
|
8
|
+
readonly protocolVersion: number;
|
|
9
|
+
readonly payload: Json;
|
|
10
|
+
readonly requestKey?: string;
|
|
11
|
+
readonly fingerprint: string;
|
|
12
|
+
}
|
|
13
|
+
export interface Service {
|
|
14
|
+
readonly namespace: string;
|
|
15
|
+
readonly command: (input: Invocation) => Effect.Effect<WireReceipt, CommandError>;
|
|
16
|
+
readonly query: (input: Query) => Effect.Effect<QueryResult<Outcome>, ReadError>;
|
|
17
|
+
readonly receipt: (address: Address, commandId: string) => Effect.Effect<Option.Option<WireReceipt>, ReadError>;
|
|
18
|
+
readonly events: (address: Address, options: SubscribeOptions) => Stream.Stream<Event<Json>, ReadError | CursorExpired>;
|
|
19
|
+
readonly hosted: (input: HostedInvocation) => Effect.Effect<Outcome, CommandError | HostUnavailable>;
|
|
20
|
+
}
|
|
21
|
+
declare const Client_base: Context.ServiceClass<Client, "effect-actors/Client", Service>;
|
|
22
|
+
export declare class Client extends Client_base {
|
|
23
|
+
}
|
|
24
|
+
export interface HttpOptions {
|
|
25
|
+
readonly namespace: string;
|
|
26
|
+
readonly baseUrl: string;
|
|
27
|
+
}
|
|
28
|
+
export { layerHttp } from "./http.js";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
declare const InvalidInput_base: Schema.Class<InvalidInput, Schema.Struct<{
|
|
3
|
+
readonly _tag: Schema.tag<"effect-actors/InvalidInput">;
|
|
4
|
+
readonly message: Schema.String;
|
|
5
|
+
}>, import("effect/Cause").YieldableError>;
|
|
6
|
+
export declare class InvalidInput extends InvalidInput_base {
|
|
7
|
+
}
|
|
8
|
+
declare const CommandConflict_base: Schema.Class<CommandConflict, Schema.Struct<{
|
|
9
|
+
readonly _tag: Schema.tag<"effect-actors/CommandConflict">;
|
|
10
|
+
readonly commandId: Schema.String;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
|
+
export declare class CommandConflict extends CommandConflict_base {
|
|
13
|
+
}
|
|
14
|
+
declare const RevisionConflict_base: Schema.Class<RevisionConflict, Schema.Struct<{
|
|
15
|
+
readonly _tag: Schema.tag<"effect-actors/RevisionConflict">;
|
|
16
|
+
readonly expected: Schema.String;
|
|
17
|
+
readonly actual: Schema.String;
|
|
18
|
+
}>, import("effect/Cause").YieldableError>;
|
|
19
|
+
export declare class RevisionConflict extends RevisionConflict_base {
|
|
20
|
+
}
|
|
21
|
+
declare const CursorExpired_base: Schema.Class<CursorExpired, Schema.Struct<{
|
|
22
|
+
readonly _tag: Schema.tag<"effect-actors/CursorExpired">;
|
|
23
|
+
readonly message: Schema.String;
|
|
24
|
+
}>, import("effect/Cause").YieldableError>;
|
|
25
|
+
export declare class CursorExpired extends CursorExpired_base {
|
|
26
|
+
}
|
|
27
|
+
declare const StorageUnavailable_base: Schema.Class<StorageUnavailable, Schema.Struct<{
|
|
28
|
+
readonly _tag: Schema.tag<"effect-actors/StorageUnavailable">;
|
|
29
|
+
readonly retryable: Schema.Boolean;
|
|
30
|
+
}>, import("effect/Cause").YieldableError>;
|
|
31
|
+
export declare class StorageUnavailable extends StorageUnavailable_base {
|
|
32
|
+
}
|
|
33
|
+
declare const OwnershipLost_base: Schema.Class<OwnershipLost, Schema.Struct<{
|
|
34
|
+
readonly _tag: Schema.tag<"effect-actors/OwnershipLost">;
|
|
35
|
+
readonly actorType: Schema.String;
|
|
36
|
+
}>, import("effect/Cause").YieldableError>;
|
|
37
|
+
export declare class OwnershipLost extends OwnershipLost_base {
|
|
38
|
+
}
|
|
39
|
+
declare const Overloaded_base: Schema.Class<Overloaded, Schema.Struct<{
|
|
40
|
+
readonly _tag: Schema.tag<"effect-actors/Overloaded">;
|
|
41
|
+
readonly retryAfterMillis: Schema.Number;
|
|
42
|
+
}>, import("effect/Cause").YieldableError>;
|
|
43
|
+
export declare class Overloaded extends Overloaded_base {
|
|
44
|
+
}
|
|
45
|
+
declare const Unauthorized_base: Schema.Class<Unauthorized, Schema.Struct<{
|
|
46
|
+
readonly _tag: Schema.tag<"effect-actors/Unauthorized">;
|
|
47
|
+
readonly message: Schema.String;
|
|
48
|
+
}>, import("effect/Cause").YieldableError>;
|
|
49
|
+
export declare class Unauthorized extends Unauthorized_base {
|
|
50
|
+
}
|
|
51
|
+
declare const OutcomeUnknown_base: Schema.Class<OutcomeUnknown, Schema.Struct<{
|
|
52
|
+
readonly _tag: Schema.tag<"effect-actors/OutcomeUnknown">;
|
|
53
|
+
readonly commandId: Schema.String;
|
|
54
|
+
}>, import("effect/Cause").YieldableError>;
|
|
55
|
+
export declare class OutcomeUnknown extends OutcomeUnknown_base {
|
|
56
|
+
}
|
|
57
|
+
declare const UnsupportedVersion_base: Schema.Class<UnsupportedVersion, Schema.Struct<{
|
|
58
|
+
readonly _tag: Schema.tag<"effect-actors/UnsupportedVersion">;
|
|
59
|
+
readonly actorType: Schema.String;
|
|
60
|
+
readonly version: Schema.Int;
|
|
61
|
+
}>, import("effect/Cause").YieldableError>;
|
|
62
|
+
export declare class UnsupportedVersion extends UnsupportedVersion_base {
|
|
63
|
+
}
|
|
64
|
+
declare const MigrationRejected_base: Schema.Class<MigrationRejected, Schema.Struct<{
|
|
65
|
+
readonly _tag: Schema.tag<"effect-actors/MigrationRejected">;
|
|
66
|
+
readonly fromVersion: Schema.Int;
|
|
67
|
+
readonly toVersion: Schema.Int;
|
|
68
|
+
readonly message: Schema.String;
|
|
69
|
+
}>, import("effect/Cause").YieldableError>;
|
|
70
|
+
export declare class MigrationRejected extends MigrationRejected_base {
|
|
71
|
+
}
|
|
72
|
+
declare const RegistrationError_base: Schema.Class<RegistrationError, Schema.Struct<{
|
|
73
|
+
readonly _tag: Schema.tag<"effect-actors/RegistrationError">;
|
|
74
|
+
readonly message: Schema.String;
|
|
75
|
+
}>, import("effect/Cause").YieldableError>;
|
|
76
|
+
export declare class RegistrationError extends RegistrationError_base {
|
|
77
|
+
}
|
|
78
|
+
declare const HostUnavailable_base: Schema.Class<HostUnavailable, Schema.Struct<{
|
|
79
|
+
readonly _tag: Schema.tag<"effect-actors/HostUnavailable">;
|
|
80
|
+
readonly retryable: Schema.Boolean;
|
|
81
|
+
}>, import("effect/Cause").YieldableError>;
|
|
82
|
+
export declare class HostUnavailable extends HostUnavailable_base {
|
|
83
|
+
}
|
|
84
|
+
export type ReadError = InvalidInput | StorageUnavailable | OwnershipLost | Overloaded | Unauthorized | UnsupportedVersion | MigrationRejected;
|
|
85
|
+
export type CommandError = ReadError | CommandConflict | RevisionConflict | OutcomeUnknown | MigrationRejected;
|
|
86
|
+
export type StoreError = InvalidInput | StorageUnavailable | OwnershipLost | UnsupportedVersion | MigrationRejected;
|
|
87
|
+
export declare const ReadErrorSchema: Schema.Union<readonly [typeof InvalidInput, typeof StorageUnavailable, typeof OwnershipLost, typeof Overloaded, typeof Unauthorized, typeof UnsupportedVersion, typeof MigrationRejected]>;
|
|
88
|
+
export declare const CommandErrorSchema: Schema.Union<readonly [Schema.Union<readonly [typeof InvalidInput, typeof StorageUnavailable, typeof OwnershipLost, typeof Overloaded, typeof Unauthorized, typeof UnsupportedVersion, typeof MigrationRejected]>, typeof CommandConflict, typeof RevisionConflict, typeof OutcomeUnknown, typeof MigrationRejected]>;
|
|
89
|
+
export declare const EventErrorSchema: Schema.Union<readonly [Schema.Union<readonly [typeof InvalidInput, typeof StorageUnavailable, typeof OwnershipLost, typeof Overloaded, typeof Unauthorized, typeof UnsupportedVersion, typeof MigrationRejected]>, typeof CursorExpired]>;
|
|
90
|
+
export declare const HostedErrorSchema: Schema.Union<readonly [Schema.Union<readonly [Schema.Union<readonly [typeof InvalidInput, typeof StorageUnavailable, typeof OwnershipLost, typeof Overloaded, typeof Unauthorized, typeof UnsupportedVersion, typeof MigrationRejected]>, typeof CommandConflict, typeof RevisionConflict, typeof OutcomeUnknown, typeof MigrationRejected]>, typeof HostUnavailable]>;
|
|
91
|
+
export declare const FrameworkErrorSchema: Schema.Union<readonly [Schema.Union<readonly [Schema.Union<readonly [Schema.Union<readonly [typeof InvalidInput, typeof StorageUnavailable, typeof OwnershipLost, typeof Overloaded, typeof Unauthorized, typeof UnsupportedVersion, typeof MigrationRejected]>, typeof CommandConflict, typeof RevisionConflict, typeof OutcomeUnknown, typeof MigrationRejected]>, typeof HostUnavailable]>, typeof CursorExpired, typeof RegistrationError]>;
|
|
92
|
+
export type FrameworkError = typeof FrameworkErrorSchema.Type;
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export class InvalidInput extends Schema.Error("effect-actors/InvalidInput")({
|
|
3
|
+
_tag: Schema.tag("effect-actors/InvalidInput"),
|
|
4
|
+
message: Schema.String,
|
|
5
|
+
}) {
|
|
6
|
+
}
|
|
7
|
+
export class CommandConflict extends Schema.Error("effect-actors/CommandConflict")({
|
|
8
|
+
_tag: Schema.tag("effect-actors/CommandConflict"),
|
|
9
|
+
commandId: Schema.String,
|
|
10
|
+
}) {
|
|
11
|
+
}
|
|
12
|
+
export class RevisionConflict extends Schema.Error("effect-actors/RevisionConflict")({
|
|
13
|
+
_tag: Schema.tag("effect-actors/RevisionConflict"),
|
|
14
|
+
expected: Schema.String,
|
|
15
|
+
actual: Schema.String,
|
|
16
|
+
}) {
|
|
17
|
+
}
|
|
18
|
+
export class CursorExpired extends Schema.Error("effect-actors/CursorExpired")({
|
|
19
|
+
_tag: Schema.tag("effect-actors/CursorExpired"),
|
|
20
|
+
message: Schema.String,
|
|
21
|
+
}) {
|
|
22
|
+
}
|
|
23
|
+
export class StorageUnavailable extends Schema.Error("effect-actors/StorageUnavailable")({
|
|
24
|
+
_tag: Schema.tag("effect-actors/StorageUnavailable"),
|
|
25
|
+
retryable: Schema.Boolean,
|
|
26
|
+
}) {
|
|
27
|
+
}
|
|
28
|
+
export class OwnershipLost extends Schema.Error("effect-actors/OwnershipLost")({
|
|
29
|
+
_tag: Schema.tag("effect-actors/OwnershipLost"),
|
|
30
|
+
actorType: Schema.String,
|
|
31
|
+
}) {
|
|
32
|
+
}
|
|
33
|
+
export class Overloaded extends Schema.Error("effect-actors/Overloaded")({
|
|
34
|
+
_tag: Schema.tag("effect-actors/Overloaded"),
|
|
35
|
+
retryAfterMillis: Schema.Number,
|
|
36
|
+
}) {
|
|
37
|
+
}
|
|
38
|
+
export class Unauthorized extends Schema.Error("effect-actors/Unauthorized")({
|
|
39
|
+
_tag: Schema.tag("effect-actors/Unauthorized"),
|
|
40
|
+
message: Schema.String,
|
|
41
|
+
}) {
|
|
42
|
+
}
|
|
43
|
+
export class OutcomeUnknown extends Schema.Error("effect-actors/OutcomeUnknown")({
|
|
44
|
+
_tag: Schema.tag("effect-actors/OutcomeUnknown"),
|
|
45
|
+
commandId: Schema.String,
|
|
46
|
+
}) {
|
|
47
|
+
}
|
|
48
|
+
export class UnsupportedVersion extends Schema.Error("effect-actors/UnsupportedVersion")({
|
|
49
|
+
_tag: Schema.tag("effect-actors/UnsupportedVersion"),
|
|
50
|
+
actorType: Schema.String,
|
|
51
|
+
version: Schema.Int,
|
|
52
|
+
}) {
|
|
53
|
+
}
|
|
54
|
+
export class MigrationRejected extends Schema.Error("effect-actors/MigrationRejected")({
|
|
55
|
+
_tag: Schema.tag("effect-actors/MigrationRejected"),
|
|
56
|
+
fromVersion: Schema.Int,
|
|
57
|
+
toVersion: Schema.Int,
|
|
58
|
+
message: Schema.String,
|
|
59
|
+
}) {
|
|
60
|
+
}
|
|
61
|
+
export class RegistrationError extends Schema.Error("effect-actors/RegistrationError")({
|
|
62
|
+
_tag: Schema.tag("effect-actors/RegistrationError"),
|
|
63
|
+
message: Schema.String,
|
|
64
|
+
}) {
|
|
65
|
+
}
|
|
66
|
+
export class HostUnavailable extends Schema.Error("effect-actors/HostUnavailable")({
|
|
67
|
+
_tag: Schema.tag("effect-actors/HostUnavailable"),
|
|
68
|
+
retryable: Schema.Boolean,
|
|
69
|
+
}) {
|
|
70
|
+
}
|
|
71
|
+
export const ReadErrorSchema = Schema.Union([
|
|
72
|
+
InvalidInput,
|
|
73
|
+
StorageUnavailable,
|
|
74
|
+
OwnershipLost,
|
|
75
|
+
Overloaded,
|
|
76
|
+
Unauthorized,
|
|
77
|
+
UnsupportedVersion,
|
|
78
|
+
MigrationRejected,
|
|
79
|
+
]);
|
|
80
|
+
export const CommandErrorSchema = Schema.Union([
|
|
81
|
+
ReadErrorSchema,
|
|
82
|
+
CommandConflict,
|
|
83
|
+
RevisionConflict,
|
|
84
|
+
OutcomeUnknown,
|
|
85
|
+
MigrationRejected,
|
|
86
|
+
]);
|
|
87
|
+
export const EventErrorSchema = Schema.Union([ReadErrorSchema, CursorExpired]);
|
|
88
|
+
export const HostedErrorSchema = Schema.Union([CommandErrorSchema, HostUnavailable]);
|
|
89
|
+
export const FrameworkErrorSchema = Schema.Union([
|
|
90
|
+
HostedErrorSchema,
|
|
91
|
+
CursorExpired,
|
|
92
|
+
RegistrationError,
|
|
93
|
+
]);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Effect, Ref } from "effect";
|
|
2
|
+
import type { Scope } from "effect";
|
|
3
|
+
import type { Address } from "../protocol/index.js";
|
|
4
|
+
import type { GatewayFault } from "./fault.js";
|
|
5
|
+
import type { Access, Principal, SecurityService } from "./security.js";
|
|
6
|
+
export declare const authenticate: (security: SecurityService, request: Parameters<SecurityService["authenticate"]>[0]) => Effect.Effect<Principal, GatewayFault>;
|
|
7
|
+
export declare const authorize: (security: SecurityService, principal: Principal, access: Access) => Effect.Effect<void, GatewayFault>;
|
|
8
|
+
export declare const admit: (security: SecurityService, principal: Principal, access: Access) => Effect.Effect<void, GatewayFault>;
|
|
9
|
+
export declare const assertTenant: (principal: Principal, address: Address) => Effect.Effect<void, GatewayFault>;
|
|
10
|
+
export declare const leaseConnection: (connections: Ref.Ref<ReadonlyMap<string, number>>, principal: Principal, maximum: number) => Effect.Effect<void, GatewayFault, Scope.Scope>;
|
|
11
|
+
export declare const recheck: (security: SecurityService, request: Parameters<SecurityService["authenticate"]>[0], initial: Principal, access?: Access) => Effect.Effect<void, GatewayFault>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Effect, Ref } from "effect";
|
|
2
|
+
import { validateRequestId } from "./http/validation.js";
|
|
3
|
+
import { overloaded, unauthenticated, unauthorized } from "./fault.js";
|
|
4
|
+
import { connectionKey } from "./security.js";
|
|
5
|
+
export const authenticate = (security, request) => security.authenticate(request).pipe(Effect.mapError(() => unauthenticated()), Effect.flatMap((principal) => Effect.all([
|
|
6
|
+
validateRequestId(principal.subject).pipe(Effect.mapError(() => unauthenticated())),
|
|
7
|
+
validateRequestId(principal.tenant).pipe(Effect.mapError(() => unauthenticated())),
|
|
8
|
+
]).pipe(Effect.as(principal))));
|
|
9
|
+
export const authorize = (security, principal, access) => security.authorize(principal, access).pipe(Effect.mapError(() => unauthorized()));
|
|
10
|
+
export const admit = (security, principal, access) => security
|
|
11
|
+
.admit(principal, access)
|
|
12
|
+
.pipe(Effect.mapError((error) => overloaded(error.retryAfterMillis)));
|
|
13
|
+
export const assertTenant = (principal, address) => principal.tenant === address.tenant ? Effect.void : Effect.fail(unauthorized());
|
|
14
|
+
export const leaseConnection = (connections, principal, maximum) => Effect.acquireRelease(Ref.modify(connections, (current) => {
|
|
15
|
+
const key = connectionKey(principal);
|
|
16
|
+
const used = current.get(key) ?? 0;
|
|
17
|
+
if (used >= maximum)
|
|
18
|
+
return [false, current];
|
|
19
|
+
const next = new Map(current);
|
|
20
|
+
next.set(key, used + 1);
|
|
21
|
+
return [true, next];
|
|
22
|
+
}), (leased) => leased
|
|
23
|
+
? Ref.update(connections, (current) => {
|
|
24
|
+
const next = new Map(current);
|
|
25
|
+
const key = connectionKey(principal);
|
|
26
|
+
const used = next.get(key) ?? 0;
|
|
27
|
+
if (used <= 1)
|
|
28
|
+
next.delete(key);
|
|
29
|
+
else
|
|
30
|
+
next.set(key, used - 1);
|
|
31
|
+
return next;
|
|
32
|
+
})
|
|
33
|
+
: Effect.void).pipe(Effect.flatMap((leased) => (leased ? Effect.void : Effect.fail(overloaded(1_000)))));
|
|
34
|
+
export const recheck = (security, request, initial, access) => authenticate(security, request).pipe(Effect.flatMap((current) => {
|
|
35
|
+
if (current.subject !== initial.subject || current.tenant !== initial.tenant)
|
|
36
|
+
return Effect.fail(unauthorized());
|
|
37
|
+
return access === undefined ? Effect.void : authorize(security, current, access);
|
|
38
|
+
}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { Address, Json, Procedure, QueryResult as ActorQueryResult, WireReceipt as ActorWireReceipt } from "../protocol/index.js";
|
|
3
|
+
import { QueryResult } from "../protocol/schemas.js";
|
|
4
|
+
import type { GatewayFault } from "./fault.js";
|
|
5
|
+
import type { Registry } from "./options.js";
|
|
6
|
+
export declare const mapInputError: (error: unknown) => GatewayFault;
|
|
7
|
+
export declare const validateAddress: (registry: Registry, address: Address) => Effect.Effect<Address, GatewayFault>;
|
|
8
|
+
export declare const stateProcedure: (registry: Registry, address: Address, version: number, name: string, kind: "command" | "query") => Effect.Effect<Procedure, GatewayFault>;
|
|
9
|
+
export declare const hostedProcedure: (registry: Registry, address: Address, version: number, name: string) => Effect.Effect<Procedure, GatewayFault>;
|
|
10
|
+
export declare const validatePayload: (procedure: Procedure, payload: Json) => Effect.Effect<Json, GatewayFault>;
|
|
11
|
+
export declare const validateReceipt: (procedure: Procedure, address: Address, receipt: ActorWireReceipt) => Effect.Effect<ActorWireReceipt, GatewayFault>;
|
|
12
|
+
export declare const validateQueryResult: (procedure: Procedure, address: Address, result: ActorQueryResult<Json>) => Effect.Effect<(typeof QueryResult)["Type"], GatewayFault>;
|
|
13
|
+
export declare const validateOutcome: (procedure: Procedure, value: Json) => Effect.Effect<Json, GatewayFault>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { CommandErrorSchema } from "../errors/index.js";
|
|
3
|
+
import { addressId, boundary, decode, readCursor } from "../protocol/index.js";
|
|
4
|
+
import { Outcome, QueryResult, Receipt as WireReceipt } from "../protocol/schemas.js";
|
|
5
|
+
import { addressFrom, validatePublicName } from "./http/validation.js";
|
|
6
|
+
import { invalidInput, isGatewayFault, notFound, unavailable, unsupportedVersion } from "./fault.js";
|
|
7
|
+
export const mapInputError = (error) => isGatewayFault(error) ? error : invalidInput();
|
|
8
|
+
export const validateAddress = (registry, address) => address.namespace === registry.options.namespace
|
|
9
|
+
? boundary(() => addressId(address)).pipe(Effect.mapError(mapInputError), Effect.andThen(() => validatePublicName(address.actorType, registry.isPublicOperation).pipe(Effect.andThen(() => addressFrom(registry.options.namespace, address.tenant, address.actorType, address.key)))))
|
|
10
|
+
: Effect.fail(invalidInput());
|
|
11
|
+
export const stateProcedure = (registry, address, version, name, kind) => {
|
|
12
|
+
const protocol = registry.get(address.actorType);
|
|
13
|
+
if (!protocol || protocol.kind !== "state")
|
|
14
|
+
return Effect.fail(notFound());
|
|
15
|
+
if (protocol.version !== version)
|
|
16
|
+
return Effect.fail(unsupportedVersion());
|
|
17
|
+
const procedure = registry.stateProcedure(address.actorType, name, kind);
|
|
18
|
+
return procedure === undefined ? Effect.fail(notFound()) : Effect.succeed(procedure);
|
|
19
|
+
};
|
|
20
|
+
export const hostedProcedure = (registry, address, version, name) => {
|
|
21
|
+
const protocol = registry.get(address.actorType);
|
|
22
|
+
if (!protocol || protocol.kind !== "hosted")
|
|
23
|
+
return Effect.fail(notFound());
|
|
24
|
+
if (protocol.version !== version)
|
|
25
|
+
return Effect.fail(unsupportedVersion());
|
|
26
|
+
const procedure = registry.hostedProcedure(address.actorType, name);
|
|
27
|
+
return procedure === undefined ? Effect.fail(notFound()) : Effect.succeed(procedure);
|
|
28
|
+
};
|
|
29
|
+
export const validatePayload = (procedure, payload) => decode(procedure.payload, payload).pipe(Effect.as(payload), Effect.mapError(mapInputError));
|
|
30
|
+
export const validateReceipt = (procedure, address, receipt) => decode(WireReceipt, receipt).pipe(Effect.flatMap((checked) => boundary(() => readCursor(address, checked.cursor)).pipe(Effect.mapError(() => unavailable()), Effect.flatMap(() => {
|
|
31
|
+
if (checked.value._tag === "Success") {
|
|
32
|
+
return decode(procedure.success, checked.value.value).pipe(Effect.as(checked));
|
|
33
|
+
}
|
|
34
|
+
const failure = checked.value.error;
|
|
35
|
+
return decode(CommandErrorSchema, failure).pipe(Effect.as(checked), Effect.matchEffect({
|
|
36
|
+
onFailure: () => decode(procedure.error, failure).pipe(Effect.as(checked)),
|
|
37
|
+
onSuccess: Effect.succeed,
|
|
38
|
+
}));
|
|
39
|
+
}))), Effect.mapError(() => unavailable()));
|
|
40
|
+
export const validateQueryResult = (procedure, address, result) => decode(QueryResult, result).pipe(Effect.flatMap((checked) => boundary(() => readCursor(address, checked.cursor)).pipe(Effect.mapError(() => unavailable()), Effect.flatMap(() => checked.value._tag === "Success"
|
|
41
|
+
? decode(procedure.success, checked.value.value).pipe(Effect.as(checked))
|
|
42
|
+
: decode(procedure.error, checked.value.error).pipe(Effect.as(checked))))), Effect.mapError(() => unavailable()));
|
|
43
|
+
export const validateOutcome = (procedure, value) => decode(Outcome, value).pipe(Effect.flatMap((checked) => checked._tag === "Success"
|
|
44
|
+
? decode(procedure.success, checked.value).pipe(Effect.as(value))
|
|
45
|
+
: decode(procedure.error, checked.error).pipe(Effect.as(value))), Effect.mapError(() => unavailable()));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type FaultCode = "InvalidInput" | "Unauthenticated" | "Unauthorized" | "NotFound" | "UnsupportedVersion" | "CommandConflict" | "RevisionConflict" | "CursorExpired" | "Overloaded" | "OutcomeUnknown" | "HostUnavailable" | "Unavailable" | "SlowConsumer";
|
|
2
|
+
export interface GatewayFault {
|
|
3
|
+
readonly _tag: "GatewayFault";
|
|
4
|
+
readonly code: FaultCode;
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
readonly commandId?: string;
|
|
8
|
+
readonly expected?: string;
|
|
9
|
+
readonly actual?: string;
|
|
10
|
+
readonly retryAfterMillis?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const invalidInput: () => GatewayFault;
|
|
13
|
+
export declare const unauthenticated: () => GatewayFault;
|
|
14
|
+
export declare const unauthorized: () => GatewayFault;
|
|
15
|
+
export declare const notFound: () => GatewayFault;
|
|
16
|
+
export declare const unsupportedVersion: () => GatewayFault;
|
|
17
|
+
export declare const cursorExpired: () => GatewayFault;
|
|
18
|
+
export declare const unavailable: () => GatewayFault;
|
|
19
|
+
export declare const slowConsumer: () => GatewayFault;
|
|
20
|
+
export declare const overloaded: (retryAfterMillis: number) => GatewayFault;
|
|
21
|
+
export declare const outcomeUnknown: (commandId: string) => GatewayFault;
|
|
22
|
+
export declare const isGatewayFault: (value: unknown) => value is GatewayFault;
|
|
23
|
+
export declare const fromServiceError: (error: unknown, commandId?: string) => GatewayFault;
|
|
24
|
+
export interface SafeGatewayError {
|
|
25
|
+
readonly code: FaultCode;
|
|
26
|
+
readonly message: string;
|
|
27
|
+
readonly commandId?: string;
|
|
28
|
+
readonly expected?: string;
|
|
29
|
+
readonly actual?: string;
|
|
30
|
+
readonly retryAfterMillis?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare const safeError: (fault: GatewayFault) => SafeGatewayError;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const make = (code, status, message, fields = {}) => ({ _tag: "GatewayFault", code, status, message, ...fields });
|
|
2
|
+
export const invalidInput = () => make("InvalidInput", 400, "Invalid request");
|
|
3
|
+
export const unauthenticated = () => make("Unauthenticated", 401, "Authentication required");
|
|
4
|
+
export const unauthorized = () => make("Unauthorized", 403, "Access denied");
|
|
5
|
+
export const notFound = () => make("NotFound", 404, "Actor or operation was not found");
|
|
6
|
+
export const unsupportedVersion = () => make("UnsupportedVersion", 400, "Unsupported protocol version");
|
|
7
|
+
export const cursorExpired = () => make("CursorExpired", 410, "Event cursor has expired");
|
|
8
|
+
export const unavailable = () => make("Unavailable", 503, "Service temporarily unavailable");
|
|
9
|
+
export const slowConsumer = () => make("SlowConsumer", 503, "Event consumer is too slow");
|
|
10
|
+
export const overloaded = (retryAfterMillis) => make("Overloaded", 429, "Request overloaded", { retryAfterMillis });
|
|
11
|
+
export const outcomeUnknown = (commandId) => make("OutcomeUnknown", 503, "Command outcome is unknown", { commandId });
|
|
12
|
+
export const isGatewayFault = (value) => typeof value === "object" &&
|
|
13
|
+
value !== null &&
|
|
14
|
+
value._tag === "GatewayFault";
|
|
15
|
+
const errorTag = (error) => typeof error === "object" &&
|
|
16
|
+
error !== null &&
|
|
17
|
+
typeof error._tag === "string"
|
|
18
|
+
? error._tag
|
|
19
|
+
: undefined;
|
|
20
|
+
const stringField = (error, key) => {
|
|
21
|
+
if (typeof error !== "object" || error === null)
|
|
22
|
+
return undefined;
|
|
23
|
+
const value = error[key];
|
|
24
|
+
return typeof value === "string" ? value : undefined;
|
|
25
|
+
};
|
|
26
|
+
const numberField = (error, key) => {
|
|
27
|
+
if (typeof error !== "object" || error === null)
|
|
28
|
+
return undefined;
|
|
29
|
+
const value = error[key];
|
|
30
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
31
|
+
};
|
|
32
|
+
export const fromServiceError = (error, commandId) => {
|
|
33
|
+
if (isGatewayFault(error))
|
|
34
|
+
return error;
|
|
35
|
+
switch (errorTag(error)) {
|
|
36
|
+
case "effect-actors/InvalidInput":
|
|
37
|
+
return invalidInput();
|
|
38
|
+
case "effect-actors/Unauthorized":
|
|
39
|
+
return unauthorized();
|
|
40
|
+
case "effect-actors/Overloaded":
|
|
41
|
+
return overloaded(numberField(error, "retryAfterMillis") ?? 1_000);
|
|
42
|
+
case "effect-actors/UnsupportedVersion":
|
|
43
|
+
return unsupportedVersion();
|
|
44
|
+
case "effect-actors/CommandConflict": {
|
|
45
|
+
const conflictedCommandId = stringField(error, "commandId") ?? commandId;
|
|
46
|
+
return conflictedCommandId === undefined
|
|
47
|
+
? make("CommandConflict", 409, "Command identity conflicts with existing input")
|
|
48
|
+
: make("CommandConflict", 409, "Command identity conflicts with existing input", {
|
|
49
|
+
commandId: conflictedCommandId,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
case "effect-actors/RevisionConflict": {
|
|
53
|
+
const expected = stringField(error, "expected");
|
|
54
|
+
const actual = stringField(error, "actual");
|
|
55
|
+
if (expected !== undefined && actual !== undefined) {
|
|
56
|
+
return make("RevisionConflict", 409, "Revision conflict", { expected, actual });
|
|
57
|
+
}
|
|
58
|
+
if (expected !== undefined)
|
|
59
|
+
return make("RevisionConflict", 409, "Revision conflict", { expected });
|
|
60
|
+
return actual === undefined
|
|
61
|
+
? make("RevisionConflict", 409, "Revision conflict")
|
|
62
|
+
: make("RevisionConflict", 409, "Revision conflict", { actual });
|
|
63
|
+
}
|
|
64
|
+
case "effect-actors/CursorExpired":
|
|
65
|
+
return cursorExpired();
|
|
66
|
+
case "effect-actors/OutcomeUnknown":
|
|
67
|
+
return outcomeUnknown(stringField(error, "commandId") ?? commandId ?? "");
|
|
68
|
+
case "effect-actors/HostUnavailable":
|
|
69
|
+
return make("HostUnavailable", 503, "Hosted actor is unavailable");
|
|
70
|
+
case "effect-actors/StorageUnavailable":
|
|
71
|
+
case "effect-actors/OwnershipLost":
|
|
72
|
+
case "effect-actors/MigrationRejected":
|
|
73
|
+
return unavailable();
|
|
74
|
+
default:
|
|
75
|
+
return unavailable();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export const safeError = (fault) => ({
|
|
79
|
+
code: fault.code,
|
|
80
|
+
message: fault.message,
|
|
81
|
+
...(fault.commandId === undefined ? {} : { commandId: fault.commandId }),
|
|
82
|
+
...(fault.expected === undefined ? {} : { expected: fault.expected }),
|
|
83
|
+
...(fault.actual === undefined ? {} : { actual: fault.actual }),
|
|
84
|
+
...(fault.retryAfterMillis === undefined ? {} : { retryAfterMillis: fault.retryAfterMillis }),
|
|
85
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
|
+
import type { Scope } from "effect";
|
|
3
|
+
import type { Service as ClientService } from "../../client/service.js";
|
|
4
|
+
import type { Event as ActorEvent, Json, JsonSchema } from "../../protocol/index.js";
|
|
5
|
+
import type { GatewayFault } from "../fault.js";
|
|
6
|
+
import type { Options } from "../options.js";
|
|
7
|
+
export declare const boundedEvents: (client: ClientService, address: Parameters<ClientService["events"]>[0], after: string, eventSchema: JsonSchema, options: Options, recheck: Effect.Effect<void, GatewayFault>) => Effect.Effect<Stream.Stream<ActorEvent<Json>, GatewayFault>, never, Scope.Scope>;
|
|
8
|
+
export declare const sseBody: (events: Stream.Stream<ActorEvent<Json>, GatewayFault>) => Stream.Stream<Uint8Array>;
|