effect 4.0.7 → 4.1.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/README.md +31 -1
- package/THIRD_PARTY_NOTICES.md +62 -0
- package/dist/BigDecimal.d.ts +139 -7
- package/dist/BigDecimal.d.ts.map +1 -1
- package/dist/BigDecimal.js +86 -14
- package/dist/BigDecimal.js.map +1 -1
- package/dist/Channel.d.ts +12 -6
- package/dist/Channel.d.ts.map +1 -1
- package/dist/Channel.js +9 -3
- package/dist/Channel.js.map +1 -1
- package/dist/Config.d.ts +7 -1
- package/dist/Config.d.ts.map +1 -1
- package/dist/Config.js.map +1 -1
- package/dist/Context.d.ts +18 -1
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +27 -8
- package/dist/Context.js.map +1 -1
- package/dist/Cron.d.ts +81 -11
- package/dist/Cron.d.ts.map +1 -1
- package/dist/Cron.js +101 -26
- package/dist/Cron.js.map +1 -1
- package/dist/Duration.d.ts +78 -0
- package/dist/Duration.d.ts.map +1 -1
- package/dist/Duration.js +123 -0
- package/dist/Duration.js.map +1 -1
- package/dist/Effect.d.ts +164 -7
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +11 -1
- package/dist/Effect.js.map +1 -1
- package/dist/Equal.d.ts +2 -2
- package/dist/Equal.d.ts.map +1 -1
- package/dist/Equal.js +3 -3
- package/dist/Equal.js.map +1 -1
- package/dist/FileSystem.d.ts +59 -4
- package/dist/FileSystem.d.ts.map +1 -1
- package/dist/FileSystem.js +9 -2
- package/dist/FileSystem.js.map +1 -1
- package/dist/Glob.d.ts +165 -0
- package/dist/Glob.d.ts.map +1 -0
- package/dist/Glob.js +127 -0
- package/dist/Glob.js.map +1 -0
- package/dist/Hash.d.ts +3 -2
- package/dist/Hash.d.ts.map +1 -1
- package/dist/Hash.js +5 -4
- package/dist/Hash.js.map +1 -1
- package/dist/Iterable.d.ts.map +1 -1
- package/dist/Iterable.js +11 -8
- package/dist/Iterable.js.map +1 -1
- package/dist/Match.d.ts +86 -0
- package/dist/Match.d.ts.map +1 -1
- package/dist/Match.js +72 -0
- package/dist/Match.js.map +1 -1
- package/dist/Path.d.ts.map +1 -1
- package/dist/Path.js +5 -1
- package/dist/Path.js.map +1 -1
- package/dist/Pool.d.ts +51 -0
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +61 -10
- package/dist/Pool.js.map +1 -1
- package/dist/Random.d.ts.map +1 -1
- package/dist/Random.js +3 -3
- package/dist/Random.js.map +1 -1
- package/dist/RcMap.d.ts.map +1 -1
- package/dist/RcMap.js +8 -2
- package/dist/RcMap.js.map +1 -1
- package/dist/Schedule.d.ts +18 -1
- package/dist/Schedule.d.ts.map +1 -1
- package/dist/Schedule.js +7 -2
- package/dist/Schedule.js.map +1 -1
- package/dist/Schema.d.ts +668 -42
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +652 -1
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaGetter.d.ts +6 -1
- package/dist/SchemaGetter.d.ts.map +1 -1
- package/dist/SchemaGetter.js +22 -11
- package/dist/SchemaGetter.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts +18 -2
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +20 -2
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/SchemaTransformation.d.ts +10 -5
- package/dist/SchemaTransformation.d.ts.map +1 -1
- package/dist/SchemaTransformation.js +8 -5
- package/dist/SchemaTransformation.js.map +1 -1
- package/dist/ScopedCache.d.ts.map +1 -1
- package/dist/ScopedCache.js +5 -7
- package/dist/ScopedCache.js.map +1 -1
- package/dist/Semaphore.d.ts +130 -0
- package/dist/Semaphore.d.ts.map +1 -1
- package/dist/Semaphore.js +40 -0
- package/dist/Semaphore.js.map +1 -1
- package/dist/Stream.d.ts +13 -4
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +9 -0
- package/dist/Stream.js.map +1 -1
- package/dist/Terminal.d.ts +18 -1
- package/dist/Terminal.d.ts.map +1 -1
- package/dist/Terminal.js +1 -1
- package/dist/Terminal.js.map +1 -1
- package/dist/ai/AiError.d.ts +43 -116
- package/dist/ai/AiError.d.ts.map +1 -1
- package/dist/ai/AiError.js +53 -72
- package/dist/ai/AiError.js.map +1 -1
- package/dist/ai/Decision.d.ts +196 -15
- package/dist/ai/Decision.d.ts.map +1 -1
- package/dist/ai/Decision.js +130 -0
- package/dist/ai/Decision.js.map +1 -1
- package/dist/ai/DecisionModel.d.ts +54 -13
- package/dist/ai/DecisionModel.d.ts.map +1 -1
- package/dist/ai/DecisionModel.js +123 -12
- package/dist/ai/DecisionModel.js.map +1 -1
- package/dist/ai/McpClient.d.ts +171 -19
- package/dist/ai/McpClient.d.ts.map +1 -1
- package/dist/ai/McpClient.js +277 -66
- package/dist/ai/McpClient.js.map +1 -1
- package/dist/ai/McpSchema.d.ts +108 -0
- package/dist/ai/McpSchema.d.ts.map +1 -1
- package/dist/ai/McpSchema.js +99 -0
- package/dist/ai/McpSchema.js.map +1 -1
- package/dist/ai/McpServer.d.ts +744 -29
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +763 -136
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/McpTest.d.ts +130 -0
- package/dist/ai/McpTest.d.ts.map +1 -0
- package/dist/ai/McpTest.js +237 -0
- package/dist/ai/McpTest.js.map +1 -0
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +20 -11
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +1 -25
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js +1 -10
- package/dist/ai/Response.js.map +1 -1
- package/dist/ai/Tool.d.ts +19 -1
- package/dist/ai/Tool.d.ts.map +1 -1
- package/dist/ai/Tool.js +5 -7
- package/dist/ai/Tool.js.map +1 -1
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +5 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/internal/mcpCore.js +89 -15
- package/dist/ai/internal/mcpCore.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +7 -6
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +7 -6
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +7 -6
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +7 -6
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +60 -17
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js +25 -0
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +8 -6
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- package/dist/ai/internal/mcpSkills.d.ts +2 -0
- package/dist/ai/internal/mcpSkills.d.ts.map +1 -0
- package/dist/ai/internal/mcpSkills.js +428 -0
- package/dist/ai/internal/mcpSkills.js.map +1 -0
- package/dist/ai/internal/mcpStatefulRuntime.js +6 -1
- package/dist/ai/internal/mcpStatefulRuntime.js.map +1 -1
- package/dist/cli/Argument.d.ts +6 -0
- package/dist/cli/Argument.d.ts.map +1 -1
- package/dist/cli/Argument.js +2 -0
- package/dist/cli/Argument.js.map +1 -1
- package/dist/cli/CliOutput.d.ts +65 -1
- package/dist/cli/CliOutput.d.ts.map +1 -1
- package/dist/cli/CliOutput.js +94 -37
- package/dist/cli/CliOutput.js.map +1 -1
- package/dist/cli/Command.d.ts +23 -0
- package/dist/cli/Command.d.ts.map +1 -1
- package/dist/cli/Command.js +8 -0
- package/dist/cli/Command.js.map +1 -1
- package/dist/cli/Flag.d.ts +107 -0
- package/dist/cli/Flag.d.ts.map +1 -1
- package/dist/cli/Flag.js +37 -0
- package/dist/cli/Flag.js.map +1 -1
- package/dist/cli/HelpDoc.d.ts +15 -1
- package/dist/cli/HelpDoc.d.ts.map +1 -1
- package/dist/cli/Param.d.ts +6 -0
- package/dist/cli/Param.d.ts.map +1 -1
- package/dist/cli/Param.js +2 -0
- package/dist/cli/Param.js.map +1 -1
- package/dist/cli/Primitive.d.ts +14 -0
- package/dist/cli/Primitive.d.ts.map +1 -1
- package/dist/cli/Primitive.js +15 -1
- package/dist/cli/Primitive.js.map +1 -1
- package/dist/cli/Prompt.d.ts +1 -0
- package/dist/cli/Prompt.d.ts.map +1 -1
- package/dist/cli/Prompt.js +1 -0
- package/dist/cli/Prompt.js.map +1 -1
- package/dist/cli/internal/help.d.ts.map +1 -1
- package/dist/cli/internal/help.js +17 -10
- package/dist/cli/internal/help.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +15 -5
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Reply.d.ts +1 -1
- package/dist/cluster/Reply.d.ts.map +1 -1
- package/dist/cluster/Reply.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +93 -28
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/cluster/ShardingConfig.d.ts +72 -6
- package/dist/cluster/ShardingConfig.d.ts.map +1 -1
- package/dist/cluster/ShardingConfig.js +4 -0
- package/dist/cluster/ShardingConfig.js.map +1 -1
- package/dist/cluster/SingleRunner.d.ts +3 -3
- package/dist/cluster/SingleRunner.d.ts.map +1 -1
- package/dist/cluster/SingleRunner.js +1 -1
- package/dist/cluster/SqlMessageStorage.d.ts +37 -6
- package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
- package/dist/cluster/SqlMessageStorage.js +34 -7
- package/dist/cluster/SqlMessageStorage.js.map +1 -1
- package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
- package/dist/cluster/SqlRunnerStorage.js +23 -8
- package/dist/cluster/SqlRunnerStorage.js.map +1 -1
- package/dist/cluster/internal/entityManager.js +15 -7
- package/dist/cluster/internal/entityManager.js.map +1 -1
- package/dist/crypto/Aead.d.ts +323 -0
- package/dist/crypto/Aead.d.ts.map +1 -0
- package/dist/crypto/Aead.js +331 -0
- package/dist/crypto/Aead.js.map +1 -0
- package/dist/crypto/Cipher.d.ts +236 -0
- package/dist/crypto/Cipher.d.ts.map +1 -0
- package/dist/crypto/Cipher.js +233 -0
- package/dist/crypto/Cipher.js.map +1 -0
- package/dist/crypto/CryptoError.d.ts +212 -0
- package/dist/crypto/CryptoError.d.ts.map +1 -0
- package/dist/crypto/CryptoError.js +202 -0
- package/dist/crypto/CryptoError.js.map +1 -0
- package/dist/crypto/Digest.d.ts +143 -0
- package/dist/crypto/Digest.d.ts.map +1 -0
- package/dist/crypto/Digest.js +146 -0
- package/dist/crypto/Digest.js.map +1 -0
- package/dist/crypto/Kdf.d.ts +220 -0
- package/dist/crypto/Kdf.d.ts.map +1 -0
- package/dist/crypto/Kdf.js +212 -0
- package/dist/crypto/Kdf.js.map +1 -0
- package/dist/crypto/KeyAgreement.d.ts +324 -0
- package/dist/crypto/KeyAgreement.d.ts.map +1 -0
- package/dist/crypto/KeyAgreement.js +303 -0
- package/dist/crypto/KeyAgreement.js.map +1 -0
- package/dist/crypto/KeyFormat.d.ts +341 -0
- package/dist/crypto/KeyFormat.d.ts.map +1 -0
- package/dist/crypto/KeyFormat.js +556 -0
- package/dist/crypto/KeyFormat.js.map +1 -0
- package/dist/crypto/KeyWrap.d.ts +390 -0
- package/dist/crypto/KeyWrap.d.ts.map +1 -0
- package/dist/crypto/KeyWrap.js +381 -0
- package/dist/crypto/KeyWrap.js.map +1 -0
- package/dist/crypto/Mac.d.ts +278 -0
- package/dist/crypto/Mac.d.ts.map +1 -0
- package/dist/crypto/Mac.js +288 -0
- package/dist/crypto/Mac.js.map +1 -0
- package/dist/crypto/SecureRandom.d.ts +340 -0
- package/dist/crypto/SecureRandom.d.ts.map +1 -0
- package/dist/crypto/SecureRandom.js +257 -0
- package/dist/crypto/SecureRandom.js.map +1 -0
- package/dist/crypto/Signature.d.ts +449 -0
- package/dist/crypto/Signature.d.ts.map +1 -0
- package/dist/crypto/Signature.js +484 -0
- package/dist/crypto/Signature.js.map +1 -0
- package/dist/crypto/WebCrypto.d.ts +80 -0
- package/dist/crypto/WebCrypto.d.ts.map +1 -0
- package/dist/crypto/WebCrypto.js +63 -0
- package/dist/crypto/WebCrypto.js.map +1 -0
- package/dist/crypto/index.d.ts +65 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +66 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/internal/asymmetric.d.ts +2 -0
- package/dist/crypto/internal/asymmetric.d.ts.map +1 -0
- package/dist/crypto/internal/asymmetric.js +210 -0
- package/dist/crypto/internal/asymmetric.js.map +1 -0
- package/dist/crypto/internal/chacha.d.ts +46 -0
- package/dist/crypto/internal/chacha.d.ts.map +1 -0
- package/dist/crypto/internal/chacha.js +885 -0
- package/dist/crypto/internal/chacha.js.map +1 -0
- package/dist/crypto/internal/der.d.ts +6 -0
- package/dist/crypto/internal/der.d.ts.map +1 -0
- package/dist/crypto/internal/der.js +86 -0
- package/dist/crypto/internal/der.js.map +1 -0
- package/dist/crypto/internal/digest.d.ts +2 -0
- package/dist/crypto/internal/digest.d.ts.map +1 -0
- package/dist/crypto/internal/digest.js +15 -0
- package/dist/crypto/internal/digest.js.map +1 -0
- package/dist/crypto/internal/key.d.ts +2 -0
- package/dist/crypto/internal/key.d.ts.map +1 -0
- package/dist/crypto/internal/key.js +171 -0
- package/dist/crypto/internal/key.js.map +1 -0
- package/dist/crypto/internal/md5.d.ts +39 -0
- package/dist/crypto/internal/md5.d.ts.map +1 -0
- package/dist/crypto/internal/md5.js +179 -0
- package/dist/crypto/internal/md5.js.map +1 -0
- package/dist/crypto/internal/webCrypto.d.ts +2 -0
- package/dist/crypto/internal/webCrypto.d.ts.map +1 -0
- package/dist/crypto/internal/webCrypto.js +111 -0
- package/dist/crypto/internal/webCrypto.js.map +1 -0
- package/dist/encoding/Base64Url.d.ts.map +1 -1
- package/dist/encoding/Base64Url.js +29 -5
- package/dist/encoding/Base64Url.js.map +1 -1
- package/dist/encoding/Csv.d.ts +608 -0
- package/dist/encoding/Csv.d.ts.map +1 -0
- package/dist/encoding/Csv.js +718 -0
- package/dist/encoding/Csv.js.map +1 -0
- package/dist/encoding/Hex.d.ts +3 -2
- package/dist/encoding/Hex.d.ts.map +1 -1
- package/dist/encoding/Hex.js +3 -2
- package/dist/encoding/Hex.js.map +1 -1
- package/dist/encoding/Ndjson.d.ts +5 -4
- package/dist/encoding/Ndjson.d.ts.map +1 -1
- package/dist/encoding/Ndjson.js +6 -5
- package/dist/encoding/Ndjson.js.map +1 -1
- package/dist/encoding/SchemaBinary.d.ts.map +1 -1
- package/dist/encoding/SchemaBinary.js +42 -4
- package/dist/encoding/SchemaBinary.js.map +1 -1
- package/dist/encoding/Sse.d.ts +3 -1
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +5 -3
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/encoding/Toml.d.ts +17 -3
- package/dist/encoding/Toml.d.ts.map +1 -1
- package/dist/encoding/Toml.js +104 -16
- package/dist/encoding/Toml.js.map +1 -1
- package/dist/encoding/Yaml.d.ts +8 -1
- package/dist/encoding/Yaml.d.ts.map +1 -1
- package/dist/encoding/Yaml.js +54 -18
- package/dist/encoding/Yaml.js.map +1 -1
- package/dist/encoding/index.d.ts +5 -0
- package/dist/encoding/index.d.ts.map +1 -1
- package/dist/encoding/index.js +5 -0
- package/dist/encoding/index.js.map +1 -1
- package/dist/eventlog/EventJournal.d.ts.map +1 -1
- package/dist/eventlog/EventJournal.js +6 -3
- package/dist/eventlog/EventJournal.js.map +1 -1
- package/dist/eventlog/EventLog.d.ts +126 -4
- package/dist/eventlog/EventLog.d.ts.map +1 -1
- package/dist/eventlog/EventLog.js +207 -12
- package/dist/eventlog/EventLog.js.map +1 -1
- package/dist/eventlog/EventLogEncryption.d.ts +41 -4
- package/dist/eventlog/EventLogEncryption.d.ts.map +1 -1
- package/dist/eventlog/EventLogEncryption.js +83 -64
- package/dist/eventlog/EventLogEncryption.js.map +1 -1
- package/dist/eventlog/EventLogMessage.d.ts +279 -0
- package/dist/eventlog/EventLogMessage.d.ts.map +1 -1
- package/dist/eventlog/EventLogMessage.js +217 -1
- package/dist/eventlog/EventLogMessage.js.map +1 -1
- package/dist/eventlog/EventLogRemote.d.ts +200 -28
- package/dist/eventlog/EventLogRemote.d.ts.map +1 -1
- package/dist/eventlog/EventLogRemote.js +186 -16
- package/dist/eventlog/EventLogRemote.js.map +1 -1
- package/dist/eventlog/EventLogServer.d.ts +10 -1
- package/dist/eventlog/EventLogServer.d.ts.map +1 -1
- package/dist/eventlog/EventLogServer.js +45 -22
- package/dist/eventlog/EventLogServer.js.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.d.ts +336 -5
- package/dist/eventlog/EventLogServerEncrypted.d.ts.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.js +769 -14
- package/dist/eventlog/EventLogServerEncrypted.js.map +1 -1
- package/dist/eventlog/EventLogServerUnencrypted.d.ts +17 -0
- package/dist/eventlog/EventLogServerUnencrypted.d.ts.map +1 -1
- package/dist/eventlog/EventLogServerUnencrypted.js +14 -2
- package/dist/eventlog/EventLogServerUnencrypted.js.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.d.ts +26 -3
- package/dist/eventlog/EventLogSessionAuth.d.ts.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.js +63 -64
- package/dist/eventlog/EventLogSessionAuth.js.map +1 -1
- package/dist/eventlog/EventRow.d.ts +4 -0
- package/dist/eventlog/EventRow.d.ts.map +1 -1
- package/dist/eventlog/EventRow.js +12 -2
- package/dist/eventlog/EventRow.js.map +1 -1
- package/dist/eventlog/SqlEventJournal.js +2 -2
- package/dist/eventlog/SqlEventJournal.js.map +1 -1
- package/dist/eventlog/SqlEventLogServerEncrypted.d.ts +21 -5
- package/dist/eventlog/SqlEventLogServerEncrypted.d.ts.map +1 -1
- package/dist/eventlog/SqlEventLogServerEncrypted.js +1048 -113
- package/dist/eventlog/SqlEventLogServerEncrypted.js.map +1 -1
- package/dist/eventlog/SqlEventLogServerUnencrypted.js +2 -2
- package/dist/eventlog/SqlEventLogServerUnencrypted.js.map +1 -1
- package/dist/eventlog/internal/batches.d.ts +12 -0
- package/dist/eventlog/internal/batches.d.ts.map +1 -0
- package/dist/eventlog/internal/batches.js +26 -0
- package/dist/eventlog/internal/batches.js.map +1 -0
- package/dist/eventlog/internal/compaction.d.ts +38 -0
- package/dist/eventlog/internal/compaction.d.ts.map +1 -0
- package/dist/eventlog/internal/compaction.js +33 -0
- package/dist/eventlog/internal/compaction.js.map +1 -0
- package/dist/eventlog/internal/identityRootSecretDerivation.js +66 -67
- package/dist/eventlog/internal/identityRootSecretDerivation.js.map +1 -1
- package/dist/eventlog/internal/remoteCompaction.d.ts +31 -0
- package/dist/eventlog/internal/remoteCompaction.d.ts.map +1 -0
- package/dist/eventlog/internal/remoteCompaction.js +76 -0
- package/dist/eventlog/internal/remoteCompaction.js.map +1 -0
- package/dist/graphql/GraphQLClient.d.ts +3 -1
- package/dist/graphql/GraphQLClient.d.ts.map +1 -1
- package/dist/graphql/GraphQLClient.js.map +1 -1
- package/dist/graphql/GraphQLClientError.d.ts +14 -9
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -1
- package/dist/graphql/GraphQLClientError.js +14 -21
- package/dist/graphql/GraphQLClientError.js.map +1 -1
- package/dist/graphql/GraphQLProtocol.d.ts +12 -3
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
- package/dist/graphql/GraphQLProtocol.js +4 -1
- package/dist/graphql/GraphQLProtocol.js.map +1 -1
- package/dist/http/Cookies.d.ts +172 -0
- package/dist/http/Cookies.d.ts.map +1 -1
- package/dist/http/Cookies.js +143 -1
- package/dist/http/Cookies.js.map +1 -1
- package/dist/http/FetchHttpClient.d.ts +26 -2
- package/dist/http/FetchHttpClient.d.ts.map +1 -1
- package/dist/http/FetchHttpClient.js +78 -15
- package/dist/http/FetchHttpClient.js.map +1 -1
- package/dist/http/HttpClient.d.ts +268 -15
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +140 -7
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpClientRequest.d.ts +9 -5
- package/dist/http/HttpClientRequest.d.ts.map +1 -1
- package/dist/http/HttpClientRequest.js +2 -3
- package/dist/http/HttpClientRequest.js.map +1 -1
- package/dist/http/HttpClientResponse.d.ts.map +1 -1
- package/dist/http/HttpClientResponse.js +3 -2
- package/dist/http/HttpClientResponse.js.map +1 -1
- package/dist/http/HttpMethod.d.ts +7 -1
- package/dist/http/HttpMethod.d.ts.map +1 -1
- package/dist/http/HttpMethod.js +3 -8
- package/dist/http/HttpMethod.js.map +1 -1
- package/dist/http/HttpServerRequest.d.ts +5 -2
- package/dist/http/HttpServerRequest.d.ts.map +1 -1
- package/dist/http/HttpServerRequest.js +44 -39
- package/dist/http/HttpServerRequest.js.map +1 -1
- package/dist/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/http/HttpServerResponse.js +3 -2
- package/dist/http/HttpServerResponse.js.map +1 -1
- package/dist/http/HttpTraceContext.d.ts +2 -1
- package/dist/http/HttpTraceContext.d.ts.map +1 -1
- package/dist/http/HttpTraceContext.js +7 -5
- package/dist/http/HttpTraceContext.js.map +1 -1
- package/dist/http/ProblemDetails.d.ts +96 -0
- package/dist/http/ProblemDetails.d.ts.map +1 -0
- package/dist/http/ProblemDetails.js +98 -0
- package/dist/http/ProblemDetails.js.map +1 -0
- package/dist/http/index.d.ts +5 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +5 -0
- package/dist/http/index.js.map +1 -1
- package/dist/http/internal/decompression.js +3 -2
- package/dist/http/internal/decompression.js.map +1 -1
- package/dist/http/internal/mimeTypes.d.ts +4 -1
- package/dist/http/internal/mimeTypes.d.ts.map +1 -1
- package/dist/http/internal/mimeTypes.js +4 -1
- package/dist/http/internal/mimeTypes.js.map +1 -1
- package/dist/http/internal/urlParams.js +8 -0
- package/dist/http/internal/urlParams.js.map +1 -1
- package/dist/http-api/HttpApiBuilder.js +19 -5
- package/dist/http-api/HttpApiBuilder.js.map +1 -1
- package/dist/http-api/HttpApiClient.d.ts +63 -0
- package/dist/http-api/HttpApiClient.d.ts.map +1 -1
- package/dist/http-api/HttpApiClient.js +51 -0
- package/dist/http-api/HttpApiClient.js.map +1 -1
- package/dist/http-api/HttpApiEndpoint.d.ts.map +1 -1
- package/dist/http-api/HttpApiEndpoint.js +14 -5
- package/dist/http-api/HttpApiEndpoint.js.map +1 -1
- package/dist/http-api/HttpApiError.d.ts +185 -13
- package/dist/http-api/HttpApiError.d.ts.map +1 -1
- package/dist/http-api/HttpApiError.js +128 -0
- package/dist/http-api/HttpApiError.js.map +1 -1
- package/dist/http-api/HttpApiMiddleware.d.ts +64 -17
- package/dist/http-api/HttpApiMiddleware.d.ts.map +1 -1
- package/dist/http-api/HttpApiMiddleware.js +85 -1
- package/dist/http-api/HttpApiMiddleware.js.map +1 -1
- package/dist/http-api/HttpApiSchema.d.ts +76 -14
- package/dist/http-api/HttpApiSchema.d.ts.map +1 -1
- package/dist/http-api/HttpApiSchema.js +54 -1
- package/dist/http-api/HttpApiSchema.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/dateTime.js +49 -24
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +35 -14
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/glob.d.ts +2 -0
- package/dist/internal/glob.d.ts.map +1 -0
- package/dist/internal/glob.js +808 -0
- package/dist/internal/glob.js.map +1 -0
- package/dist/internal/graphql.js +21 -7
- package/dist/internal/graphql.js.map +1 -1
- package/dist/internal/graphqlHeaders.d.ts +7 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -1
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -1
- package/dist/internal/matcher.js +10 -0
- package/dist/internal/matcher.js.map +1 -1
- package/dist/internal/persistence.js +9 -0
- package/dist/internal/persistence.js.map +1 -1
- package/dist/internal/rcRef.js +4 -1
- package/dist/internal/rcRef.js.map +1 -1
- package/dist/internal/request.js +29 -42
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/fromJsonSchemaDocument.js +7 -8
- package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +6 -3
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/trie.js +42 -13
- package/dist/internal/trie.js.map +1 -1
- package/dist/internal/ulid.js +41 -0
- package/dist/internal/ulid.js.map +1 -1
- package/dist/internal/uuid.js +52 -8
- package/dist/internal/uuid.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/jose/JoseError.d.ts +285 -0
- package/dist/jose/JoseError.d.ts.map +1 -0
- package/dist/jose/JoseError.js +269 -0
- package/dist/jose/JoseError.js.map +1 -0
- package/dist/jose/Jwe.d.ts +128 -0
- package/dist/jose/Jwe.d.ts.map +1 -0
- package/dist/jose/Jwe.js +134 -0
- package/dist/jose/Jwe.js.map +1 -0
- package/dist/jose/Jwk.d.ts +597 -0
- package/dist/jose/Jwk.d.ts.map +1 -0
- package/dist/jose/Jwk.js +503 -0
- package/dist/jose/Jwk.js.map +1 -0
- package/dist/jose/Jwks.d.ts +236 -0
- package/dist/jose/Jwks.d.ts.map +1 -0
- package/dist/jose/Jwks.js +306 -0
- package/dist/jose/Jwks.js.map +1 -0
- package/dist/jose/Jws.d.ts +177 -0
- package/dist/jose/Jws.d.ts.map +1 -0
- package/dist/jose/Jws.js +183 -0
- package/dist/jose/Jws.js.map +1 -0
- package/dist/jose/Jwt.d.ts +270 -0
- package/dist/jose/Jwt.d.ts.map +1 -0
- package/dist/jose/Jwt.js +293 -0
- package/dist/jose/Jwt.js.map +1 -0
- package/dist/jose/index.d.ts +51 -0
- package/dist/jose/index.d.ts.map +1 -0
- package/dist/jose/index.js +52 -0
- package/dist/jose/index.js.map +1 -0
- package/dist/jose/internal/compact.d.ts +2 -0
- package/dist/jose/internal/compact.d.ts.map +1 -0
- package/dist/jose/internal/compact.js +179 -0
- package/dist/jose/internal/compact.js.map +1 -0
- package/dist/net/Host.d.ts.map +1 -1
- package/dist/net/Host.js +5 -10
- package/dist/net/Host.js.map +1 -1
- package/dist/observability/Otlp.d.ts +15 -4
- package/dist/observability/Otlp.d.ts.map +1 -1
- package/dist/observability/Otlp.js +10 -0
- package/dist/observability/Otlp.js.map +1 -1
- package/dist/observability/OtlpResource.d.ts +7 -0
- package/dist/observability/OtlpResource.d.ts.map +1 -1
- package/dist/observability/OtlpResource.js +11 -1
- package/dist/observability/OtlpResource.js.map +1 -1
- package/dist/observability/OtlpSerialization.d.ts +1 -1
- package/dist/observability/OtlpSerialization.d.ts.map +1 -1
- package/dist/observability/OtlpSerialization.js +10 -4
- package/dist/observability/OtlpSerialization.js.map +1 -1
- package/dist/observability/internal/otlpProtobuf.js +3 -3
- package/dist/observability/internal/otlpProtobuf.js.map +1 -1
- package/dist/observability/internal/protobuf.js +6 -0
- package/dist/observability/internal/protobuf.js.map +1 -1
- package/dist/persistence/CircuitBreaker.d.ts +98 -38
- package/dist/persistence/CircuitBreaker.d.ts.map +1 -1
- package/dist/persistence/CircuitBreaker.js +67 -29
- package/dist/persistence/CircuitBreaker.js.map +1 -1
- package/dist/persistence/KeyValueStore.d.ts +47 -2
- package/dist/persistence/KeyValueStore.d.ts.map +1 -1
- package/dist/persistence/KeyValueStore.js +129 -18
- package/dist/persistence/KeyValueStore.js.map +1 -1
- package/dist/persistence/PersistedQueue.d.ts +9 -0
- package/dist/persistence/PersistedQueue.d.ts.map +1 -1
- package/dist/persistence/PersistedQueue.js +94 -52
- package/dist/persistence/PersistedQueue.js.map +1 -1
- package/dist/persistence/Persistence.d.ts +10 -4
- package/dist/persistence/Persistence.d.ts.map +1 -1
- package/dist/persistence/Persistence.js +18 -11
- package/dist/persistence/Persistence.js.map +1 -1
- package/dist/persistence/RateLimiter.d.ts +8 -0
- package/dist/persistence/RateLimiter.d.ts.map +1 -1
- package/dist/persistence/RateLimiter.js +63 -12
- package/dist/persistence/RateLimiter.js.map +1 -1
- package/dist/persistence/index.d.ts +1 -1
- package/dist/persistence/index.js +1 -1
- package/dist/process/ChildProcess.d.ts +207 -67
- package/dist/process/ChildProcess.d.ts.map +1 -1
- package/dist/process/ChildProcess.js +56 -158
- package/dist/process/ChildProcess.js.map +1 -1
- package/dist/process/ChildProcessSpawner.d.ts +5 -0
- package/dist/process/ChildProcessSpawner.d.ts.map +1 -1
- package/dist/process/ChildProcessSpawner.js.map +1 -1
- package/dist/process/CommandLine.d.ts +294 -0
- package/dist/process/CommandLine.d.ts.map +1 -0
- package/dist/process/CommandLine.js +481 -0
- package/dist/process/CommandLine.js.map +1 -0
- package/dist/process/index.d.ts +5 -0
- package/dist/process/index.d.ts.map +1 -1
- package/dist/process/index.js +5 -0
- package/dist/process/index.js.map +1 -1
- package/dist/reactivity/Atom.d.ts +2 -1
- package/dist/reactivity/Atom.d.ts.map +1 -1
- package/dist/reactivity/Atom.js +3 -4
- package/dist/reactivity/Atom.js.map +1 -1
- package/dist/reactivity/AtomRegistry.js +17 -7
- package/dist/reactivity/AtomRegistry.js.map +1 -1
- package/dist/rpc/Rpc.d.ts +1 -1
- package/dist/rpc/Rpc.d.ts.map +1 -1
- package/dist/rpc/Rpc.js.map +1 -1
- package/dist/rpc/RpcClient.d.ts +28 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +78 -27
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcMessage.d.ts +13 -3
- package/dist/rpc/RpcMessage.d.ts.map +1 -1
- package/dist/rpc/RpcMessage.js +1 -0
- package/dist/rpc/RpcMessage.js.map +1 -1
- package/dist/rpc/RpcSerialization.d.ts.map +1 -1
- package/dist/rpc/RpcSerialization.js +6 -0
- package/dist/rpc/RpcSerialization.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts +31 -0
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +139 -66
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/schema/Model.d.ts +14 -0
- package/dist/schema/Model.d.ts.map +1 -1
- package/dist/schema/Model.js +18 -3
- package/dist/schema/Model.js.map +1 -1
- package/dist/sql/Migrator.d.ts +9 -20
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +20 -53
- package/dist/sql/Migrator.js.map +1 -1
- package/dist/sql/MigratorFileSystem.d.ts +21 -0
- package/dist/sql/MigratorFileSystem.d.ts.map +1 -0
- package/dist/sql/MigratorFileSystem.js +57 -0
- package/dist/sql/MigratorFileSystem.js.map +1 -0
- package/dist/sql/SqlClient.d.ts +38 -3
- package/dist/sql/SqlClient.d.ts.map +1 -1
- package/dist/sql/SqlClient.js +9 -12
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/sql/Statement.d.ts +61 -9
- package/dist/sql/Statement.d.ts.map +1 -1
- package/dist/sql/Statement.js +24 -33
- package/dist/sql/Statement.js.map +1 -1
- package/dist/sql/index.js +1 -1
- package/dist/testing/index.d.ts +0 -5
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +0 -5
- package/dist/testing/index.js.map +1 -1
- package/dist/workflow/Workflow.d.ts +4 -0
- package/dist/workflow/Workflow.d.ts.map +1 -1
- package/dist/workflow/Workflow.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +5 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/dist/workflow/internal/crypto.js +9 -8
- package/dist/workflow/internal/crypto.js.map +1 -1
- package/package.json +7 -1
- package/src/BigDecimal.ts +173 -16
- package/src/Channel.ts +17 -11
- package/src/Config.ts +7 -1
- package/src/Context.ts +34 -10
- package/src/Cron.ts +178 -29
- package/src/Duration.ts +140 -0
- package/src/Effect.ts +914 -85
- package/src/Equal.ts +3 -3
- package/src/FileSystem.ts +65 -4
- package/src/Glob.ts +205 -0
- package/src/Hash.ts +5 -4
- package/src/Iterable.ts +8 -5
- package/src/Match.ts +127 -0
- package/src/Path.ts +6 -1
- package/src/Pool.ts +75 -16
- package/src/Random.ts +3 -3
- package/src/RcMap.ts +5 -1
- package/src/Schedule.ts +26 -3
- package/src/Schema.ts +1191 -63
- package/src/SchemaGetter.ts +27 -11
- package/src/SchemaRepresentation.ts +23 -2
- package/src/SchemaTransformation.ts +10 -5
- package/src/ScopedCache.ts +5 -9
- package/src/Semaphore.ts +141 -0
- package/src/Stream.ts +15 -6
- package/src/Terminal.ts +19 -1
- package/src/ai/AiError.ts +56 -80
- package/src/ai/Decision.ts +257 -6
- package/src/ai/DecisionModel.ts +171 -28
- package/src/ai/McpClient.ts +488 -91
- package/src/ai/McpSchema.ts +119 -0
- package/src/ai/McpServer.ts +1525 -319
- package/src/ai/McpTest.ts +380 -0
- package/src/ai/Prompt.ts +16 -11
- package/src/ai/Response.ts +2 -22
- package/src/ai/Tool.ts +32 -18
- package/src/ai/index.ts +6 -0
- package/src/ai/internal/mcpCore.ts +243 -56
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +7 -8
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +7 -8
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +7 -8
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +7 -8
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +56 -17
- package/src/ai/internal/mcpProtocol.ts +44 -0
- package/src/ai/internal/mcpRuntime.ts +15 -8
- package/src/ai/internal/mcpSkills.ts +480 -0
- package/src/ai/internal/mcpStatefulRuntime.ts +14 -2
- package/src/cli/Argument.ts +6 -0
- package/src/cli/CliOutput.ts +117 -49
- package/src/cli/Command.ts +23 -0
- package/src/cli/Flag.ts +107 -0
- package/src/cli/HelpDoc.ts +18 -1
- package/src/cli/Param.ts +6 -0
- package/src/cli/Primitive.ts +16 -1
- package/src/cli/Prompt.ts +1 -0
- package/src/cli/internal/help.ts +21 -9
- package/src/cluster/ClusterWorkflowEngine.ts +22 -8
- package/src/cluster/Reply.ts +1 -1
- package/src/cluster/Sharding.ts +96 -26
- package/src/cluster/ShardingConfig.ts +41 -3
- package/src/cluster/SingleRunner.ts +3 -3
- package/src/cluster/SqlMessageStorage.ts +49 -17
- package/src/cluster/SqlRunnerStorage.ts +37 -27
- package/src/cluster/internal/entityManager.ts +24 -10
- package/src/crypto/Aead.ts +517 -0
- package/src/crypto/Cipher.ts +326 -0
- package/src/crypto/CryptoError.ts +242 -0
- package/src/crypto/Digest.ts +167 -0
- package/src/crypto/Kdf.ts +315 -0
- package/src/crypto/KeyAgreement.ts +489 -0
- package/src/crypto/KeyFormat.ts +741 -0
- package/src/crypto/KeyWrap.ts +639 -0
- package/src/crypto/Mac.ts +386 -0
- package/src/crypto/SecureRandom.ts +459 -0
- package/src/crypto/Signature.ts +673 -0
- package/src/crypto/WebCrypto.ts +84 -0
- package/src/crypto/index.ts +78 -0
- package/src/crypto/internal/asymmetric.ts +330 -0
- package/src/crypto/internal/chacha.ts +1088 -0
- package/src/crypto/internal/der.ts +110 -0
- package/src/crypto/internal/digest.ts +16 -0
- package/src/crypto/internal/key.ts +242 -0
- package/src/crypto/internal/md5.ts +170 -0
- package/src/crypto/internal/webCrypto.ts +150 -0
- package/src/encoding/Base64Url.ts +32 -5
- package/src/encoding/Csv.ts +1298 -0
- package/src/encoding/Hex.ts +3 -2
- package/src/encoding/Ndjson.ts +6 -5
- package/src/encoding/SchemaBinary.ts +40 -4
- package/src/encoding/Sse.ts +5 -3
- package/src/encoding/Toml.ts +85 -20
- package/src/encoding/Yaml.ts +61 -18
- package/src/encoding/index.ts +6 -0
- package/src/eventlog/EventJournal.ts +4 -3
- package/src/eventlog/EventLog.ts +347 -15
- package/src/eventlog/EventLogEncryption.ts +118 -110
- package/src/eventlog/EventLogMessage.ts +273 -1
- package/src/eventlog/EventLogRemote.ts +367 -23
- package/src/eventlog/EventLogServer.ts +54 -20
- package/src/eventlog/EventLogServerEncrypted.ts +1135 -67
- package/src/eventlog/EventLogServerUnencrypted.ts +19 -2
- package/src/eventlog/EventLogSessionAuth.ts +82 -70
- package/src/eventlog/EventRow.ts +12 -3
- package/src/eventlog/SqlEventJournal.ts +2 -2
- package/src/eventlog/SqlEventLogServerEncrypted.ts +1244 -136
- package/src/eventlog/SqlEventLogServerUnencrypted.ts +2 -2
- package/src/eventlog/internal/batches.ts +30 -0
- package/src/eventlog/internal/compaction.ts +64 -0
- package/src/eventlog/internal/identityRootSecretDerivation.ts +93 -123
- package/src/eventlog/internal/remoteCompaction.ts +92 -0
- package/src/graphql/GraphQLClient.ts +3 -1
- package/src/graphql/GraphQLClientError.ts +15 -22
- package/src/graphql/GraphQLProtocol.ts +12 -3
- package/src/http/Cookies.ts +281 -1
- package/src/http/FetchHttpClient.ts +88 -16
- package/src/http/HttpClient.ts +299 -33
- package/src/http/HttpClientRequest.ts +12 -9
- package/src/http/HttpClientResponse.ts +3 -2
- package/src/http/HttpMethod.ts +10 -2
- package/src/http/HttpServerRequest.ts +76 -53
- package/src/http/HttpServerResponse.ts +3 -2
- package/src/http/HttpTraceContext.ts +7 -4
- package/src/http/ProblemDetails.ts +149 -0
- package/src/http/index.ts +6 -0
- package/src/http/internal/decompression.ts +4 -4
- package/src/http/internal/mimeTypes.ts +4 -1
- package/src/http/internal/urlParams.ts +8 -0
- package/src/http-api/HttpApiBuilder.ts +24 -12
- package/src/http-api/HttpApiClient.ts +67 -1
- package/src/http-api/HttpApiEndpoint.ts +14 -5
- package/src/http-api/HttpApiError.ts +250 -0
- package/src/http-api/HttpApiMiddleware.ts +95 -1
- package/src/http-api/HttpApiSchema.ts +106 -15
- package/src/index.ts +6 -6
- package/src/internal/dateTime.ts +54 -24
- package/src/internal/effect.ts +40 -18
- package/src/internal/glob.ts +952 -0
- package/src/internal/graphql.ts +27 -9
- package/src/internal/graphqlHeaders.ts +13 -0
- package/src/internal/matcher.ts +17 -0
- package/src/internal/persistence.ts +11 -0
- package/src/internal/rcRef.ts +4 -1
- package/src/internal/request.ts +51 -58
- package/src/internal/schema/fromJsonSchemaDocument.ts +11 -10
- package/src/internal/schema/toCodeDocument.ts +7 -3
- package/src/internal/trie.ts +43 -14
- package/src/internal/ulid.ts +44 -0
- package/src/internal/uuid.ts +57 -16
- package/src/internal/version.ts +1 -1
- package/src/jose/JoseError.ts +329 -0
- package/src/jose/Jwe.ts +196 -0
- package/src/jose/Jwk.ts +777 -0
- package/src/jose/Jwks.ts +418 -0
- package/src/jose/Jws.ts +312 -0
- package/src/jose/Jwt.ts +493 -0
- package/src/jose/index.ts +58 -0
- package/src/jose/internal/compact.ts +195 -0
- package/src/net/Host.ts +5 -10
- package/src/observability/Otlp.ts +21 -4
- package/src/observability/OtlpResource.ts +9 -1
- package/src/observability/OtlpSerialization.ts +13 -5
- package/src/observability/internal/otlpProtobuf.ts +6 -6
- package/src/observability/internal/protobuf.ts +10 -0
- package/src/persistence/CircuitBreaker.ts +113 -48
- package/src/persistence/KeyValueStore.ts +219 -22
- package/src/persistence/PersistedQueue.ts +128 -59
- package/src/persistence/Persistence.ts +20 -13
- package/src/persistence/RateLimiter.ts +84 -15
- package/src/persistence/index.ts +1 -1
- package/src/process/ChildProcess.ts +255 -260
- package/src/process/ChildProcessSpawner.ts +5 -0
- package/src/process/CommandLine.ts +630 -0
- package/src/process/index.ts +6 -0
- package/src/reactivity/Atom.ts +5 -5
- package/src/reactivity/AtomRegistry.ts +20 -9
- package/src/rpc/Rpc.ts +3 -1
- package/src/rpc/RpcClient.ts +94 -38
- package/src/rpc/RpcMessage.ts +13 -3
- package/src/rpc/RpcSerialization.ts +3 -0
- package/src/rpc/RpcServer.ts +176 -78
- package/src/schema/Model.ts +26 -3
- package/src/sql/Migrator.ts +38 -80
- package/src/sql/MigratorFileSystem.ts +71 -0
- package/src/sql/SqlClient.ts +45 -24
- package/src/sql/Statement.ts +60 -29
- package/src/sql/index.ts +1 -1
- package/src/testing/index.ts +0 -6
- package/src/workflow/Workflow.ts +4 -0
- package/src/workflow/WorkflowEngine.ts +5 -0
- package/src/workflow/internal/crypto.ts +15 -11
- package/dist/Crypto.d.ts +0 -244
- package/dist/Crypto.d.ts.map +0 -1
- package/dist/Crypto.js +0 -139
- package/dist/Crypto.js.map +0 -1
- package/dist/testing/TestCrypto.d.ts +0 -73
- package/dist/testing/TestCrypto.d.ts.map +0 -1
- package/dist/testing/TestCrypto.js +0 -87
- package/dist/testing/TestCrypto.js.map +0 -1
- package/src/Crypto.ts +0 -347
- package/src/testing/TestCrypto.ts +0 -94
|
@@ -12,13 +12,22 @@
|
|
|
12
12
|
* @since 4.0.0
|
|
13
13
|
*/
|
|
14
14
|
import * as Arr from "../Array.ts"
|
|
15
|
+
import * as Cause from "../Cause.ts"
|
|
16
|
+
import * as Clock from "../Clock.ts"
|
|
15
17
|
import * as Context from "../Context.ts"
|
|
18
|
+
import * as SecureRandom from "../crypto/SecureRandom.ts"
|
|
19
|
+
import * as Data from "../Data.ts"
|
|
20
|
+
import * as Duration from "../Duration.ts"
|
|
16
21
|
import * as Effect from "../Effect.ts"
|
|
17
22
|
import * as Equal from "../Equal.ts"
|
|
18
23
|
import * as Uuid from "../internal/uuid.ts"
|
|
24
|
+
import * as Latch from "../Latch.ts"
|
|
19
25
|
import * as Layer from "../Layer.ts"
|
|
20
26
|
import * as PubSub from "../PubSub.ts"
|
|
27
|
+
import * as Pull from "../Pull.ts"
|
|
21
28
|
import * as RcMap from "../RcMap.ts"
|
|
29
|
+
import type * as Rpc from "../rpc/Rpc.ts"
|
|
30
|
+
import type * as RpcGroup from "../rpc/RpcGroup.ts"
|
|
22
31
|
import * as RpcServer from "../rpc/RpcServer.ts"
|
|
23
32
|
import type * as Scope from "../Scope.ts"
|
|
24
33
|
import * as Stream from "../Stream.ts"
|
|
@@ -26,6 +35,13 @@ import { type EntryId, makeRemoteIdUnsafe, type RemoteId } from "./EventJournal.
|
|
|
26
35
|
import type { EncryptedBatch, EncryptedRemoteBatch } from "./EventLogEncryption.ts"
|
|
27
36
|
import {
|
|
28
37
|
ChangesRpc,
|
|
38
|
+
ChunkedMessage,
|
|
39
|
+
type CompactionInputHeader,
|
|
40
|
+
CompactionInputRpc,
|
|
41
|
+
CompactionPartRpc,
|
|
42
|
+
CompactionStatus,
|
|
43
|
+
type EventLogAuthentication,
|
|
44
|
+
EventLogCompactionRpcs,
|
|
29
45
|
EventLogProtocolError,
|
|
30
46
|
EventLogRemoteRpcs,
|
|
31
47
|
type StoreId,
|
|
@@ -33,6 +49,15 @@ import {
|
|
|
33
49
|
WriteEntries
|
|
34
50
|
} from "./EventLogMessage.ts"
|
|
35
51
|
import * as EventLogServer from "./EventLogServer.ts"
|
|
52
|
+
import {
|
|
53
|
+
hourMillis,
|
|
54
|
+
lagHour,
|
|
55
|
+
logCommit,
|
|
56
|
+
logNoOp,
|
|
57
|
+
logRollback,
|
|
58
|
+
maxSegments,
|
|
59
|
+
positionsPerSegment
|
|
60
|
+
} from "./internal/compaction.ts"
|
|
36
61
|
|
|
37
62
|
/**
|
|
38
63
|
* Provides RPC handlers for the encrypted event-log server.
|
|
@@ -43,72 +68,87 @@ import * as EventLogServer from "./EventLogServer.ts"
|
|
|
43
68
|
* sequences. Change streams encode stored batches, excluding this connection's
|
|
44
69
|
* writes.
|
|
45
70
|
*
|
|
71
|
+
* It also serves `EventLogCompactionRpcs`. If `Storage` provides `compaction`,
|
|
72
|
+
* it sets `HelloResponse.compaction` and applies the compaction policy;
|
|
73
|
+
* otherwise those handlers fail with `NotFound`.
|
|
74
|
+
*
|
|
46
75
|
* @stability unstable
|
|
47
76
|
* @category layers
|
|
48
77
|
* @since 4.0.0
|
|
49
78
|
*/
|
|
50
|
-
export const layerRpcHandlers
|
|
79
|
+
export const layerRpcHandlers: Layer.Layer<
|
|
80
|
+
| Rpc.ToHandler<RpcGroup.Rpcs<typeof EventLogRemoteRpcs>>
|
|
81
|
+
| Rpc.ToHandler<RpcGroup.Rpcs<typeof EventLogCompactionRpcs>>
|
|
82
|
+
| EventLogAuthentication,
|
|
83
|
+
never,
|
|
84
|
+
Storage
|
|
85
|
+
> = Layer.unwrap(Effect.gen(function*() {
|
|
51
86
|
const storage = yield* Storage
|
|
52
87
|
const remoteId = yield* storage.getId
|
|
53
88
|
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
storage.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
publicKey: undefined,
|
|
65
|
-
code: "InternalServerError",
|
|
66
|
-
message: "Decoding failure"
|
|
67
|
-
})
|
|
68
|
-
)
|
|
69
|
-
)
|
|
70
|
-
if (!authenticatedPublicKeys.has(request.publicKey)) {
|
|
71
|
-
return yield* new EventLogProtocolError({
|
|
72
|
-
requestTag: "WriteEntries",
|
|
73
|
-
publicKey: request.publicKey,
|
|
74
|
-
code: "Forbidden",
|
|
75
|
-
message: "Identity is not authenticated"
|
|
76
|
-
})
|
|
77
|
-
}
|
|
78
|
-
if (request.batches.length === 0) return []
|
|
79
|
-
const entryIds = request.batches.flatMap((batch) => batch.entryIds)
|
|
80
|
-
return yield* storage.write(request.publicKey, request.storeId, request.batches).pipe(
|
|
81
|
-
Effect.flatMap((stored) => toWriteAcks(storage, request.publicKey, request.storeId, request.batches, stored)),
|
|
82
|
-
(write) => writes.track({ publicKey: request.publicKey, storeId: request.storeId, entryIds }, write),
|
|
83
|
-
Effect.catchCause((_) =>
|
|
84
|
-
Effect.fail(
|
|
89
|
+
return Layer.merge(
|
|
90
|
+
EventLogServer.layerRpcHandlers({
|
|
91
|
+
remoteId,
|
|
92
|
+
firstSequence: storage.firstSequence,
|
|
93
|
+
compaction: storage.compaction !== undefined,
|
|
94
|
+
getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) =>
|
|
95
|
+
storage.getOrCreateSessionAuthBinding(publicKey, signingPublicKey),
|
|
96
|
+
onWrite: Effect.fnUntraced(function*(data, authenticatedPublicKeys, writes) {
|
|
97
|
+
const request = yield* WriteEntries.decode(data).pipe(
|
|
98
|
+
Effect.mapError((_) =>
|
|
85
99
|
new EventLogProtocolError({
|
|
86
100
|
requestTag: "WriteEntries",
|
|
87
|
-
publicKey:
|
|
101
|
+
publicKey: undefined,
|
|
88
102
|
code: "InternalServerError",
|
|
89
|
-
message: "
|
|
103
|
+
message: "Decoding failure"
|
|
90
104
|
})
|
|
91
105
|
)
|
|
92
106
|
)
|
|
93
|
-
|
|
107
|
+
if (!authenticatedPublicKeys.has(request.publicKey)) {
|
|
108
|
+
return yield* new EventLogProtocolError({
|
|
109
|
+
requestTag: "WriteEntries",
|
|
110
|
+
publicKey: request.publicKey,
|
|
111
|
+
code: "Forbidden",
|
|
112
|
+
message: "Identity is not authenticated"
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
if (request.batches.length === 0) return []
|
|
116
|
+
const entryIds = request.batches.flatMap((batch) => batch.entryIds)
|
|
117
|
+
return yield* storage.write(request.publicKey, request.storeId, request.batches).pipe(
|
|
118
|
+
Effect.flatMap((stored) => toWriteAcks(storage, request.publicKey, request.storeId, request.batches, stored)),
|
|
119
|
+
(write) => writes.track({ publicKey: request.publicKey, storeId: request.storeId, entryIds }, write),
|
|
120
|
+
Effect.catchCause((_) =>
|
|
121
|
+
Effect.fail(
|
|
122
|
+
new EventLogProtocolError({
|
|
123
|
+
requestTag: "WriteEntries",
|
|
124
|
+
publicKey: request.publicKey,
|
|
125
|
+
code: "InternalServerError",
|
|
126
|
+
message: "Persistence failure"
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
}),
|
|
132
|
+
changes: ({ publicKey, startSequence, storeId, writes }) =>
|
|
133
|
+
writes.filter(
|
|
134
|
+
{
|
|
135
|
+
publicKey,
|
|
136
|
+
storeId,
|
|
137
|
+
entryIds: (batch) => batch.entryIds,
|
|
138
|
+
sequence: (batch) => batch.sequence + batch.entryIds.length - 1
|
|
139
|
+
},
|
|
140
|
+
storage.changes(publicKey, storeId, startSequence)
|
|
141
|
+
).pipe(
|
|
142
|
+
Stream.mapArrayEffect((batches) => Effect.map(ChangesRpc.encodeEncrypted(batches), Arr.of))
|
|
143
|
+
)
|
|
94
144
|
}),
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
publicKey,
|
|
99
|
-
storeId,
|
|
100
|
-
entryIds: (batch) => batch.entryIds,
|
|
101
|
-
sequence: (batch) => batch.sequence + batch.entryIds.length - 1
|
|
102
|
-
},
|
|
103
|
-
storage.changes(publicKey, storeId, startSequence)
|
|
104
|
-
).pipe(
|
|
105
|
-
Stream.mapArrayEffect((batches) => Effect.map(ChangesRpc.encodeEncrypted(batches), Arr.of))
|
|
106
|
-
)
|
|
107
|
-
})
|
|
145
|
+
layerCompactionRpcHandlers(storage)
|
|
146
|
+
)
|
|
108
147
|
}))
|
|
109
148
|
|
|
110
149
|
// Acknowledges each entry occurrence in request order: at its position in the
|
|
111
|
-
// batch this write stored, or, for a skipped batch, where it was first stored
|
|
150
|
+
// batch this write stored, or, for a skipped batch, where it was first stored
|
|
151
|
+
// if that is above the active compaction base.
|
|
112
152
|
const toWriteAcks = Effect.fnUntraced(function*(
|
|
113
153
|
storage: Storage["Service"],
|
|
114
154
|
publicKey: string,
|
|
@@ -125,8 +165,13 @@ const toWriteAcks = Effect.fnUntraced(function*(
|
|
|
125
165
|
const skipped = batches.flatMap((batch, i) => storedAt[i] === undefined ? batch.entryIds : [])
|
|
126
166
|
const firstStored = new Map<string, number>()
|
|
127
167
|
if (skipped.length > 0) {
|
|
168
|
+
// An id first stored at or below the active base may have been dropped, so
|
|
169
|
+
// acking it would move the client's cursor past kept entries.
|
|
170
|
+
const base = storage.compaction === undefined
|
|
171
|
+
? -Infinity
|
|
172
|
+
: (yield* storage.compaction.status(publicKey, storeId)).base
|
|
128
173
|
for (const ack of yield* storage.entrySequences(publicKey, storeId, skipped)) {
|
|
129
|
-
firstStored.set(Uuid.stringify(ack.entryId), ack.sequence)
|
|
174
|
+
if (ack.sequence > base) firstStored.set(Uuid.stringify(ack.entryId), ack.sequence)
|
|
130
175
|
}
|
|
131
176
|
}
|
|
132
177
|
const acks: Array<WriteAck> = []
|
|
@@ -139,6 +184,260 @@ const toWriteAcks = Effect.fnUntraced(function*(
|
|
|
139
184
|
return acks
|
|
140
185
|
})
|
|
141
186
|
|
|
187
|
+
const layerCompactionRpcHandlers = (storage: Storage["Service"]) =>
|
|
188
|
+
EventLogCompactionRpcs.toLayer(Effect.sync(() => {
|
|
189
|
+
const compaction = storage.compaction
|
|
190
|
+
const toFrames = EventLogServer.makeFramer()
|
|
191
|
+
|
|
192
|
+
type Request = { readonly publicKey: string; readonly storeId: StoreId }
|
|
193
|
+
const protocolError = (
|
|
194
|
+
requestTag: string,
|
|
195
|
+
request: Request,
|
|
196
|
+
code: EventLogProtocolError["code"],
|
|
197
|
+
message: string
|
|
198
|
+
) =>
|
|
199
|
+
new EventLogProtocolError({ requestTag, publicKey: request.publicKey, storeId: request.storeId, code, message })
|
|
200
|
+
const fromCompactionError =
|
|
201
|
+
(requestTag: string, request: Request) => (error: CompactionNotFound | CompactionInvalidRequest) =>
|
|
202
|
+
error._tag === "CompactionNotFound"
|
|
203
|
+
? protocolError(requestTag, request, "NotFound", "Compaction stage not found")
|
|
204
|
+
: protocolError(requestTag, request, "InvalidRequest", error.message)
|
|
205
|
+
|
|
206
|
+
const withCompaction = <A>(
|
|
207
|
+
requestTag: string,
|
|
208
|
+
request: Request,
|
|
209
|
+
client: Rpc.ServerClient,
|
|
210
|
+
f: (compaction: StorageCompaction) => Effect.Effect<A, CompactionNotFound | CompactionInvalidRequest>
|
|
211
|
+
): Effect.Effect<A, EventLogProtocolError> => {
|
|
212
|
+
if (!Context.getOrUndefined(client.annotations, EventLogServer.AuthenticatedIdentities)?.has(request.publicKey)) {
|
|
213
|
+
return Effect.fail(protocolError(requestTag, request, "Forbidden", "Identity is not authenticated"))
|
|
214
|
+
}
|
|
215
|
+
if (compaction === undefined) {
|
|
216
|
+
return Effect.fail(protocolError(requestTag, request, "NotFound", "Compaction is not supported"))
|
|
217
|
+
}
|
|
218
|
+
return Effect.mapError(f(compaction), fromCompactionError(requestTag, request))
|
|
219
|
+
}
|
|
220
|
+
const withCompactionStream = <A>(
|
|
221
|
+
requestTag: string,
|
|
222
|
+
request: Request,
|
|
223
|
+
client: Rpc.ServerClient,
|
|
224
|
+
f: (compaction: StorageCompaction) => Stream.Stream<A, CompactionNotFound>
|
|
225
|
+
): Stream.Stream<A, EventLogProtocolError> =>
|
|
226
|
+
Stream.unwrap(
|
|
227
|
+
withCompaction(
|
|
228
|
+
requestTag,
|
|
229
|
+
request,
|
|
230
|
+
client,
|
|
231
|
+
(compaction) => Effect.succeed(Stream.mapError(f(compaction), fromCompactionError(requestTag, request)))
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
const getStatus = Effect.fnUntraced(function*(compaction: StorageCompaction, publicKey: string, storeId: StoreId) {
|
|
236
|
+
const status = yield* compaction.status(publicKey, storeId)
|
|
237
|
+
return { status, wanted: isCompactionWanted(status, compaction.config.thresholdFloor) }
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return EventLogCompactionRpcs.of({
|
|
241
|
+
"EventLog.CompactionStatus": (request, { client }) =>
|
|
242
|
+
withCompaction("CompactionStatus", request, client, (compaction) =>
|
|
243
|
+
Effect.map(getStatus(compaction, request.publicKey, request.storeId), ({ status, wanted }) =>
|
|
244
|
+
new CompactionStatus({ wanted, head: status.head }))),
|
|
245
|
+
"EventLog.CompactionBegin": (request, { client }) =>
|
|
246
|
+
withCompaction("CompactionBegin", request, client, (compaction) =>
|
|
247
|
+
Effect.gen(function*() {
|
|
248
|
+
const { status, wanted } = yield* getStatus(compaction, request.publicKey, request.storeId)
|
|
249
|
+
if (!wanted) {
|
|
250
|
+
return { _tag: "NotWanted" } as const
|
|
251
|
+
}
|
|
252
|
+
const result = yield* compaction.begin(request.publicKey, request.storeId, { through: status.lagHead })
|
|
253
|
+
return result._tag === "Busy"
|
|
254
|
+
? { _tag: "Busy" } as const
|
|
255
|
+
: { _tag: "Started", stageId: result.stageId } as const
|
|
256
|
+
})),
|
|
257
|
+
"EventLog.CompactionInput": (request, { client }) =>
|
|
258
|
+
withCompactionStream("CompactionInput", request, client, (compaction) =>
|
|
259
|
+
compaction.inputHeaders(request.publicKey, request.storeId, request.stageId).pipe(
|
|
260
|
+
Stream.mapArrayEffect((headers) =>
|
|
261
|
+
Effect.map(
|
|
262
|
+
Effect.orDie(
|
|
263
|
+
CompactionInputRpc.encodeHeaders(headers as Arr.NonEmptyReadonlyArray<CompactionInputHeader>)
|
|
264
|
+
),
|
|
265
|
+
toFrames
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
)),
|
|
269
|
+
"EventLog.CompactionFetch": (request, { client }) =>
|
|
270
|
+
withCompactionStream("CompactionFetch", request, client, (compaction) =>
|
|
271
|
+
compaction.fetch(request.publicKey, request.storeId, request.stageId, request.batches).pipe(
|
|
272
|
+
Stream.mapArrayEffect((batches) =>
|
|
273
|
+
Effect.map(Effect.orDie(ChangesRpc.encodeEncrypted(batches)), toFrames)
|
|
274
|
+
)
|
|
275
|
+
)),
|
|
276
|
+
"EventLog.CompactionPart": (request, { client }) =>
|
|
277
|
+
withCompaction("CompactionPart", request, client, (compaction) =>
|
|
278
|
+
Effect.gen(function*() {
|
|
279
|
+
const { publicKey, stageId, storeId } = request
|
|
280
|
+
const data = ChunkedMessage.join(getPendingSegments(client, request), request.chunk)
|
|
281
|
+
if (data === undefined) {
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
const batches = yield* CompactionPartRpc.decodeSegment(data).pipe(
|
|
285
|
+
Effect.catch(() =>
|
|
286
|
+
Effect.andThen(
|
|
287
|
+
compaction.abort(publicKey, storeId, stageId),
|
|
288
|
+
Effect.fail(new CompactionInvalidRequest({ stageId, message: "Invalid segment" }))
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
yield* compaction.stagePart(publicKey, storeId, stageId, request.chunk.id, batches)
|
|
293
|
+
})),
|
|
294
|
+
"EventLog.CompactionAbort": (request, { client }) =>
|
|
295
|
+
withCompaction("CompactionAbort", request, client, (compaction) => {
|
|
296
|
+
releasePendingSegments(client, request)
|
|
297
|
+
return compaction.abort(request.publicKey, request.storeId, request.stageId)
|
|
298
|
+
}),
|
|
299
|
+
"EventLog.CompactionCommit": (request, { client }) =>
|
|
300
|
+
withCompaction("CompactionCommit", request, client, (compaction) => {
|
|
301
|
+
const { publicKey, stageId, storeId } = request
|
|
302
|
+
releasePendingSegments(client, request)
|
|
303
|
+
if (request.result._tag === "NoOp") {
|
|
304
|
+
return Effect.as(compaction.recordNoOp(publicKey, storeId, stageId), "Committed" as const)
|
|
305
|
+
}
|
|
306
|
+
return checkSnapshot(compaction, publicKey, storeId, stageId, request.result.segmentCount).pipe(
|
|
307
|
+
Effect.tapError((error) =>
|
|
308
|
+
error._tag === "CompactionInvalidRequest" ? compaction.abort(publicKey, storeId, stageId) : Effect.void
|
|
309
|
+
),
|
|
310
|
+
Effect.flatMap((virtualSequences) =>
|
|
311
|
+
compaction.commit(publicKey, storeId, stageId, virtualSequences)
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
}))
|
|
317
|
+
|
|
318
|
+
// The input past the lag must reach the threshold. After `k` consecutive
|
|
319
|
+
// no-ops, the lag head must also have moved `threshold * 2^(k-1)` past the
|
|
320
|
+
// latest one.
|
|
321
|
+
const isCompactionWanted = (status: CompactionStoreStatus, thresholdFloor: number): boolean => {
|
|
322
|
+
const threshold = Math.max(status.activeKeptCount, thresholdFloor)
|
|
323
|
+
const pending = status.lagHead - status.base
|
|
324
|
+
if (pending <= 0 || pending < threshold) return false
|
|
325
|
+
if (status.noOp === undefined) return true
|
|
326
|
+
return status.lagHead - status.noOp.at >= threshold * 2 ** (status.noOp.count - 1)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Checks the staged batches against the stage's input in one pass over both
|
|
330
|
+
// and returns each batch's virtual start sequence. The kept ids, read in
|
|
331
|
+
// segments 0 to segmentCount - 1, must be a non-empty, repeat-free
|
|
332
|
+
// subsequence of the input's ids.
|
|
333
|
+
const checkSnapshot = Effect.fnUntraced(function*(
|
|
334
|
+
compaction: StorageCompaction,
|
|
335
|
+
publicKey: string,
|
|
336
|
+
storeId: StoreId,
|
|
337
|
+
stageId: string,
|
|
338
|
+
segmentCount: number
|
|
339
|
+
) {
|
|
340
|
+
const invalid = (message: string) => Effect.fail(new CompactionInvalidRequest({ stageId, message }))
|
|
341
|
+
const pullInput = yield* Stream.toPull(compaction.inputHeaders(publicKey, storeId, stageId))
|
|
342
|
+
let headers: ReadonlyArray<CompactionBatchHeader> = []
|
|
343
|
+
let headerIndex = 0
|
|
344
|
+
let entryIndex = 0
|
|
345
|
+
let lastInputSequence = -1
|
|
346
|
+
const nextInput = Effect.gen(function*() {
|
|
347
|
+
while (true) {
|
|
348
|
+
const header = headers[headerIndex]
|
|
349
|
+
if (header === undefined) {
|
|
350
|
+
const chunk = yield* Pull.catchDone(pullInput, () => Effect.succeed(undefined))
|
|
351
|
+
if (chunk === undefined) return undefined
|
|
352
|
+
headers = chunk
|
|
353
|
+
headerIndex = 0
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
if (entryIndex === header.entryIds.length) {
|
|
357
|
+
headerIndex++
|
|
358
|
+
entryIndex = 0
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
const sequence = header.sequence + entryIndex
|
|
362
|
+
const id = Uuid.stringify(header.entryIds[entryIndex++])
|
|
363
|
+
lastInputSequence = sequence
|
|
364
|
+
return { id, sequence }
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
// The input sequence of each batch's last kept entry, and its size.
|
|
369
|
+
const ends: Array<number> = []
|
|
370
|
+
const sizes: Array<number> = []
|
|
371
|
+
let segment = -1
|
|
372
|
+
yield* Stream.runForEach(compaction.stagedHeaders(publicKey, storeId, stageId), (header) =>
|
|
373
|
+
Effect.gen(function*() {
|
|
374
|
+
const headerSegment = Math.floor(header.position / positionsPerSegment)
|
|
375
|
+
if (headerSegment !== segment) {
|
|
376
|
+
if (headerSegment !== segment + 1 || headerSegment >= segmentCount) {
|
|
377
|
+
return yield* invalid("Unexpected segment")
|
|
378
|
+
}
|
|
379
|
+
segment = headerSegment
|
|
380
|
+
}
|
|
381
|
+
let last = -1
|
|
382
|
+
for (const entryId of header.entryIds) {
|
|
383
|
+
const id = Uuid.stringify(entryId)
|
|
384
|
+
let input = yield* nextInput
|
|
385
|
+
while (input !== undefined && input.id !== id) input = yield* nextInput
|
|
386
|
+
if (input === undefined) return yield* invalid("Kept entry is not in the input, or is out of order")
|
|
387
|
+
last = input.sequence
|
|
388
|
+
}
|
|
389
|
+
ends.push(last)
|
|
390
|
+
sizes.push(header.entryIds.length)
|
|
391
|
+
}))
|
|
392
|
+
if (ends.length === 0) return yield* invalid("Snapshot is empty")
|
|
393
|
+
if (segment !== segmentCount - 1) return yield* invalid("Missing segment")
|
|
394
|
+
while ((yield* nextInput) !== undefined) {
|
|
395
|
+
// Drain the input to learn its last sequence.
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Each batch ends at its last kept entry's input sequence, and the final one
|
|
399
|
+
// at the end of the input, so no batch starts below its entries' input.
|
|
400
|
+
return ends.map((end, i) => (i === ends.length - 1 ? lastInputSequence : end) - sizes[i] + 1)
|
|
401
|
+
}, Effect.scoped)
|
|
402
|
+
|
|
403
|
+
// One connection's unjoined segment pieces, keyed by identity, store and
|
|
404
|
+
// stage. The JSON tuple key keeps the components unambiguous.
|
|
405
|
+
class PendingSegments extends Context.Service<
|
|
406
|
+
PendingSegments,
|
|
407
|
+
Map<string, ReturnType<typeof ChunkedMessage.initialJoinState>>
|
|
408
|
+
>()("effect/eventlog/EventLogServerEncrypted/PendingSegments") {}
|
|
409
|
+
|
|
410
|
+
const pendingSegmentsKey = (
|
|
411
|
+
request: { readonly publicKey: string; readonly storeId: StoreId; readonly stageId: string }
|
|
412
|
+
) => JSON.stringify([request.publicKey, request.storeId, request.stageId])
|
|
413
|
+
|
|
414
|
+
const getPendingSegments = (client: Rpc.ServerClient, request: {
|
|
415
|
+
readonly publicKey: string
|
|
416
|
+
readonly storeId: StoreId
|
|
417
|
+
readonly stageId: string
|
|
418
|
+
}) => {
|
|
419
|
+
let stages = Context.getOrUndefined(client.annotations, PendingSegments)
|
|
420
|
+
if (stages === undefined) {
|
|
421
|
+
stages = new Map()
|
|
422
|
+
client.annotate(PendingSegments, stages)
|
|
423
|
+
}
|
|
424
|
+
const key = pendingSegmentsKey(request)
|
|
425
|
+
let pending = stages.get(key)
|
|
426
|
+
if (pending === undefined) {
|
|
427
|
+
pending = ChunkedMessage.initialJoinState()
|
|
428
|
+
stages.set(key, pending)
|
|
429
|
+
}
|
|
430
|
+
return pending
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const releasePendingSegments = (client: Rpc.ServerClient, request: {
|
|
434
|
+
readonly publicKey: string
|
|
435
|
+
readonly storeId: StoreId
|
|
436
|
+
readonly stageId: string
|
|
437
|
+
}) => {
|
|
438
|
+
Context.getOrUndefined(client.annotations, PendingSegments)?.delete(pendingSegmentsKey(request))
|
|
439
|
+
}
|
|
440
|
+
|
|
142
441
|
/**
|
|
143
442
|
* Provides an encrypted event-log RPC server using `EventLogRemoteRpcs` and the
|
|
144
443
|
* encrypted server RPC handlers.
|
|
@@ -153,7 +452,8 @@ const toWriteAcks = Effect.fnUntraced(function*(
|
|
|
153
452
|
* This layer installs `EventLogRemoteRpcs` on the provided RPC server protocol
|
|
154
453
|
* and wires those RPCs to `layerRpcHandlers`. Encrypted batches, session
|
|
155
454
|
* authentication bindings, remote ids, and change streams are delegated to
|
|
156
|
-
* `Storage`.
|
|
455
|
+
* `Storage`. It also installs `EventLogCompactionRpcs`, which clients call
|
|
456
|
+
* only when `Storage` provides `compaction`.
|
|
157
457
|
*
|
|
158
458
|
* @see {@link layerRpcHandlers} for the encrypted handler layer without installing an RPC server protocol
|
|
159
459
|
* @see {@link Storage} for the storage service required by this layer
|
|
@@ -163,9 +463,9 @@ const toWriteAcks = Effect.fnUntraced(function*(
|
|
|
163
463
|
* @category layers
|
|
164
464
|
* @since 4.0.0
|
|
165
465
|
*/
|
|
166
|
-
export const layer: Layer.Layer<never, never, RpcServer.Protocol | Storage> = RpcServer.layer(
|
|
167
|
-
|
|
168
|
-
)
|
|
466
|
+
export const layer: Layer.Layer<never, never, RpcServer.Protocol | Storage> = RpcServer.layer(
|
|
467
|
+
EventLogRemoteRpcs.merge(EventLogCompactionRpcs)
|
|
468
|
+
).pipe(Layer.provide(layerRpcHandlers))
|
|
169
469
|
|
|
170
470
|
/**
|
|
171
471
|
* Defines the backing store service used by the encrypted event-log server.
|
|
@@ -189,6 +489,10 @@ export const layer: Layer.Layer<never, never, RpcServer.Protocol | Storage> = Rp
|
|
|
189
489
|
* whose last sequence is at or after `startSequence`, followed by newly stored
|
|
190
490
|
* batches.
|
|
191
491
|
*
|
|
492
|
+
* Storages that support remote compaction provide `compaction`. With an active
|
|
493
|
+
* snapshot, `changes` serves snapshot batches by their virtual sequences for a
|
|
494
|
+
* start sequence at or below the snapshot base, then the originals above it.
|
|
495
|
+
*
|
|
192
496
|
* @stability unstable
|
|
193
497
|
* @category services
|
|
194
498
|
* @since 4.0.0
|
|
@@ -199,6 +503,11 @@ export class Storage extends Context.Service<Storage, {
|
|
|
199
503
|
* Lowest assigned sequence (default 0). Overestimating skips entries.
|
|
200
504
|
*/
|
|
201
505
|
readonly firstSequence?: number | undefined
|
|
506
|
+
/**
|
|
507
|
+
* Returns the signing key bound to `publicKey`, binding `signingPublicKey`
|
|
508
|
+
* when none is bound yet. The server only passes keys whose signature it
|
|
509
|
+
* has verified, and rejects a key that differs from the returned binding.
|
|
510
|
+
*/
|
|
202
511
|
readonly getOrCreateSessionAuthBinding: (
|
|
203
512
|
publicKey: string,
|
|
204
513
|
signingPublicKey: Uint8Array<ArrayBuffer>
|
|
@@ -218,33 +527,372 @@ export class Storage extends Context.Service<Storage, {
|
|
|
218
527
|
storeId: StoreId,
|
|
219
528
|
startSequence: number
|
|
220
529
|
) => Stream.Stream<EncryptedRemoteBatch>
|
|
530
|
+
readonly compaction?: StorageCompaction | undefined
|
|
221
531
|
}>()("effect/eventlog/EventLogServer/Storage") {}
|
|
222
532
|
|
|
223
533
|
/**
|
|
224
|
-
*
|
|
534
|
+
* Storage primitives for encrypted remote compaction.
|
|
535
|
+
*
|
|
536
|
+
* **Details**
|
|
537
|
+
*
|
|
538
|
+
* The server owns the policy: trigger threshold, lag, no-op backoff, commit
|
|
539
|
+
* checks and virtual sequences. Each primitive is atomic per store.
|
|
540
|
+
*
|
|
541
|
+
* A store always has one active activation. Before the first commit it is the
|
|
542
|
+
* originals, with base `firstSequence - 1`. `begin` opens a stage pinned to the
|
|
543
|
+
* active activation and to `through`. The stage claim expires `stageTtl` after
|
|
544
|
+
* begin or the latest stored Part, and `begin` takes over an expired claim.
|
|
545
|
+
* Stage-scoped primitives fail with `CompactionNotFound` once the stage has
|
|
546
|
+
* expired, been aborted or committed, or when it belongs to another store, so
|
|
547
|
+
* a commit is answered only once.
|
|
548
|
+
*
|
|
549
|
+
* `stagePart` stores one segment of encrypted snapshot batches, or answers
|
|
550
|
+
* `AlreadyStored` for a segment it already has. It doesn't check entry ids:
|
|
551
|
+
* the server's commit checks reject repeats, and readers dedupe by id.
|
|
552
|
+
* `commit` takes every staged batch's virtual
|
|
553
|
+
* start sequence in position order, and activates the staged set only if the
|
|
554
|
+
* pinned activation is still active.
|
|
555
|
+
*
|
|
556
|
+
* `rollback` reactivates the previous generation under a new activation id
|
|
557
|
+
* and re-appends, at the head, the input of the stage that built the withdrawn
|
|
558
|
+
* one: the previous batch set, then the originals between the two bases.
|
|
559
|
+
* Clients that read the withdrawn snapshot get what it dropped through
|
|
560
|
+
* `changes`. A sweep on `Clock` prunes a generation, and the originals it
|
|
561
|
+
* replaced, `grace` after it was superseded or withdrawn.
|
|
562
|
+
*
|
|
563
|
+
* @stability unstable
|
|
564
|
+
* @category models
|
|
565
|
+
* @since 4.0.0
|
|
566
|
+
*/
|
|
567
|
+
export interface StorageCompaction {
|
|
568
|
+
readonly config: CompactionConfig
|
|
569
|
+
readonly status: (publicKey: string, storeId: StoreId) => Effect.Effect<CompactionStoreStatus>
|
|
570
|
+
readonly begin: (
|
|
571
|
+
publicKey: string,
|
|
572
|
+
storeId: StoreId,
|
|
573
|
+
options: { readonly through: number }
|
|
574
|
+
) => Effect.Effect<CompactionBeginResult>
|
|
575
|
+
/**
|
|
576
|
+
* Batch headers in input order: the pinned batch set, then the originals in
|
|
577
|
+
* `(base, through]`.
|
|
578
|
+
*/
|
|
579
|
+
readonly inputHeaders: (
|
|
580
|
+
publicKey: string,
|
|
581
|
+
storeId: StoreId,
|
|
582
|
+
stageId: string
|
|
583
|
+
) => Stream.Stream<CompactionBatchHeader, CompactionNotFound>
|
|
584
|
+
/**
|
|
585
|
+
* Input batches by their input sequence: snapshot batches at or below the
|
|
586
|
+
* pinned base, originals above it.
|
|
587
|
+
*/
|
|
588
|
+
readonly fetch: (
|
|
589
|
+
publicKey: string,
|
|
590
|
+
storeId: StoreId,
|
|
591
|
+
stageId: string,
|
|
592
|
+
sequences: ReadonlyArray<number>
|
|
593
|
+
) => Stream.Stream<EncryptedRemoteBatch, CompactionNotFound>
|
|
594
|
+
readonly stagePart: (
|
|
595
|
+
publicKey: string,
|
|
596
|
+
storeId: StoreId,
|
|
597
|
+
stageId: string,
|
|
598
|
+
segment: number,
|
|
599
|
+
batches: Arr.NonEmptyReadonlyArray<EncryptedBatch>
|
|
600
|
+
) => Effect.Effect<"Stored" | "AlreadyStored", CompactionInvalidRequest | CompactionNotFound>
|
|
601
|
+
/**
|
|
602
|
+
* Staged batch headers in position order (segment, then batch index).
|
|
603
|
+
*/
|
|
604
|
+
readonly stagedHeaders: (
|
|
605
|
+
publicKey: string,
|
|
606
|
+
storeId: StoreId,
|
|
607
|
+
stageId: string
|
|
608
|
+
) => Stream.Stream<{ readonly position: number; readonly entryIds: ReadonlyArray<EntryId> }, CompactionNotFound>
|
|
609
|
+
/**
|
|
610
|
+
* Logs the outcome with this commit's own activation id, not the one active
|
|
611
|
+
* when the log runs.
|
|
612
|
+
*/
|
|
613
|
+
readonly commit: (
|
|
614
|
+
publicKey: string,
|
|
615
|
+
storeId: StoreId,
|
|
616
|
+
stageId: string,
|
|
617
|
+
virtualSequences: ReadonlyArray<number>
|
|
618
|
+
) => Effect.Effect<"Committed" | "Superseded", CompactionNotFound>
|
|
619
|
+
/**
|
|
620
|
+
* Records a no-op for the stage and logs it.
|
|
621
|
+
*/
|
|
622
|
+
readonly recordNoOp: (publicKey: string, storeId: StoreId, stageId: string) => Effect.Effect<void, CompactionNotFound>
|
|
623
|
+
/**
|
|
624
|
+
* Releases an open stage and discards what it staged. Aborting a stage that
|
|
625
|
+
* has left `open` is a no-op.
|
|
626
|
+
*/
|
|
627
|
+
readonly abort: (publicKey: string, storeId: StoreId, stageId: string) => Effect.Effect<void>
|
|
628
|
+
readonly rollback: (
|
|
629
|
+
publicKey: string,
|
|
630
|
+
storeId: StoreId,
|
|
631
|
+
expectedActivationId: string
|
|
632
|
+
) => Effect.Effect<string, CompactionRollbackError>
|
|
633
|
+
/**
|
|
634
|
+
* Every activation of the store, oldest first.
|
|
635
|
+
*/
|
|
636
|
+
readonly history: (publicKey: string, storeId: StoreId) => Effect.Effect<ReadonlyArray<CompactionActivation>>
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* The compaction state of one store.
|
|
641
|
+
*
|
|
642
|
+
* **Details**
|
|
643
|
+
*
|
|
644
|
+
* `base` is `firstSequence - 1` while the originals are active. `lagHead` is
|
|
645
|
+
* the highest last sequence in a storage-time hour that ended before
|
|
646
|
+
* `now - lag`.
|
|
647
|
+
*
|
|
648
|
+
* @stability unstable
|
|
649
|
+
* @category models
|
|
650
|
+
* @since 4.0.0
|
|
651
|
+
*/
|
|
652
|
+
export interface CompactionStoreStatus {
|
|
653
|
+
readonly head: number
|
|
654
|
+
readonly base: number
|
|
655
|
+
readonly activeKeptCount: number
|
|
656
|
+
readonly lagHead: number
|
|
657
|
+
readonly noOp: { readonly at: number; readonly count: number } | undefined
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* The result of opening a compaction stage.
|
|
662
|
+
*
|
|
663
|
+
* @stability unstable
|
|
664
|
+
* @category models
|
|
665
|
+
* @since 4.0.0
|
|
666
|
+
*/
|
|
667
|
+
export type CompactionBeginResult = {
|
|
668
|
+
readonly _tag: "Started"
|
|
669
|
+
readonly stageId: string
|
|
670
|
+
} | {
|
|
671
|
+
readonly _tag: "Busy"
|
|
672
|
+
readonly expiresAt: number
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* The header of one compaction input batch.
|
|
677
|
+
*
|
|
678
|
+
* @stability unstable
|
|
679
|
+
* @category models
|
|
680
|
+
* @since 4.0.0
|
|
681
|
+
*/
|
|
682
|
+
export interface CompactionBatchHeader {
|
|
683
|
+
readonly sequence: number
|
|
684
|
+
readonly entryIds: ReadonlyArray<EntryId>
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* One entry of a store's activation log.
|
|
689
|
+
*
|
|
690
|
+
* **Details**
|
|
691
|
+
*
|
|
692
|
+
* `keptCount` is `undefined` for the originals. `restorable` is `false` once
|
|
693
|
+
* the generation's prune has started.
|
|
694
|
+
*
|
|
695
|
+
* @stability unstable
|
|
696
|
+
* @category models
|
|
697
|
+
* @since 4.0.0
|
|
698
|
+
*/
|
|
699
|
+
export interface CompactionActivation {
|
|
700
|
+
readonly activationId: string
|
|
701
|
+
readonly base: number
|
|
702
|
+
readonly keptCount: number | undefined
|
|
703
|
+
readonly active: boolean
|
|
704
|
+
readonly supersededAt: number | undefined
|
|
705
|
+
readonly restorable: boolean
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Error raised when a compaction stage has expired, was aborted or committed,
|
|
710
|
+
* or is unknown to the store.
|
|
711
|
+
*
|
|
712
|
+
* @stability unstable
|
|
713
|
+
* @category errors
|
|
714
|
+
* @since 4.0.0
|
|
715
|
+
*/
|
|
716
|
+
export class CompactionNotFound extends Data.TaggedError("CompactionNotFound")<{
|
|
717
|
+
readonly stageId: string
|
|
718
|
+
}> {}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Error raised when a compaction Part fails validation.
|
|
722
|
+
*
|
|
723
|
+
* @stability unstable
|
|
724
|
+
* @category errors
|
|
725
|
+
* @since 4.0.0
|
|
726
|
+
*/
|
|
727
|
+
export class CompactionInvalidRequest extends Data.TaggedError("CompactionInvalidRequest")<{
|
|
728
|
+
readonly stageId: string
|
|
729
|
+
readonly message: string
|
|
730
|
+
}> {}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Error raised when a rollback is refused.
|
|
734
|
+
*
|
|
735
|
+
* **Details**
|
|
736
|
+
*
|
|
737
|
+
* `NotActive` means the expected activation is not the active one,
|
|
738
|
+
* `NoPrevious` that there is no earlier generation, and `PruneStarted` that
|
|
739
|
+
* the previous generation has started to be pruned.
|
|
740
|
+
*
|
|
741
|
+
* @stability unstable
|
|
742
|
+
* @category errors
|
|
743
|
+
* @since 4.0.0
|
|
744
|
+
*/
|
|
745
|
+
export class CompactionRollbackError extends Data.TaggedError("CompactionRollbackError")<{
|
|
746
|
+
readonly reason: "NotActive" | "NoPrevious" | "PruneStarted"
|
|
747
|
+
}> {}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Tuning for encrypted remote compaction.
|
|
751
|
+
*
|
|
752
|
+
* **Details**
|
|
753
|
+
*
|
|
754
|
+
* `grace` is how long a superseded or withdrawn generation is kept before it
|
|
755
|
+
* is pruned (default 30 days). `lag` is how old data must be, by storage time,
|
|
756
|
+
* before it can be compacted (default 24 hours). `stageTtl` is how long a stage
|
|
757
|
+
* claim lasts after its latest Part (default 10 minutes). `thresholdFloor` is
|
|
758
|
+
* the smallest number of new entries that makes compaction wanted (default
|
|
759
|
+
* 1,000). `sweepInterval` is how often pruning runs (default 1 hour).
|
|
760
|
+
*
|
|
761
|
+
* @stability unstable
|
|
762
|
+
* @category models
|
|
763
|
+
* @since 4.0.0
|
|
764
|
+
*/
|
|
765
|
+
export interface CompactionOptions {
|
|
766
|
+
readonly grace?: Duration.Input | undefined
|
|
767
|
+
readonly lag?: Duration.Input | undefined
|
|
768
|
+
readonly stageTtl?: Duration.Input | undefined
|
|
769
|
+
readonly thresholdFloor?: number | undefined
|
|
770
|
+
readonly sweepInterval?: Duration.Input | undefined
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Resolved compaction tuning, shared by a storage and the server using it.
|
|
775
|
+
*
|
|
776
|
+
* @stability unstable
|
|
777
|
+
* @category models
|
|
778
|
+
* @since 4.0.0
|
|
779
|
+
*/
|
|
780
|
+
export interface CompactionConfig {
|
|
781
|
+
readonly grace: Duration.Duration
|
|
782
|
+
readonly lag: Duration.Duration
|
|
783
|
+
readonly stageTtl: Duration.Duration
|
|
784
|
+
readonly thresholdFloor: number
|
|
785
|
+
readonly sweepInterval: Duration.Duration
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Resolves compaction options against their defaults.
|
|
790
|
+
*
|
|
791
|
+
* **Details**
|
|
792
|
+
*
|
|
793
|
+
* Dies with an `IllegalArgumentError` unless `grace` is longer than both `lag`
|
|
794
|
+
* and `stageTtl`, `stageTtl` and `sweepInterval` are positive, and
|
|
795
|
+
* `thresholdFloor` is a non-negative integer.
|
|
796
|
+
*
|
|
797
|
+
* @stability unstable
|
|
798
|
+
* @category constructors
|
|
799
|
+
* @since 4.0.0
|
|
800
|
+
*/
|
|
801
|
+
export const makeCompactionConfig = (options?: CompactionOptions | undefined): Effect.Effect<CompactionConfig> =>
|
|
802
|
+
Effect.suspend(() => {
|
|
803
|
+
const config: CompactionConfig = {
|
|
804
|
+
grace: Duration.fromInputUnsafe(options?.grace ?? "30 days"),
|
|
805
|
+
lag: Duration.fromInputUnsafe(options?.lag ?? "24 hours"),
|
|
806
|
+
stageTtl: Duration.fromInputUnsafe(options?.stageTtl ?? "10 minutes"),
|
|
807
|
+
thresholdFloor: options?.thresholdFloor ?? 1000,
|
|
808
|
+
sweepInterval: Duration.fromInputUnsafe(options?.sweepInterval ?? "1 hour")
|
|
809
|
+
}
|
|
810
|
+
if (
|
|
811
|
+
!Duration.isGreaterThan(config.grace, config.lag) ||
|
|
812
|
+
!Duration.isGreaterThan(config.grace, config.stageTtl)
|
|
813
|
+
) {
|
|
814
|
+
return Effect.die(new Cause.IllegalArgumentError("Compaction grace must be longer than lag and stageTtl"))
|
|
815
|
+
}
|
|
816
|
+
if (!Duration.isGreaterThan(config.stageTtl, Duration.zero)) {
|
|
817
|
+
return Effect.die(new Cause.IllegalArgumentError("Compaction stageTtl must be positive"))
|
|
818
|
+
}
|
|
819
|
+
if (!Duration.isGreaterThan(config.sweepInterval, Duration.zero)) {
|
|
820
|
+
return Effect.die(new Cause.IllegalArgumentError("Compaction sweepInterval must be positive"))
|
|
821
|
+
}
|
|
822
|
+
if (!Number.isSafeInteger(config.thresholdFloor) || config.thresholdFloor < 0) {
|
|
823
|
+
return Effect.die(new Cause.IllegalArgumentError("Compaction thresholdFloor must be a non-negative integer"))
|
|
824
|
+
}
|
|
825
|
+
return Effect.succeed(config)
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Creates an in-memory encrypted server `Storage` with default compaction
|
|
830
|
+
* tuning.
|
|
225
831
|
*
|
|
226
832
|
* **Details**
|
|
227
833
|
*
|
|
228
834
|
* Data, session authentication bindings, and streams are process-local and are
|
|
229
835
|
* released with the surrounding scope.
|
|
230
836
|
*
|
|
837
|
+
* @see {@link makeStorageMemoryWith} to configure compaction
|
|
838
|
+
*
|
|
839
|
+
* @stability unstable
|
|
840
|
+
* @category constructors
|
|
841
|
+
* @since 4.0.0
|
|
842
|
+
*/
|
|
843
|
+
export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.Scope> = Effect.suspend(() =>
|
|
844
|
+
makeStorageMemoryWith({})
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Creates an in-memory encrypted server `Storage`.
|
|
849
|
+
*
|
|
850
|
+
* **Details**
|
|
851
|
+
*
|
|
852
|
+
* Data, session authentication bindings, and streams are process-local and are
|
|
853
|
+
* released with the surrounding scope. The storage always provides
|
|
854
|
+
* `compaction`. Its sweep runs on `Clock` only while some store has a
|
|
855
|
+
* generation to prune or a stage to expire.
|
|
856
|
+
*
|
|
857
|
+
* Dies when the compaction options are invalid (see {@link makeCompactionConfig}).
|
|
858
|
+
*
|
|
231
859
|
* @stability unstable
|
|
232
860
|
* @category constructors
|
|
233
861
|
* @since 4.0.0
|
|
234
862
|
*/
|
|
235
|
-
export const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
863
|
+
export const makeStorageMemoryWith = Effect.fnUntraced(function*(options: {
|
|
864
|
+
readonly compaction?: CompactionOptions | undefined
|
|
865
|
+
}): Effect.fn.Return<Storage["Service"], never, Scope.Scope> {
|
|
866
|
+
const config = yield* makeCompactionConfig(options.compaction)
|
|
867
|
+
const graceMillis = Duration.toMillis(config.grace)
|
|
868
|
+
const lagMillis = Duration.toMillis(config.lag)
|
|
869
|
+
const stageTtlMillis = Duration.toMillis(config.stageTtl)
|
|
870
|
+
const journals = new Map<string, MemoryJournal>()
|
|
242
871
|
const sessionAuthBindings = new Map<string, Uint8Array<ArrayBuffer>>()
|
|
243
872
|
const remoteId = makeRemoteIdUnsafe()
|
|
244
873
|
const ensureJournal = (scopeKey: string) => {
|
|
245
874
|
let journal = journals.get(scopeKey)
|
|
246
875
|
if (journal) return journal
|
|
247
|
-
|
|
876
|
+
const originals: MemoryActivation = {
|
|
877
|
+
id: SecureRandom.uuidV4Unsafe(),
|
|
878
|
+
set: undefined,
|
|
879
|
+
base: -1,
|
|
880
|
+
keptCount: undefined,
|
|
881
|
+
previous: undefined,
|
|
882
|
+
supersededAt: undefined,
|
|
883
|
+
pruneStartedAt: undefined
|
|
884
|
+
}
|
|
885
|
+
journal = {
|
|
886
|
+
knownIds: new Map(),
|
|
887
|
+
batches: [],
|
|
888
|
+
nextSequence: 0,
|
|
889
|
+
buckets: new Map(),
|
|
890
|
+
activations: [originals],
|
|
891
|
+
active: originals,
|
|
892
|
+
noOp: undefined,
|
|
893
|
+
openStage: undefined,
|
|
894
|
+
nextSweepAt: undefined
|
|
895
|
+
}
|
|
248
896
|
journals.set(scopeKey, journal)
|
|
249
897
|
return journal
|
|
250
898
|
}
|
|
@@ -257,6 +905,346 @@ export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.S
|
|
|
257
905
|
idleTimeToLive: 60000
|
|
258
906
|
})
|
|
259
907
|
|
|
908
|
+
// Open while any store has something for the sweep to do, so an idle
|
|
909
|
+
// storage never sleeps on the clock.
|
|
910
|
+
const sweepPending = Latch.makeUnsafe(false)
|
|
911
|
+
const schedulePrune = (journal: MemoryJournal, at: number) => {
|
|
912
|
+
journal.nextSweepAt = journal.nextSweepAt === undefined ? at : Math.min(journal.nextSweepAt, at)
|
|
913
|
+
sweepPending.openUnsafe()
|
|
914
|
+
}
|
|
915
|
+
const closeStage = (journal: MemoryJournal, stage: MemoryStage) => {
|
|
916
|
+
if (journal.openStage === stage) journal.openStage = undefined
|
|
917
|
+
}
|
|
918
|
+
const sweep = (now: number) => {
|
|
919
|
+
let pending = false
|
|
920
|
+
for (const journal of journals.values()) {
|
|
921
|
+
if (journal.openStage !== undefined && journal.openStage.expiresAt <= now) closeStage(journal, journal.openStage)
|
|
922
|
+
if (journal.nextSweepAt !== undefined && journal.nextSweepAt <= now) prune(journal, now)
|
|
923
|
+
pending ||= journal.openStage !== undefined || journal.nextSweepAt !== undefined
|
|
924
|
+
}
|
|
925
|
+
if (!pending) sweepPending.closeUnsafe()
|
|
926
|
+
}
|
|
927
|
+
// Keeps what the active generation and every generation still within grace
|
|
928
|
+
// need, which is their batch sets and the originals above their bases.
|
|
929
|
+
const prune = (journal: MemoryJournal, now: number) => {
|
|
930
|
+
const kept = journal.activations.filter((activation) =>
|
|
931
|
+
activation === journal.active ||
|
|
932
|
+
(activation.pruneStartedAt === undefined && activation.supersededAt! + graceMillis > now)
|
|
933
|
+
)
|
|
934
|
+
const keptSets = new Set(kept.map((activation) => activation.set))
|
|
935
|
+
for (const activation of journal.activations) {
|
|
936
|
+
if (kept.includes(activation) || activation.pruneStartedAt !== undefined) continue
|
|
937
|
+
activation.pruneStartedAt = now
|
|
938
|
+
if (activation.set !== undefined && !keptSets.has(activation.set)) activation.set.batches = []
|
|
939
|
+
}
|
|
940
|
+
const keptBase = Math.min(...kept.map((activation) => activation.base))
|
|
941
|
+
const first = journal.batches.findIndex((batch) => batch.sequence + batch.entryIds.length - 1 > keptBase)
|
|
942
|
+
journal.batches.splice(0, first === -1 ? journal.batches.length : first)
|
|
943
|
+
journal.nextSweepAt = undefined
|
|
944
|
+
for (const activation of journal.activations) {
|
|
945
|
+
if (activation !== journal.active && activation.pruneStartedAt === undefined) {
|
|
946
|
+
schedulePrune(journal, activation.supersededAt! + graceMillis)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
yield* Effect.forkScoped(
|
|
951
|
+
Effect.forever(
|
|
952
|
+
sweepPending.await.pipe(
|
|
953
|
+
Effect.andThen(Effect.sleep(config.sweepInterval)),
|
|
954
|
+
Effect.andThen(Clock.currentTimeMillis),
|
|
955
|
+
Effect.flatMap((now) => Effect.sync(() => sweep(now)))
|
|
956
|
+
)
|
|
957
|
+
)
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
// The highest last sequence stored in an hour that ended before now - lag.
|
|
961
|
+
const lagHead = (journal: MemoryJournal, now: number) => {
|
|
962
|
+
const pastLag = lagHour(now, lagMillis)
|
|
963
|
+
let head = -1
|
|
964
|
+
for (const [hour, lastSequence] of journal.buckets) {
|
|
965
|
+
if (hour <= pastLag) head = Math.max(head, lastSequence)
|
|
966
|
+
}
|
|
967
|
+
return head
|
|
968
|
+
}
|
|
969
|
+
const recordBucket = (journal: MemoryJournal, now: number, lastSequence: number) => {
|
|
970
|
+
const hour = Math.floor(now / hourMillis)
|
|
971
|
+
const existing = journal.buckets.get(hour)
|
|
972
|
+
if (existing !== undefined) {
|
|
973
|
+
journal.buckets.set(hour, Math.max(existing, lastSequence))
|
|
974
|
+
return
|
|
975
|
+
}
|
|
976
|
+
journal.buckets.set(hour, lastSequence)
|
|
977
|
+
// Only the newest bucket past the lag matters for the lag head.
|
|
978
|
+
const pastLag = Array.from(journal.buckets.keys()).filter((bucket) => bucket <= lagHour(now, lagMillis))
|
|
979
|
+
const newest = Math.max(...pastLag)
|
|
980
|
+
for (const bucket of pastLag) {
|
|
981
|
+
if (bucket < newest) journal.buckets.delete(bucket)
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// The input of a stage: its pinned batch set, then originals in (base, through].
|
|
985
|
+
const inputBatches = (journal: MemoryJournal, stage: MemoryStage): ReadonlyArray<EncryptedRemoteBatch> => [
|
|
986
|
+
...(stage.pinned.set?.batches ?? []),
|
|
987
|
+
...journal.batches.filter((batch) =>
|
|
988
|
+
batch.sequence > stage.pinned.base && batch.sequence + batch.entryIds.length - 1 <= stage.through
|
|
989
|
+
)
|
|
990
|
+
]
|
|
991
|
+
// A stage that Part, NoOp and Commit can act on.
|
|
992
|
+
const findOpenStage = (publicKey: string, storeId: StoreId, stageId: string, now: number) => {
|
|
993
|
+
const journal = journals.get(makeEncryptedScopeKey({ publicKey, storeId }))
|
|
994
|
+
const stage = journal?.openStage
|
|
995
|
+
if (journal === undefined || stage === undefined || stage.id !== stageId) return undefined
|
|
996
|
+
if (stage.expiresAt <= now) {
|
|
997
|
+
closeStage(journal, stage)
|
|
998
|
+
return undefined
|
|
999
|
+
}
|
|
1000
|
+
return { stage, journal }
|
|
1001
|
+
}
|
|
1002
|
+
// Call within the same execution-time block that captures the input rows.
|
|
1003
|
+
const findInputStage = (
|
|
1004
|
+
publicKey: string,
|
|
1005
|
+
storeId: StoreId,
|
|
1006
|
+
stageId: string,
|
|
1007
|
+
now: number
|
|
1008
|
+
) => {
|
|
1009
|
+
const found = findOpenStage(publicKey, storeId, stageId, now)
|
|
1010
|
+
return found?.stage.pinned.pruneStartedAt === undefined ? found : undefined
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const compaction: StorageCompaction = {
|
|
1014
|
+
config,
|
|
1015
|
+
status: (publicKey, storeId) =>
|
|
1016
|
+
Effect.map(Clock.currentTimeMillis, (now) => {
|
|
1017
|
+
const journal = ensureJournal(makeEncryptedScopeKey({ publicKey, storeId }))
|
|
1018
|
+
return {
|
|
1019
|
+
head: journal.nextSequence - 1,
|
|
1020
|
+
base: journal.active.base,
|
|
1021
|
+
activeKeptCount: journal.active.keptCount ?? 0,
|
|
1022
|
+
lagHead: lagHead(journal, now),
|
|
1023
|
+
noOp: journal.noOp
|
|
1024
|
+
}
|
|
1025
|
+
}),
|
|
1026
|
+
begin: (publicKey, storeId, { through }) =>
|
|
1027
|
+
Effect.map(Clock.currentTimeMillis, (now): CompactionBeginResult => {
|
|
1028
|
+
const scopeKey = makeEncryptedScopeKey({ publicKey, storeId })
|
|
1029
|
+
const journal = ensureJournal(scopeKey)
|
|
1030
|
+
const open = journal.openStage
|
|
1031
|
+
if (open !== undefined) {
|
|
1032
|
+
if (open.expiresAt > now) return { _tag: "Busy", expiresAt: open.expiresAt }
|
|
1033
|
+
closeStage(journal, open)
|
|
1034
|
+
}
|
|
1035
|
+
const stage: MemoryStage = {
|
|
1036
|
+
id: SecureRandom.uuidV4Unsafe(),
|
|
1037
|
+
pinned: journal.active,
|
|
1038
|
+
through,
|
|
1039
|
+
expiresAt: now + stageTtlMillis,
|
|
1040
|
+
staged: new Map()
|
|
1041
|
+
}
|
|
1042
|
+
journal.openStage = stage
|
|
1043
|
+
sweepPending.openUnsafe()
|
|
1044
|
+
return { _tag: "Started", stageId: stage.id }
|
|
1045
|
+
}),
|
|
1046
|
+
inputHeaders: (publicKey, storeId, stageId) =>
|
|
1047
|
+
Clock.currentTimeMillis.pipe(
|
|
1048
|
+
Effect.flatMap((now) => {
|
|
1049
|
+
const found = findInputStage(publicKey, storeId, stageId, now)
|
|
1050
|
+
if (found === undefined) return Effect.fail(new CompactionNotFound({ stageId }))
|
|
1051
|
+
const { journal, stage } = found
|
|
1052
|
+
return Effect.succeed(Stream.fromArray(
|
|
1053
|
+
inputBatches(journal, stage).map((batch): CompactionBatchHeader => ({
|
|
1054
|
+
sequence: batch.sequence,
|
|
1055
|
+
entryIds: batch.entryIds
|
|
1056
|
+
}))
|
|
1057
|
+
))
|
|
1058
|
+
}),
|
|
1059
|
+
Stream.unwrap
|
|
1060
|
+
),
|
|
1061
|
+
fetch: (publicKey, storeId, stageId, sequences) =>
|
|
1062
|
+
Clock.currentTimeMillis.pipe(
|
|
1063
|
+
Effect.flatMap((now) => {
|
|
1064
|
+
const found = findInputStage(publicKey, storeId, stageId, now)
|
|
1065
|
+
if (found === undefined) return Effect.fail(new CompactionNotFound({ stageId }))
|
|
1066
|
+
const { journal, stage } = found
|
|
1067
|
+
const fetched: Array<EncryptedRemoteBatch> = []
|
|
1068
|
+
for (const sequence of sequences) {
|
|
1069
|
+
const batch = sequence <= stage.pinned.base
|
|
1070
|
+
? findBatch(stage.pinned.set?.batches ?? [], sequence)
|
|
1071
|
+
: sequence <= stage.through
|
|
1072
|
+
? findBatch(journal.batches, sequence)
|
|
1073
|
+
: undefined
|
|
1074
|
+
if (batch !== undefined) fetched.push(batch)
|
|
1075
|
+
}
|
|
1076
|
+
return Effect.succeed(Stream.fromArray(fetched))
|
|
1077
|
+
}),
|
|
1078
|
+
Stream.unwrap
|
|
1079
|
+
),
|
|
1080
|
+
stagePart: (publicKey, storeId, stageId, segment, batches) =>
|
|
1081
|
+
Effect.flatMap(Clock.currentTimeMillis, (now): ReturnType<StorageCompaction["stagePart"]> => {
|
|
1082
|
+
const found = findOpenStage(publicKey, storeId, stageId, now)
|
|
1083
|
+
if (found === undefined) return Effect.fail(new CompactionNotFound({ stageId }))
|
|
1084
|
+
const { journal, stage } = found
|
|
1085
|
+
const reject = (message: string) => {
|
|
1086
|
+
closeStage(journal, stage)
|
|
1087
|
+
return Effect.fail(new CompactionInvalidRequest({ stageId, message }))
|
|
1088
|
+
}
|
|
1089
|
+
if (!Number.isSafeInteger(segment) || segment < 0 || segment >= maxSegments) {
|
|
1090
|
+
return reject("Invalid segment index")
|
|
1091
|
+
}
|
|
1092
|
+
if (batches.length > positionsPerSegment) return reject("Too many batches in a segment")
|
|
1093
|
+
// Positions are the staged rows' key, so a retried segment inserts nothing.
|
|
1094
|
+
let inserted = 0
|
|
1095
|
+
batches.forEach((batch, i) => {
|
|
1096
|
+
const position = segment * positionsPerSegment + i
|
|
1097
|
+
if (stage.staged.has(position)) return
|
|
1098
|
+
stage.staged.set(position, batch)
|
|
1099
|
+
inserted++
|
|
1100
|
+
})
|
|
1101
|
+
if (inserted === 0) return Effect.succeed("AlreadyStored" as const)
|
|
1102
|
+
stage.expiresAt = now + stageTtlMillis
|
|
1103
|
+
return Effect.succeed("Stored" as const)
|
|
1104
|
+
}),
|
|
1105
|
+
stagedHeaders: (publicKey, storeId, stageId) =>
|
|
1106
|
+
Effect.flatMap(Clock.currentTimeMillis, (now) => {
|
|
1107
|
+
const found = findOpenStage(publicKey, storeId, stageId, now)
|
|
1108
|
+
if (found === undefined) {
|
|
1109
|
+
return Effect.fail(new CompactionNotFound({ stageId }))
|
|
1110
|
+
}
|
|
1111
|
+
return Effect.succeed(Stream.fromArray(
|
|
1112
|
+
stagedBatches(found.stage).map(({ batch, position }) => ({
|
|
1113
|
+
position,
|
|
1114
|
+
entryIds: batch.entryIds
|
|
1115
|
+
}))
|
|
1116
|
+
))
|
|
1117
|
+
}).pipe(Stream.unwrap),
|
|
1118
|
+
commit: (publicKey, storeId, stageId, virtualSequences) =>
|
|
1119
|
+
Effect.flatMap(Clock.currentTimeMillis, (now) => {
|
|
1120
|
+
const found = findOpenStage(publicKey, storeId, stageId, now)
|
|
1121
|
+
if (found === undefined) return Effect.fail(new CompactionNotFound({ stageId }))
|
|
1122
|
+
const { journal, stage } = found
|
|
1123
|
+
if (stage.pinned !== journal.active) {
|
|
1124
|
+
const keptCount = stagedBatches(stage).reduce((count, { batch }) => count + batch.entryIds.length, 0)
|
|
1125
|
+
closeStage(journal, stage)
|
|
1126
|
+
return Effect.as(
|
|
1127
|
+
logCommit({
|
|
1128
|
+
publicKey,
|
|
1129
|
+
storeId,
|
|
1130
|
+
result: "Superseded",
|
|
1131
|
+
through: stage.through,
|
|
1132
|
+
keptCount,
|
|
1133
|
+
activationId: undefined
|
|
1134
|
+
}),
|
|
1135
|
+
"Superseded" as const
|
|
1136
|
+
)
|
|
1137
|
+
}
|
|
1138
|
+
const staged = stagedBatches(stage)
|
|
1139
|
+
if (staged.length !== virtualSequences.length) {
|
|
1140
|
+
return Effect.die(new Cause.IllegalArgumentError("Expected one virtual sequence per staged batch"))
|
|
1141
|
+
}
|
|
1142
|
+
const keptCount = staged.reduce((count, { batch }) => count + batch.entryIds.length, 0)
|
|
1143
|
+
const activation: MemoryActivation = {
|
|
1144
|
+
id: SecureRandom.uuidV4Unsafe(),
|
|
1145
|
+
set: {
|
|
1146
|
+
batches: staged.map(({ batch }, i): EncryptedRemoteBatch => ({ ...batch, sequence: virtualSequences[i] }))
|
|
1147
|
+
},
|
|
1148
|
+
base: stage.through,
|
|
1149
|
+
keptCount,
|
|
1150
|
+
previous: journal.active,
|
|
1151
|
+
supersededAt: undefined,
|
|
1152
|
+
pruneStartedAt: undefined
|
|
1153
|
+
}
|
|
1154
|
+
journal.active.supersededAt = now
|
|
1155
|
+
journal.activations.push(activation)
|
|
1156
|
+
journal.active = activation
|
|
1157
|
+
journal.noOp = undefined
|
|
1158
|
+
schedulePrune(journal, now + graceMillis)
|
|
1159
|
+
closeStage(journal, stage)
|
|
1160
|
+
// Log this commit's activation id. A concurrent rollback could change
|
|
1161
|
+
// the active one before the logger runs.
|
|
1162
|
+
return Effect.as(
|
|
1163
|
+
logCommit({
|
|
1164
|
+
publicKey,
|
|
1165
|
+
storeId,
|
|
1166
|
+
result: "Committed",
|
|
1167
|
+
through: stage.through,
|
|
1168
|
+
keptCount,
|
|
1169
|
+
activationId: activation.id
|
|
1170
|
+
}),
|
|
1171
|
+
"Committed" as const
|
|
1172
|
+
)
|
|
1173
|
+
}),
|
|
1174
|
+
recordNoOp: (publicKey, storeId, stageId) =>
|
|
1175
|
+
Effect.flatMap(Clock.currentTimeMillis, (now) => {
|
|
1176
|
+
const found = findOpenStage(publicKey, storeId, stageId, now)
|
|
1177
|
+
if (found === undefined) return Effect.fail(new CompactionNotFound({ stageId }))
|
|
1178
|
+
const { journal, stage } = found
|
|
1179
|
+
// A rollback since begin cleared the no-op state, so a stale run leaves it.
|
|
1180
|
+
if (stage.pinned === journal.active) {
|
|
1181
|
+
journal.noOp = { at: stage.through, count: (journal.noOp?.count ?? 0) + 1 }
|
|
1182
|
+
}
|
|
1183
|
+
closeStage(journal, stage)
|
|
1184
|
+
return logNoOp({ publicKey, storeId, stageId, through: stage.through })
|
|
1185
|
+
}),
|
|
1186
|
+
abort: (publicKey, storeId, stageId) =>
|
|
1187
|
+
Effect.sync(() => {
|
|
1188
|
+
const journal = journals.get(makeEncryptedScopeKey({ publicKey, storeId }))
|
|
1189
|
+
if (journal?.openStage?.id === stageId) closeStage(journal, journal.openStage)
|
|
1190
|
+
}),
|
|
1191
|
+
rollback: Effect.fnUntraced(function*(publicKey, storeId, expectedActivationId) {
|
|
1192
|
+
const scopeKey = makeEncryptedScopeKey({ publicKey, storeId })
|
|
1193
|
+
const pubsub = yield* RcMap.get(pubsubs, scopeKey)
|
|
1194
|
+
const now = yield* Clock.currentTimeMillis
|
|
1195
|
+
return yield* Effect.suspend(() => {
|
|
1196
|
+
const journal = ensureJournal(scopeKey)
|
|
1197
|
+
const withdrawn = journal.active
|
|
1198
|
+
if (withdrawn.id !== expectedActivationId) {
|
|
1199
|
+
return Effect.fail(new CompactionRollbackError({ reason: "NotActive" }))
|
|
1200
|
+
}
|
|
1201
|
+
const target = withdrawn.previous
|
|
1202
|
+
if (target === undefined) return Effect.fail(new CompactionRollbackError({ reason: "NoPrevious" }))
|
|
1203
|
+
if (target.pruneStartedAt !== undefined) {
|
|
1204
|
+
return Effect.fail(new CompactionRollbackError({ reason: "PruneStarted" }))
|
|
1205
|
+
}
|
|
1206
|
+
const activation: MemoryActivation = {
|
|
1207
|
+
...target,
|
|
1208
|
+
id: SecureRandom.uuidV4Unsafe(),
|
|
1209
|
+
supersededAt: undefined,
|
|
1210
|
+
pruneStartedAt: undefined
|
|
1211
|
+
}
|
|
1212
|
+
// The input of the stage that built the withdrawn generation.
|
|
1213
|
+
const input = [
|
|
1214
|
+
...(target.set?.batches ?? []),
|
|
1215
|
+
...journal.batches.filter((batch) =>
|
|
1216
|
+
batch.sequence > target.base && batch.sequence + batch.entryIds.length - 1 <= withdrawn.base
|
|
1217
|
+
)
|
|
1218
|
+
]
|
|
1219
|
+
withdrawn.supersededAt = now
|
|
1220
|
+
journal.activations.push(activation)
|
|
1221
|
+
journal.active = activation
|
|
1222
|
+
journal.noOp = undefined
|
|
1223
|
+
schedulePrune(journal, now + graceMillis)
|
|
1224
|
+
for (const { encryptedBatch, entryIds, iv } of input) {
|
|
1225
|
+
const batch: EncryptedRemoteBatch = { sequence: journal.nextSequence, entryIds, iv, encryptedBatch }
|
|
1226
|
+
journal.nextSequence += entryIds.length
|
|
1227
|
+
journal.batches.push(batch)
|
|
1228
|
+
PubSub.publishUnsafe(pubsub, batch)
|
|
1229
|
+
}
|
|
1230
|
+
if (input.length > 0) recordBucket(journal, now, journal.nextSequence - 1)
|
|
1231
|
+
return Effect.as(logRollback({ publicKey, storeId, activation, withdrawn }), activation.id)
|
|
1232
|
+
})
|
|
1233
|
+
}, Effect.scoped),
|
|
1234
|
+
history: (publicKey, storeId) =>
|
|
1235
|
+
Effect.sync(() => {
|
|
1236
|
+
const journal = ensureJournal(makeEncryptedScopeKey({ publicKey, storeId }))
|
|
1237
|
+
return journal.activations.map((activation): CompactionActivation => ({
|
|
1238
|
+
activationId: activation.id,
|
|
1239
|
+
base: activation.base,
|
|
1240
|
+
keptCount: activation.keptCount,
|
|
1241
|
+
active: activation === journal.active,
|
|
1242
|
+
supersededAt: activation === journal.active ? undefined : activation.supersededAt,
|
|
1243
|
+
restorable: activation.pruneStartedAt === undefined
|
|
1244
|
+
}))
|
|
1245
|
+
})
|
|
1246
|
+
}
|
|
1247
|
+
|
|
260
1248
|
return Storage.of({
|
|
261
1249
|
getId: Effect.succeed(remoteId),
|
|
262
1250
|
getOrCreateSessionAuthBinding: (publicKey, signingPublicKey) =>
|
|
@@ -269,6 +1257,7 @@ export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.S
|
|
|
269
1257
|
write: Effect.fnUntraced(function*(publicKey, storeId, batches) {
|
|
270
1258
|
const scopeKey = makeEncryptedScopeKey({ publicKey, storeId })
|
|
271
1259
|
const pubsub = yield* RcMap.get(pubsubs, scopeKey)
|
|
1260
|
+
const now = yield* Clock.currentTimeMillis
|
|
272
1261
|
const journal = ensureJournal(scopeKey)
|
|
273
1262
|
const stored: Array<EncryptedRemoteBatch> = []
|
|
274
1263
|
for (const batch of batches) {
|
|
@@ -283,6 +1272,7 @@ export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.S
|
|
|
283
1272
|
stored.push(remoteBatch)
|
|
284
1273
|
PubSub.publishUnsafe(pubsub, remoteBatch)
|
|
285
1274
|
}
|
|
1275
|
+
if (stored.length > 0) recordBucket(journal, now, journal.nextSequence - 1)
|
|
286
1276
|
return stored
|
|
287
1277
|
}, Effect.scoped),
|
|
288
1278
|
entrySequences: (publicKey, storeId, entryIds) =>
|
|
@@ -299,17 +1289,35 @@ export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.S
|
|
|
299
1289
|
const scopeKey = makeEncryptedScopeKey({ publicKey, storeId })
|
|
300
1290
|
const pubsub = yield* RcMap.get(pubsubs, scopeKey)
|
|
301
1291
|
const subscription = yield* PubSub.subscribe(pubsub)
|
|
302
|
-
const
|
|
1292
|
+
const journal = ensureJournal(scopeKey)
|
|
1293
|
+
const { base, set } = journal.active
|
|
1294
|
+
const live = Stream.fromSubscription(subscription)
|
|
1295
|
+
// Below the base an active snapshot replaces the originals.
|
|
1296
|
+
if (set !== undefined && startSequence <= base) {
|
|
1297
|
+
if (set.batches.length === 0) {
|
|
1298
|
+
return Stream.die(new Error(`The active compaction snapshot of store ${storeId} has no batches`))
|
|
1299
|
+
}
|
|
1300
|
+
const tail = journal.batches.filter((batch) => batch.sequence > base)
|
|
1301
|
+
return Stream.fromArray([
|
|
1302
|
+
...set.batches.filter((batch) => batch.sequence + batch.entryIds.length > startSequence),
|
|
1303
|
+
...tail
|
|
1304
|
+
]).pipe(Stream.concat(live))
|
|
1305
|
+
}
|
|
1306
|
+
const batches = journal.batches
|
|
303
1307
|
const first = batches.findIndex((batch) => batch.sequence + batch.entryIds.length > startSequence)
|
|
304
1308
|
return Stream.fromArray(first === -1 ? [] : batches.slice(first)).pipe(
|
|
305
|
-
Stream.concat(
|
|
1309
|
+
Stream.concat(live)
|
|
306
1310
|
)
|
|
307
|
-
}, Stream.unwrap)
|
|
1311
|
+
}, Stream.unwrap),
|
|
1312
|
+
compaction
|
|
308
1313
|
})
|
|
309
1314
|
})
|
|
310
1315
|
|
|
311
1316
|
/**
|
|
312
|
-
* Provides encrypted server `Storage` using the in-memory implementation
|
|
1317
|
+
* Provides encrypted server `Storage` using the in-memory implementation with
|
|
1318
|
+
* default compaction tuning.
|
|
1319
|
+
*
|
|
1320
|
+
* @see {@link layerStorageMemoryWith} to configure compaction
|
|
313
1321
|
*
|
|
314
1322
|
* @stability unstable
|
|
315
1323
|
* @category layers
|
|
@@ -317,6 +1325,66 @@ export const makeStorageMemory: Effect.Effect<Storage["Service"], never, Scope.S
|
|
|
317
1325
|
*/
|
|
318
1326
|
export const layerStorageMemory: Layer.Layer<Storage> = Layer.effect(Storage)(makeStorageMemory)
|
|
319
1327
|
|
|
1328
|
+
/**
|
|
1329
|
+
* Provides encrypted server `Storage` using the in-memory implementation.
|
|
1330
|
+
*
|
|
1331
|
+
* @stability unstable
|
|
1332
|
+
* @category layers
|
|
1333
|
+
* @since 4.0.0
|
|
1334
|
+
*/
|
|
1335
|
+
export const layerStorageMemoryWith = (options: {
|
|
1336
|
+
readonly compaction?: CompactionOptions | undefined
|
|
1337
|
+
}): Layer.Layer<Storage> => Layer.effect(Storage)(makeStorageMemoryWith(options))
|
|
1338
|
+
|
|
1339
|
+
interface MemoryActivation {
|
|
1340
|
+
readonly id: string
|
|
1341
|
+
// undefined while the originals are active.
|
|
1342
|
+
readonly set: { batches: ReadonlyArray<EncryptedRemoteBatch> } | undefined
|
|
1343
|
+
readonly base: number
|
|
1344
|
+
readonly keptCount: number | undefined
|
|
1345
|
+
readonly previous: MemoryActivation | undefined
|
|
1346
|
+
supersededAt: number | undefined
|
|
1347
|
+
pruneStartedAt: number | undefined
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
interface MemoryStage {
|
|
1351
|
+
readonly id: string
|
|
1352
|
+
readonly pinned: MemoryActivation
|
|
1353
|
+
readonly through: number
|
|
1354
|
+
expiresAt: number
|
|
1355
|
+
// Staged batches by position.
|
|
1356
|
+
readonly staged: Map<number, EncryptedBatch>
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
interface MemoryJournal {
|
|
1360
|
+
// Entry id to the sequence it was first stored at. Never pruned.
|
|
1361
|
+
readonly knownIds: Map<string, number>
|
|
1362
|
+
// Originals, in sequence order.
|
|
1363
|
+
readonly batches: Array<EncryptedRemoteBatch>
|
|
1364
|
+
nextSequence: number
|
|
1365
|
+
// Storage-time hour to the highest last sequence stored in it.
|
|
1366
|
+
readonly buckets: Map<number, number>
|
|
1367
|
+
readonly activations: Array<MemoryActivation>
|
|
1368
|
+
active: MemoryActivation
|
|
1369
|
+
noOp: { readonly at: number; readonly count: number } | undefined
|
|
1370
|
+
openStage: MemoryStage | undefined
|
|
1371
|
+
nextSweepAt: number | undefined
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const stagedBatches = (stage: MemoryStage) =>
|
|
1375
|
+
Array.from(stage.staged, ([position, batch]) => ({ batch, position })).sort((a, b) => a.position - b.position)
|
|
1376
|
+
|
|
1377
|
+
const findBatch = (batches: ReadonlyArray<EncryptedRemoteBatch>, sequence: number) => {
|
|
1378
|
+
let low = 0
|
|
1379
|
+
let high = batches.length
|
|
1380
|
+
while (low < high) {
|
|
1381
|
+
const mid = (low + high) >>> 1
|
|
1382
|
+
if (batches[mid].sequence < sequence) low = mid + 1
|
|
1383
|
+
else high = mid
|
|
1384
|
+
}
|
|
1385
|
+
return batches[low]?.sequence === sequence ? batches[low] : undefined
|
|
1386
|
+
}
|
|
1387
|
+
|
|
320
1388
|
const makeEncryptedScopeKey = ({ publicKey, storeId }: {
|
|
321
1389
|
readonly publicKey: string
|
|
322
1390
|
readonly storeId: StoreId
|