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
|
@@ -86,6 +86,10 @@ export interface Residency {
|
|
|
86
86
|
readonly releaseUnsafe: () => void
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
interface RequestTransaction {
|
|
90
|
+
settle?: ((outcome: Exit.Exit<unknown, unknown>) => Effect.Effect<void>) | undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
// Represents the entities managed by this entity manager
|
|
90
94
|
/**
|
|
91
95
|
* @internal
|
|
@@ -100,10 +104,13 @@ export type EntityState = {
|
|
|
100
104
|
/** Excludes requests awaiting their first dispatch from replay. */
|
|
101
105
|
delivered: boolean
|
|
102
106
|
sentExit: boolean
|
|
107
|
+
/** Treat early termination interrupts like shutdown interrupts. */
|
|
108
|
+
terminating?: boolean | undefined
|
|
103
109
|
lastSentChunk: Option.Option<Reply.Chunk<Rpc.Any>>
|
|
104
110
|
sequence: number
|
|
105
111
|
/** Set when the request should not outlive its caller. */
|
|
106
112
|
callerScope?: Scope.Scope | undefined
|
|
113
|
+
transaction?: RequestTransaction | undefined
|
|
107
114
|
}>
|
|
108
115
|
lastActiveCheck: number
|
|
109
116
|
write: RpcServer.RpcServer<any>["write"]
|
|
@@ -152,9 +159,13 @@ export const make = Effect.fnUntraced(function*<
|
|
|
152
159
|
entityRpcs.set(KeepAliveRpc._tag, KeepAliveRpc as any)
|
|
153
160
|
|
|
154
161
|
const activeServers = new Map<EntityId, EntityState>()
|
|
162
|
+
// Entities finishing their in-flight requests after removal, which still
|
|
163
|
+
// accept interrupts and acks
|
|
164
|
+
const drainingServers = new Map<EntityId, EntityState>()
|
|
155
165
|
const serverCloseLatches = new Map<EntityAddress, {
|
|
156
166
|
readonly closed: Latch.Latch
|
|
157
167
|
readonly force: Latch.Latch
|
|
168
|
+
closing: boolean
|
|
158
169
|
}>()
|
|
159
170
|
const processedRequestIds = new Set<Snowflake.Snowflake>()
|
|
160
171
|
|
|
@@ -181,7 +192,8 @@ export const make = Effect.fnUntraced(function*<
|
|
|
181
192
|
const keepAliveLatch = Latch.makeUnsafe()
|
|
182
193
|
const closeLatches = {
|
|
183
194
|
closed: Latch.makeUnsafe(),
|
|
184
|
-
force: Latch.makeUnsafe()
|
|
195
|
+
force: Latch.makeUnsafe(),
|
|
196
|
+
closing: false
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
yield* Scope.addFinalizer(
|
|
@@ -264,22 +276,23 @@ export const make = Effect.fnUntraced(function*<
|
|
|
264
276
|
// They will be retried when the entity is restarted.
|
|
265
277
|
// Also, if the request is uninterruptible, we ignore the
|
|
266
278
|
// interrupt.
|
|
279
|
+
const persisted = storageEnabled && Context.get(request.message.annotations, Persisted)
|
|
267
280
|
if (
|
|
268
|
-
|
|
269
|
-
Context.get(request.message.annotations, Persisted) &&
|
|
281
|
+
persisted &&
|
|
270
282
|
Exit.hasInterrupts(response.exit) &&
|
|
271
|
-
(isShuttingDown || isUninterruptibleForServer(request.message.annotations))
|
|
283
|
+
(isShuttingDown || request.terminating || isUninterruptibleForServer(request.message.annotations))
|
|
272
284
|
) {
|
|
273
|
-
if (!isShuttingDown) {
|
|
285
|
+
if (!isShuttingDown && !request.terminating) {
|
|
274
286
|
request.sentExit = false
|
|
275
287
|
return server.write(0, requestEnvelope(request), requestWriteOptions(request)).pipe(
|
|
288
|
+
Effect.setContext(handlerContext),
|
|
276
289
|
Effect.forkIn(handlerScope)
|
|
277
290
|
)
|
|
278
291
|
}
|
|
279
292
|
activeRequests.delete(Snowflake.Snowflake(response.requestId))
|
|
280
293
|
return options.storage.unregisterReplyHandler(request.message.envelope.requestId)
|
|
281
294
|
}
|
|
282
|
-
|
|
295
|
+
const save = retryRespond(
|
|
283
296
|
4,
|
|
284
297
|
Effect.suspend(() =>
|
|
285
298
|
request.message.respond(
|
|
@@ -290,22 +303,53 @@ export const make = Effect.fnUntraced(function*<
|
|
|
290
303
|
})
|
|
291
304
|
)
|
|
292
305
|
)
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
306
|
+
)
|
|
307
|
+
const complete = Effect.sync(() => {
|
|
308
|
+
if (storageEnabled) {
|
|
309
|
+
processedRequestIds.add(request.message.envelope.requestId)
|
|
310
|
+
}
|
|
311
|
+
activeRequests.delete(Snowflake.Snowflake(response.requestId))
|
|
299
312
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
313
|
+
// Start the idle timer when the last request completes.
|
|
314
|
+
if (activeRequests.size === 0) {
|
|
315
|
+
state.lastActiveCheck = clock.currentTimeMillisUnsafe()
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
const respond = Effect.orDie(Effect.andThen(save, complete))
|
|
319
|
+
const transaction = request.transaction
|
|
320
|
+
if (!transaction) return respond
|
|
321
|
+
const exit = response.exit
|
|
322
|
+
|
|
323
|
+
if (!persisted) {
|
|
324
|
+
if (Exit.isSuccess(exit)) return respond
|
|
325
|
+
transaction.settle = () => respond
|
|
326
|
+
return Effect.void
|
|
327
|
+
}
|
|
304
328
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
329
|
+
if (Exit.isSuccess(exit)) {
|
|
330
|
+
transaction.settle = (outcome) =>
|
|
331
|
+
Exit.isSuccess(outcome) ? complete : Effect.flatMap(
|
|
332
|
+
hasStoredExit(request.message.envelope.requestId),
|
|
333
|
+
(committed) => {
|
|
334
|
+
if (!committed) return restartFrom(outcome.cause)
|
|
335
|
+
// Commit succeeded despite the error; let callers read the saved reply.
|
|
336
|
+
return Effect.andThen(
|
|
337
|
+
complete,
|
|
338
|
+
options.storage.unregisterReplyHandler(request.message.envelope.requestId)
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
return Effect.orDie(save)
|
|
343
|
+
}
|
|
344
|
+
transaction.settle = (outcome) =>
|
|
345
|
+
// Transaction wrappers can add defects, but not typed errors; interrupts may be repeated.
|
|
346
|
+
Exit.isSuccess(outcome) || !outcome.cause.reasons.some((reason) =>
|
|
347
|
+
Cause.isDieReason(reason) &&
|
|
348
|
+
!exit.cause.reasons.some((r) => Cause.isDieReason(r) && r.defect === reason.defect)
|
|
349
|
+
)
|
|
350
|
+
? respond
|
|
351
|
+
: restartFrom(outcome.cause)
|
|
352
|
+
return Effect.void
|
|
309
353
|
}
|
|
310
354
|
case "Chunk": {
|
|
311
355
|
const request = activeRequests.get(Snowflake.Snowflake(response.requestId))
|
|
@@ -330,10 +374,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
330
374
|
))
|
|
331
375
|
}
|
|
332
376
|
case "Defect": {
|
|
333
|
-
|
|
334
|
-
const rebuild = writeRef.rebuildUnsafe({ from: server.write, prepare: replay })
|
|
335
|
-
if (!rebuild) return Effect.void
|
|
336
|
-
return Effect.forkIn(restart(Cause.die(response.defect), rebuild), managerScope)
|
|
377
|
+
return restartFrom(Cause.die(response.defect))
|
|
337
378
|
}
|
|
338
379
|
case "ClientEnd": {
|
|
339
380
|
return endLatch.open
|
|
@@ -345,6 +386,21 @@ export const make = Effect.fnUntraced(function*<
|
|
|
345
386
|
Effect.setContext(Context.merge(handlerContext, handlers))
|
|
346
387
|
)
|
|
347
388
|
|
|
389
|
+
// Replay if the stored reply cannot be read.
|
|
390
|
+
const hasStoredExit = (requestId: Snowflake.Snowflake): Effect.Effect<boolean> =>
|
|
391
|
+
options.storage.repliesForUnfiltered([requestId]).pipe(
|
|
392
|
+
Effect.map((replies) => replies.some((reply) => reply._tag === "WithExit")),
|
|
393
|
+
Effect.catchCause(() => Effect.succeed(false))
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
// Do not inherit the failed handler's transaction context.
|
|
397
|
+
const restartFrom = (cause: Cause.Cause<unknown>): Effect.Effect<void> => {
|
|
398
|
+
if (!isActive()) return endLatch.open
|
|
399
|
+
const rebuild = writeRef.rebuildUnsafe({ from: server.write, prepare: replay })
|
|
400
|
+
if (!rebuild) return Effect.void
|
|
401
|
+
return Effect.forkIn(Effect.setContext(restart(cause, rebuild), handlerContext), managerScope)
|
|
402
|
+
}
|
|
403
|
+
|
|
348
404
|
yield* Scope.addFinalizer(
|
|
349
405
|
handlerScope,
|
|
350
406
|
Effect.sync(() => {
|
|
@@ -415,15 +471,32 @@ export const make = Effect.fnUntraced(function*<
|
|
|
415
471
|
Effect.suspend(() => {
|
|
416
472
|
activeServers.delete(address.entityId)
|
|
417
473
|
retired.openUnsafe()
|
|
474
|
+
closeLatches.closing = true
|
|
418
475
|
acquireEntity(address)
|
|
476
|
+
drainingServers.set(address.entityId, state)
|
|
419
477
|
return Effect.raceFirst(
|
|
420
|
-
|
|
478
|
+
Effect.gen(function*() {
|
|
479
|
+
for (const [requestId, request] of activeRequests) {
|
|
480
|
+
if (
|
|
481
|
+
!request.delivered || request.sentExit ||
|
|
482
|
+
!Context.get(request.message.annotations, ClusterSchema.InterruptOnTermination) ||
|
|
483
|
+
isUninterruptibleForServer(request.message.annotations)
|
|
484
|
+
) continue
|
|
485
|
+
request.terminating = true
|
|
486
|
+
yield* state.write(0, { _tag: "Interrupt", requestId: requestId as any, interruptors: [] })
|
|
487
|
+
}
|
|
488
|
+
yield* state.write(0, { _tag: "Eof" })
|
|
489
|
+
}).pipe(
|
|
421
490
|
Effect.andThen(endLatch.await),
|
|
422
491
|
Effect.timeoutOption(config.entityTerminationTimeout),
|
|
423
492
|
Effect.interruptible
|
|
424
493
|
),
|
|
425
494
|
Effect.interruptible(closeLatches.force.await)
|
|
426
|
-
)
|
|
495
|
+
).pipe(Effect.ensuring(Effect.sync(() => {
|
|
496
|
+
if (drainingServers.get(address.entityId) === state) {
|
|
497
|
+
drainingServers.delete(address.entityId)
|
|
498
|
+
}
|
|
499
|
+
})))
|
|
427
500
|
})
|
|
428
501
|
)
|
|
429
502
|
if (!options.sharding.hasShardId(address.shardId)) {
|
|
@@ -458,9 +531,38 @@ export const make = Effect.fnUntraced(function*<
|
|
|
458
531
|
Effect.forkIn(managerScope)
|
|
459
532
|
)
|
|
460
533
|
|
|
534
|
+
function sendEnvelope(server: EntityState, message: Message.IncomingEnvelope): Effect.Effect<void> {
|
|
535
|
+
const entry = server.activeRequests.get(message.envelope.requestId)
|
|
536
|
+
if (!entry) {
|
|
537
|
+
return Effect.void
|
|
538
|
+
} else if (
|
|
539
|
+
message.envelope._tag === "AckChunk" &&
|
|
540
|
+
Option.isSome(entry.lastSentChunk) &&
|
|
541
|
+
message.envelope.replyId !== entry.lastSentChunk.value.id
|
|
542
|
+
) {
|
|
543
|
+
return Effect.void
|
|
544
|
+
}
|
|
545
|
+
return server.write(
|
|
546
|
+
0,
|
|
547
|
+
message.envelope._tag === "AckChunk"
|
|
548
|
+
? { _tag: "Ack", requestId: message.envelope.requestId as any }
|
|
549
|
+
: {
|
|
550
|
+
_tag: "Interrupt",
|
|
551
|
+
requestId: message.envelope.requestId as any,
|
|
552
|
+
interruptors: []
|
|
553
|
+
}
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
|
|
461
557
|
function sendLocal<R extends Rpc.Any>(
|
|
462
558
|
message: Message.IncomingLocal<R>
|
|
463
559
|
): Effect.Effect<void, EntityNotAssignedToRunner | MailboxFull | AlreadyProcessingMessage> {
|
|
560
|
+
if (message._tag === "IncomingEnvelope") {
|
|
561
|
+
const draining = drainingServers.get(message.envelope.address.entityId)
|
|
562
|
+
if (draining?.activeRequests.has(message.envelope.requestId)) {
|
|
563
|
+
return sendEnvelope(draining, message)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
464
566
|
return Effect.provideService(
|
|
465
567
|
Effect.flatMap(
|
|
466
568
|
entities.get(message.envelope.address),
|
|
@@ -557,26 +659,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
557
659
|
return server.write(0, requestEnvelope(entry), requestWriteOptions(entry))
|
|
558
660
|
}
|
|
559
661
|
case "IncomingEnvelope": {
|
|
560
|
-
|
|
561
|
-
if (!entry) {
|
|
562
|
-
return Effect.void
|
|
563
|
-
} else if (
|
|
564
|
-
message.envelope._tag === "AckChunk" &&
|
|
565
|
-
Option.isSome(entry.lastSentChunk) &&
|
|
566
|
-
message.envelope.replyId !== entry.lastSentChunk.value.id
|
|
567
|
-
) {
|
|
568
|
-
return Effect.void
|
|
569
|
-
}
|
|
570
|
-
return server.write(
|
|
571
|
-
0,
|
|
572
|
-
message.envelope._tag === "AckChunk"
|
|
573
|
-
? { _tag: "Ack", requestId: message.envelope.requestId as any }
|
|
574
|
-
: {
|
|
575
|
-
_tag: "Interrupt",
|
|
576
|
-
requestId: message.envelope.requestId as any,
|
|
577
|
-
interruptors: []
|
|
578
|
-
}
|
|
579
|
-
)
|
|
662
|
+
return sendEnvelope(server, message)
|
|
580
663
|
}
|
|
581
664
|
}
|
|
582
665
|
}
|
|
@@ -596,11 +679,24 @@ export const make = Effect.fnUntraced(function*<
|
|
|
596
679
|
})
|
|
597
680
|
|
|
598
681
|
const requestWriteOptions = (
|
|
599
|
-
entry: {
|
|
682
|
+
entry: {
|
|
683
|
+
readonly message: Message.IncomingRequestLocal<any>
|
|
684
|
+
readonly callerScope?: Scope.Scope | undefined
|
|
685
|
+
transaction?: RequestTransaction | undefined
|
|
686
|
+
}
|
|
600
687
|
): Parameters<EntityState["write"]>[2] => {
|
|
601
|
-
const onTransaction = Context.get(entry.message.annotations, WithTransaction)
|
|
602
|
-
?
|
|
603
|
-
:
|
|
688
|
+
const onTransaction = !Context.get(entry.message.annotations, WithTransaction)
|
|
689
|
+
? undefined
|
|
690
|
+
: <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
691
|
+
// Interrupts during commit must not hide the outcome from settle.
|
|
692
|
+
Effect.uninterruptibleMask((restore) => {
|
|
693
|
+
const transaction: RequestTransaction = {}
|
|
694
|
+
entry.transaction = transaction
|
|
695
|
+
return Effect.onExit(options.storage.withTransaction(restore(effect)), (outcome) => {
|
|
696
|
+
if (entry.transaction === transaction) entry.transaction = undefined
|
|
697
|
+
return transaction.settle ? transaction.settle(outcome) : Effect.void
|
|
698
|
+
})
|
|
699
|
+
})
|
|
604
700
|
const onCaller = entry.callerScope && bindToCaller(entry.callerScope)
|
|
605
701
|
if (!onCaller) return onTransaction && { onRequest: onTransaction }
|
|
606
702
|
return { onRequest: onTransaction ? (effect) => onCaller(onTransaction(effect)) : onCaller }
|
|
@@ -621,13 +717,13 @@ export const make = Effect.fnUntraced(function*<
|
|
|
621
717
|
}
|
|
622
718
|
})
|
|
623
719
|
}
|
|
624
|
-
activeServers
|
|
625
|
-
|
|
626
|
-
fibers.push(runFork(entities.removeIgnore(state.address)))
|
|
627
|
-
}
|
|
628
|
-
})
|
|
720
|
+
// Look entities up by address: `activeServers` is keyed by entity id,
|
|
721
|
+
// so an entity with the same id on another shard can hide this one.
|
|
629
722
|
serverCloseLatches.forEach((latches, address) => {
|
|
630
723
|
if (shardId[Equal.symbol](address.shardId)) {
|
|
724
|
+
if (!latches.closing) {
|
|
725
|
+
fibers.push(runFork(entities.removeIgnore(address)))
|
|
726
|
+
}
|
|
631
727
|
fibers.push(runFork(latches.closed.await))
|
|
632
728
|
}
|
|
633
729
|
})
|
|
@@ -338,13 +338,6 @@ class RouterImpl<A> implements Router.Router<A> {
|
|
|
338
338
|
path = path.replace(FULL_PATH_REGEXP, "/")
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
// This must be run before sanitizeUrl as the resulting function
|
|
342
|
-
// .sliceParameter must be constructed with same URL string used
|
|
343
|
-
// throughout the rest of this function.
|
|
344
|
-
if (this.options.ignoreDuplicateSlashes) {
|
|
345
|
-
path = removeDuplicateSlashes(path)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
341
|
let sanitizedUrl
|
|
349
342
|
try {
|
|
350
343
|
sanitizedUrl = safeDecodeURI(path)
|
|
@@ -355,6 +348,10 @@ class RouterImpl<A> implements Router.Router<A> {
|
|
|
355
348
|
return undefined
|
|
356
349
|
}
|
|
357
350
|
|
|
351
|
+
if (this.options.ignoreDuplicateSlashes) {
|
|
352
|
+
path = removeDuplicateSlashes(path)
|
|
353
|
+
}
|
|
354
|
+
|
|
358
355
|
if (this.options.ignoreTrailingSlash) {
|
|
359
356
|
path = trimLastSlash(path)
|
|
360
357
|
}
|
package/src/http/HttpClient.ts
CHANGED
|
@@ -1466,6 +1466,8 @@ export declare namespace WithRateLimiter {
|
|
|
1466
1466
|
* It can update limits by inspecting common rate limit response headers and
|
|
1467
1467
|
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1468
1468
|
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1469
|
+
* When a response reports the remaining budget, requests wait for the
|
|
1470
|
+
* reported reset once that budget is exhausted.
|
|
1469
1471
|
*
|
|
1470
1472
|
* **Gotchas**
|
|
1471
1473
|
*
|
|
@@ -1486,6 +1488,8 @@ export const withRateLimiter: {
|
|
|
1486
1488
|
* It can update limits by inspecting common rate limit response headers and
|
|
1487
1489
|
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1488
1490
|
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1491
|
+
* When a response reports the remaining budget, requests wait for the
|
|
1492
|
+
* reported reset once that budget is exhausted.
|
|
1489
1493
|
*
|
|
1490
1494
|
* **Gotchas**
|
|
1491
1495
|
*
|
|
@@ -1508,6 +1512,8 @@ export const withRateLimiter: {
|
|
|
1508
1512
|
* It can update limits by inspecting common rate limit response headers and
|
|
1509
1513
|
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1510
1514
|
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1515
|
+
* When a response reports the remaining budget, requests wait for the
|
|
1516
|
+
* reported reset once that budget is exhausted.
|
|
1511
1517
|
*
|
|
1512
1518
|
* **Gotchas**
|
|
1513
1519
|
*
|
|
@@ -1551,14 +1557,36 @@ export const withRateLimiter: {
|
|
|
1551
1557
|
return initialState
|
|
1552
1558
|
}
|
|
1553
1559
|
|
|
1560
|
+
const budgets = new Map<string, RateLimitBudget>()
|
|
1561
|
+
const getBudget = (key: string): RateLimitBudget => {
|
|
1562
|
+
let budget = budgets.get(key)
|
|
1563
|
+
if (budget === undefined) {
|
|
1564
|
+
budget = { sent: 0, observed: 0, remaining: 0, resetAt: 0 }
|
|
1565
|
+
budgets.set(key, budget)
|
|
1566
|
+
}
|
|
1567
|
+
return budget
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1554
1570
|
const onResponse = options.disableResponseInspection
|
|
1555
1571
|
? undefined
|
|
1556
|
-
: (clock: Clock, key: string, headers: Headers.Headers, tokens: number) => {
|
|
1572
|
+
: (clock: Clock, key: string, headers: Headers.Headers, tokens: number, sent: number) => {
|
|
1557
1573
|
const current = getState(key)
|
|
1558
1574
|
const next = parseRateLimiterState(current, clock, headers, tokens, headerNames)
|
|
1559
1575
|
if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) {
|
|
1560
1576
|
states.set(key, next)
|
|
1561
1577
|
}
|
|
1578
|
+
const remaining = parseRateLimitRemaining(headers, headerNames)
|
|
1579
|
+
const budget = getBudget(key)
|
|
1580
|
+
// responses to earlier requests carry stale counts
|
|
1581
|
+
if (remaining === undefined || sent <= budget.observed) {
|
|
1582
|
+
return
|
|
1583
|
+
}
|
|
1584
|
+
const resetAfter = parseRateLimitWindow(clock, headers, headerNames)
|
|
1585
|
+
budget.observed = sent
|
|
1586
|
+
budget.remaining = remaining
|
|
1587
|
+
if (resetAfter !== undefined) {
|
|
1588
|
+
budget.resetAt = clock.currentTimeMillisUnsafe() + Duration.toMillis(resetAfter)
|
|
1589
|
+
}
|
|
1562
1590
|
}
|
|
1563
1591
|
|
|
1564
1592
|
return transform(self, function loop(effect, request, retries = 0): Effect.Effect<
|
|
@@ -1570,6 +1598,15 @@ export const withRateLimiter: {
|
|
|
1570
1598
|
const clock = fiber.getRef(Clock)
|
|
1571
1599
|
const key = resolveKey(request)
|
|
1572
1600
|
const tokens = Math.max(resolveTokens(request), 1)
|
|
1601
|
+
const budget = getBudget(key)
|
|
1602
|
+
const now = clock.currentTimeMillisUnsafe()
|
|
1603
|
+
// requests admitted after the observed one are not reflected in its count
|
|
1604
|
+
if (budget.resetAt > now && budget.remaining - (budget.sent - budget.observed) < tokens) {
|
|
1605
|
+
return Effect.flatMap(Effect.sleep(budget.resetAt - now), () => loop(effect, request, retries))
|
|
1606
|
+
}
|
|
1607
|
+
// counted at admission, so requests already admitted are sent even if a
|
|
1608
|
+
// later response exhausts the budget
|
|
1609
|
+
const sent = budget.sent += tokens
|
|
1573
1610
|
const current = getState(key)
|
|
1574
1611
|
const canRetry = options.times === undefined || retries < options.times
|
|
1575
1612
|
function retry(retryAfter: Duration.Duration | undefined) {
|
|
@@ -1581,7 +1618,7 @@ export const withRateLimiter: {
|
|
|
1581
1618
|
response: HttpClientResponse.HttpClientResponse,
|
|
1582
1619
|
adaptive: RateLimiter.AdaptiveConsumeResult | undefined
|
|
1583
1620
|
) => {
|
|
1584
|
-
onResponse?.(clock, key, response.headers, tokens)
|
|
1621
|
+
onResponse?.(clock, key, response.headers, tokens, sent)
|
|
1585
1622
|
if (options.disableResponseInspection || response.status !== 429) {
|
|
1586
1623
|
return Effect.succeed</**
|
|
1587
1624
|
* Applies request rate limiting using the `RateLimiter` service.
|
|
@@ -1591,6 +1628,8 @@ export const withRateLimiter: {
|
|
|
1591
1628
|
* It can update limits by inspecting common rate limit response headers and
|
|
1592
1629
|
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1593
1630
|
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1631
|
+
* When a response reports the remaining budget, requests wait for the
|
|
1632
|
+
* reported reset once that budget is exhausted.
|
|
1594
1633
|
*
|
|
1595
1634
|
* **Gotchas**
|
|
1596
1635
|
*
|
|
@@ -1614,6 +1653,8 @@ export const withRateLimiter: {
|
|
|
1614
1653
|
* It can update limits by inspecting common rate limit response headers and
|
|
1615
1654
|
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1616
1655
|
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1656
|
+
* When a response reports the remaining budget, requests wait for the
|
|
1657
|
+
* reported reset once that budget is exhausted.
|
|
1617
1658
|
*
|
|
1618
1659
|
* **Gotchas**
|
|
1619
1660
|
*
|
|
@@ -1637,6 +1678,8 @@ export const withRateLimiter: {
|
|
|
1637
1678
|
* It can update limits by inspecting common rate limit response headers and
|
|
1638
1679
|
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1639
1680
|
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1681
|
+
* When a response reports the remaining budget, requests wait for the
|
|
1682
|
+
* reported reset once that budget is exhausted.
|
|
1640
1683
|
*
|
|
1641
1684
|
* **Gotchas**
|
|
1642
1685
|
*
|
|
@@ -1756,6 +1799,17 @@ interface RateLimiterState {
|
|
|
1756
1799
|
readonly initial: boolean
|
|
1757
1800
|
}
|
|
1758
1801
|
|
|
1802
|
+
interface RateLimitBudget {
|
|
1803
|
+
/** Total tokens admitted for the key. */
|
|
1804
|
+
sent: number
|
|
1805
|
+
/** The `sent` total of the request whose remaining count was last applied. */
|
|
1806
|
+
observed: number
|
|
1807
|
+
/** The remaining count reported in response to the `observed` request. */
|
|
1808
|
+
remaining: number
|
|
1809
|
+
/** When the server budget resets, in epoch milliseconds. */
|
|
1810
|
+
resetAt: number
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1759
1813
|
const parseRateLimiterState = (
|
|
1760
1814
|
state: RateLimiterState,
|
|
1761
1815
|
clock: Clock,
|
|
@@ -93,7 +93,7 @@ export const b3: FromHeaders = (headers) => {
|
|
|
93
93
|
return Option.none()
|
|
94
94
|
}
|
|
95
95
|
const parts = headers["b3"].split("-")
|
|
96
|
-
if (parts.length < 2) {
|
|
96
|
+
if (parts.length < 2 || isB3Ids(parts[0], parts[1]) === false) {
|
|
97
97
|
return Option.none()
|
|
98
98
|
}
|
|
99
99
|
return Option.some(Tracer.externalSpan({
|
|
@@ -116,7 +116,10 @@ export const b3: FromHeaders = (headers) => {
|
|
|
116
116
|
* @since 4.0.0
|
|
117
117
|
*/
|
|
118
118
|
export const xb3: FromHeaders = (headers) => {
|
|
119
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
!(headers["x-b3-traceid"]) || !(headers["x-b3-spanid"]) ||
|
|
121
|
+
isB3Ids(headers["x-b3-traceid"], headers["x-b3-spanid"]) === false
|
|
122
|
+
) {
|
|
120
123
|
return Option.none()
|
|
121
124
|
}
|
|
122
125
|
return Option.some(Tracer.externalSpan({
|
|
@@ -130,6 +133,9 @@ export const xb3: FromHeaders = (headers) => {
|
|
|
130
133
|
const w3cTraceId = /^[0-9a-f]{32}$/i
|
|
131
134
|
const w3cSpanId = /^[0-9a-f]{16}$/i
|
|
132
135
|
|
|
136
|
+
const b3TraceId = /^(?:[0-9a-f]{16}|[0-9a-f]{32})$/i
|
|
137
|
+
const isB3Ids = (traceId: string, spanId: string): boolean => b3TraceId.test(traceId) && w3cSpanId.test(spanId)
|
|
138
|
+
|
|
133
139
|
/**
|
|
134
140
|
* Decodes an external span safely from the W3C `traceparent` header.
|
|
135
141
|
*
|
package/src/internal/effect.ts
CHANGED
|
@@ -1507,6 +1507,7 @@ const succeedPayload = function(this: { readonly payload: any }, _value: unknown
|
|
|
1507
1507
|
// V8 includes the property name of a stored continuation in its stack trace.
|
|
1508
1508
|
// Other engines need an explicit frame for the stack cleaner to cut at.
|
|
1509
1509
|
const continuationMarksStack = (() => {
|
|
1510
|
+
if (getStackTraceLimit() === 0) return false
|
|
1510
1511
|
const marker = "~effect/Effect/stackProbe"
|
|
1511
1512
|
const probe = {
|
|
1512
1513
|
[marker]: function stackProbe() {
|
|
@@ -4453,7 +4454,7 @@ export const cachedInvalidateWithTTL: {
|
|
|
4453
4454
|
const infiniteTTL = constant(Infinity)
|
|
4454
4455
|
|
|
4455
4456
|
interface CachedRun<A, E> {
|
|
4456
|
-
fiber: FiberImpl<A, E>
|
|
4457
|
+
readonly fiber: FiberImpl<A, E>
|
|
4457
4458
|
awaiters: number
|
|
4458
4459
|
}
|
|
4459
4460
|
|
|
@@ -4470,17 +4471,19 @@ const makeCachedUnsafe = <A, E, R>(
|
|
|
4470
4471
|
onExitUnsafe(fiber, () => {
|
|
4471
4472
|
// Abandon the run once every caller has left, unless it already finished.
|
|
4472
4473
|
if (--run.awaiters > 0 || current !== run) return
|
|
4473
|
-
// Detach
|
|
4474
|
-
// one that is being interrupted.
|
|
4474
|
+
// Detach before interruption so new callers start a fresh run.
|
|
4475
4475
|
current = undefined
|
|
4476
|
-
return fiberInterrupt(run.fiber
|
|
4476
|
+
return fiberInterrupt(run.fiber)
|
|
4477
4477
|
})
|
|
4478
|
-
return fiberJoin(run.fiber
|
|
4478
|
+
return fiberJoin(run.fiber)
|
|
4479
4479
|
}
|
|
4480
4480
|
|
|
4481
4481
|
return [
|
|
4482
4482
|
withFiber((fiber) => {
|
|
4483
|
-
if (current !== undefined)
|
|
4483
|
+
if (current !== undefined) {
|
|
4484
|
+
// A run joining itself is not an awaiter, so external callers can still abandon it.
|
|
4485
|
+
return fiber === current.fiber ? fiberJoin(current.fiber) : join(fiber, current)
|
|
4486
|
+
}
|
|
4484
4487
|
if (
|
|
4485
4488
|
exit !== undefined &&
|
|
4486
4489
|
(expiresAt === Infinity || fiber.getRef(ClockRef).currentTimeMillisUnsafe() < expiresAt)
|
|
@@ -4489,10 +4492,11 @@ const makeCachedUnsafe = <A, E, R>(
|
|
|
4489
4492
|
}
|
|
4490
4493
|
exit = undefined
|
|
4491
4494
|
const clock = fiber.getRef(ClockRef)
|
|
4492
|
-
const run: CachedRun<A, E> = { fiber:
|
|
4495
|
+
const run: CachedRun<A, E> = { fiber: new FiberImpl(fiber.context), awaiters: 0 }
|
|
4493
4496
|
current = run
|
|
4494
|
-
|
|
4495
|
-
|
|
4497
|
+
// Join before evaluating, which may synchronously re-enter and interrupt other awaiters.
|
|
4498
|
+
const joined = join(fiber, run)
|
|
4499
|
+
run.fiber.evaluate(
|
|
4496
4500
|
onExitPrimitive(self, (exit_) => {
|
|
4497
4501
|
// An abandoned run must not overwrite or clear a replacement run.
|
|
4498
4502
|
if (current !== run) return
|
|
@@ -4502,11 +4506,9 @@ const makeCachedUnsafe = <A, E, R>(
|
|
|
4502
4506
|
const duration = ttlMillis(exit_)
|
|
4503
4507
|
expiresAt = duration === Infinity ? Infinity : clock.currentTimeMillisUnsafe() + duration
|
|
4504
4508
|
exit = exit_
|
|
4505
|
-
})
|
|
4506
|
-
true,
|
|
4507
|
-
true
|
|
4509
|
+
}) as any
|
|
4508
4510
|
)
|
|
4509
|
-
return run.fiber._exit ??
|
|
4511
|
+
return run.fiber._exit ?? joined
|
|
4510
4512
|
}),
|
|
4511
4513
|
sync(() => {
|
|
4512
4514
|
exit = undefined
|
package/src/internal/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = "4.0.
|
|
2
|
+
export const version = "4.0.3"
|