effect 4.0.2 → 4.0.3
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/dist/Context.js +3 -3
- package/dist/Context.js.map +1 -1
- package/dist/Crypto.d.ts +33 -5
- package/dist/Crypto.d.ts.map +1 -1
- package/dist/Crypto.js +8 -2
- package/dist/Crypto.js.map +1 -1
- package/dist/DateTime.d.ts +9 -6
- package/dist/DateTime.d.ts.map +1 -1
- package/dist/DateTime.js +9 -6
- package/dist/DateTime.js.map +1 -1
- package/dist/Duration.d.ts +6 -6
- package/dist/Duration.d.ts.map +1 -1
- package/dist/Duration.js +27 -8
- package/dist/Duration.js.map +1 -1
- package/dist/FileSystem.d.ts +44 -2
- package/dist/FileSystem.d.ts.map +1 -1
- package/dist/FileSystem.js.map +1 -1
- package/dist/Schema.d.ts +473 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +355 -4
- package/dist/Schema.js.map +1 -1
- package/dist/Stdio.d.ts +7 -1
- package/dist/Stdio.d.ts.map +1 -1
- package/dist/Stdio.js +1 -0
- package/dist/Stdio.js.map +1 -1
- package/dist/Stream.d.ts +11 -11
- package/dist/Stream.js +5 -5
- package/dist/ai/McpServer.d.ts +6 -0
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +65 -23
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +5 -0
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +1 -0
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- package/dist/cli/CliOutput.d.ts +27 -0
- package/dist/cli/CliOutput.d.ts.map +1 -1
- package/dist/cli/CliOutput.js +33 -10
- package/dist/cli/CliOutput.js.map +1 -1
- package/dist/cli/Command.d.ts.map +1 -1
- package/dist/cli/Command.js +6 -3
- package/dist/cli/Command.js.map +1 -1
- package/dist/cli/Completions.d.ts +3 -3
- package/dist/cli/Completions.d.ts.map +1 -1
- package/dist/cli/Completions.js +8 -5
- package/dist/cli/Completions.js.map +1 -1
- package/dist/cli/GlobalFlag.d.ts +5 -4
- package/dist/cli/GlobalFlag.d.ts.map +1 -1
- package/dist/cli/GlobalFlag.js +3 -3
- package/dist/cli/GlobalFlag.js.map +1 -1
- package/dist/cli/Prompt.d.ts +17 -1
- package/dist/cli/Prompt.d.ts.map +1 -1
- package/dist/cli/Prompt.js +119 -114
- package/dist/cli/Prompt.js.map +1 -1
- package/dist/cli/internal/ansi.d.ts +0 -1
- package/dist/cli/internal/ansi.d.ts.map +1 -1
- package/dist/cli/internal/ansi.js +20 -3
- package/dist/cli/internal/ansi.js.map +1 -1
- package/dist/cli/internal/completions/powershell.d.ts +2 -0
- package/dist/cli/internal/completions/powershell.d.ts.map +1 -0
- package/dist/cli/internal/completions/powershell.js +273 -0
- package/dist/cli/internal/completions/powershell.js.map +1 -0
- package/dist/cli/internal/completions/zsh.js +3 -2
- package/dist/cli/internal/completions/zsh.js.map +1 -1
- package/dist/cli/internal/wizard.d.ts +3 -3
- package/dist/cli/internal/wizard.d.ts.map +1 -1
- package/dist/cli/internal/wizard.js +21 -18
- package/dist/cli/internal/wizard.js.map +1 -1
- package/dist/cluster/ClusterSchema.d.ts +28 -0
- package/dist/cluster/ClusterSchema.d.ts.map +1 -1
- package/dist/cluster/ClusterSchema.js +30 -0
- package/dist/cluster/ClusterSchema.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +10 -18
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/MessageStorage.d.ts +18 -3
- package/dist/cluster/MessageStorage.d.ts.map +1 -1
- package/dist/cluster/MessageStorage.js +31 -11
- package/dist/cluster/MessageStorage.js.map +1 -1
- package/dist/cluster/Runners.d.ts.map +1 -1
- package/dist/cluster/Runners.js +8 -2
- package/dist/cluster/Runners.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +181 -62
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts +59 -19
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +84 -55
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts +68 -19
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +191 -113
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +104 -37
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/http/FindMyWay/internal/router.js +3 -6
- package/dist/http/FindMyWay/internal/router.js.map +1 -1
- package/dist/http/HttpClient.d.ts +6 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +39 -2
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +4 -2
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/internal/effect.js +12 -7
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/net/AddressResolver.d.ts +145 -0
- package/dist/net/AddressResolver.d.ts.map +1 -0
- package/dist/net/AddressResolver.js +114 -0
- package/dist/net/AddressResolver.js.map +1 -0
- package/dist/net/Dns.d.ts +596 -0
- package/dist/net/Dns.d.ts.map +1 -0
- package/dist/net/Dns.js +549 -0
- package/dist/net/Dns.js.map +1 -0
- package/dist/net/Host.d.ts +320 -0
- package/dist/net/Host.d.ts.map +1 -0
- package/dist/net/Host.js +342 -0
- package/dist/net/Host.js.map +1 -0
- package/dist/net/IpInterface.d.ts +76 -3
- package/dist/net/IpInterface.d.ts.map +1 -1
- package/dist/net/IpInterface.js +53 -4
- package/dist/net/IpInterface.js.map +1 -1
- package/dist/net/IpNetwork.d.ts +82 -8
- package/dist/net/IpNetwork.d.ts.map +1 -1
- package/dist/net/IpNetwork.js +60 -4
- package/dist/net/IpNetwork.js.map +1 -1
- package/dist/net/NetAddress.d.ts +388 -35
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +350 -34
- package/dist/net/NetAddress.js.map +1 -1
- package/dist/net/index.d.ts +15 -0
- package/dist/net/index.d.ts.map +1 -1
- package/dist/net/index.js +15 -0
- package/dist/net/index.js.map +1 -1
- package/dist/observability/OtlpMetrics.d.ts.map +1 -1
- package/dist/observability/OtlpMetrics.js +15 -21
- package/dist/observability/OtlpMetrics.js.map +1 -1
- package/dist/observability/PrometheusMetrics.js +1 -0
- package/dist/observability/PrometheusMetrics.js.map +1 -1
- package/dist/persistence/PersistedQueue.d.ts +32 -3
- package/dist/persistence/PersistedQueue.d.ts.map +1 -1
- package/dist/persistence/PersistedQueue.js +75 -16
- package/dist/persistence/PersistedQueue.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts +22 -6
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +12 -5
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts +6 -0
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +42 -20
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/socket/DatagramSocket.d.ts +456 -0
- package/dist/socket/DatagramSocket.d.ts.map +1 -0
- package/dist/socket/DatagramSocket.js +535 -0
- package/dist/socket/DatagramSocket.js.map +1 -0
- package/dist/socket/Socket.d.ts +14 -1
- package/dist/socket/Socket.d.ts.map +1 -1
- package/dist/socket/Socket.js +9 -1
- package/dist/socket/Socket.js.map +1 -1
- package/dist/socket/index.d.ts +5 -0
- package/dist/socket/index.d.ts.map +1 -1
- package/dist/socket/index.js +5 -0
- package/dist/socket/index.js.map +1 -1
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +7 -2
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/SqlClient.d.ts +7 -1
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +31 -6
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/testing/TestCrypto.d.ts +73 -0
- package/dist/testing/TestCrypto.d.ts.map +1 -0
- package/dist/testing/TestCrypto.js +87 -0
- package/dist/testing/TestCrypto.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts +54 -0
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +78 -49
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +2 -1
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +1 -1
- package/src/Context.ts +3 -3
- package/src/Crypto.ts +43 -9
- package/src/DateTime.ts +9 -6
- package/src/Duration.ts +36 -16
- package/src/FileSystem.ts +48 -2
- package/src/Schema.ts +669 -5
- package/src/Stdio.ts +9 -2
- package/src/Stream.ts +11 -11
- package/src/ai/McpServer.ts +63 -23
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +5 -0
- package/src/ai/internal/mcpProtocol.ts +3 -0
- package/src/ai/internal/mcpRuntime.ts +2 -0
- package/src/cli/CliOutput.ts +33 -16
- package/src/cli/Command.ts +4 -3
- package/src/cli/Completions.ts +9 -6
- package/src/cli/GlobalFlag.ts +19 -11
- package/src/cli/Prompt.ts +159 -149
- package/src/cli/internal/ansi.ts +26 -3
- package/src/cli/internal/completions/powershell.ts +322 -0
- package/src/cli/internal/completions/zsh.ts +4 -2
- package/src/cli/internal/wizard.ts +34 -26
- package/src/cluster/ClusterSchema.ts +32 -0
- package/src/cluster/ClusterWorkflowEngine.ts +13 -24
- package/src/cluster/MessageStorage.ts +45 -14
- package/src/cluster/Runners.ts +8 -2
- package/src/cluster/Sharding.ts +207 -49
- package/src/cluster/SqlMessageStorage.ts +118 -84
- package/src/cluster/SqlRunnerStorage.ts +224 -128
- package/src/cluster/internal/entityManager.ts +151 -55
- package/src/http/FindMyWay/internal/router.ts +4 -7
- package/src/http/HttpClient.ts +56 -2
- package/src/http/HttpTraceContext.ts +8 -2
- package/src/internal/effect.ts +15 -13
- package/src/internal/version.ts +1 -1
- package/src/net/AddressResolver.ts +222 -0
- package/src/net/Dns.ts +1035 -0
- package/src/net/Host.ts +472 -0
- package/src/net/IpInterface.ts +117 -7
- package/src/net/IpNetwork.ts +116 -17
- package/src/net/NetAddress.ts +611 -76
- package/src/net/index.ts +18 -0
- package/src/observability/OtlpMetrics.ts +12 -23
- package/src/observability/PrometheusMetrics.ts +1 -0
- package/src/persistence/PersistedQueue.ts +146 -53
- package/src/rpc/RpcClient.ts +41 -19
- package/src/rpc/RpcServer.ts +60 -25
- package/src/socket/DatagramSocket.ts +919 -0
- package/src/socket/Socket.ts +22 -2
- package/src/socket/index.ts +6 -0
- package/src/sql/Migrator.ts +21 -5
- package/src/sql/SqlClient.ts +39 -15
- package/src/testing/TestCrypto.ts +94 -0
- package/src/testing/index.ts +6 -0
- package/src/workflow/DurableDeferred.ts +123 -48
- package/src/workflow/WorkflowEngine.ts +2 -1
package/src/socket/Socket.ts
CHANGED
|
@@ -799,13 +799,18 @@ export interface WebSocketLike {
|
|
|
799
799
|
}
|
|
800
800
|
|
|
801
801
|
/**
|
|
802
|
-
*
|
|
802
|
+
* Client options for Node and Bun WebSocket implementations, allowing
|
|
803
|
+
* handshake headers and subprotocols to be supplied together.
|
|
803
804
|
*
|
|
804
805
|
* @stability unstable
|
|
805
806
|
* @category models
|
|
806
807
|
* @since 4.0.0
|
|
807
808
|
*/
|
|
808
809
|
export interface WebSocketClientOptions {
|
|
810
|
+
/**
|
|
811
|
+
* Subprotocols to offer in the opening handshake.
|
|
812
|
+
*/
|
|
813
|
+
readonly protocols?: string | Array<string> | undefined
|
|
809
814
|
/**
|
|
810
815
|
* Headers to include in the opening handshake.
|
|
811
816
|
*
|
|
@@ -857,6 +862,7 @@ export class WebSocketConstructor extends Context.Service<
|
|
|
857
862
|
|
|
858
863
|
/**
|
|
859
864
|
* Layer that provides `WebSocketConstructor` using `globalThis.WebSocket`.
|
|
865
|
+
* Rejects client options objects, including those with handshake headers.
|
|
860
866
|
*
|
|
861
867
|
* @stability unstable
|
|
862
868
|
* @category layers
|
|
@@ -874,6 +880,8 @@ export const layerWebSocketConstructorGlobal: Layer.Layer<WebSocketConstructor>
|
|
|
874
880
|
/**
|
|
875
881
|
* Creates a `Socket` backed by a `WebSocketConstructor`, dialing the
|
|
876
882
|
* WebSocket for each reader acquisition.
|
|
883
|
+
* Optional handshake headers require a Node or Bun constructor; without
|
|
884
|
+
* headers, protocols are passed directly for browser compatibility.
|
|
877
885
|
*
|
|
878
886
|
* @stability unstable
|
|
879
887
|
* @category constructors
|
|
@@ -882,13 +890,21 @@ export const layerWebSocketConstructorGlobal: Layer.Layer<WebSocketConstructor>
|
|
|
882
890
|
export const makeWebSocket = (url: string | Effect.Effect<string>, options?: {
|
|
883
891
|
readonly openTimeout?: Duration.Input | undefined
|
|
884
892
|
readonly protocols?: string | Array<string> | undefined
|
|
893
|
+
readonly headers?: Readonly<Record<string, string>> | undefined
|
|
885
894
|
readonly highWaterMark?: number | undefined
|
|
886
895
|
}): Effect.Effect<Socket, never, WebSocketConstructor> =>
|
|
887
896
|
WebSocketConstructor.use((makeWs) =>
|
|
888
897
|
fromWebSocket(
|
|
889
898
|
Effect.acquireRelease(
|
|
890
899
|
(typeof url === "string" ? Effect.succeed(url) : url).pipe(
|
|
891
|
-
Effect.map((url) =>
|
|
900
|
+
Effect.map((url) =>
|
|
901
|
+
makeWs(
|
|
902
|
+
url,
|
|
903
|
+
options?.headers === undefined
|
|
904
|
+
? options?.protocols
|
|
905
|
+
: { protocols: options.protocols, headers: options.headers }
|
|
906
|
+
)
|
|
907
|
+
)
|
|
892
908
|
),
|
|
893
909
|
(ws) => Effect.sync(() => ws.close(1000))
|
|
894
910
|
),
|
|
@@ -1165,6 +1181,7 @@ const defaultHighWaterMark = 64 * 1024
|
|
|
1165
1181
|
/**
|
|
1166
1182
|
* Creates a binary `Channel` backed by a WebSocket URL, requiring a
|
|
1167
1183
|
* `WebSocketConstructor` service.
|
|
1184
|
+
* Supports subprotocols and, with a Node or Bun constructor, handshake headers.
|
|
1168
1185
|
*
|
|
1169
1186
|
* @stability unstable
|
|
1170
1187
|
* @category constructors
|
|
@@ -1175,6 +1192,7 @@ export const makeWebSocketChannel = <IE = never>(
|
|
|
1175
1192
|
options?: {
|
|
1176
1193
|
readonly openTimeout?: Duration.Input | undefined
|
|
1177
1194
|
readonly protocols?: string | Array<string> | undefined
|
|
1195
|
+
readonly headers?: Readonly<Record<string, string>> | undefined
|
|
1178
1196
|
readonly highWaterMark?: number | undefined
|
|
1179
1197
|
}
|
|
1180
1198
|
): Channel.Channel<
|
|
@@ -1193,6 +1211,7 @@ export const makeWebSocketChannel = <IE = never>(
|
|
|
1193
1211
|
/**
|
|
1194
1212
|
* Layer that provides a `Socket` service backed by a WebSocket URL or URL
|
|
1195
1213
|
* effect.
|
|
1214
|
+
* Supports subprotocols and, with a Node or Bun constructor, handshake headers.
|
|
1196
1215
|
*
|
|
1197
1216
|
* @stability unstable
|
|
1198
1217
|
* @category layers
|
|
@@ -1203,6 +1222,7 @@ export const layerWebSocket: (
|
|
|
1203
1222
|
options?: {
|
|
1204
1223
|
readonly openTimeout?: Duration.Input | undefined
|
|
1205
1224
|
readonly protocols?: string | Array<string> | undefined
|
|
1225
|
+
readonly headers?: Readonly<Record<string, string>> | undefined
|
|
1206
1226
|
readonly highWaterMark?: number | undefined
|
|
1207
1227
|
} | undefined
|
|
1208
1228
|
) => Layer.Layer<Socket, never, WebSocketConstructor> = flow(makeWebSocket, Layer.effect(Socket))
|
package/src/socket/index.ts
CHANGED
package/src/sql/Migrator.ts
CHANGED
|
@@ -140,14 +140,21 @@ export const make = <RD = never>({
|
|
|
140
140
|
PRIMARY KEY (migration_id)
|
|
141
141
|
)`,
|
|
142
142
|
pg: () =>
|
|
143
|
-
Effect.
|
|
144
|
-
sql
|
|
145
|
-
() =>
|
|
146
|
-
sql`CREATE TABLE ${sql(table)} (
|
|
143
|
+
Effect.flatMap(
|
|
144
|
+
migrationsTableExists(sql, table),
|
|
145
|
+
(exists) =>
|
|
146
|
+
exists ? Effect.void : Effect.asVoid(sql`CREATE TABLE ${sql(table)} (
|
|
147
147
|
migration_id integer primary key,
|
|
148
148
|
created_at timestamp with time zone not null default now(),
|
|
149
149
|
name text not null
|
|
150
|
-
)`
|
|
150
|
+
)`).pipe(
|
|
151
|
+
Effect.catch((error) =>
|
|
152
|
+
Effect.flatMap(
|
|
153
|
+
migrationsTableExists(sql, table),
|
|
154
|
+
(exists) => exists ? Effect.void : Effect.fail(error)
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
)
|
|
151
158
|
),
|
|
152
159
|
orElse: () =>
|
|
153
160
|
sql`CREATE TABLE IF NOT EXISTS ${sql(table)} (
|
|
@@ -328,6 +335,15 @@ export const make = <RD = never>({
|
|
|
328
335
|
return completed
|
|
329
336
|
})
|
|
330
337
|
|
|
338
|
+
const migrationsTableExists = (sql: Client.SqlClient, table: string): Effect.Effect<boolean, SqlError> => {
|
|
339
|
+
// Match query quoting: to_regclass folds unquoted names to lowercase.
|
|
340
|
+
const quotedTable = sql`${sql(table)}`.compile(true)[0]
|
|
341
|
+
return Effect.map(
|
|
342
|
+
sql`SELECT 1 AS found WHERE to_regclass(${quotedTable}) IS NOT NULL`.withoutTransform,
|
|
343
|
+
(rows) => rows.length > 0
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
331
347
|
const migrationOrder = Order.make<ResolvedMigration>(([a], [b]) => Order.Number(a, b))
|
|
332
348
|
|
|
333
349
|
const isConstraintConflict = (error: SqlError): boolean =>
|
package/src/sql/SqlClient.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @stability unstable
|
|
10
10
|
* @since 4.0.0
|
|
11
11
|
*/
|
|
12
|
-
import
|
|
12
|
+
import * as Cause from "../Cause.ts"
|
|
13
13
|
import { Clock } from "../Clock.ts"
|
|
14
14
|
import * as Context from "../Context.ts"
|
|
15
15
|
import * as Effect from "../Effect.ts"
|
|
@@ -280,6 +280,12 @@ export const make = Effect.fnUntraced(function*(options: SqlClient.MakeOptions)
|
|
|
280
280
|
*
|
|
281
281
|
* **Details**
|
|
282
282
|
*
|
|
283
|
+
* Both acquirers lease the connection into the current scope and hold the
|
|
284
|
+
* semaphore until that scope closes, so a statement or stream keeps the
|
|
285
|
+
* connection until it finishes and another fiber cannot begin a transaction
|
|
286
|
+
* underneath it. Effects that use the connection directly, such as an export,
|
|
287
|
+
* have to run inside `Effect.scoped`.
|
|
288
|
+
*
|
|
283
289
|
* SQLite can keep a transaction open after COMMIT fails, for example on a
|
|
284
290
|
* deferred foreign key violation. `onCommitFailure` rolls it back before the
|
|
285
291
|
* connection is reused. If that ROLLBACK fails, the connection is rejected, and
|
|
@@ -300,7 +306,7 @@ export const makeSqliteAcquirers = <C extends Connection.Connection>(options: {
|
|
|
300
306
|
readonly semaphore: Semaphore.Semaphore
|
|
301
307
|
readonly isTransaction?: ((conn: C) => boolean) | undefined
|
|
302
308
|
}): {
|
|
303
|
-
readonly acquirer: Effect.Effect<C, SqlError>
|
|
309
|
+
readonly acquirer: Effect.Effect<C, SqlError, Scope.Scope>
|
|
304
310
|
readonly transactionAcquirer: Effect.Effect<C, SqlError, Scope.Scope>
|
|
305
311
|
readonly onCommitFailure: (conn: Connection.Connection) => Effect.Effect<void, SqlError>
|
|
306
312
|
} => {
|
|
@@ -336,15 +342,17 @@ export const makeSqliteAcquirers = <C extends Connection.Connection>(options: {
|
|
|
336
342
|
})
|
|
337
343
|
})
|
|
338
344
|
|
|
345
|
+
const lease = Effect.uninterruptibleMask((restore) =>
|
|
346
|
+
Effect.flatMap(Effect.scope, (scope) =>
|
|
347
|
+
restore(semaphore.take(1)).pipe(
|
|
348
|
+
Effect.andThen(Scope.addFinalizer(scope, semaphore.release(1))),
|
|
349
|
+
Effect.andThen(available)
|
|
350
|
+
))
|
|
351
|
+
)
|
|
352
|
+
|
|
339
353
|
return {
|
|
340
|
-
acquirer:
|
|
341
|
-
transactionAcquirer:
|
|
342
|
-
Effect.flatMap(Effect.scope, (scope) =>
|
|
343
|
-
restore(semaphore.take(1)).pipe(
|
|
344
|
-
Effect.andThen(Scope.addFinalizer(scope, semaphore.release(1))),
|
|
345
|
-
Effect.andThen(available)
|
|
346
|
-
))
|
|
347
|
-
),
|
|
354
|
+
acquirer: lease,
|
|
355
|
+
transactionAcquirer: lease,
|
|
348
356
|
onCommitFailure: (conn) => Effect.tapCause(rollback(conn as C), (cause) => Effect.sync(() => poison(cause)))
|
|
349
357
|
}
|
|
350
358
|
}
|
|
@@ -359,6 +367,18 @@ const isNoTransactionError = (error: SqlError): boolean => {
|
|
|
359
367
|
return /no transaction is active/i.test(message);
|
|
360
368
|
}
|
|
361
369
|
|
|
370
|
+
/**
|
|
371
|
+
* A failed COMMIT cleanup leaves the connection in an unsafe state, so both the
|
|
372
|
+
* COMMIT error and the cleanup error are promoted to defects: typed recovery
|
|
373
|
+
* must not be able to hide them.
|
|
374
|
+
*/
|
|
375
|
+
const failuresToDefects = <E>(cause: Cause.Cause<E>): Cause.Cause<never> =>
|
|
376
|
+
Cause.fromReasons(cause.reasons.map((reason) =>
|
|
377
|
+
Cause.isFailReason(reason)
|
|
378
|
+
? Cause.makeDieReason(reason.error).annotate(Cause.reasonAnnotations(reason))
|
|
379
|
+
: reason
|
|
380
|
+
))
|
|
381
|
+
|
|
362
382
|
/**
|
|
363
383
|
* Builds a transaction wrapper that begins top-level transactions, uses
|
|
364
384
|
* savepoints for nested transactions, commits on success, and rolls back on
|
|
@@ -424,15 +444,19 @@ export const makeWithTransaction = <I, S>(options: {
|
|
|
424
444
|
)
|
|
425
445
|
),
|
|
426
446
|
(exit) => {
|
|
427
|
-
let effect: Effect.Effect<void>
|
|
447
|
+
let effect: Effect.Effect<void, SqlError>
|
|
428
448
|
if (Exit.isSuccess(exit)) {
|
|
429
449
|
if (id === 0) {
|
|
430
450
|
span.event("effect.sql.transaction.commit", clock.currentTimeNanosUnsafe())
|
|
431
451
|
const onCommitFailure = options.onCommitFailure
|
|
432
|
-
effect =
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
452
|
+
effect = onCommitFailure
|
|
453
|
+
? Effect.catchCause(options.commit(conn), (commitCause) =>
|
|
454
|
+
Effect.matchCauseEffect(onCommitFailure(conn), {
|
|
455
|
+
onFailure: (cleanupCause) =>
|
|
456
|
+
Effect.failCause(failuresToDefects(Cause.combine(commitCause, cleanupCause))),
|
|
457
|
+
onSuccess: () => Effect.failCause(commitCause)
|
|
458
|
+
}))
|
|
459
|
+
: options.commit(conn)
|
|
436
460
|
} else {
|
|
437
461
|
span.event("effect.sql.transaction.savepoint", clock.currentTimeNanosUnsafe())
|
|
438
462
|
effect = Effect.void
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides a deterministic implementation of the Effect `Crypto` service for
|
|
3
|
+
* tests.
|
|
4
|
+
*
|
|
5
|
+
* Random bytes, numbers, shuffles, UUIDs, and ULIDs are derived from a seeded
|
|
6
|
+
* pseudo-random generator. Digest operations are delegated to an underlying
|
|
7
|
+
* platform `Crypto` service.
|
|
8
|
+
*
|
|
9
|
+
* @stability unstable
|
|
10
|
+
* @since 4.0.0
|
|
11
|
+
*/
|
|
12
|
+
import * as Crypto from "../Crypto.ts"
|
|
13
|
+
import * as Effect from "../Effect.ts"
|
|
14
|
+
import * as Layer from "../Layer.ts"
|
|
15
|
+
import * as Random from "../Random.ts"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a deterministic `Crypto` service using a seeded pseudo-random
|
|
19
|
+
* generator.
|
|
20
|
+
*
|
|
21
|
+
* **When to use**
|
|
22
|
+
*
|
|
23
|
+
* Use to construct a `Crypto` service whose random operations are reproducible
|
|
24
|
+
* in tests while preserving the digest implementation of a platform `Crypto`
|
|
25
|
+
* service.
|
|
26
|
+
*
|
|
27
|
+
* **Details**
|
|
28
|
+
*
|
|
29
|
+
* The effect requires an existing `Crypto` service and delegates digest
|
|
30
|
+
* operations to it. Each evaluation starts a new random sequence from `seed`;
|
|
31
|
+
* every other random operation is derived from that sequence.
|
|
32
|
+
*
|
|
33
|
+
* **Gotchas**
|
|
34
|
+
*
|
|
35
|
+
* The generated values are deterministic and must not be used for
|
|
36
|
+
* security-sensitive purposes. The sequence depends on the order in which
|
|
37
|
+
* random operations are evaluated. UUIDv7 and ULID values also include the
|
|
38
|
+
* current `Clock` time, which must be controlled separately when their complete
|
|
39
|
+
* output needs to be reproducible.
|
|
40
|
+
*
|
|
41
|
+
* @see {@link layer} for providing the service as a layer
|
|
42
|
+
*
|
|
43
|
+
* @stability unstable
|
|
44
|
+
* @category constructors
|
|
45
|
+
* @since 4.0.0
|
|
46
|
+
*/
|
|
47
|
+
export const make: (
|
|
48
|
+
seed: string | number
|
|
49
|
+
) => Effect.Effect<Crypto.Crypto, never, Crypto.Crypto> = Effect.fnUntraced(
|
|
50
|
+
function*(_seed: string | number) {
|
|
51
|
+
const crypto = yield* Crypto.Crypto
|
|
52
|
+
const seededRandom = yield* Random.Random
|
|
53
|
+
return Crypto.make({
|
|
54
|
+
randomBytes: (size) => {
|
|
55
|
+
const bytes = new Uint8Array(size)
|
|
56
|
+
for (let i = 0; i < size; i++) {
|
|
57
|
+
bytes[i] = Math.floor(seededRandom.nextDoubleUnsafe() * 256)
|
|
58
|
+
}
|
|
59
|
+
return bytes
|
|
60
|
+
},
|
|
61
|
+
digest: crypto.digest
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
(effect, seed) => Random.withSeed(effect, seed)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a layer that provides deterministic `Crypto` random operations from
|
|
69
|
+
* a seed.
|
|
70
|
+
*
|
|
71
|
+
* **When to use**
|
|
72
|
+
*
|
|
73
|
+
* Use to provide reproducible random bytes, numbers, shuffles, UUIDs, and ULIDs
|
|
74
|
+
* to an Effect program under test.
|
|
75
|
+
*
|
|
76
|
+
* **Details**
|
|
77
|
+
*
|
|
78
|
+
* The layer requires an existing platform `Crypto` service whose digest
|
|
79
|
+
* implementation is retained. Each layer build starts the random sequence from
|
|
80
|
+
* `seed`.
|
|
81
|
+
*
|
|
82
|
+
* **Gotchas**
|
|
83
|
+
*
|
|
84
|
+
* UUIDv7 and ULID values also depend on the current `Clock`. Provide a test
|
|
85
|
+
* clock when their complete output needs to be reproducible.
|
|
86
|
+
*
|
|
87
|
+
* @see {@link make} for constructing the service directly
|
|
88
|
+
*
|
|
89
|
+
* @stability unstable
|
|
90
|
+
* @category layers
|
|
91
|
+
* @since 4.0.0
|
|
92
|
+
*/
|
|
93
|
+
export const layer = (seed: string | number): Layer.Layer<Crypto.Crypto, never, Crypto.Crypto> =>
|
|
94
|
+
Layer.effect(Crypto.Crypto, make(seed))
|
package/src/testing/index.ts
CHANGED
|
@@ -17,6 +17,12 @@ export * as TestClock from "./TestClock.ts"
|
|
|
17
17
|
*/
|
|
18
18
|
export * as TestConsole from "./TestConsole.ts"
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @stability unstable
|
|
22
|
+
* @since 4.0.0
|
|
23
|
+
*/
|
|
24
|
+
export * as TestCrypto from "./TestCrypto.ts"
|
|
25
|
+
|
|
20
26
|
/**
|
|
21
27
|
* @stability unstable
|
|
22
28
|
* @since 4.0.0
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
* @stability unstable
|
|
11
11
|
* @since 4.0.0
|
|
12
12
|
*/
|
|
13
|
-
import * as Arr from "../Array.ts"
|
|
14
13
|
import type { NonEmptyReadonlyArray } from "../Array.ts"
|
|
15
14
|
import type * as Brand from "../Brand.ts"
|
|
16
15
|
import * as Cause from "../Cause.ts"
|
|
@@ -18,8 +17,9 @@ import * as Context from "../Context.ts"
|
|
|
18
17
|
import * as Effect from "../Effect.ts"
|
|
19
18
|
import * as Base64Url from "../encoding/Base64Url.ts"
|
|
20
19
|
import * as Exit from "../Exit.ts"
|
|
21
|
-
import * as
|
|
20
|
+
import * as Fiber from "../Fiber.ts"
|
|
22
21
|
import { dual } from "../Function.ts"
|
|
22
|
+
import * as Latch from "../Latch.ts"
|
|
23
23
|
import * as Option from "../Option.ts"
|
|
24
24
|
import * as Schema from "../Schema.ts"
|
|
25
25
|
import * as SchemaGetter from "../SchemaGetter.ts"
|
|
@@ -180,6 +180,20 @@ export {
|
|
|
180
180
|
* Runs an effect and records its exit into the durable deferred, resuming
|
|
181
181
|
* workflows that are waiting on that deferred.
|
|
182
182
|
*
|
|
183
|
+
* **Details**
|
|
184
|
+
*
|
|
185
|
+
* The effect and the write of its exit count as one in-flight activity of the
|
|
186
|
+
* workflow, so concurrent branches parked on durable waits do not suspend the
|
|
187
|
+
* run until the exit is recorded. This applies even when the effect itself is
|
|
188
|
+
* not an activity. If the effect parks on a durable wait, the run suspends
|
|
189
|
+
* once the other in-flight activities finish, and nothing is recorded.
|
|
190
|
+
*
|
|
191
|
+
* **Gotchas**
|
|
192
|
+
*
|
|
193
|
+
* A long-running effect keeps the run active instead of letting a parked
|
|
194
|
+
* sibling suspend it. Completing a deferred the run is waiting on still
|
|
195
|
+
* interrupts and replays the run.
|
|
196
|
+
*
|
|
183
197
|
* @stability unstable
|
|
184
198
|
* @category combinators
|
|
185
199
|
* @since 4.0.0
|
|
@@ -189,6 +203,20 @@ export const into: {
|
|
|
189
203
|
* Runs an effect and records its exit into the durable deferred, resuming
|
|
190
204
|
* workflows that are waiting on that deferred.
|
|
191
205
|
*
|
|
206
|
+
* **Details**
|
|
207
|
+
*
|
|
208
|
+
* The effect and the write of its exit count as one in-flight activity of the
|
|
209
|
+
* workflow, so concurrent branches parked on durable waits do not suspend the
|
|
210
|
+
* run until the exit is recorded. This applies even when the effect itself is
|
|
211
|
+
* not an activity. If the effect parks on a durable wait, the run suspends
|
|
212
|
+
* once the other in-flight activities finish, and nothing is recorded.
|
|
213
|
+
*
|
|
214
|
+
* **Gotchas**
|
|
215
|
+
*
|
|
216
|
+
* A long-running effect keeps the run active instead of letting a parked
|
|
217
|
+
* sibling suspend it. Completing a deferred the run is waiting on still
|
|
218
|
+
* interrupts and replays the run.
|
|
219
|
+
*
|
|
192
220
|
* @stability unstable
|
|
193
221
|
* @category combinators
|
|
194
222
|
* @since 4.0.0
|
|
@@ -210,6 +238,20 @@ export const into: {
|
|
|
210
238
|
* Runs an effect and records its exit into the durable deferred, resuming
|
|
211
239
|
* workflows that are waiting on that deferred.
|
|
212
240
|
*
|
|
241
|
+
* **Details**
|
|
242
|
+
*
|
|
243
|
+
* The effect and the write of its exit count as one in-flight activity of the
|
|
244
|
+
* workflow, so concurrent branches parked on durable waits do not suspend the
|
|
245
|
+
* run until the exit is recorded. This applies even when the effect itself is
|
|
246
|
+
* not an activity. If the effect parks on a durable wait, the run suspends
|
|
247
|
+
* once the other in-flight activities finish, and nothing is recorded.
|
|
248
|
+
*
|
|
249
|
+
* **Gotchas**
|
|
250
|
+
*
|
|
251
|
+
* A long-running effect keeps the run active instead of letting a parked
|
|
252
|
+
* sibling suspend it. Completing a deferred the run is waiting on still
|
|
253
|
+
* interrupts and replays the run.
|
|
254
|
+
*
|
|
213
255
|
* @stability unstable
|
|
214
256
|
* @category combinators
|
|
215
257
|
* @since 4.0.0
|
|
@@ -243,43 +285,86 @@ export const into: {
|
|
|
243
285
|
| Error["DecodingServices"]
|
|
244
286
|
| Success["EncodingServices"]
|
|
245
287
|
| Error["EncodingServices"]
|
|
246
|
-
> =>
|
|
247
|
-
Effect.contextWith(
|
|
248
|
-
(context: Context.Context<WorkflowEngine | WorkflowInstance>) => {
|
|
249
|
-
const engine = Context.get(context, EngineTag)
|
|
250
|
-
const parentInstance = Context.get(context, InstanceTag)
|
|
251
|
-
const instance = { ...parentInstance }
|
|
252
|
-
return Effect.onExit(
|
|
253
|
-
Effect.provideService(effect, InstanceTag, instance),
|
|
254
|
-
Effect.fnUntraced(function*(exit) {
|
|
255
|
-
if (Exit.isFailure(exit)) {
|
|
256
|
-
const [interrupts, reasons] = Arr.partition(
|
|
257
|
-
exit.cause.reasons,
|
|
258
|
-
Filter.fromPredicate(Cause.isInterruptReason)
|
|
259
|
-
)
|
|
260
|
-
if (interrupts.length === exit.cause.reasons.length) {
|
|
261
|
-
// An interrupt-only exit is never a result: the effect was
|
|
262
|
-
// suspended, preempted or interrupted, so record nothing.
|
|
263
|
-
if (instance.suspended) {
|
|
264
|
-
parentInstance.suspended = true
|
|
265
|
-
}
|
|
266
|
-
return
|
|
267
|
-
} else if (interrupts.length > 0) {
|
|
268
|
-
exit = Exit.failCause(Cause.fromReasons(reasons))
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
yield* engine.deferredDone(self, {
|
|
272
|
-
workflowName: instance.workflow._tag,
|
|
273
|
-
executionId: instance.executionId,
|
|
274
|
-
deferredName: self.name,
|
|
275
|
-
exit
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
)
|
|
279
|
-
}
|
|
280
|
-
)
|
|
288
|
+
> => recordHeld(effect, self, false)
|
|
281
289
|
)
|
|
282
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Records `exit` into `self`. An interrupt-only exit is never a result: the
|
|
293
|
+
* effect was suspended, preempted or interrupted, so nothing is recorded.
|
|
294
|
+
* Interrupts mixed into a failure are dropped from the recorded cause.
|
|
295
|
+
*/
|
|
296
|
+
const recordExit = (
|
|
297
|
+
engine: WorkflowEngine["Service"],
|
|
298
|
+
instance: WorkflowInstance["Service"],
|
|
299
|
+
self: DurableDeferred<any, any>,
|
|
300
|
+
exit: Exit.Exit<any, any>
|
|
301
|
+
) => {
|
|
302
|
+
if (Exit.isFailure(exit)) {
|
|
303
|
+
if (Cause.hasInterruptsOnly(exit.cause)) return Effect.void
|
|
304
|
+
if (Cause.hasInterrupts(exit.cause)) {
|
|
305
|
+
exit = Exit.failCause(Cause.fromReasons(
|
|
306
|
+
exit.cause.reasons.filter((reason) => !Cause.isInterruptReason(reason))
|
|
307
|
+
))
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return engine.deferredDone(self, {
|
|
311
|
+
workflowName: instance.workflow._tag,
|
|
312
|
+
executionId: instance.executionId,
|
|
313
|
+
deferredName: self.name,
|
|
314
|
+
exit
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Runs `effect` as one activity of the enclosing workflow and records its exit
|
|
320
|
+
* into `self`, returning a previously recorded exit instead when `checkCache`
|
|
321
|
+
* is set. The hold spans the lookup, `effect` and the write, so a parked
|
|
322
|
+
* sibling cannot suspend the run before the continuation starts.
|
|
323
|
+
*/
|
|
324
|
+
const recordHeld = Effect.fnUntraced(function*<A, E, R>(
|
|
325
|
+
effect: Effect.Effect<A, E, R>,
|
|
326
|
+
self: DurableDeferred<any, any>,
|
|
327
|
+
checkCache: boolean
|
|
328
|
+
): Effect.fn.Return<A, E, R | WorkflowEngine | WorkflowInstance> {
|
|
329
|
+
const engine = yield* EngineTag
|
|
330
|
+
const instance = yield* InstanceTag
|
|
331
|
+
const exit = yield* Workflow.wrapActivityResult(
|
|
332
|
+
Effect.gen(function*() {
|
|
333
|
+
if (checkCache) {
|
|
334
|
+
const cached = yield* engine.deferredResult(self)
|
|
335
|
+
if (Option.isSome(cached)) return cached
|
|
336
|
+
}
|
|
337
|
+
// `effect` counts on its own activity state, so a durable await inside
|
|
338
|
+
// it waits for its own siblings and not for the hold above. The copy
|
|
339
|
+
// starts unsuspended: a parked arm of an enclosing race leaves that
|
|
340
|
+
// copy marked suspended while only interrupting its own fiber.
|
|
341
|
+
const local: WorkflowInstance["Service"] = {
|
|
342
|
+
...instance,
|
|
343
|
+
suspended: false,
|
|
344
|
+
activityState: { count: 0, latch: Latch.makeUnsafe() }
|
|
345
|
+
}
|
|
346
|
+
// A parked `effect` interrupts the fiber it runs on. The child fiber
|
|
347
|
+
// keeps that from skipping the suspension handling below.
|
|
348
|
+
const fiber = yield* effect.pipe(
|
|
349
|
+
Effect.provideService(InstanceTag, local),
|
|
350
|
+
Effect.onExit((exit) => recordExit(engine, instance, self, exit)),
|
|
351
|
+
Effect.forkChild({ startImmediately: true })
|
|
352
|
+
)
|
|
353
|
+
const exit = yield* Effect.onInterrupt(Fiber.await(fiber), () => Fiber.interrupt(fiber))
|
|
354
|
+
return local.suspended && Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)
|
|
355
|
+
? Option.none()
|
|
356
|
+
: Option.some(exit)
|
|
357
|
+
}),
|
|
358
|
+
// Every branch of `effect` parked: release the hold and let the enclosing
|
|
359
|
+
// activities finish before suspending. External preemption does not wait.
|
|
360
|
+
Option.isNone
|
|
361
|
+
)
|
|
362
|
+
if (Option.isNone(exit)) {
|
|
363
|
+
return yield* Workflow.suspend(instance)
|
|
364
|
+
}
|
|
365
|
+
return yield* exit.value
|
|
366
|
+
})
|
|
367
|
+
|
|
283
368
|
/**
|
|
284
369
|
* Runs effects as a durable race, returning a previously persisted result when
|
|
285
370
|
* present or completing a named deferred with the first result.
|
|
@@ -312,17 +397,7 @@ export const raceAll = <
|
|
|
312
397
|
success: options.success,
|
|
313
398
|
error: options.error
|
|
314
399
|
})
|
|
315
|
-
return Effect.
|
|
316
|
-
const engine = yield* EngineTag
|
|
317
|
-
const exit = yield* engine.deferredResult(deferred)
|
|
318
|
-
if (Option.isSome(exit)) {
|
|
319
|
-
return yield* exit.value
|
|
320
|
-
}
|
|
321
|
-
return yield* into(
|
|
322
|
-
Effect.raceAll(options.effects),
|
|
323
|
-
deferred
|
|
324
|
-
)
|
|
325
|
-
})
|
|
400
|
+
return recordHeld(Effect.raceAll(options.effects), deferred, true)
|
|
326
401
|
}
|
|
327
402
|
|
|
328
403
|
/**
|
|
@@ -826,7 +826,8 @@ export const layerMemory: Layer.Layer<WorkflowEngine> = Layer.effect(WorkflowEng
|
|
|
826
826
|
Workflow.intoResult,
|
|
827
827
|
Effect.provideService(WorkflowInstance, activityInstance),
|
|
828
828
|
Effect.onExit((exit) => {
|
|
829
|
-
|
|
829
|
+
// A preempted activity must run again on the next replay.
|
|
830
|
+
state.exit = Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause) ? undefined : exit
|
|
830
831
|
return Effect.void
|
|
831
832
|
})
|
|
832
833
|
)
|