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
|
@@ -5,24 +5,32 @@
|
|
|
5
5
|
* `EventLogServerEncrypted` when entries should be stored without exposing
|
|
6
6
|
* plaintext event data to the database. It persists the server remote id,
|
|
7
7
|
* session authentication bindings, and encrypted batch tables, assigns stable
|
|
8
|
-
* sequence numbers,
|
|
9
|
-
* encrypting writes and decrypting reads.
|
|
8
|
+
* sequence numbers, streams changes, and stores encrypted compaction snapshots.
|
|
9
|
+
* Clients remain responsible for encrypting writes and decrypting reads.
|
|
10
10
|
*
|
|
11
11
|
* @stability unstable
|
|
12
12
|
* @since 4.0.0
|
|
13
13
|
*/
|
|
14
14
|
import * as Arr from "../Array.js";
|
|
15
|
+
import * as Cause from "../Cause.js";
|
|
16
|
+
import * as Clock from "../Clock.js";
|
|
17
|
+
import * as Digest from "../crypto/Digest.js";
|
|
18
|
+
import * as SecureRandom from "../crypto/SecureRandom.js";
|
|
19
|
+
import * as Duration from "../Duration.js";
|
|
15
20
|
import * as Effect from "../Effect.js";
|
|
16
21
|
import * as Layer from "../Layer.js";
|
|
22
|
+
import * as Option from "../Option.js";
|
|
17
23
|
import * as PubSub from "../PubSub.js";
|
|
18
24
|
import * as RcMap from "../RcMap.js";
|
|
25
|
+
import * as Schedule from "../Schedule.js";
|
|
19
26
|
import * as Schema from "../Schema.js";
|
|
20
27
|
import * as Semaphore from "../Semaphore.js";
|
|
21
28
|
import * as SqlClient from "../sql/SqlClient.js";
|
|
29
|
+
import * as SqlError from "../sql/SqlError.js";
|
|
22
30
|
import * as Stream from "../Stream.js";
|
|
23
31
|
import { makeRemoteIdUnsafe } from "./EventJournal.js";
|
|
24
|
-
import * as EventLogEncryption from "./EventLogEncryption.js";
|
|
25
32
|
import * as EventLogServerEncrypted from "./EventLogServerEncrypted.js";
|
|
33
|
+
import { hourMillis, lagHour, logCommit, logNoOp, logRollback, maxSegments, positionsPerSegment } from "./internal/compaction.js";
|
|
26
34
|
/**
|
|
27
35
|
* Creates encrypted event-log server `Storage` backed by SQL.
|
|
28
36
|
*
|
|
@@ -32,6 +40,11 @@ import * as EventLogServerEncrypted from "./EventLogServerEncrypted.js";
|
|
|
32
40
|
* batches in dialect-specific tables, creating per-identity/store batch and entry
|
|
33
41
|
* id tables as needed.
|
|
34
42
|
*
|
|
43
|
+
* The storage always provides `compaction`. Its tables are created on startup
|
|
44
|
+
* and when a store is first opened. A sweep in the surrounding scope prunes
|
|
45
|
+
* superseded generations every `sweepInterval`. Startup dies on invalid
|
|
46
|
+
* compaction options (see `EventLogServerEncrypted.makeCompactionConfig`).
|
|
47
|
+
*
|
|
35
48
|
* **Gotchas**
|
|
36
49
|
*
|
|
37
50
|
* Storage does not retry sequence conflicts between competing instances.
|
|
@@ -40,13 +53,26 @@ import * as EventLogServerEncrypted from "./EventLogServerEncrypted.js";
|
|
|
40
53
|
* are local to each storage instance; clients on other instances must reconnect
|
|
41
54
|
* to read those writes.
|
|
42
55
|
*
|
|
56
|
+
* Inside a caller's transaction, `write` and `compaction.rollback` publish to
|
|
57
|
+
* live readers after the outermost commit, and not at all if it rolls back.
|
|
58
|
+
* Concurrent caller transactions may reach live readers out of sequence order.
|
|
59
|
+
* `changes` always reads in committed order.
|
|
60
|
+
*
|
|
61
|
+
* Instances sharing a database should share compaction options. The instance
|
|
62
|
+
* that writes a generation or stage fixes its prune time or expiry.
|
|
63
|
+
*
|
|
43
64
|
* @stability unstable
|
|
44
65
|
* @category constructors
|
|
45
66
|
* @since 4.0.0
|
|
46
67
|
*/
|
|
47
68
|
export const makeStorage = options => Effect.gen(function* () {
|
|
48
|
-
const
|
|
69
|
+
const digest = yield* Digest.Digest;
|
|
49
70
|
const sql = (yield* SqlClient.SqlClient).withoutTransforms();
|
|
71
|
+
const config = yield* EventLogServerEncrypted.makeCompactionConfig(options?.compaction);
|
|
72
|
+
const graceMillis = Duration.toMillis(config.grace);
|
|
73
|
+
const lagMillis = Duration.toMillis(config.lag);
|
|
74
|
+
const stageTtlMillis = Duration.toMillis(config.stageTtl);
|
|
75
|
+
const sweepIntervalMillis = Duration.toMillis(config.sweepInterval);
|
|
50
76
|
const tablePrefix = options?.entryTablePrefix ?? "effect_events";
|
|
51
77
|
const remoteIdTable = options?.remoteIdTable ?? "effect_remote_id";
|
|
52
78
|
const sessionAuthBindingsTable = `${tablePrefix}_session_auth_bindings`;
|
|
@@ -94,6 +120,110 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
94
120
|
signing_public_key BLOB NOT NULL
|
|
95
121
|
)`
|
|
96
122
|
});
|
|
123
|
+
const types = sql.onDialectOrElse({
|
|
124
|
+
pg: () => ({
|
|
125
|
+
id: "TEXT",
|
|
126
|
+
int: "INTEGER",
|
|
127
|
+
bigint: "BIGINT",
|
|
128
|
+
bytes: "BYTEA",
|
|
129
|
+
entryId: "BYTEA",
|
|
130
|
+
iv: "BYTEA"
|
|
131
|
+
}),
|
|
132
|
+
mysql: () => ({
|
|
133
|
+
id: "VARCHAR(36)",
|
|
134
|
+
int: "INT",
|
|
135
|
+
bigint: "BIGINT",
|
|
136
|
+
bytes: "LONGBLOB",
|
|
137
|
+
entryId: "BINARY(16)",
|
|
138
|
+
iv: "BINARY(12)"
|
|
139
|
+
}),
|
|
140
|
+
mssql: () => ({
|
|
141
|
+
id: "NVARCHAR(36)",
|
|
142
|
+
int: "INT",
|
|
143
|
+
bigint: "BIGINT",
|
|
144
|
+
bytes: "VARBINARY(MAX)",
|
|
145
|
+
entryId: "VARBINARY(16)",
|
|
146
|
+
iv: "VARBINARY(12)"
|
|
147
|
+
}),
|
|
148
|
+
orElse: () => ({
|
|
149
|
+
id: "TEXT",
|
|
150
|
+
int: "INTEGER",
|
|
151
|
+
bigint: "INTEGER",
|
|
152
|
+
bytes: "BLOB",
|
|
153
|
+
entryId: "BLOB",
|
|
154
|
+
iv: "BLOB"
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
// Two instances can open the same table at once, and PostgreSQL and SQL
|
|
158
|
+
// Server can fail the loser of a concurrent create, so creation retries.
|
|
159
|
+
const createTable = (name, columns, indexes = []) => sql.onDialectOrElse({
|
|
160
|
+
mysql: () => {
|
|
161
|
+
const definitions = [columns, ...indexes.map(index => `INDEX (${index})`)].join(", ");
|
|
162
|
+
return Effect.asVoid(sql`CREATE TABLE IF NOT EXISTS ${sql(name)} (${sql.literal(definitions)})`);
|
|
163
|
+
},
|
|
164
|
+
mssql: () => sql`IF OBJECT_ID(${name}, N'U') IS NULL CREATE TABLE ${sql(name)} (${sql.literal(columns)})`.pipe(Effect.andThen(Effect.forEach(indexes, (index, i) => {
|
|
165
|
+
const indexName = `${name}_i${i}`;
|
|
166
|
+
return sql`
|
|
167
|
+
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${indexName} AND object_id = OBJECT_ID(${name}))
|
|
168
|
+
CREATE INDEX ${sql(indexName)} ON ${sql(name)} (${sql.literal(index)})`;
|
|
169
|
+
}, {
|
|
170
|
+
discard: true
|
|
171
|
+
}))),
|
|
172
|
+
orElse: () => sql`CREATE TABLE IF NOT EXISTS ${sql(name)} (${sql.literal(columns)})`.pipe(Effect.andThen(Effect.forEach(indexes, (index, i) => sql`CREATE INDEX IF NOT EXISTS ${sql(`${name}_i${i}`)} ON ${sql(name)} (${sql.literal(index)})`, {
|
|
173
|
+
discard: true
|
|
174
|
+
})))
|
|
175
|
+
}).pipe(Effect.retry({
|
|
176
|
+
times: 3
|
|
177
|
+
}));
|
|
178
|
+
const storesTable = `${tablePrefix}_compaction_stores`;
|
|
179
|
+
const bucketsTable = `${tablePrefix}_compaction_buckets`;
|
|
180
|
+
const activationsTable = `${tablePrefix}_compaction_activations`;
|
|
181
|
+
const stagesTable = `${tablePrefix}_compaction_stages`;
|
|
182
|
+
const storesSql = sql(storesTable);
|
|
183
|
+
const bucketsSql = sql(bucketsTable);
|
|
184
|
+
const activationsSql = sql(activationsTable);
|
|
185
|
+
const stagesSql = sql(stagesTable);
|
|
186
|
+
// One row per store, keyed by the scope hash its tables are named after.
|
|
187
|
+
// "Nothing" is 0 for the base, `pruned_through` and the lag head.
|
|
188
|
+
yield* createTable(storesTable, `scope ${types.id} PRIMARY KEY,
|
|
189
|
+
next_sequence ${types.int} NOT NULL,
|
|
190
|
+
active_activation ${types.id} NOT NULL,
|
|
191
|
+
active_batch_set ${types.id} NULL,
|
|
192
|
+
base_sequence ${types.int} NOT NULL,
|
|
193
|
+
active_kept_count ${types.int} NOT NULL,
|
|
194
|
+
no_op_at ${types.int} NULL,
|
|
195
|
+
no_op_count ${types.int} NOT NULL,
|
|
196
|
+
open_stage ${types.id} NULL,
|
|
197
|
+
next_sweep_at ${types.bigint} NULL,
|
|
198
|
+
sweep_lease_until ${types.bigint} NULL,
|
|
199
|
+
sweep_lease_owner ${types.id} NULL,
|
|
200
|
+
pruned_through ${types.int} NOT NULL`, ["next_sweep_at"]);
|
|
201
|
+
// The highest last sequence stored in each storage-time hour.
|
|
202
|
+
yield* createTable(bucketsTable, `scope ${types.id} NOT NULL,
|
|
203
|
+
bucket_hour ${types.bigint} NOT NULL,
|
|
204
|
+
last_sequence ${types.int} NOT NULL,
|
|
205
|
+
PRIMARY KEY (scope, bucket_hour)`);
|
|
206
|
+
// A `batch_set` of null means the originals only. `prunable_at` is set when
|
|
207
|
+
// the activation is superseded or withdrawn.
|
|
208
|
+
yield* createTable(activationsTable, `id ${types.id} PRIMARY KEY,
|
|
209
|
+
scope ${types.id} NOT NULL,
|
|
210
|
+
ordinal ${types.int} NOT NULL,
|
|
211
|
+
batch_set ${types.id} NULL,
|
|
212
|
+
base_sequence ${types.int} NOT NULL,
|
|
213
|
+
kept_count ${types.int} NULL,
|
|
214
|
+
previous ${types.id} NULL,
|
|
215
|
+
superseded_at ${types.bigint} NULL,
|
|
216
|
+
prunable_at ${types.bigint} NULL,
|
|
217
|
+
prune_started_at ${types.bigint} NULL`, ["scope, ordinal"]);
|
|
218
|
+
// A stage's pending batch set is its own id. A committed stage's row is
|
|
219
|
+
// deleted at commit. Other stages go from `open` to `closed`, and the
|
|
220
|
+
// sweep deletes what they staged, then the row.
|
|
221
|
+
yield* createTable(stagesTable, `id ${types.id} PRIMARY KEY,
|
|
222
|
+
scope ${types.id} NOT NULL,
|
|
223
|
+
state ${types.id} NOT NULL,
|
|
224
|
+
pinned ${types.id} NOT NULL,
|
|
225
|
+
through_sequence ${types.int} NOT NULL,
|
|
226
|
+
expires_at ${types.bigint} NOT NULL`, ["scope, state", "state, expires_at"]);
|
|
97
227
|
const remoteId = yield* sql`SELECT remote_id FROM ${remoteIdTableSql}`.pipe(Effect.flatMap(results => {
|
|
98
228
|
if (results.length > 0) {
|
|
99
229
|
return Effect.succeed(results[0].remote_id);
|
|
@@ -101,96 +231,260 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
101
231
|
const created = makeRemoteIdUnsafe();
|
|
102
232
|
return Effect.as(sql`INSERT INTO ${remoteIdTableSql} (remote_id) VALUES (${created})`, created);
|
|
103
233
|
}));
|
|
234
|
+
const scopeTables = scope => {
|
|
235
|
+
const table = `${tablePrefix}_${scope}`;
|
|
236
|
+
return {
|
|
237
|
+
tableSql: sql(table),
|
|
238
|
+
idsTableSql: sql(`${table}_ids`),
|
|
239
|
+
snapshotTableSql: sql(`${table}_snapshot`)
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
const ensureTables = Effect.fnUntraced(function* (scope, tables) {
|
|
243
|
+
const table = `${tablePrefix}_${scope}`;
|
|
244
|
+
const {
|
|
245
|
+
idsTableSql,
|
|
246
|
+
tableSql
|
|
247
|
+
} = tables;
|
|
248
|
+
const indexSql = sql(`${table}_last_sequence`);
|
|
249
|
+
yield* sql.onDialectOrElse({
|
|
250
|
+
pg: () => sql`
|
|
251
|
+
CREATE TABLE IF NOT EXISTS ${tableSql} (
|
|
252
|
+
sequence INTEGER PRIMARY KEY,
|
|
253
|
+
last_sequence INTEGER NOT NULL,
|
|
254
|
+
iv BYTEA NOT NULL,
|
|
255
|
+
entry_ids BYTEA NOT NULL,
|
|
256
|
+
encrypted_batch BYTEA NOT NULL
|
|
257
|
+
)`.pipe(Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`), Effect.andThen(sql`
|
|
258
|
+
CREATE TABLE IF NOT EXISTS ${idsTableSql} (
|
|
259
|
+
entry_id BYTEA PRIMARY KEY,
|
|
260
|
+
sequence INTEGER NOT NULL
|
|
261
|
+
)`)),
|
|
262
|
+
mysql: () => sql`
|
|
263
|
+
CREATE TABLE IF NOT EXISTS ${tableSql} (
|
|
264
|
+
sequence INT PRIMARY KEY,
|
|
265
|
+
last_sequence INT NOT NULL,
|
|
266
|
+
iv BINARY(12) NOT NULL,
|
|
267
|
+
entry_ids LONGBLOB NOT NULL,
|
|
268
|
+
encrypted_batch LONGBLOB NOT NULL,
|
|
269
|
+
INDEX (last_sequence)
|
|
270
|
+
)`.pipe(Effect.andThen(sql`
|
|
271
|
+
CREATE TABLE IF NOT EXISTS ${idsTableSql} (
|
|
272
|
+
entry_id BINARY(16) PRIMARY KEY,
|
|
273
|
+
sequence INT NOT NULL
|
|
274
|
+
)`)),
|
|
275
|
+
mssql: () => sql`
|
|
276
|
+
IF OBJECT_ID(${table}, N'U') IS NULL
|
|
277
|
+
CREATE TABLE ${tableSql} (
|
|
278
|
+
sequence INT PRIMARY KEY,
|
|
279
|
+
last_sequence INT NOT NULL,
|
|
280
|
+
iv VARBINARY(12) NOT NULL,
|
|
281
|
+
entry_ids VARBINARY(MAX) NOT NULL,
|
|
282
|
+
encrypted_batch VARBINARY(MAX) NOT NULL,
|
|
283
|
+
INDEX ${indexSql} NONCLUSTERED (last_sequence)
|
|
284
|
+
)`.pipe(Effect.andThen(sql`
|
|
285
|
+
IF OBJECT_ID(${`${table}_ids`}, N'U') IS NULL
|
|
286
|
+
CREATE TABLE ${idsTableSql} (
|
|
287
|
+
entry_id VARBINARY(16) PRIMARY KEY,
|
|
288
|
+
sequence INT NOT NULL
|
|
289
|
+
)`)),
|
|
290
|
+
orElse: () => sql`
|
|
291
|
+
CREATE TABLE IF NOT EXISTS ${tableSql} (
|
|
292
|
+
sequence INTEGER PRIMARY KEY,
|
|
293
|
+
last_sequence INTEGER NOT NULL,
|
|
294
|
+
iv BLOB NOT NULL,
|
|
295
|
+
entry_ids BLOB NOT NULL,
|
|
296
|
+
encrypted_batch BLOB NOT NULL
|
|
297
|
+
)`.pipe(Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`), Effect.andThen(sql`
|
|
298
|
+
CREATE TABLE IF NOT EXISTS ${idsTableSql} (
|
|
299
|
+
entry_id BLOB PRIMARY KEY,
|
|
300
|
+
sequence INTEGER NOT NULL
|
|
301
|
+
)`))
|
|
302
|
+
}).pipe(Effect.retry({
|
|
303
|
+
times: 3
|
|
304
|
+
}));
|
|
305
|
+
// Staged and committed snapshot batches. `sequence` and
|
|
306
|
+
// `last_sequence` stay null until commit.
|
|
307
|
+
yield* createTable(`${table}_snapshot`, `batch_set ${types.id} NOT NULL,
|
|
308
|
+
batch_position ${types.bigint} NOT NULL,
|
|
309
|
+
sequence ${types.int} NULL,
|
|
310
|
+
last_sequence ${types.int} NULL,
|
|
311
|
+
entry_count ${types.int} NOT NULL,
|
|
312
|
+
iv ${types.iv} NOT NULL,
|
|
313
|
+
entry_ids ${types.bytes} NOT NULL,
|
|
314
|
+
encrypted_batch ${types.bytes} NOT NULL,
|
|
315
|
+
PRIMARY KEY (batch_set, batch_position)`, ["batch_set, last_sequence"]);
|
|
316
|
+
yield* ensureStore(scope, tables);
|
|
317
|
+
});
|
|
318
|
+
// Stores whose tables and row this instance has ensured, so a store
|
|
319
|
+
// reopened after idling skips the DDL.
|
|
320
|
+
const ensured = new Set();
|
|
321
|
+
const lookup = Effect.fnUntraced(function* (scopeKey) {
|
|
322
|
+
const scope = (yield* digest.digestHex(Digest.sha256, new TextEncoder().encode(scopeKey))).slice(0, 16);
|
|
323
|
+
const tables = scopeTables(scope);
|
|
324
|
+
if (!ensured.has(scope)) {
|
|
325
|
+
yield* ensureTables(scope, tables);
|
|
326
|
+
ensured.add(scope);
|
|
327
|
+
}
|
|
328
|
+
const pubsub = yield* Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown);
|
|
329
|
+
const semaphore = yield* Semaphore.make(1);
|
|
330
|
+
return {
|
|
331
|
+
pubsub,
|
|
332
|
+
semaphore,
|
|
333
|
+
scope,
|
|
334
|
+
...tables
|
|
335
|
+
};
|
|
336
|
+
}, withTracerDisabled);
|
|
104
337
|
const resources = yield* RcMap.make({
|
|
105
|
-
lookup
|
|
106
|
-
const scopeHash = (yield* encryptions.sha256String(new TextEncoder().encode(scopeKey))).slice(0, 16);
|
|
107
|
-
const table = `${tablePrefix}_${scopeHash}`;
|
|
108
|
-
const tableSql = sql(table);
|
|
109
|
-
const idsTableSql = sql(`${table}_ids`);
|
|
110
|
-
const indexSql = sql(`${table}_last_sequence`);
|
|
111
|
-
yield* sql.onDialectOrElse({
|
|
112
|
-
pg: () => sql`
|
|
113
|
-
CREATE TABLE IF NOT EXISTS ${tableSql} (
|
|
114
|
-
sequence INTEGER PRIMARY KEY,
|
|
115
|
-
last_sequence INTEGER NOT NULL,
|
|
116
|
-
iv BYTEA NOT NULL,
|
|
117
|
-
entry_ids BYTEA NOT NULL,
|
|
118
|
-
encrypted_batch BYTEA NOT NULL
|
|
119
|
-
)`.pipe(Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`), Effect.andThen(sql`
|
|
120
|
-
CREATE TABLE IF NOT EXISTS ${idsTableSql} (
|
|
121
|
-
entry_id BYTEA PRIMARY KEY,
|
|
122
|
-
sequence INTEGER NOT NULL
|
|
123
|
-
)`)),
|
|
124
|
-
mysql: () => sql`
|
|
125
|
-
CREATE TABLE IF NOT EXISTS ${tableSql} (
|
|
126
|
-
sequence INT PRIMARY KEY,
|
|
127
|
-
last_sequence INT NOT NULL,
|
|
128
|
-
iv BINARY(12) NOT NULL,
|
|
129
|
-
entry_ids LONGBLOB NOT NULL,
|
|
130
|
-
encrypted_batch LONGBLOB NOT NULL,
|
|
131
|
-
INDEX (last_sequence)
|
|
132
|
-
)`.pipe(Effect.andThen(sql`
|
|
133
|
-
CREATE TABLE IF NOT EXISTS ${idsTableSql} (
|
|
134
|
-
entry_id BINARY(16) PRIMARY KEY,
|
|
135
|
-
sequence INT NOT NULL
|
|
136
|
-
)`)),
|
|
137
|
-
mssql: () => sql`
|
|
138
|
-
IF OBJECT_ID(${table}, N'U') IS NULL
|
|
139
|
-
CREATE TABLE ${tableSql} (
|
|
140
|
-
sequence INT PRIMARY KEY,
|
|
141
|
-
last_sequence INT NOT NULL,
|
|
142
|
-
iv VARBINARY(12) NOT NULL,
|
|
143
|
-
entry_ids VARBINARY(MAX) NOT NULL,
|
|
144
|
-
encrypted_batch VARBINARY(MAX) NOT NULL,
|
|
145
|
-
INDEX ${indexSql} NONCLUSTERED (last_sequence)
|
|
146
|
-
)`.pipe(Effect.andThen(sql`
|
|
147
|
-
IF OBJECT_ID(${`${table}_ids`}, N'U') IS NULL
|
|
148
|
-
CREATE TABLE ${idsTableSql} (
|
|
149
|
-
entry_id VARBINARY(16) PRIMARY KEY,
|
|
150
|
-
sequence INT NOT NULL
|
|
151
|
-
)`)),
|
|
152
|
-
orElse: () => sql`
|
|
153
|
-
CREATE TABLE IF NOT EXISTS ${tableSql} (
|
|
154
|
-
sequence INTEGER PRIMARY KEY,
|
|
155
|
-
last_sequence INTEGER NOT NULL,
|
|
156
|
-
iv BLOB NOT NULL,
|
|
157
|
-
entry_ids BLOB NOT NULL,
|
|
158
|
-
encrypted_batch BLOB NOT NULL
|
|
159
|
-
)`.pipe(Effect.andThen(sql`CREATE INDEX IF NOT EXISTS ${indexSql} ON ${tableSql} (last_sequence)`), Effect.andThen(sql`
|
|
160
|
-
CREATE TABLE IF NOT EXISTS ${idsTableSql} (
|
|
161
|
-
entry_id BLOB PRIMARY KEY,
|
|
162
|
-
sequence INTEGER NOT NULL
|
|
163
|
-
)`))
|
|
164
|
-
});
|
|
165
|
-
const pubsub = yield* Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown);
|
|
166
|
-
// Serializes writes within this storage instance.
|
|
167
|
-
const semaphore = yield* Semaphore.make(1);
|
|
168
|
-
return {
|
|
169
|
-
pubsub,
|
|
170
|
-
semaphore,
|
|
171
|
-
tableSql,
|
|
172
|
-
idsTableSql
|
|
173
|
-
};
|
|
174
|
-
}, withTracerDisabled),
|
|
338
|
+
lookup,
|
|
175
339
|
idleTimeToLive: "5 minutes"
|
|
176
340
|
});
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
341
|
+
// Statements that return rows the caller decodes.
|
|
342
|
+
const query = statement => statement;
|
|
343
|
+
// Inserts the rows whose `keys` are not stored yet, leaving stored rows as
|
|
344
|
+
// they are, and returns how many were inserted. Every row has the same
|
|
345
|
+
// columns, and SQL Server's MERGE needs them unique by key.
|
|
346
|
+
const insertNew = (tableSql, rows, keys) => sql.onDialectOrElse({
|
|
347
|
+
mysql: () => sql`INSERT IGNORE INTO ${tableSql} ${sql.insert(rows)}`.raw.pipe(Effect.map(result => result.affectedRows)),
|
|
348
|
+
mssql: () => {
|
|
349
|
+
const columns = Object.keys(rows[0]);
|
|
350
|
+
const values = sql.join(", ", false)(rows.map(row => sql`(${sql.join(", ", false)(columns.map(column => sql`${row[column]}`))})`));
|
|
351
|
+
return sql`
|
|
352
|
+
MERGE ${tableSql} WITH (HOLDLOCK) AS target
|
|
353
|
+
USING (VALUES ${values}) AS source (${sql.literal(columns.join(", "))})
|
|
354
|
+
ON ${sql.literal(keys.map(key => `target.${key} = source.${key}`).join(" AND "))}
|
|
355
|
+
WHEN NOT MATCHED THEN
|
|
356
|
+
INSERT (${sql.literal(columns.join(", "))})
|
|
357
|
+
VALUES (${sql.literal(columns.map(column => `source.${column}`).join(", "))})
|
|
358
|
+
OUTPUT inserted.${sql.literal(keys[0])};
|
|
359
|
+
`.pipe(Effect.map(inserted => inserted.length));
|
|
360
|
+
},
|
|
361
|
+
orElse: () => sql`INSERT INTO ${tableSql} ${sql.insert(rows)} ON CONFLICT DO NOTHING RETURNING ${sql(keys[0])}`.pipe(Effect.map(inserted => inserted.length))
|
|
362
|
+
});
|
|
363
|
+
const upsertBucket = (scope, hour, lastSequence) => sql.onDialectOrElse({
|
|
364
|
+
pg: () => sql`
|
|
365
|
+
INSERT INTO ${bucketsSql} (scope, bucket_hour, last_sequence) VALUES (${scope}, ${hour}, ${lastSequence})
|
|
366
|
+
ON CONFLICT (scope, bucket_hour)
|
|
367
|
+
DO UPDATE SET last_sequence = GREATEST(${bucketsSql}.last_sequence, EXCLUDED.last_sequence)`,
|
|
368
|
+
mysql: () => sql`
|
|
369
|
+
INSERT INTO ${bucketsSql} (scope, bucket_hour, last_sequence) VALUES (${scope}, ${hour}, ${lastSequence})
|
|
370
|
+
ON DUPLICATE KEY UPDATE last_sequence = GREATEST(last_sequence, VALUES(last_sequence))`,
|
|
181
371
|
mssql: () => sql`
|
|
182
|
-
MERGE ${
|
|
183
|
-
USING (VALUES ${
|
|
184
|
-
ON target.
|
|
372
|
+
MERGE ${bucketsSql} WITH (HOLDLOCK) AS target
|
|
373
|
+
USING (VALUES (${scope}, ${hour}, ${lastSequence})) AS source (scope, bucket_hour, last_sequence)
|
|
374
|
+
ON target.scope = source.scope AND target.bucket_hour = source.bucket_hour
|
|
375
|
+
WHEN MATCHED AND source.last_sequence > target.last_sequence THEN
|
|
376
|
+
UPDATE SET last_sequence = source.last_sequence
|
|
185
377
|
WHEN NOT MATCHED THEN
|
|
186
|
-
INSERT (
|
|
187
|
-
|
|
188
|
-
`.pipe(Effect.map(inserted => inserted.length)),
|
|
378
|
+
INSERT (scope, bucket_hour, last_sequence)
|
|
379
|
+
VALUES (source.scope, source.bucket_hour, source.last_sequence);`,
|
|
189
380
|
orElse: () => sql`
|
|
190
|
-
INSERT INTO ${
|
|
191
|
-
ON CONFLICT
|
|
192
|
-
|
|
381
|
+
INSERT INTO ${bucketsSql} (scope, bucket_hour, last_sequence) VALUES (${scope}, ${hour}, ${lastSequence})
|
|
382
|
+
ON CONFLICT (scope, bucket_hour)
|
|
383
|
+
DO UPDATE SET last_sequence = MAX(${bucketsSql}.last_sequence, excluded.last_sequence)`
|
|
384
|
+
});
|
|
385
|
+
const maxLastSequence = tableSql => Effect.map(query(sql`SELECT MAX(last_sequence) AS last_sequence FROM ${tableSql}`), ([row]) => Number(row?.last_sequence ?? 0));
|
|
386
|
+
// Creates the store row on first open. The first instance to insert it
|
|
387
|
+
// also records the originals activation and seeds a bucket at the current
|
|
388
|
+
// last sequence, so data written before the upgrade becomes compactable.
|
|
389
|
+
const ensureStore = Effect.fnUntraced(function* (scope, tables) {
|
|
390
|
+
const existing = yield* query(sql`SELECT scope FROM ${storesSql} WHERE scope = ${scope}`);
|
|
391
|
+
if (existing.length > 0) return;
|
|
392
|
+
const now = yield* Clock.currentTimeMillis;
|
|
393
|
+
yield* sql.withTransaction(Effect.gen(function* () {
|
|
394
|
+
const lastSequence = yield* maxLastSequence(tables.tableSql);
|
|
395
|
+
const originals = SecureRandom.uuidV4Unsafe();
|
|
396
|
+
const inserted = yield* insertNew(storesSql, [{
|
|
397
|
+
scope,
|
|
398
|
+
next_sequence: lastSequence + 1,
|
|
399
|
+
active_activation: originals,
|
|
400
|
+
active_batch_set: null,
|
|
401
|
+
base_sequence: 0,
|
|
402
|
+
active_kept_count: 0,
|
|
403
|
+
no_op_at: null,
|
|
404
|
+
no_op_count: 0,
|
|
405
|
+
open_stage: null,
|
|
406
|
+
next_sweep_at: null,
|
|
407
|
+
sweep_lease_until: null,
|
|
408
|
+
sweep_lease_owner: null,
|
|
409
|
+
pruned_through: 0
|
|
410
|
+
}], ["scope"]);
|
|
411
|
+
if (inserted === 0) return;
|
|
412
|
+
yield* sql`INSERT INTO ${activationsSql} ${sql.insert({
|
|
413
|
+
id: originals,
|
|
414
|
+
scope,
|
|
415
|
+
ordinal: 0,
|
|
416
|
+
batch_set: null,
|
|
417
|
+
base_sequence: 0,
|
|
418
|
+
kept_count: null,
|
|
419
|
+
previous: null,
|
|
420
|
+
superseded_at: null,
|
|
421
|
+
prunable_at: null,
|
|
422
|
+
prune_started_at: null
|
|
423
|
+
})}`;
|
|
424
|
+
if (lastSequence > 0) yield* upsertBucket(scope, Math.floor(now / hourMillis), lastSequence);
|
|
425
|
+
}));
|
|
426
|
+
});
|
|
427
|
+
const storeColumns = sql.literal("next_sequence, active_activation, active_batch_set, base_sequence, active_kept_count, no_op_at, " + "no_op_count, open_stage, next_sweep_at, sweep_lease_until, sweep_lease_owner, pruned_through");
|
|
428
|
+
const selectStore = scope => Effect.map(query(sql`SELECT ${storeColumns} FROM ${storesSql} WHERE scope = ${scope}`), ([row]) => row === undefined ? undefined : toStoreRow(row));
|
|
429
|
+
// Row locks held for the rest of the transaction. SQLite transactions take
|
|
430
|
+
// the database write lock instead.
|
|
431
|
+
const lockHint = sql.onDialectOrElse({
|
|
432
|
+
mssql: () => sql.literal("WITH (UPDLOCK, ROWLOCK, HOLDLOCK)"),
|
|
433
|
+
orElse: () => sql.literal("")
|
|
193
434
|
});
|
|
435
|
+
const forUpdate = sql.onDialectOrElse({
|
|
436
|
+
pg: () => sql.literal("FOR UPDATE"),
|
|
437
|
+
mysql: () => sql.literal("FOR UPDATE"),
|
|
438
|
+
orElse: () => sql.literal("")
|
|
439
|
+
});
|
|
440
|
+
const noClause = sql.literal("");
|
|
441
|
+
// Locks the store row for the rest of the transaction. Lock order is store
|
|
442
|
+
// row, then stage row, then snapshot rows.
|
|
443
|
+
const lockStore = scope => query(sql`SELECT ${storeColumns} FROM ${storesSql} ${lockHint} WHERE scope = ${scope} ${forUpdate}`).pipe(Effect.flatMap(([row]) => row === undefined ? Effect.die(new Cause.NoSuchElementError(`No compaction store row for scope ${scope}`)) : Effect.succeed(toStoreRow(row))));
|
|
444
|
+
const stageColumns = sql.literal("id, scope, state, pinned, through_sequence, expires_at");
|
|
445
|
+
const selectStage = (scope, stageId, lock) => query(sql`
|
|
446
|
+
SELECT ${stageColumns} FROM ${stagesSql} ${lock ? lockHint : noClause}
|
|
447
|
+
WHERE id = ${stageId} ${lock ? forUpdate : noClause}`).pipe(Effect.map(([row]) => {
|
|
448
|
+
if (row === undefined) return undefined;
|
|
449
|
+
const stage = toStageRow(row);
|
|
450
|
+
return stage.scope === scope ? stage : undefined;
|
|
451
|
+
}));
|
|
452
|
+
const selectActivation = id => Effect.map(query(sql`SELECT ${activationColumns} FROM ${activationsSql} WHERE id = ${id}`), ([row]) => row === undefined ? undefined : toActivationRow(row));
|
|
453
|
+
const selectActivations = scope => Effect.map(query(sql`SELECT ${activationColumns} FROM ${activationsSql} WHERE scope = ${scope} ORDER BY ordinal`), rows => rows.map(toActivationRow));
|
|
454
|
+
const activationColumns = sql.literal("id, ordinal, batch_set, base_sequence, kept_count, previous, superseded_at, prunable_at, prune_started_at");
|
|
455
|
+
const releaseClaim = (scope, stageId) => sql`UPDATE ${storesSql} SET open_stage = NULL WHERE scope = ${scope} AND open_stage = ${stageId}`;
|
|
456
|
+
// Closes an open stage, leaving what it staged to the sweep, and releases
|
|
457
|
+
// the store's busy claim.
|
|
458
|
+
const closeStage = (scope, stageId) => sql`UPDATE ${stagesSql} SET state = ${"closed"} WHERE id = ${stageId} AND state = ${"open"}`.pipe(Effect.andThen(releaseClaim(scope, stageId)));
|
|
459
|
+
const scheduleSweep = (scope, at) => sql`
|
|
460
|
+
UPDATE ${storesSql}
|
|
461
|
+
SET next_sweep_at = CASE WHEN next_sweep_at IS NULL OR next_sweep_at > ${at} THEN ${at} ELSE next_sweep_at END
|
|
462
|
+
WHERE scope = ${scope}`;
|
|
463
|
+
// The clauses that cap a statement at `rows` rows, for `SELECT ${top} ... ${limit}`.
|
|
464
|
+
const rowCap = rows => ({
|
|
465
|
+
top: sql.onDialectOrElse({
|
|
466
|
+
mssql: () => sql.literal(`TOP (${rows})`),
|
|
467
|
+
orElse: () => noClause
|
|
468
|
+
}),
|
|
469
|
+
limit: sql.onDialectOrElse({
|
|
470
|
+
mssql: () => noClause,
|
|
471
|
+
orElse: () => sql.literal(`LIMIT ${rows}`)
|
|
472
|
+
})
|
|
473
|
+
});
|
|
474
|
+
const {
|
|
475
|
+
limit,
|
|
476
|
+
top
|
|
477
|
+
} = rowCap(pageSize);
|
|
478
|
+
const {
|
|
479
|
+
limit: chunkLimit,
|
|
480
|
+
top: chunkTop
|
|
481
|
+
} = rowCap(pruneChunkRows);
|
|
482
|
+
// Streams rows in key order, one page per statement.
|
|
483
|
+
const paged = (first, page, key) => Stream.paginate(first, after => Effect.map(page(after), rows => [rows, rows.length < pageSize ? Option.none() : Option.some(key(rows[rows.length - 1]))]));
|
|
484
|
+
const setRows = (snapshotTableSql, batchSet) => paged(-1, after => query(sql`
|
|
485
|
+
SELECT ${top} batch_position, sequence, entry_ids FROM ${snapshotTableSql}
|
|
486
|
+
WHERE batch_set = ${batchSet} AND batch_position > ${after}
|
|
487
|
+
ORDER BY batch_position ${limit}`), row => Number(row.batch_position));
|
|
194
488
|
const getSessionAuthBinding = publicKey => sql`
|
|
195
489
|
SELECT public_key, signing_public_key
|
|
196
490
|
FROM ${sessionAuthBindingsTableSql}
|
|
@@ -199,6 +493,571 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
199
493
|
const row = rows[0];
|
|
200
494
|
return row === undefined ? undefined : row.signing_public_key;
|
|
201
495
|
}), Effect.orDie);
|
|
496
|
+
// Runs `f` with the store's resources and the time, read before any
|
|
497
|
+
// transaction starts. SQL failures are defects.
|
|
498
|
+
const withStore = (publicKey, storeId, f) => Effect.zip(RcMap.get(resources, makeEncryptedScopeKey(publicKey, storeId)), Clock.currentTimeMillis).pipe(Effect.flatMap(([store, now]) => f(store, now)), dieOnSqlError, Effect.scoped, withTracerDisabled);
|
|
499
|
+
const notFound = stageId => Effect.fail(new EventLogServerEncrypted.CompactionNotFound({
|
|
500
|
+
stageId
|
|
501
|
+
}));
|
|
502
|
+
// An open stage whose pin isn't being pruned yet, for Input and Fetch.
|
|
503
|
+
const findInputStage = Effect.fnUntraced(function* (scope, stageId, now) {
|
|
504
|
+
const stage = yield* selectStage(scope, stageId, false);
|
|
505
|
+
if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) return undefined;
|
|
506
|
+
const pinned = yield* selectActivation(stage.pinned);
|
|
507
|
+
if (pinned === undefined || pinned.pruneStartedAt !== undefined) return undefined;
|
|
508
|
+
return {
|
|
509
|
+
stage,
|
|
510
|
+
pinned
|
|
511
|
+
};
|
|
512
|
+
});
|
|
513
|
+
// Fails a read that may have been truncated by a prune of its pin.
|
|
514
|
+
const recheckPin = (stageId, pinned) => Effect.flatMap(selectActivation(pinned), activation => activation === undefined || activation.pruneStartedAt !== undefined ? notFound(stageId) : Effect.void);
|
|
515
|
+
const status = Effect.fnUntraced(function* ({
|
|
516
|
+
scope,
|
|
517
|
+
tableSql
|
|
518
|
+
}, now) {
|
|
519
|
+
const store = yield* selectStore(scope);
|
|
520
|
+
const lastSequence = yield* maxLastSequence(tableSql);
|
|
521
|
+
const [lag] = yield* query(sql`
|
|
522
|
+
SELECT MAX(last_sequence) AS last_sequence FROM ${bucketsSql}
|
|
523
|
+
WHERE scope = ${scope} AND bucket_hour <= ${lagHour(now, lagMillis)}`);
|
|
524
|
+
return {
|
|
525
|
+
head: Math.max(store.nextSequence - 1, lastSequence),
|
|
526
|
+
base: store.base,
|
|
527
|
+
activeKeptCount: store.activeKeptCount,
|
|
528
|
+
lagHead: Number(lag?.last_sequence ?? 0),
|
|
529
|
+
noOp: store.noOpAt === undefined ? undefined : {
|
|
530
|
+
at: store.noOpAt,
|
|
531
|
+
count: store.noOpCount
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
});
|
|
535
|
+
const begin = Effect.fnUntraced(function* ({
|
|
536
|
+
scope
|
|
537
|
+
}, now, through) {
|
|
538
|
+
const store = yield* lockStore(scope);
|
|
539
|
+
if (store.openStage !== undefined) {
|
|
540
|
+
const open = yield* selectStage(scope, store.openStage, true);
|
|
541
|
+
if (open !== undefined && open.state === "open") {
|
|
542
|
+
if (open.expiresAt > now) {
|
|
543
|
+
return {
|
|
544
|
+
_tag: "Busy",
|
|
545
|
+
expiresAt: open.expiresAt
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
yield* closeStage(scope, open.id);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const stageId = SecureRandom.uuidV4Unsafe();
|
|
552
|
+
yield* sql`INSERT INTO ${stagesSql} ${sql.insert({
|
|
553
|
+
id: stageId,
|
|
554
|
+
scope,
|
|
555
|
+
state: "open",
|
|
556
|
+
pinned: store.activeActivation,
|
|
557
|
+
through_sequence: through,
|
|
558
|
+
expires_at: now + stageTtlMillis
|
|
559
|
+
})}`;
|
|
560
|
+
yield* sql`UPDATE ${storesSql} SET open_stage = ${stageId} WHERE scope = ${scope}`;
|
|
561
|
+
return {
|
|
562
|
+
_tag: "Started",
|
|
563
|
+
stageId
|
|
564
|
+
};
|
|
565
|
+
}, sql.withTransaction);
|
|
566
|
+
const inputHeaders = Effect.fnUntraced(function* ({
|
|
567
|
+
scope,
|
|
568
|
+
snapshotTableSql,
|
|
569
|
+
tableSql
|
|
570
|
+
}, now, stageId) {
|
|
571
|
+
const found = yield* findInputStage(scope, stageId, now);
|
|
572
|
+
if (found === undefined) {
|
|
573
|
+
return yield* notFound(stageId);
|
|
574
|
+
}
|
|
575
|
+
const {
|
|
576
|
+
pinned,
|
|
577
|
+
stage
|
|
578
|
+
} = found;
|
|
579
|
+
// Membership comes from the batch rows, never from the id table.
|
|
580
|
+
const snapshot = pinned.batchSet === undefined ? Stream.empty : setRows(snapshotTableSql, pinned.batchSet);
|
|
581
|
+
const tail = paged(pinned.base, after => query(sql`
|
|
582
|
+
SELECT ${top} sequence, entry_ids FROM ${tableSql}
|
|
583
|
+
WHERE sequence > ${after} AND last_sequence <= ${stage.through}
|
|
584
|
+
ORDER BY sequence ${limit}`), row => Number(row.sequence));
|
|
585
|
+
return snapshot.pipe(Stream.concat(tail), Stream.map(row => ({
|
|
586
|
+
sequence: Number(row.sequence),
|
|
587
|
+
entryIds: toEntryIds(row.entry_ids)
|
|
588
|
+
})), Stream.orDie, Stream.concat(Stream.fromEffectDrain(dieOnSqlError(recheckPin(stageId, pinned.id)))));
|
|
589
|
+
});
|
|
590
|
+
const fetch = Effect.fnUntraced(function* ({
|
|
591
|
+
scope,
|
|
592
|
+
snapshotTableSql,
|
|
593
|
+
tableSql
|
|
594
|
+
}, now, stageId, sequences) {
|
|
595
|
+
const found = yield* findInputStage(scope, stageId, now);
|
|
596
|
+
if (found === undefined) {
|
|
597
|
+
return yield* notFound(stageId);
|
|
598
|
+
}
|
|
599
|
+
const {
|
|
600
|
+
pinned,
|
|
601
|
+
stage
|
|
602
|
+
} = found;
|
|
603
|
+
const bySequence = new Map();
|
|
604
|
+
if (pinned.batchSet !== undefined) {
|
|
605
|
+
for (const chunk of Arr.chunksOf(sequences.filter(sequence => sequence <= pinned.base), 200)) {
|
|
606
|
+
const rows = yield* sql`
|
|
607
|
+
SELECT sequence, iv, entry_ids, encrypted_batch FROM ${snapshotTableSql}
|
|
608
|
+
WHERE batch_set = ${pinned.batchSet} AND ${sql.in("sequence", chunk)}
|
|
609
|
+
`.pipe(Effect.flatMap(decodeBatches), Effect.orDie);
|
|
610
|
+
for (const batch of rows) bySequence.set(batch.sequence, batch);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const originals = sequences.filter(sequence => sequence > pinned.base && sequence <= stage.through);
|
|
614
|
+
for (const chunk of Arr.chunksOf(originals, 200)) {
|
|
615
|
+
const rows = yield* sql`
|
|
616
|
+
SELECT sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
|
|
617
|
+
WHERE ${sql.in("sequence", chunk)}
|
|
618
|
+
`.pipe(Effect.flatMap(decodeBatches), Effect.orDie);
|
|
619
|
+
for (const batch of rows) bySequence.set(batch.sequence, batch);
|
|
620
|
+
}
|
|
621
|
+
yield* recheckPin(stageId, pinned.id);
|
|
622
|
+
return Stream.fromArray(sequences.flatMap(sequence => {
|
|
623
|
+
const batch = bySequence.get(sequence);
|
|
624
|
+
return batch === undefined ? [] : [batch];
|
|
625
|
+
}));
|
|
626
|
+
});
|
|
627
|
+
const stagePart = (store, now, stageId, segment, batches) => {
|
|
628
|
+
const invalid = !Number.isSafeInteger(segment) || segment < 0 || segment >= maxSegments ? "Invalid segment index" : batches.length > positionsPerSegment ? "Too many batches in a segment" : undefined;
|
|
629
|
+
// A Part that fails validation closes the stage.
|
|
630
|
+
if (invalid !== undefined) {
|
|
631
|
+
return Effect.andThen(abort(store, stageId), Effect.fail(new EventLogServerEncrypted.CompactionInvalidRequest({
|
|
632
|
+
stageId,
|
|
633
|
+
message: invalid
|
|
634
|
+
})));
|
|
635
|
+
}
|
|
636
|
+
const {
|
|
637
|
+
scope,
|
|
638
|
+
snapshotTableSql
|
|
639
|
+
} = store;
|
|
640
|
+
return sql.withTransaction(Effect.gen(function* () {
|
|
641
|
+
yield* lockStore(scope);
|
|
642
|
+
const stage = yield* selectStage(scope, stageId, true);
|
|
643
|
+
if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
|
|
644
|
+
return yield* notFound(stageId);
|
|
645
|
+
}
|
|
646
|
+
// The snapshot table's key makes a retried segment insert nothing.
|
|
647
|
+
// `sequence` and `last_sequence` stay null until commit.
|
|
648
|
+
const rows = batches.map((batch, i) => ({
|
|
649
|
+
batch_set: stageId,
|
|
650
|
+
batch_position: segment * positionsPerSegment + i,
|
|
651
|
+
entry_count: batch.entryIds.length,
|
|
652
|
+
iv: batch.iv,
|
|
653
|
+
entry_ids: concatEntryIds(batch.entryIds),
|
|
654
|
+
encrypted_batch: batch.encryptedBatch
|
|
655
|
+
}));
|
|
656
|
+
let inserted = 0;
|
|
657
|
+
for (const chunk of Arr.chunksOf(rows, 200)) {
|
|
658
|
+
inserted += yield* insertNew(snapshotTableSql, chunk, ["batch_set", "batch_position"]);
|
|
659
|
+
}
|
|
660
|
+
if (inserted === 0) return "AlreadyStored";
|
|
661
|
+
yield* sql`UPDATE ${stagesSql} SET expires_at = ${now + stageTtlMillis} WHERE id = ${stageId}`;
|
|
662
|
+
return "Stored";
|
|
663
|
+
}));
|
|
664
|
+
};
|
|
665
|
+
const stagedHeaders = Effect.fnUntraced(function* ({
|
|
666
|
+
scope,
|
|
667
|
+
snapshotTableSql
|
|
668
|
+
}, now, stageId) {
|
|
669
|
+
const stage = yield* selectStage(scope, stageId, false);
|
|
670
|
+
if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
|
|
671
|
+
return yield* notFound(stageId);
|
|
672
|
+
}
|
|
673
|
+
return setRows(snapshotTableSql, stageId).pipe(Stream.map(row => ({
|
|
674
|
+
position: Number(row.batch_position),
|
|
675
|
+
entryIds: toEntryIds(row.entry_ids)
|
|
676
|
+
})), Stream.orDie);
|
|
677
|
+
});
|
|
678
|
+
const commit = Effect.fnUntraced(function* ({
|
|
679
|
+
scope,
|
|
680
|
+
snapshotTableSql
|
|
681
|
+
}, now, stageId, virtualSequences) {
|
|
682
|
+
const store = yield* lockStore(scope);
|
|
683
|
+
const stage = yield* selectStage(scope, stageId, true);
|
|
684
|
+
if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
|
|
685
|
+
return yield* notFound(stageId);
|
|
686
|
+
}
|
|
687
|
+
const staged = yield* query(sql`
|
|
688
|
+
SELECT batch_position, entry_count FROM ${snapshotTableSql}
|
|
689
|
+
WHERE batch_set = ${stageId} ORDER BY batch_position`);
|
|
690
|
+
let keptCount = 0;
|
|
691
|
+
for (const row of staged) keptCount += Number(row.entry_count);
|
|
692
|
+
const audit = {
|
|
693
|
+
through: stage.through,
|
|
694
|
+
keptCount
|
|
695
|
+
};
|
|
696
|
+
if (stage.pinned !== store.activeActivation) {
|
|
697
|
+
yield* closeStage(scope, stageId);
|
|
698
|
+
return {
|
|
699
|
+
result: "Superseded",
|
|
700
|
+
activationId: undefined,
|
|
701
|
+
...audit
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
if (staged.length !== virtualSequences.length) {
|
|
705
|
+
return yield* Effect.die(new Cause.IllegalArgumentError("Expected one virtual sequence per staged batch"));
|
|
706
|
+
}
|
|
707
|
+
for (let i = 0; i < staged.length; i++) {
|
|
708
|
+
const sequence = virtualSequences[i];
|
|
709
|
+
const lastSequence = sequence + Number(staged[i].entry_count) - 1;
|
|
710
|
+
yield* sql`
|
|
711
|
+
UPDATE ${snapshotTableSql} SET sequence = ${sequence}, last_sequence = ${lastSequence}
|
|
712
|
+
WHERE batch_set = ${stageId} AND batch_position = ${Number(staged[i].batch_position)}`;
|
|
713
|
+
}
|
|
714
|
+
const activationId = SecureRandom.uuidV4Unsafe();
|
|
715
|
+
yield* insertActivation(scope, {
|
|
716
|
+
id: activationId,
|
|
717
|
+
batch_set: stageId,
|
|
718
|
+
base_sequence: stage.through,
|
|
719
|
+
kept_count: keptCount,
|
|
720
|
+
previous: store.activeActivation
|
|
721
|
+
});
|
|
722
|
+
yield* supersede(store.activeActivation, now);
|
|
723
|
+
yield* sql`
|
|
724
|
+
UPDATE ${storesSql}
|
|
725
|
+
SET active_activation = ${activationId}, active_batch_set = ${stageId},
|
|
726
|
+
base_sequence = ${stage.through}, active_kept_count = ${keptCount},
|
|
727
|
+
no_op_at = NULL, no_op_count = 0
|
|
728
|
+
WHERE scope = ${scope}`;
|
|
729
|
+
yield* scheduleSweep(scope, now + graceMillis);
|
|
730
|
+
// The staged rows are now the active batch set.
|
|
731
|
+
yield* sql`DELETE FROM ${stagesSql} WHERE id = ${stageId}`;
|
|
732
|
+
yield* releaseClaim(scope, stageId);
|
|
733
|
+
return {
|
|
734
|
+
result: "Committed",
|
|
735
|
+
activationId,
|
|
736
|
+
...audit
|
|
737
|
+
};
|
|
738
|
+
}, sql.withTransaction);
|
|
739
|
+
const recordNoOp = Effect.fnUntraced(function* ({
|
|
740
|
+
scope
|
|
741
|
+
}, now, stageId) {
|
|
742
|
+
const store = yield* lockStore(scope);
|
|
743
|
+
const stage = yield* selectStage(scope, stageId, true);
|
|
744
|
+
if (stage === undefined || stage.state !== "open" || stage.expiresAt <= now) {
|
|
745
|
+
return yield* notFound(stageId);
|
|
746
|
+
}
|
|
747
|
+
// A rollback since begin cleared the no-op state, so a stale run leaves it.
|
|
748
|
+
if (stage.pinned === store.activeActivation) {
|
|
749
|
+
yield* sql`
|
|
750
|
+
UPDATE ${storesSql} SET no_op_at = ${stage.through}, no_op_count = ${store.noOpCount + 1}
|
|
751
|
+
WHERE scope = ${scope}`;
|
|
752
|
+
}
|
|
753
|
+
yield* closeStage(scope, stageId);
|
|
754
|
+
return stage.through;
|
|
755
|
+
}, sql.withTransaction);
|
|
756
|
+
const rollbackTransaction = Effect.fnUntraced(function* ({
|
|
757
|
+
pubsub,
|
|
758
|
+
scope,
|
|
759
|
+
snapshotTableSql,
|
|
760
|
+
tableSql
|
|
761
|
+
}, now, expectedActivationId) {
|
|
762
|
+
const refuse = reason => Effect.fail(new EventLogServerEncrypted.CompactionRollbackError({
|
|
763
|
+
reason
|
|
764
|
+
}));
|
|
765
|
+
const store = yield* lockStore(scope);
|
|
766
|
+
if (store.activeActivation !== expectedActivationId) return yield* refuse("NotActive");
|
|
767
|
+
const withdrawn = yield* selectActivation(store.activeActivation);
|
|
768
|
+
const target = withdrawn.previous === undefined ? undefined : yield* selectActivation(withdrawn.previous);
|
|
769
|
+
if (target === undefined) return yield* refuse("NoPrevious");
|
|
770
|
+
if (target.pruneStartedAt !== undefined) return yield* refuse("PruneStarted");
|
|
771
|
+
const activationId = SecureRandom.uuidV4Unsafe();
|
|
772
|
+
yield* insertActivation(scope, {
|
|
773
|
+
id: activationId,
|
|
774
|
+
batch_set: target.batchSet ?? null,
|
|
775
|
+
base_sequence: target.base,
|
|
776
|
+
kept_count: target.keptCount ?? null,
|
|
777
|
+
previous: target.previous ?? null
|
|
778
|
+
});
|
|
779
|
+
yield* supersede(withdrawn.id, now);
|
|
780
|
+
yield* sql`
|
|
781
|
+
UPDATE ${storesSql}
|
|
782
|
+
SET active_activation = ${activationId}, active_batch_set = ${target.batchSet ?? null},
|
|
783
|
+
base_sequence = ${target.base}, active_kept_count = ${target.keptCount ?? 0},
|
|
784
|
+
no_op_at = NULL, no_op_count = 0
|
|
785
|
+
WHERE scope = ${scope}`;
|
|
786
|
+
yield* scheduleSweep(scope, now + graceMillis);
|
|
787
|
+
// Re-appends the input of the stage that built the withdrawn generation,
|
|
788
|
+
// the target's batch set and then the originals between the two bases,
|
|
789
|
+
// at sequences taken as `write` takes them.
|
|
790
|
+
let nextSequence = Math.max(store.nextSequence, (yield* maxLastSequence(tableSql)) + 1);
|
|
791
|
+
const reappended = [];
|
|
792
|
+
const reappend = rows => {
|
|
793
|
+
const inserted = [];
|
|
794
|
+
for (const row of rows) {
|
|
795
|
+
const entry_ids = row.entry_ids;
|
|
796
|
+
const batch = {
|
|
797
|
+
sequence: nextSequence,
|
|
798
|
+
entryIds: toEntryIds(entry_ids),
|
|
799
|
+
iv: row.iv,
|
|
800
|
+
encryptedBatch: row.encrypted_batch
|
|
801
|
+
};
|
|
802
|
+
nextSequence += batch.entryIds.length;
|
|
803
|
+
reappended.push(batch);
|
|
804
|
+
inserted.push({
|
|
805
|
+
sequence: batch.sequence,
|
|
806
|
+
last_sequence: nextSequence - 1,
|
|
807
|
+
iv: batch.iv,
|
|
808
|
+
entry_ids,
|
|
809
|
+
encrypted_batch: batch.encryptedBatch
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return Effect.forEach(Arr.chunksOf(inserted, 200), chunk => sql`INSERT INTO ${tableSql} ${sql.insert(chunk)}`, {
|
|
813
|
+
discard: true
|
|
814
|
+
});
|
|
815
|
+
};
|
|
816
|
+
if (target.batchSet !== undefined) {
|
|
817
|
+
yield* Stream.runForEachArray(paged(-1, after => query(sql`
|
|
818
|
+
SELECT ${top} batch_position, iv, entry_ids, encrypted_batch FROM ${snapshotTableSql}
|
|
819
|
+
WHERE batch_set = ${target.batchSet} AND batch_position > ${after}
|
|
820
|
+
ORDER BY batch_position ${limit}`), row => Number(row.batch_position)), reappend);
|
|
821
|
+
}
|
|
822
|
+
// The copies land above `withdrawn.base`, so the pages never reach them.
|
|
823
|
+
yield* Stream.runForEachArray(paged(target.base, after => query(sql`
|
|
824
|
+
SELECT ${top} sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
|
|
825
|
+
WHERE sequence > ${after} AND last_sequence <= ${withdrawn.base}
|
|
826
|
+
ORDER BY sequence ${limit}`), row => Number(row.sequence)), reappend);
|
|
827
|
+
if (reappended.length > 0) {
|
|
828
|
+
yield* sql`UPDATE ${storesSql} SET next_sequence = ${nextSequence} WHERE scope = ${scope}`;
|
|
829
|
+
yield* upsertBucket(scope, Math.floor(now / hourMillis), nextSequence - 1);
|
|
830
|
+
yield* sql.afterCommit(Effect.asVoid(PubSub.publishAll(pubsub, reappended)));
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
activationId,
|
|
834
|
+
target,
|
|
835
|
+
withdrawn
|
|
836
|
+
};
|
|
837
|
+
}, sql.withTransaction);
|
|
838
|
+
// The re-appended batches are published after the outermost commit. When
|
|
839
|
+
// the storage owns the transaction, that happens before the semaphore is
|
|
840
|
+
// released, so live readers get them in sequence order.
|
|
841
|
+
const rollback = (store, now, expectedActivationId) => store.semaphore.withPermits(1)(rollbackTransaction(store, now, expectedActivationId));
|
|
842
|
+
const history = Effect.fnUntraced(function* ({
|
|
843
|
+
scope
|
|
844
|
+
}) {
|
|
845
|
+
const store = yield* selectStore(scope);
|
|
846
|
+
const activations = yield* selectActivations(scope);
|
|
847
|
+
return activations.map(activation => {
|
|
848
|
+
const active = activation.id === store.activeActivation;
|
|
849
|
+
return {
|
|
850
|
+
activationId: activation.id,
|
|
851
|
+
base: activation.base,
|
|
852
|
+
keptCount: activation.keptCount,
|
|
853
|
+
active,
|
|
854
|
+
supersededAt: active ? undefined : activation.supersededAt,
|
|
855
|
+
restorable: activation.pruneStartedAt === undefined
|
|
856
|
+
};
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
const abort = Effect.fnUntraced(function* ({
|
|
860
|
+
scope
|
|
861
|
+
}, stageId) {
|
|
862
|
+
yield* lockStore(scope);
|
|
863
|
+
const stage = yield* selectStage(scope, stageId, true);
|
|
864
|
+
if (stage?.state === "open") yield* closeStage(scope, stageId);
|
|
865
|
+
}, sql.withTransaction);
|
|
866
|
+
const compaction = {
|
|
867
|
+
config,
|
|
868
|
+
status: (publicKey, storeId) => withStore(publicKey, storeId, status),
|
|
869
|
+
begin: (publicKey, storeId, {
|
|
870
|
+
through
|
|
871
|
+
}) => withStore(publicKey, storeId, (store, now) => begin(store, now, through)),
|
|
872
|
+
inputHeaders: (publicKey, storeId, stageId) => Stream.unwrap(withStore(publicKey, storeId, (store, now) => inputHeaders(store, now, stageId))),
|
|
873
|
+
fetch: (publicKey, storeId, stageId, sequences) => Stream.unwrap(withStore(publicKey, storeId, (store, now) => fetch(store, now, stageId, sequences))),
|
|
874
|
+
stagePart: (publicKey, storeId, stageId, segment, batches) => withStore(publicKey, storeId, (store, now) => stagePart(store, now, stageId, segment, batches)),
|
|
875
|
+
stagedHeaders: (publicKey, storeId, stageId) => Stream.unwrap(withStore(publicKey, storeId, (store, now) => stagedHeaders(store, now, stageId))),
|
|
876
|
+
commit: (publicKey, storeId, stageId, virtualSequences) => withStore(publicKey, storeId, (store, now) => commit(store, now, stageId, virtualSequences)).pipe(Effect.tap(outcome => logCommit({
|
|
877
|
+
publicKey,
|
|
878
|
+
storeId,
|
|
879
|
+
...outcome
|
|
880
|
+
})), Effect.map(outcome => outcome.result)),
|
|
881
|
+
recordNoOp: (publicKey, storeId, stageId) => withStore(publicKey, storeId, (store, now) => recordNoOp(store, now, stageId)).pipe(Effect.flatMap(through => logNoOp({
|
|
882
|
+
publicKey,
|
|
883
|
+
storeId,
|
|
884
|
+
stageId,
|
|
885
|
+
through
|
|
886
|
+
}))),
|
|
887
|
+
abort: (publicKey, storeId, stageId) => withStore(publicKey, storeId, store => abort(store, stageId)),
|
|
888
|
+
rollback: (publicKey, storeId, expectedActivationId) => withStore(publicKey, storeId, (store, now) => rollback(store, now, expectedActivationId)).pipe(Effect.tap(({
|
|
889
|
+
activationId,
|
|
890
|
+
target,
|
|
891
|
+
withdrawn
|
|
892
|
+
}) => logRollback({
|
|
893
|
+
publicKey,
|
|
894
|
+
storeId,
|
|
895
|
+
activation: {
|
|
896
|
+
...target,
|
|
897
|
+
id: activationId
|
|
898
|
+
},
|
|
899
|
+
withdrawn
|
|
900
|
+
})), Effect.map(outcome => outcome.activationId)),
|
|
901
|
+
history: (publicKey, storeId) => withStore(publicKey, storeId, store => history(store))
|
|
902
|
+
};
|
|
903
|
+
const insertActivation = Effect.fnUntraced(function* (scope, activation) {
|
|
904
|
+
const [last] = yield* query(sql`SELECT MAX(ordinal) AS ordinal FROM ${activationsSql} WHERE scope = ${scope}`);
|
|
905
|
+
yield* sql`INSERT INTO ${activationsSql} ${sql.insert({
|
|
906
|
+
...activation,
|
|
907
|
+
scope,
|
|
908
|
+
ordinal: Number(last?.ordinal ?? 0) + 1,
|
|
909
|
+
superseded_at: null,
|
|
910
|
+
prunable_at: null,
|
|
911
|
+
prune_started_at: null
|
|
912
|
+
})}`;
|
|
913
|
+
});
|
|
914
|
+
// Supersedes or withdraws an activation, prunable `grace` later.
|
|
915
|
+
const supersede = (activationId, now) => sql`
|
|
916
|
+
UPDATE ${activationsSql} SET superseded_at = ${now}, prunable_at = ${now + graceMillis}
|
|
917
|
+
WHERE id = ${activationId}`;
|
|
918
|
+
// Deletes up to `pruneChunkRows` rows matching `where` in key order and
|
|
919
|
+
// returns whether it found any. No new rows match `where`, so a later call
|
|
920
|
+
// resumes an interrupted one.
|
|
921
|
+
const deleteChunk = Effect.fnUntraced(function* (tableSql, key, where) {
|
|
922
|
+
const keys = yield* query(sql`
|
|
923
|
+
SELECT ${chunkTop} ${sql(key)} AS chunk_key FROM ${tableSql}
|
|
924
|
+
WHERE ${where} ORDER BY ${sql(key)} ${chunkLimit}`);
|
|
925
|
+
if (keys.length === 0) return false;
|
|
926
|
+
yield* sql`DELETE FROM ${tableSql} WHERE ${where} AND ${sql(key)} <= ${keys[keys.length - 1].chunk_key}`;
|
|
927
|
+
return true;
|
|
928
|
+
});
|
|
929
|
+
// Runs one chunk of a prune under the store row lock, if this sweeper
|
|
930
|
+
// still holds the lease and the active activation hasn't changed.
|
|
931
|
+
const pruneChunk = (scope, now, lease, f) => sql.withTransaction(Effect.gen(function* () {
|
|
932
|
+
const store = yield* lockStore(scope);
|
|
933
|
+
if (store.sweepLeaseOwner !== lease.owner || store.activeActivation !== lease.activeActivation) {
|
|
934
|
+
return Option.none();
|
|
935
|
+
}
|
|
936
|
+
const value = yield* f;
|
|
937
|
+
yield* sql`UPDATE ${storesSql} SET sweep_lease_until = ${now + sweepIntervalMillis} WHERE scope = ${scope}`;
|
|
938
|
+
return Option.some(value);
|
|
939
|
+
}));
|
|
940
|
+
// Prunes what no activation still needs: the batch sets of generations
|
|
941
|
+
// past their grace, and the originals at or below the lowest base still
|
|
942
|
+
// kept. Deletes are idempotent, so a later sweep finishes one that died.
|
|
943
|
+
const pruneStore = Effect.fnUntraced(function* (scope, now) {
|
|
944
|
+
const {
|
|
945
|
+
snapshotTableSql,
|
|
946
|
+
tableSql
|
|
947
|
+
} = scopeTables(scope);
|
|
948
|
+
const owner = SecureRandom.uuidV4Unsafe();
|
|
949
|
+
// Claim the store and mark what this prune removes before deleting anything.
|
|
950
|
+
const plan = yield* sql.withTransaction(Effect.gen(function* () {
|
|
951
|
+
const store = yield* lockStore(scope);
|
|
952
|
+
if (store.nextSweepAt === undefined || store.nextSweepAt > now) return undefined;
|
|
953
|
+
if (store.sweepLeaseUntil !== undefined && store.sweepLeaseUntil > now) return undefined;
|
|
954
|
+
const activations = yield* selectActivations(scope);
|
|
955
|
+
const isKept = activation => activation.id === store.activeActivation || activation.pruneStartedAt === undefined && (activation.prunableAt ?? Infinity) > now;
|
|
956
|
+
const kept = activations.filter(isKept);
|
|
957
|
+
const pruned = activations.filter(activation => !isKept(activation));
|
|
958
|
+
const starting = pruned.filter(activation => activation.pruneStartedAt === undefined);
|
|
959
|
+
for (const chunk of Arr.chunksOf(starting.map(activation => activation.id), 200)) {
|
|
960
|
+
yield* sql`UPDATE ${activationsSql} SET prune_started_at = ${now} WHERE ${sql.in("id", chunk)}`;
|
|
961
|
+
}
|
|
962
|
+
const keptSets = new Set(kept.map(activation => activation.batchSet));
|
|
963
|
+
yield* sql`
|
|
964
|
+
UPDATE ${storesSql} SET sweep_lease_until = ${now + sweepIntervalMillis}, sweep_lease_owner = ${owner}
|
|
965
|
+
WHERE scope = ${scope}`;
|
|
966
|
+
return {
|
|
967
|
+
lease: {
|
|
968
|
+
owner,
|
|
969
|
+
activeActivation: store.activeActivation
|
|
970
|
+
},
|
|
971
|
+
sets: Array.from(new Set(pruned.flatMap(activation => activation.batchSet === undefined || keptSets.has(activation.batchSet) ? [] : [activation.batchSet]))),
|
|
972
|
+
keptBase: kept.reduce((base, activation) => Math.min(base, activation.base), Infinity),
|
|
973
|
+
prunedThrough: store.prunedThrough
|
|
974
|
+
};
|
|
975
|
+
}));
|
|
976
|
+
if (plan === undefined) return;
|
|
977
|
+
for (const set of plan.sets) {
|
|
978
|
+
while (true) {
|
|
979
|
+
const deleted = yield* pruneChunk(scope, now, plan.lease, deleteChunk(snapshotTableSql, "batch_position", sql`batch_set = ${set}`));
|
|
980
|
+
if (Option.isNone(deleted)) return;
|
|
981
|
+
if (!deleted.value) break;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
for (let start = plan.prunedThrough; start < plan.keptBase; start += pruneChunkSequences) {
|
|
985
|
+
const end = Math.min(start + pruneChunkSequences, plan.keptBase);
|
|
986
|
+
const held = yield* pruneChunk(scope, now, plan.lease, sql`DELETE FROM ${tableSql} WHERE last_sequence <= ${end}`.pipe(Effect.andThen(sql`
|
|
987
|
+
UPDATE ${storesSql}
|
|
988
|
+
SET pruned_through = CASE WHEN pruned_through < ${end} THEN ${end} ELSE pruned_through END
|
|
989
|
+
WHERE scope = ${scope}`)));
|
|
990
|
+
if (Option.isNone(held)) return;
|
|
991
|
+
}
|
|
992
|
+
yield* sql.withTransaction(Effect.gen(function* () {
|
|
993
|
+
const store = yield* lockStore(scope);
|
|
994
|
+
if (store.sweepLeaseOwner !== owner) return;
|
|
995
|
+
const [next] = yield* query(sql`
|
|
996
|
+
SELECT MIN(prunable_at) AS next_sweep_at FROM ${activationsSql}
|
|
997
|
+
WHERE scope = ${scope} AND id <> ${store.activeActivation} AND prune_started_at IS NULL`);
|
|
998
|
+
yield* sql`
|
|
999
|
+
UPDATE ${storesSql}
|
|
1000
|
+
SET next_sweep_at = ${next?.next_sweep_at ?? null}, sweep_lease_until = NULL, sweep_lease_owner = NULL
|
|
1001
|
+
WHERE scope = ${scope}`;
|
|
1002
|
+
}));
|
|
1003
|
+
});
|
|
1004
|
+
// Deletes a closed stage's rows matching `where`, one chunk per
|
|
1005
|
+
// transaction, each under the store row lock to keep the lock order.
|
|
1006
|
+
const deleteStaged = Effect.fnUntraced(function* (scope, tableSql, key, where) {
|
|
1007
|
+
while (yield* sql.withTransaction(Effect.andThen(lockStore(scope), deleteChunk(tableSql, key, where)))) {
|
|
1008
|
+
// Each chunk commits on its own.
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
// Closes stages past their claim, then deletes closed stages. A closed
|
|
1012
|
+
// stage's staged rows are read by nothing, so they go first in short
|
|
1013
|
+
// chunks, and the stage row last.
|
|
1014
|
+
const sweepStages = Effect.fnUntraced(function* (now) {
|
|
1015
|
+
const expired = yield* query(sql`
|
|
1016
|
+
SELECT id, scope FROM ${stagesSql} WHERE state = ${"open"} AND expires_at <= ${now}`);
|
|
1017
|
+
for (const row of expired) {
|
|
1018
|
+
const scope = String(row.scope);
|
|
1019
|
+
const stageId = String(row.id);
|
|
1020
|
+
yield* sql.withTransaction(Effect.gen(function* () {
|
|
1021
|
+
yield* lockStore(scope);
|
|
1022
|
+
const stage = yield* selectStage(scope, stageId, true);
|
|
1023
|
+
if (stage?.state === "open" && stage.expiresAt <= now) {
|
|
1024
|
+
yield* closeStage(scope, stageId);
|
|
1025
|
+
}
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
1028
|
+
const closed = yield* query(sql`SELECT id, scope FROM ${stagesSql} WHERE state = ${"closed"}`);
|
|
1029
|
+
for (const row of closed) {
|
|
1030
|
+
const scope = String(row.scope);
|
|
1031
|
+
const stageId = String(row.id);
|
|
1032
|
+
yield* deleteStaged(scope, scopeTables(scope).snapshotTableSql, "batch_position", sql`batch_set = ${stageId}`);
|
|
1033
|
+
yield* sql.withTransaction(Effect.andThen(lockStore(scope), sql`DELETE FROM ${stagesSql} WHERE id = ${stageId}`));
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
// Only the newest bucket past the lag matters for the lag head.
|
|
1037
|
+
const sweepBuckets = Effect.fnUntraced(function* (now) {
|
|
1038
|
+
const rows = yield* query(sql`
|
|
1039
|
+
SELECT scope, MAX(bucket_hour) AS newest FROM ${bucketsSql}
|
|
1040
|
+
WHERE bucket_hour <= ${lagHour(now, lagMillis)}
|
|
1041
|
+
GROUP BY scope HAVING COUNT(*) > 1`);
|
|
1042
|
+
for (const row of rows) {
|
|
1043
|
+
yield* sql`DELETE FROM ${bucketsSql} WHERE scope = ${String(row.scope)} AND bucket_hour < ${Number(row.newest)}`;
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
const logSweepFailure = cause => Effect.logWarning("Compaction sweep failed", cause).pipe(Effect.annotateLogs({
|
|
1047
|
+
service: "SqlEventLogServerEncrypted"
|
|
1048
|
+
}));
|
|
1049
|
+
const sweep = Effect.gen(function* () {
|
|
1050
|
+
const now = yield* Clock.currentTimeMillis;
|
|
1051
|
+
yield* Effect.catchCause(sweepStages(now), logSweepFailure);
|
|
1052
|
+
const due = yield* query(sql`SELECT scope FROM ${storesSql} WHERE next_sweep_at <= ${now}`);
|
|
1053
|
+
for (const row of due) {
|
|
1054
|
+
yield* Effect.catchCause(pruneStore(String(row.scope), now), logSweepFailure);
|
|
1055
|
+
}
|
|
1056
|
+
yield* Effect.catchCause(sweepBuckets(now), logSweepFailure);
|
|
1057
|
+
}).pipe(Effect.catchCause(logSweepFailure), withTracerDisabled);
|
|
1058
|
+
// A fixed schedule starts an overdue sweep straight away, so slow queries
|
|
1059
|
+
// don't push sweeps back.
|
|
1060
|
+
yield* Effect.forkScoped(Effect.delay(Effect.repeat(sweep, Schedule.fixed(config.sweepInterval)), config.sweepInterval));
|
|
202
1061
|
return EventLogServerEncrypted.Storage.of({
|
|
203
1062
|
getId: Effect.succeed(remoteId),
|
|
204
1063
|
// Auto-increment sequences start at 1.
|
|
@@ -219,16 +1078,19 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
219
1078
|
const {
|
|
220
1079
|
idsTableSql,
|
|
221
1080
|
pubsub,
|
|
1081
|
+
scope,
|
|
222
1082
|
semaphore,
|
|
223
1083
|
tableSql
|
|
224
1084
|
} = yield* RcMap.get(resources, scopeKey);
|
|
1085
|
+
const now = yield* Clock.currentTimeMillis;
|
|
225
1086
|
const writeBatches = Effect.gen(function* () {
|
|
226
1087
|
const stored = [];
|
|
1088
|
+
// The store row lock makes sequence order commit order. The row's
|
|
1089
|
+
// `next_sequence` survives pruning, and `MAX(last_sequence)` covers
|
|
1090
|
+
// instances that predate the row.
|
|
1091
|
+
const store = yield* lockStore(scope);
|
|
227
1092
|
// Read before inserting ids, which record the sequence they are stored at.
|
|
228
|
-
|
|
229
|
-
SELECT MAX(last_sequence) AS last_sequence FROM ${tableSql}
|
|
230
|
-
`;
|
|
231
|
-
let nextSequence = Number(last?.last_sequence ?? 0) + 1;
|
|
1093
|
+
let nextSequence = Math.max(store.nextSequence, (yield* maxLastSequence(tableSql)) + 1);
|
|
232
1094
|
for (const batch of batches) {
|
|
233
1095
|
// Inserting the ids decides whether the batch is new. SQL Server's
|
|
234
1096
|
// MERGE also requires unique source ids.
|
|
@@ -243,7 +1105,8 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
243
1105
|
}
|
|
244
1106
|
let inserted = 0;
|
|
245
1107
|
for (const chunk of Arr.chunksOf(ids.values(), 200)) {
|
|
246
|
-
|
|
1108
|
+
// Stored ids keep their first sequence.
|
|
1109
|
+
inserted += yield* insertNew(idsTableSql, chunk, ["entry_id"]);
|
|
247
1110
|
}
|
|
248
1111
|
if (inserted === 0) continue;
|
|
249
1112
|
const row = {
|
|
@@ -260,13 +1123,15 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
260
1123
|
});
|
|
261
1124
|
nextSequence += batch.entryIds.length;
|
|
262
1125
|
}
|
|
1126
|
+
if (stored.length > 0) {
|
|
1127
|
+
yield* sql`UPDATE ${storesSql} SET next_sequence = ${nextSequence} WHERE scope = ${scope}`;
|
|
1128
|
+
yield* upsertBucket(scope, Math.floor(now / hourMillis), nextSequence - 1);
|
|
1129
|
+
// Published after the outermost commit, as `rollback` publishes.
|
|
1130
|
+
yield* sql.afterCommit(Effect.asVoid(PubSub.publishAll(pubsub, stored)));
|
|
1131
|
+
}
|
|
263
1132
|
return stored;
|
|
264
1133
|
});
|
|
265
|
-
return yield* semaphore.withPermits(1)(
|
|
266
|
-
const stored = yield* sql.withTransaction(writeBatches);
|
|
267
|
-
yield* PubSub.publishAll(pubsub, stored);
|
|
268
|
-
return stored;
|
|
269
|
-
}));
|
|
1134
|
+
return yield* semaphore.withPermits(1)(sql.withTransaction(writeBatches));
|
|
270
1135
|
}, Effect.orDie, Effect.scoped, withTracerDisabled),
|
|
271
1136
|
entrySequences: Effect.fnUntraced(function* (publicKey, storeId, entryIds) {
|
|
272
1137
|
const {
|
|
@@ -284,22 +1149,91 @@ export const makeStorage = options => Effect.gen(function* () {
|
|
|
284
1149
|
}
|
|
285
1150
|
return acks;
|
|
286
1151
|
}, Effect.orDie, Effect.scoped, withTracerDisabled),
|
|
1152
|
+
// Reads the store row, the active set and the tail as separate
|
|
1153
|
+
// statements. A transaction would take the SQLite write lock on every
|
|
1154
|
+
// stream open, and set rows never change before they are pruned.
|
|
287
1155
|
changes: Effect.fnUntraced(function* (publicKey, storeId, startSequence) {
|
|
288
1156
|
const scopeKey = makeEncryptedScopeKey(publicKey, storeId);
|
|
289
1157
|
const {
|
|
290
1158
|
pubsub,
|
|
1159
|
+
scope,
|
|
1160
|
+
snapshotTableSql,
|
|
291
1161
|
tableSql
|
|
292
1162
|
} = yield* RcMap.get(resources, scopeKey);
|
|
293
1163
|
const subscription = yield* PubSub.subscribe(pubsub);
|
|
1164
|
+
const live = Stream.fromSubscription(subscription);
|
|
1165
|
+
const store = yield* selectStore(scope);
|
|
1166
|
+
// Below the base an active snapshot replaces the originals.
|
|
1167
|
+
if (store.activeBatchSet !== undefined && startSequence <= store.base) {
|
|
1168
|
+
const set = yield* sql`
|
|
1169
|
+
SELECT sequence, iv, entry_ids, encrypted_batch FROM ${snapshotTableSql}
|
|
1170
|
+
WHERE batch_set = ${store.activeBatchSet} AND last_sequence >= ${startSequence}
|
|
1171
|
+
ORDER BY batch_position ASC
|
|
1172
|
+
`.pipe(Effect.flatMap(decodeBatches));
|
|
1173
|
+
// The final batch ends at the base, so only an empty set reads nothing.
|
|
1174
|
+
if (set.length === 0) {
|
|
1175
|
+
return Stream.die(new Error(`The active compaction snapshot of store ${storeId} has no batches`));
|
|
1176
|
+
}
|
|
1177
|
+
const tail = yield* sql`
|
|
1178
|
+
SELECT sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
|
|
1179
|
+
WHERE sequence > ${store.base}
|
|
1180
|
+
ORDER BY sequence ASC
|
|
1181
|
+
`.pipe(Effect.flatMap(decodeBatches));
|
|
1182
|
+
return Stream.fromArray([...set, ...tail]).pipe(Stream.concat(live));
|
|
1183
|
+
}
|
|
294
1184
|
const initial = yield* sql`
|
|
295
1185
|
SELECT sequence, iv, entry_ids, encrypted_batch FROM ${tableSql}
|
|
296
1186
|
WHERE last_sequence >= ${startSequence}
|
|
297
1187
|
ORDER BY sequence ASC
|
|
298
1188
|
`.pipe(Effect.flatMap(decodeBatches));
|
|
299
|
-
return Stream.fromArray(initial).pipe(Stream.concat(
|
|
300
|
-
}, Effect.orDie, withTracerDisabled, Stream.unwrap)
|
|
1189
|
+
return Stream.fromArray(initial).pipe(Stream.concat(live));
|
|
1190
|
+
}, Effect.orDie, withTracerDisabled, Stream.unwrap),
|
|
1191
|
+
compaction
|
|
301
1192
|
});
|
|
302
1193
|
}).pipe(withTracerDisabled);
|
|
1194
|
+
// Rows per statement when streaming compaction input or staged batches.
|
|
1195
|
+
const pageSize = 500;
|
|
1196
|
+
// Sequences of originals deleted per prune chunk.
|
|
1197
|
+
const pruneChunkSequences = 10_000;
|
|
1198
|
+
// Snapshot or staged rows deleted per prune chunk.
|
|
1199
|
+
const pruneChunkRows = 200;
|
|
1200
|
+
// SQL failures are defects for the compaction primitives.
|
|
1201
|
+
const dieOnSqlError = effect => Effect.catchIf(effect, error => SqlError.isSqlError(error), error => Effect.die(error));
|
|
1202
|
+
const optionalNumber = value => value === null || value === undefined ? undefined : Number(value);
|
|
1203
|
+
const optionalString = value => value === null || value === undefined ? undefined : String(value);
|
|
1204
|
+
const toStoreRow = row => ({
|
|
1205
|
+
nextSequence: Number(row.next_sequence),
|
|
1206
|
+
activeActivation: String(row.active_activation),
|
|
1207
|
+
activeBatchSet: optionalString(row.active_batch_set),
|
|
1208
|
+
base: Number(row.base_sequence),
|
|
1209
|
+
activeKeptCount: Number(row.active_kept_count),
|
|
1210
|
+
noOpAt: optionalNumber(row.no_op_at),
|
|
1211
|
+
noOpCount: Number(row.no_op_count),
|
|
1212
|
+
openStage: optionalString(row.open_stage),
|
|
1213
|
+
nextSweepAt: optionalNumber(row.next_sweep_at),
|
|
1214
|
+
sweepLeaseUntil: optionalNumber(row.sweep_lease_until),
|
|
1215
|
+
sweepLeaseOwner: optionalString(row.sweep_lease_owner),
|
|
1216
|
+
prunedThrough: Number(row.pruned_through)
|
|
1217
|
+
});
|
|
1218
|
+
const toStageRow = row => ({
|
|
1219
|
+
id: String(row.id),
|
|
1220
|
+
scope: String(row.scope),
|
|
1221
|
+
state: String(row.state),
|
|
1222
|
+
pinned: String(row.pinned),
|
|
1223
|
+
through: Number(row.through_sequence),
|
|
1224
|
+
expiresAt: Number(row.expires_at)
|
|
1225
|
+
});
|
|
1226
|
+
const toActivationRow = row => ({
|
|
1227
|
+
id: String(row.id),
|
|
1228
|
+
ordinal: Number(row.ordinal),
|
|
1229
|
+
batchSet: optionalString(row.batch_set),
|
|
1230
|
+
base: Number(row.base_sequence),
|
|
1231
|
+
keptCount: optionalNumber(row.kept_count),
|
|
1232
|
+
previous: optionalString(row.previous),
|
|
1233
|
+
supersededAt: optionalNumber(row.superseded_at),
|
|
1234
|
+
prunableAt: optionalNumber(row.prunable_at),
|
|
1235
|
+
pruneStartedAt: optionalNumber(row.prune_started_at)
|
|
1236
|
+
});
|
|
303
1237
|
const decodeIdRows = /*#__PURE__*/Schema.decodeUnknownEffect(/*#__PURE__*/Schema.Array(/*#__PURE__*/Schema.Struct({
|
|
304
1238
|
entry_id: Schema.Uint8Array,
|
|
305
1239
|
sequence: Schema.Number
|
|
@@ -309,6 +1243,13 @@ const concatEntryIds = entryIds => {
|
|
|
309
1243
|
for (let i = 0; i < entryIds.length; i++) data.set(entryIds[i], i * 16);
|
|
310
1244
|
return data;
|
|
311
1245
|
};
|
|
1246
|
+
const toEntryIds = data => {
|
|
1247
|
+
const entryIds = [];
|
|
1248
|
+
for (let offset = 0; offset < data.byteLength; offset += 16) {
|
|
1249
|
+
entryIds.push(data.subarray(offset, offset + 16));
|
|
1250
|
+
}
|
|
1251
|
+
return entryIds;
|
|
1252
|
+
};
|
|
312
1253
|
const EncryptedRemoteBatchSql = /*#__PURE__*/Schema.Struct({
|
|
313
1254
|
sequence: Schema.Natural,
|
|
314
1255
|
iv: Schema.Uint8Array,
|
|
@@ -321,18 +1262,12 @@ const SessionAuthBindingSql = /*#__PURE__*/Schema.Struct({
|
|
|
321
1262
|
});
|
|
322
1263
|
const decodeBatchRows = /*#__PURE__*/Schema.decodeUnknownEffect(/*#__PURE__*/Schema.Array(EncryptedRemoteBatchSql));
|
|
323
1264
|
const decodeSessionAuthBindingRows = /*#__PURE__*/Schema.decodeUnknownEffect(/*#__PURE__*/Schema.Array(SessionAuthBindingSql));
|
|
324
|
-
const toEncryptedRemoteBatch = row => {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
sequence: row.sequence,
|
|
331
|
-
entryIds: entryIds,
|
|
332
|
-
iv: row.iv,
|
|
333
|
-
encryptedBatch: row.encrypted_batch
|
|
334
|
-
};
|
|
335
|
-
};
|
|
1265
|
+
const toEncryptedRemoteBatch = row => ({
|
|
1266
|
+
sequence: row.sequence,
|
|
1267
|
+
entryIds: toEntryIds(row.entry_ids),
|
|
1268
|
+
iv: row.iv,
|
|
1269
|
+
encryptedBatch: row.encrypted_batch
|
|
1270
|
+
});
|
|
336
1271
|
const decodeBatches = rows => decodeBatchRows(rows).pipe(Effect.map(batches => batches.map(toEncryptedRemoteBatch)));
|
|
337
1272
|
const decodeSessionAuthBindings = rows => decodeSessionAuthBindingRows(rows);
|
|
338
1273
|
/**
|
|
@@ -345,13 +1280,13 @@ const decodeSessionAuthBindings = rows => decodeSessionAuthBindingRows(rows);
|
|
|
345
1280
|
export const layerStorage = options => Layer.effect(EventLogServerEncrypted.Storage)(makeStorage(options));
|
|
346
1281
|
/**
|
|
347
1282
|
* Provides SQL-backed encrypted server `Storage` and supplies the default Web
|
|
348
|
-
* Crypto `
|
|
1283
|
+
* Crypto `Digest` layer.
|
|
349
1284
|
*
|
|
350
1285
|
* @stability unstable
|
|
351
1286
|
* @category layers
|
|
352
1287
|
* @since 4.0.0
|
|
353
1288
|
*/
|
|
354
|
-
export const layerStorageSubtle = options => layerStorage(options).pipe(Layer.provide(
|
|
1289
|
+
export const layerStorageSubtle = options => layerStorage(options).pipe(Layer.provide(Digest.layerWebCrypto));
|
|
355
1290
|
const makeEncryptedScopeKey = (publicKey, storeId) => `${publicKey}/${storeId}`;
|
|
356
1291
|
const withTracerDisabled = /*#__PURE__*/Effect.withTracerEnabled(false);
|
|
357
1292
|
//# sourceMappingURL=SqlEventLogServerEncrypted.js.map
|