effect 4.0.7 → 4.1.0
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 +1 -1
- package/dist/BigDecimal.d.ts.map +1 -1
- package/dist/BigDecimal.js +3 -0
- 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/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/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/Pool.d.ts +51 -0
- package/dist/Pool.d.ts.map +1 -1
- package/dist/Pool.js +59 -9
- 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 +666 -41
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +650 -0
- 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 +14 -0
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +16 -0
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/SchemaTransformation.d.ts +8 -3
- package/dist/SchemaTransformation.d.ts.map +1 -1
- package/dist/SchemaTransformation.js +6 -3
- 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/McpServer.d.ts +630 -29
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +562 -116
- 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/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.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 +17 -12
- package/dist/ai/internal/mcpCore.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js +4 -5
- package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js +4 -5
- package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js +4 -5
- package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js +4 -5
- package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js +18 -10
- package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
- package/dist/ai/internal/mcpProtocol.js +13 -0
- package/dist/ai/internal/mcpProtocol.js.map +1 -1
- package/dist/ai/internal/mcpRuntime.js +5 -3
- package/dist/ai/internal/mcpRuntime.js.map +1 -1
- 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/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +90 -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/crypto/Aead.d.ts +322 -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 +235 -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 +323 -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 +309 -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 +389 -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 +277 -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 +448 -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 +61 -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 +170 -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 +3 -2
- 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/SchemaBinary.d.ts.map +1 -1
- package/dist/encoding/SchemaBinary.js +42 -4
- package/dist/encoding/SchemaBinary.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 +119 -4
- package/dist/eventlog/EventLog.d.ts.map +1 -1
- package/dist/eventlog/EventLog.js +200 -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 +3 -0
- package/dist/eventlog/EventLogServer.d.ts.map +1 -1
- package/dist/eventlog/EventLogServer.js +18 -10
- package/dist/eventlog/EventLogServer.js.map +1 -1
- package/dist/eventlog/EventLogServerEncrypted.d.ts +326 -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/EventLogSessionAuth.d.ts +5 -3
- package/dist/eventlog/EventLogSessionAuth.d.ts.map +1 -1
- package/dist/eventlog/EventLogSessionAuth.js +41 -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/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 +79 -8
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +65 -5
- 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 +6 -17
- 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/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 +53 -0
- 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/effect.js +25 -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 +630 -0
- package/dist/internal/glob.js.map +1 -0
- 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/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/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/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/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.map +1 -1
- package/dist/rpc/RpcClient.d.ts +16 -1
- package/dist/rpc/RpcClient.d.ts.map +1 -1
- package/dist/rpc/RpcClient.js +50 -16
- package/dist/rpc/RpcClient.js.map +1 -1
- package/dist/rpc/RpcMessage.d.ts +10 -1
- 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 +0 -19
- package/dist/sql/Migrator.d.ts.map +1 -1
- package/dist/sql/Migrator.js +3 -48
- 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.map +1 -1
- package/dist/sql/SqlClient.js +5 -9
- package/dist/sql/SqlClient.js.map +1 -1
- package/dist/sql/Statement.d.ts +20 -6
- package/dist/sql/Statement.d.ts.map +1 -1
- package/dist/sql/Statement.js +0 -13
- 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/internal/crypto.js +9 -8
- package/dist/workflow/internal/crypto.js.map +1 -1
- package/package.json +3 -1
- package/src/BigDecimal.ts +3 -0
- 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/FileSystem.ts +65 -4
- package/src/Glob.ts +205 -0
- package/src/Match.ts +127 -0
- package/src/Pool.ts +73 -15
- package/src/Random.ts +3 -3
- package/src/RcMap.ts +5 -1
- package/src/Schedule.ts +26 -3
- package/src/Schema.ts +1189 -62
- package/src/SchemaGetter.ts +27 -11
- package/src/SchemaRepresentation.ts +19 -0
- package/src/SchemaTransformation.ts +8 -3
- 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/McpServer.ts +1269 -286
- package/src/ai/McpTest.ts +380 -0
- package/src/ai/Response.ts +2 -22
- package/src/ai/Tool.ts +27 -8
- package/src/ai/index.ts +6 -0
- package/src/ai/internal/mcpCore.ts +78 -44
- package/src/ai/internal/mcpProtocol/v2024_11_05.ts +4 -7
- package/src/ai/internal/mcpProtocol/v2025_03_26.ts +4 -7
- package/src/ai/internal/mcpProtocol/v2025_06_18.ts +4 -7
- package/src/ai/internal/mcpProtocol/v2025_11_25.ts +4 -7
- package/src/ai/internal/mcpProtocol/v2026_07_28.ts +15 -10
- package/src/ai/internal/mcpProtocol.ts +29 -0
- package/src/ai/internal/mcpRuntime.ts +9 -5
- 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/Sharding.ts +93 -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/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 +712 -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 +64 -0
- package/src/crypto/index.ts +78 -0
- package/src/crypto/internal/asymmetric.ts +329 -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 +235 -0
- package/src/crypto/internal/md5.ts +170 -0
- package/src/crypto/internal/webCrypto.ts +150 -0
- package/src/encoding/Base64Url.ts +5 -2
- package/src/encoding/Csv.ts +1298 -0
- package/src/encoding/Hex.ts +3 -2
- package/src/encoding/SchemaBinary.ts +40 -4
- 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 +340 -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 +23 -8
- package/src/eventlog/EventLogServerEncrypted.ts +1130 -67
- package/src/eventlog/EventLogSessionAuth.ts +56 -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/http/Cookies.ts +281 -1
- package/src/http/FetchHttpClient.ts +88 -16
- package/src/http/HttpClient.ts +89 -22
- 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 +15 -30
- 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/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 +105 -14
- package/src/index.ts +6 -6
- package/src/internal/effect.ts +34 -18
- package/src/internal/glob.ts +736 -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/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/observability/Otlp.ts +21 -4
- package/src/observability/OtlpResource.ts +9 -1
- package/src/persistence/KeyValueStore.ts +219 -22
- package/src/persistence/PersistedQueue.ts +128 -59
- package/src/persistence/Persistence.ts +20 -13
- 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 +1 -1
- package/src/rpc/RpcClient.ts +62 -26
- package/src/rpc/RpcMessage.ts +10 -1
- 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 +4 -63
- package/src/sql/MigratorFileSystem.ts +71 -0
- package/src/sql/SqlClient.ts +2 -16
- package/src/sql/Statement.ts +8 -7
- package/src/sql/index.ts +1 -1
- package/src/testing/index.ts +0 -6
- 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
|
@@ -0,0 +1,1298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encodes and decodes CSV streams, with optional schema transformations.
|
|
3
|
+
*
|
|
4
|
+
* **Details**
|
|
5
|
+
*
|
|
6
|
+
* Follows RFC 4180 with a configurable delimiter. Cells are strings, and
|
|
7
|
+
* whitespace and empty cells are kept as they are. Quoted fields can contain
|
|
8
|
+
* delimiters, line breaks and doubled quotes. Decoders accept LF, CRLF and CR
|
|
9
|
+
* line endings and strip a leading BOM. Schema adapters convert cells to and
|
|
10
|
+
* from typed values.
|
|
11
|
+
*
|
|
12
|
+
* @stability unstable
|
|
13
|
+
* @since 4.1.0
|
|
14
|
+
*/
|
|
15
|
+
import * as Arr from "../Array.ts"
|
|
16
|
+
import * as Cause from "../Cause.ts"
|
|
17
|
+
import * as Channel from "../Channel.ts"
|
|
18
|
+
import * as Data from "../Data.ts"
|
|
19
|
+
import * as Effect from "../Effect.ts"
|
|
20
|
+
import * as Predicate from "../Predicate.ts"
|
|
21
|
+
import * as Pull from "../Pull.ts"
|
|
22
|
+
import type * as Record from "../Record.ts"
|
|
23
|
+
import * as Schema from "../Schema.ts"
|
|
24
|
+
import * as SchemaAST from "../SchemaAST.ts"
|
|
25
|
+
import * as SchemaIssue from "../SchemaIssue.ts"
|
|
26
|
+
|
|
27
|
+
const CsvErrorTypeId = "~effect/encoding/Csv/CsvError"
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A CSV decoding failure.
|
|
31
|
+
*
|
|
32
|
+
* **Details**
|
|
33
|
+
*
|
|
34
|
+
* `record` is one-based and counts the header row and blank lines. `line` is
|
|
35
|
+
* the one-based physical line where the record starts. `column` is the
|
|
36
|
+
* one-based cell index of a syntax error, and `undefined` otherwise.
|
|
37
|
+
*
|
|
38
|
+
* @stability unstable
|
|
39
|
+
* @category errors
|
|
40
|
+
* @since 4.1.0
|
|
41
|
+
*/
|
|
42
|
+
export class CsvError extends Data.TaggedError("CsvError")<{
|
|
43
|
+
readonly kind: "Syntax" | "RecordSize" | "Header"
|
|
44
|
+
readonly message: string
|
|
45
|
+
readonly record: number
|
|
46
|
+
readonly line: number
|
|
47
|
+
readonly column?: number | undefined
|
|
48
|
+
}> {
|
|
49
|
+
/**
|
|
50
|
+
* Identifies CSV errors across module instances.
|
|
51
|
+
*
|
|
52
|
+
* @stability unstable
|
|
53
|
+
* @since 4.1.0
|
|
54
|
+
*/
|
|
55
|
+
readonly [CsvErrorTypeId] = CsvErrorTypeId
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Checks whether a value is a `CsvError`.
|
|
60
|
+
*
|
|
61
|
+
* @stability unstable
|
|
62
|
+
* @category guards
|
|
63
|
+
* @since 4.1.0
|
|
64
|
+
*/
|
|
65
|
+
export const isCsvError = (u: unknown): u is CsvError => Predicate.hasProperty(u, CsvErrorTypeId)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A record as an array of string cells.
|
|
69
|
+
*
|
|
70
|
+
* @stability unstable
|
|
71
|
+
* @category models
|
|
72
|
+
* @since 4.1.0
|
|
73
|
+
*/
|
|
74
|
+
export type Row = ReadonlyArray<string>
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* CSV decoding options.
|
|
78
|
+
*
|
|
79
|
+
* **Details**
|
|
80
|
+
*
|
|
81
|
+
* - `delimiter`: a single character other than a quote or line break. Defaults
|
|
82
|
+
* to `","`.
|
|
83
|
+
* - `maxRecordSize`: the maximum record length in UTF-16 code units, including
|
|
84
|
+
* quotes and delimiters but not the record separator. Defaults to 1 MiB;
|
|
85
|
+
* `Infinity` disables the limit.
|
|
86
|
+
* - `ignoreEmptyLines`: skips records with no characters. Defaults to `false`,
|
|
87
|
+
* where an empty line is a record with one empty cell.
|
|
88
|
+
* - `headers`: uses the first record as unique field names and emits the
|
|
89
|
+
* remaining records as objects of the same width. Defaults to `false`.
|
|
90
|
+
*
|
|
91
|
+
* Invalid options throw when the channel is created.
|
|
92
|
+
*
|
|
93
|
+
* @stability unstable
|
|
94
|
+
* @category configuration
|
|
95
|
+
* @since 4.1.0
|
|
96
|
+
*/
|
|
97
|
+
export interface DecodeOptions {
|
|
98
|
+
readonly delimiter?: string | undefined
|
|
99
|
+
readonly maxRecordSize?: number | undefined
|
|
100
|
+
readonly ignoreEmptyLines?: boolean | undefined
|
|
101
|
+
readonly headers?: boolean | undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* CSV encoding options.
|
|
106
|
+
*
|
|
107
|
+
* **Details**
|
|
108
|
+
*
|
|
109
|
+
* - `delimiter`: a single character other than a quote or line break. Defaults
|
|
110
|
+
* to `","`.
|
|
111
|
+
* - `newline`: the record separator written after every record. Defaults to
|
|
112
|
+
* `"\r\n"`.
|
|
113
|
+
* - `columns`: encodes object records by these keys, in order. Missing or
|
|
114
|
+
* `undefined` values become empty cells.
|
|
115
|
+
* - `headers`: writes `columns` as the first record, even for an empty stream.
|
|
116
|
+
* Requires `columns`.
|
|
117
|
+
*
|
|
118
|
+
* Invalid options throw when the channel is created.
|
|
119
|
+
*
|
|
120
|
+
* @stability unstable
|
|
121
|
+
* @category configuration
|
|
122
|
+
* @since 4.1.0
|
|
123
|
+
*/
|
|
124
|
+
export interface EncodeOptions {
|
|
125
|
+
readonly delimiter?: string | undefined
|
|
126
|
+
readonly newline?: "\n" | "\r\n" | undefined
|
|
127
|
+
readonly columns?: ReadonlyArray<string> | undefined
|
|
128
|
+
readonly headers?: boolean | undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Schema decoding options.
|
|
133
|
+
*
|
|
134
|
+
* **Details**
|
|
135
|
+
*
|
|
136
|
+
* By default the schema is wrapped in `Schema.toCodecStringTree`, so columns
|
|
137
|
+
* can use schemas such as `Schema.Number`, `Schema.Boolean` or `Schema.Date`.
|
|
138
|
+
* With header records, an empty cell for a property that is optional in the
|
|
139
|
+
* encoded schema is decoded as a missing key. Set `disableCodecs` to decode
|
|
140
|
+
* with the schema exactly as given.
|
|
141
|
+
*
|
|
142
|
+
* @stability unstable
|
|
143
|
+
* @category configuration
|
|
144
|
+
* @since 4.1.0
|
|
145
|
+
*/
|
|
146
|
+
export interface DecodeSchemaOptions extends DecodeOptions {
|
|
147
|
+
readonly disableCodecs?: boolean | undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Schema encoding options.
|
|
152
|
+
*
|
|
153
|
+
* **Details**
|
|
154
|
+
*
|
|
155
|
+
* By default the schema is wrapped in `Schema.toCodecStringTree`, so every
|
|
156
|
+
* column encodes to a string; set `disableCodecs` to encode with the schema
|
|
157
|
+
* exactly as given. `columns` defaults to the encoded keys of a struct schema
|
|
158
|
+
* and is required for other object schemas. Tuple and array schemas write
|
|
159
|
+
* cells by position and use `columns` only as header names. Every column must
|
|
160
|
+
* encode to a string, otherwise the channel throws when it is created.
|
|
161
|
+
*
|
|
162
|
+
* @stability unstable
|
|
163
|
+
* @category configuration
|
|
164
|
+
* @since 4.1.0
|
|
165
|
+
*/
|
|
166
|
+
export type EncodeSchemaOptions<S extends Schema.Constraint> = Omit<EncodeOptions, "columns"> & {
|
|
167
|
+
readonly columns?:
|
|
168
|
+
| (S["Encoded"] extends ReadonlyArray<unknown> ? ReadonlyArray<string>
|
|
169
|
+
: ReadonlyArray<
|
|
170
|
+
S["Encoded"] extends infer E ? E extends unknown ? Extract<keyof E, string> : never : never
|
|
171
|
+
>)
|
|
172
|
+
| undefined
|
|
173
|
+
readonly disableCodecs?: boolean | undefined
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const defaultMaxRecordSize = 1024 * 1024
|
|
177
|
+
|
|
178
|
+
const checkDelimiter = (delimiter: string): string => {
|
|
179
|
+
if (delimiter.length !== 1 || delimiter === "\"" || delimiter === "\r" || delimiter === "\n") {
|
|
180
|
+
throw new Error(`Csv: delimiter must be one character other than a quote or line break, got ${delimiter}`)
|
|
181
|
+
}
|
|
182
|
+
return delimiter
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const QUOTE = 34
|
|
186
|
+
const CR = 13
|
|
187
|
+
const LF = 10
|
|
188
|
+
|
|
189
|
+
// Parser states. QUOTE_IN_QUOTED follows a quote inside a quoted field, which
|
|
190
|
+
// either closes the field or starts an escaped quote.
|
|
191
|
+
const FIELD_START = 0
|
|
192
|
+
const UNQUOTED = 1
|
|
193
|
+
const QUOTED = 2
|
|
194
|
+
const QUOTE_IN_QUOTED = 3
|
|
195
|
+
|
|
196
|
+
interface Parsed {
|
|
197
|
+
readonly cells: Row
|
|
198
|
+
readonly record: number
|
|
199
|
+
readonly line: number
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
class Parser {
|
|
203
|
+
readonly delimiter: number
|
|
204
|
+
readonly maxRecordSize: number
|
|
205
|
+
readonly ignoreEmptyLines: boolean
|
|
206
|
+
|
|
207
|
+
state = FIELD_START
|
|
208
|
+
// The partial field, carried across chunks.
|
|
209
|
+
field = ""
|
|
210
|
+
cells: Array<string> = []
|
|
211
|
+
// The size of the current record before the current chunk.
|
|
212
|
+
size = 0
|
|
213
|
+
record = 1
|
|
214
|
+
line = 1
|
|
215
|
+
recordLine = 1
|
|
216
|
+
started = false
|
|
217
|
+
// A CR ended the previous record, so a leading LF completes a CRLF.
|
|
218
|
+
skipLF = false
|
|
219
|
+
// Quoted content ended with a CR, so a leading LF does not start a new line.
|
|
220
|
+
quotedCR = false
|
|
221
|
+
|
|
222
|
+
constructor(delimiter: string, maxRecordSize: number, ignoreEmptyLines: boolean) {
|
|
223
|
+
this.delimiter = delimiter.charCodeAt(0)
|
|
224
|
+
this.maxRecordSize = maxRecordSize
|
|
225
|
+
this.ignoreEmptyLines = ignoreEmptyLines
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fail(kind: "Syntax" | "RecordSize", message: string): CsvError {
|
|
229
|
+
return new CsvError({
|
|
230
|
+
kind,
|
|
231
|
+
message,
|
|
232
|
+
record: this.record,
|
|
233
|
+
line: this.recordLine,
|
|
234
|
+
column: kind === "Syntax" ? this.cells.length + 1 : undefined
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
checkSize(size: number): void {
|
|
239
|
+
if (size > this.maxRecordSize) {
|
|
240
|
+
throw this.fail("RecordSize", `CSV record exceeds maxRecordSize of ${this.maxRecordSize}`)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// A record that is already too large reports its size, not the syntax error.
|
|
245
|
+
failSyntax(size: number, message: string): CsvError {
|
|
246
|
+
this.checkSize(size)
|
|
247
|
+
return this.fail("Syntax", message)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
endRecord(size: number, out: Array<Parsed>): void {
|
|
251
|
+
this.checkSize(size)
|
|
252
|
+
if (size > 0 || !this.ignoreEmptyLines) {
|
|
253
|
+
out.push({ cells: this.cells, record: this.record, line: this.recordLine })
|
|
254
|
+
}
|
|
255
|
+
this.cells = []
|
|
256
|
+
this.size = 0
|
|
257
|
+
this.record++
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
countLines(text: string, start: number, end: number): void {
|
|
261
|
+
for (let i = start; i < end; i++) {
|
|
262
|
+
const c = text.charCodeAt(i)
|
|
263
|
+
if (c === CR) this.line++
|
|
264
|
+
else if (c === LF && !(i === start ? this.quotedCR : text.charCodeAt(i - 1) === CR)) this.line++
|
|
265
|
+
}
|
|
266
|
+
if (end > start) this.quotedCR = text.charCodeAt(end - 1) === CR
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
feed(text: string, out: Array<Parsed>): void {
|
|
270
|
+
const length = text.length
|
|
271
|
+
const delimiter = this.delimiter
|
|
272
|
+
let i = 0
|
|
273
|
+
if (length > 0 && !this.started) {
|
|
274
|
+
this.started = true
|
|
275
|
+
if (text.charCodeAt(0) === 0xFEFF) i = 1
|
|
276
|
+
}
|
|
277
|
+
// The current record starts at `start` in this chunk, so its size at `i`
|
|
278
|
+
// is `this.size + i - start`.
|
|
279
|
+
let start = i
|
|
280
|
+
let state = this.state
|
|
281
|
+
let field = this.field
|
|
282
|
+
while (i < length) {
|
|
283
|
+
if (state === FIELD_START) {
|
|
284
|
+
if (this.skipLF) {
|
|
285
|
+
this.skipLF = false
|
|
286
|
+
if (text.charCodeAt(i) === LF) {
|
|
287
|
+
start = ++i
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (text.charCodeAt(i) === QUOTE) {
|
|
292
|
+
state = QUOTED
|
|
293
|
+
this.quotedCR = false
|
|
294
|
+
i++
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
state = UNQUOTED
|
|
298
|
+
}
|
|
299
|
+
if (state === UNQUOTED) {
|
|
300
|
+
let j = i
|
|
301
|
+
let c = 0
|
|
302
|
+
while (j < length) {
|
|
303
|
+
c = text.charCodeAt(j)
|
|
304
|
+
if (c === delimiter || c === CR || c === LF || c === QUOTE) break
|
|
305
|
+
j++
|
|
306
|
+
}
|
|
307
|
+
if (j > i) field = field === "" ? text.slice(i, j) : field + text.slice(i, j)
|
|
308
|
+
if (j === length) {
|
|
309
|
+
i = j
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
if (c === QUOTE) {
|
|
313
|
+
throw this.failSyntax(this.size + j - start, "Unexpected quote in an unquoted field")
|
|
314
|
+
}
|
|
315
|
+
this.cells.push(field)
|
|
316
|
+
field = ""
|
|
317
|
+
state = FIELD_START
|
|
318
|
+
i = j + 1
|
|
319
|
+
if (c !== delimiter) {
|
|
320
|
+
this.endRecord(this.size + j - start, out)
|
|
321
|
+
this.line++
|
|
322
|
+
this.recordLine = this.line
|
|
323
|
+
this.skipLF = c === CR
|
|
324
|
+
start = i
|
|
325
|
+
}
|
|
326
|
+
continue
|
|
327
|
+
}
|
|
328
|
+
if (state === QUOTED) {
|
|
329
|
+
const end = text.indexOf("\"", i)
|
|
330
|
+
const j = end === -1 ? length : end
|
|
331
|
+
if (j > i) {
|
|
332
|
+
field += text.slice(i, j)
|
|
333
|
+
this.countLines(text, i, j)
|
|
334
|
+
}
|
|
335
|
+
if (end === -1) {
|
|
336
|
+
i = j
|
|
337
|
+
break
|
|
338
|
+
}
|
|
339
|
+
state = QUOTE_IN_QUOTED
|
|
340
|
+
i = j + 1
|
|
341
|
+
continue
|
|
342
|
+
}
|
|
343
|
+
// QUOTE_IN_QUOTED
|
|
344
|
+
const c = text.charCodeAt(i)
|
|
345
|
+
if (c === QUOTE) {
|
|
346
|
+
field += "\""
|
|
347
|
+
state = QUOTED
|
|
348
|
+
this.quotedCR = false
|
|
349
|
+
i++
|
|
350
|
+
} else if (c === delimiter || c === CR || c === LF) {
|
|
351
|
+
this.cells.push(field)
|
|
352
|
+
field = ""
|
|
353
|
+
state = FIELD_START
|
|
354
|
+
i++
|
|
355
|
+
if (c !== delimiter) {
|
|
356
|
+
this.endRecord(this.size + i - 1 - start, out)
|
|
357
|
+
this.line++
|
|
358
|
+
this.recordLine = this.line
|
|
359
|
+
this.skipLF = c === CR
|
|
360
|
+
start = i
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
throw this.failSyntax(this.size + i - start, "Expected a delimiter or line break after a closing quote")
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
this.state = state
|
|
367
|
+
this.field = field
|
|
368
|
+
this.size += length - start
|
|
369
|
+
this.checkSize(this.size)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
end(out: Array<Parsed>): void {
|
|
373
|
+
if (this.state === QUOTED) throw this.fail("Syntax", "Unterminated quoted field")
|
|
374
|
+
if (this.size > 0 || this.cells.length > 0) {
|
|
375
|
+
this.cells.push(this.field)
|
|
376
|
+
this.field = ""
|
|
377
|
+
this.state = FIELD_START
|
|
378
|
+
this.endRecord(this.size, out)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
interface Decoded {
|
|
384
|
+
readonly value: Row | Record.ReadonlyRecord<string, string>
|
|
385
|
+
readonly record: number
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const decodeRecords = <IE, Done>(options: DecodeOptions | undefined): Channel.Channel<
|
|
389
|
+
Arr.NonEmptyReadonlyArray<Decoded>,
|
|
390
|
+
IE | CsvError,
|
|
391
|
+
Done,
|
|
392
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
393
|
+
IE,
|
|
394
|
+
Done
|
|
395
|
+
> => {
|
|
396
|
+
const delimiter = checkDelimiter(options?.delimiter ?? ",")
|
|
397
|
+
const maxRecordSize = options?.maxRecordSize ?? defaultMaxRecordSize
|
|
398
|
+
if (!(maxRecordSize > 0)) {
|
|
399
|
+
throw new Error(`Csv: maxRecordSize must be positive, got ${maxRecordSize}`)
|
|
400
|
+
}
|
|
401
|
+
const ignoreEmptyLines = options?.ignoreEmptyLines === true
|
|
402
|
+
const useHeaders = options?.headers === true
|
|
403
|
+
return Channel.fromTransform((upstream, _scope) =>
|
|
404
|
+
Effect.sync(() => {
|
|
405
|
+
const parser = new Parser(delimiter, maxRecordSize, ignoreEmptyLines)
|
|
406
|
+
let headers: Row | undefined
|
|
407
|
+
// Plain assignment is faster than `Object.fromEntries`, but a `__proto__`
|
|
408
|
+
// header would set the prototype instead of a property.
|
|
409
|
+
let assignable = false
|
|
410
|
+
let done: Pull.Pull<never, never, Done> | undefined
|
|
411
|
+
|
|
412
|
+
const toRecord = (row: Parsed): Decoded => {
|
|
413
|
+
if (!useHeaders) return { value: row.cells, record: row.record }
|
|
414
|
+
if (row.cells.length !== headers!.length) {
|
|
415
|
+
throw new CsvError({
|
|
416
|
+
kind: "Header",
|
|
417
|
+
message: `Expected ${headers!.length} cells, got ${row.cells.length}`,
|
|
418
|
+
record: row.record,
|
|
419
|
+
line: row.line,
|
|
420
|
+
column: undefined
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
if (!assignable) {
|
|
424
|
+
return { value: Object.fromEntries(headers!.map((name, i) => [name, row.cells[i]])), record: row.record }
|
|
425
|
+
}
|
|
426
|
+
const value: globalThis.Record<string, string> = {}
|
|
427
|
+
for (let i = 0; i < headers!.length; i++) value[headers![i]] = row.cells[i]
|
|
428
|
+
return { value, record: row.record }
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const emit = (
|
|
432
|
+
run: (out: Array<Parsed>) => void
|
|
433
|
+
): Pull.Pull<Arr.NonEmptyReadonlyArray<Decoded>, IE | CsvError, Done> => {
|
|
434
|
+
const rows: Array<Parsed> = []
|
|
435
|
+
const out: Array<Decoded> = []
|
|
436
|
+
try {
|
|
437
|
+
run(rows)
|
|
438
|
+
for (const row of rows) {
|
|
439
|
+
if (useHeaders && headers === undefined) {
|
|
440
|
+
if (new Set(row.cells).size !== row.cells.length) {
|
|
441
|
+
throw new CsvError({
|
|
442
|
+
kind: "Header",
|
|
443
|
+
message: "Duplicate header name",
|
|
444
|
+
record: row.record,
|
|
445
|
+
line: row.line,
|
|
446
|
+
column: undefined
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
headers = row.cells
|
|
450
|
+
assignable = !headers.includes("__proto__")
|
|
451
|
+
} else {
|
|
452
|
+
out.push(toRecord(row))
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
} catch (error) {
|
|
456
|
+
if (isCsvError(error)) return Effect.fail(error)
|
|
457
|
+
throw error
|
|
458
|
+
}
|
|
459
|
+
return Arr.isArrayNonEmpty(out) ? Effect.succeed(out) : pull
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const pull: Pull.Pull<Arr.NonEmptyReadonlyArray<Decoded>, IE | CsvError, Done> = Effect.suspend(() => {
|
|
463
|
+
if (done !== undefined) return done
|
|
464
|
+
return Pull.matchEffect(upstream, {
|
|
465
|
+
onSuccess: (texts) =>
|
|
466
|
+
emit((out) => {
|
|
467
|
+
for (const text of texts) parser.feed(text, out)
|
|
468
|
+
}),
|
|
469
|
+
onFailure: Effect.failCause,
|
|
470
|
+
onDone: (value) => {
|
|
471
|
+
done = Cause.done(value)
|
|
472
|
+
return emit((out) => parser.end(out))
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
return pull
|
|
477
|
+
})
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Creates a channel that parses text chunks into CSV records.
|
|
483
|
+
*
|
|
484
|
+
* **Details**
|
|
485
|
+
*
|
|
486
|
+
* Records are arrays of cells, or objects keyed by the first record when
|
|
487
|
+
* `headers` is `true`. Parser state is created per channel run.
|
|
488
|
+
*
|
|
489
|
+
* **Example** (Parsing header records)
|
|
490
|
+
*
|
|
491
|
+
* ```ts import.meta.vitest
|
|
492
|
+
* import { Effect, Stream } from "effect"
|
|
493
|
+
* import { Csv } from "effect/encoding"
|
|
494
|
+
*
|
|
495
|
+
* const records = Stream.make("name,city\nAlice,\"San\nFrancisco\"\n").pipe(
|
|
496
|
+
* Stream.pipeThroughChannel(Csv.decodeString({ headers: true })),
|
|
497
|
+
* Stream.runCollect
|
|
498
|
+
* )
|
|
499
|
+
* await Effect.runPromise(records) // => [{ name: "Alice", city: "San\nFrancisco" }]
|
|
500
|
+
* ```
|
|
501
|
+
*
|
|
502
|
+
* @see decode
|
|
503
|
+
* @see decodeSchemaString
|
|
504
|
+
* @stability unstable
|
|
505
|
+
* @category decoding
|
|
506
|
+
* @since 4.1.0
|
|
507
|
+
*/
|
|
508
|
+
export const decodeString: {
|
|
509
|
+
/**
|
|
510
|
+
* Creates a channel that parses text chunks into CSV records.
|
|
511
|
+
*
|
|
512
|
+
* **Details**
|
|
513
|
+
*
|
|
514
|
+
* Records are arrays of cells, or objects keyed by the first record when
|
|
515
|
+
* `headers` is `true`. Parser state is created per channel run.
|
|
516
|
+
*
|
|
517
|
+
* **Example** (Parsing header records)
|
|
518
|
+
*
|
|
519
|
+
* ```ts import.meta.vitest
|
|
520
|
+
* import { Effect, Stream } from "effect"
|
|
521
|
+
* import { Csv } from "effect/encoding"
|
|
522
|
+
*
|
|
523
|
+
* const records = Stream.make("name,city\nAlice,\"San\nFrancisco\"\n").pipe(
|
|
524
|
+
* Stream.pipeThroughChannel(Csv.decodeString({ headers: true })),
|
|
525
|
+
* Stream.runCollect
|
|
526
|
+
* )
|
|
527
|
+
* await Effect.runPromise(records) // => [{ name: "Alice", city: "San\nFrancisco" }]
|
|
528
|
+
* ```
|
|
529
|
+
*
|
|
530
|
+
* @see decode
|
|
531
|
+
* @see decodeSchemaString
|
|
532
|
+
* @stability unstable
|
|
533
|
+
* @category decoding
|
|
534
|
+
* @since 4.1.0
|
|
535
|
+
*/
|
|
536
|
+
<IE = never, Done = unknown>(options: DecodeOptions & { readonly headers: true }): Channel.Channel<
|
|
537
|
+
Arr.NonEmptyReadonlyArray<Record.ReadonlyRecord<string, string>>,
|
|
538
|
+
IE | CsvError,
|
|
539
|
+
Done,
|
|
540
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
541
|
+
IE,
|
|
542
|
+
Done
|
|
543
|
+
>
|
|
544
|
+
/**
|
|
545
|
+
* Creates a channel that parses text chunks into CSV records.
|
|
546
|
+
*
|
|
547
|
+
* **Details**
|
|
548
|
+
*
|
|
549
|
+
* Records are arrays of cells, or objects keyed by the first record when
|
|
550
|
+
* `headers` is `true`. Parser state is created per channel run.
|
|
551
|
+
*
|
|
552
|
+
* **Example** (Parsing header records)
|
|
553
|
+
*
|
|
554
|
+
* ```ts import.meta.vitest
|
|
555
|
+
* import { Effect, Stream } from "effect"
|
|
556
|
+
* import { Csv } from "effect/encoding"
|
|
557
|
+
*
|
|
558
|
+
* const records = Stream.make("name,city\nAlice,\"San\nFrancisco\"\n").pipe(
|
|
559
|
+
* Stream.pipeThroughChannel(Csv.decodeString({ headers: true })),
|
|
560
|
+
* Stream.runCollect
|
|
561
|
+
* )
|
|
562
|
+
* await Effect.runPromise(records) // => [{ name: "Alice", city: "San\nFrancisco" }]
|
|
563
|
+
* ```
|
|
564
|
+
*
|
|
565
|
+
* @see decode
|
|
566
|
+
* @see decodeSchemaString
|
|
567
|
+
* @stability unstable
|
|
568
|
+
* @category decoding
|
|
569
|
+
* @since 4.1.0
|
|
570
|
+
*/
|
|
571
|
+
<IE = never, Done = unknown>(options?: DecodeOptions & { readonly headers?: false | undefined }): Channel.Channel<
|
|
572
|
+
Arr.NonEmptyReadonlyArray<Row>,
|
|
573
|
+
IE | CsvError,
|
|
574
|
+
Done,
|
|
575
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
576
|
+
IE,
|
|
577
|
+
Done
|
|
578
|
+
>
|
|
579
|
+
/**
|
|
580
|
+
* Creates a channel that parses text chunks into CSV records.
|
|
581
|
+
*
|
|
582
|
+
* **Details**
|
|
583
|
+
*
|
|
584
|
+
* Records are arrays of cells, or objects keyed by the first record when
|
|
585
|
+
* `headers` is `true`. Parser state is created per channel run.
|
|
586
|
+
*
|
|
587
|
+
* **Example** (Parsing header records)
|
|
588
|
+
*
|
|
589
|
+
* ```ts import.meta.vitest
|
|
590
|
+
* import { Effect, Stream } from "effect"
|
|
591
|
+
* import { Csv } from "effect/encoding"
|
|
592
|
+
*
|
|
593
|
+
* const records = Stream.make("name,city\nAlice,\"San\nFrancisco\"\n").pipe(
|
|
594
|
+
* Stream.pipeThroughChannel(Csv.decodeString({ headers: true })),
|
|
595
|
+
* Stream.runCollect
|
|
596
|
+
* )
|
|
597
|
+
* await Effect.runPromise(records) // => [{ name: "Alice", city: "San\nFrancisco" }]
|
|
598
|
+
* ```
|
|
599
|
+
*
|
|
600
|
+
* @see decode
|
|
601
|
+
* @see decodeSchemaString
|
|
602
|
+
* @stability unstable
|
|
603
|
+
* @category decoding
|
|
604
|
+
* @since 4.1.0
|
|
605
|
+
*/
|
|
606
|
+
<IE = never, Done = unknown>(options?: DecodeOptions): Channel.Channel<
|
|
607
|
+
Arr.NonEmptyReadonlyArray<Row | Record.ReadonlyRecord<string, string>>,
|
|
608
|
+
IE | CsvError,
|
|
609
|
+
Done,
|
|
610
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
611
|
+
IE,
|
|
612
|
+
Done
|
|
613
|
+
>
|
|
614
|
+
} =
|
|
615
|
+
(<IE, Done>(options?: DecodeOptions) =>
|
|
616
|
+
Channel.map(decodeRecords<IE, Done>(options), Arr.map((record) => record.value))) as any
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Creates a channel that parses UTF-8 byte chunks into CSV records.
|
|
620
|
+
*
|
|
621
|
+
* @see decodeString
|
|
622
|
+
* @stability unstable
|
|
623
|
+
* @category decoding
|
|
624
|
+
* @since 4.1.0
|
|
625
|
+
*/
|
|
626
|
+
export const decode: {
|
|
627
|
+
/**
|
|
628
|
+
* Creates a channel that parses UTF-8 byte chunks into CSV records.
|
|
629
|
+
*
|
|
630
|
+
* @see decodeString
|
|
631
|
+
* @stability unstable
|
|
632
|
+
* @category decoding
|
|
633
|
+
* @since 4.1.0
|
|
634
|
+
*/
|
|
635
|
+
<IE = never, Done = unknown>(options: DecodeOptions & { readonly headers: true }): Channel.Channel<
|
|
636
|
+
Arr.NonEmptyReadonlyArray<Record.ReadonlyRecord<string, string>>,
|
|
637
|
+
IE | CsvError,
|
|
638
|
+
Done,
|
|
639
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
640
|
+
IE,
|
|
641
|
+
Done
|
|
642
|
+
>
|
|
643
|
+
/**
|
|
644
|
+
* Creates a channel that parses UTF-8 byte chunks into CSV records.
|
|
645
|
+
*
|
|
646
|
+
* @see decodeString
|
|
647
|
+
* @stability unstable
|
|
648
|
+
* @category decoding
|
|
649
|
+
* @since 4.1.0
|
|
650
|
+
*/
|
|
651
|
+
<IE = never, Done = unknown>(options?: DecodeOptions & { readonly headers?: false | undefined }): Channel.Channel<
|
|
652
|
+
Arr.NonEmptyReadonlyArray<Row>,
|
|
653
|
+
IE | CsvError,
|
|
654
|
+
Done,
|
|
655
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
656
|
+
IE,
|
|
657
|
+
Done
|
|
658
|
+
>
|
|
659
|
+
/**
|
|
660
|
+
* Creates a channel that parses UTF-8 byte chunks into CSV records.
|
|
661
|
+
*
|
|
662
|
+
* @see decodeString
|
|
663
|
+
* @stability unstable
|
|
664
|
+
* @category decoding
|
|
665
|
+
* @since 4.1.0
|
|
666
|
+
*/
|
|
667
|
+
<IE = never, Done = unknown>(options?: DecodeOptions): Channel.Channel<
|
|
668
|
+
Arr.NonEmptyReadonlyArray<Row | Record.ReadonlyRecord<string, string>>,
|
|
669
|
+
IE | CsvError,
|
|
670
|
+
Done,
|
|
671
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
672
|
+
IE,
|
|
673
|
+
Done
|
|
674
|
+
>
|
|
675
|
+
} =
|
|
676
|
+
(<IE, Done>(options?: DecodeOptions) =>
|
|
677
|
+
Channel.pipeTo(Channel.decodeText<IE, Done>("utf-8", { ignoreBOM: true }), decodeString<IE, Done>(options))) as any
|
|
678
|
+
|
|
679
|
+
// Rewrites the chunk index at the head of each top-level issue path to the
|
|
680
|
+
// record number, keeping the rest of the issue unchanged.
|
|
681
|
+
const rebaseIssue = (error: Schema.SchemaError, record: (index: number) => number): Schema.SchemaError => {
|
|
682
|
+
const issue = error.issue
|
|
683
|
+
if (issue._tag !== "Composite") return error
|
|
684
|
+
return new Schema.SchemaError(
|
|
685
|
+
new SchemaIssue.Composite(
|
|
686
|
+
issue.ast,
|
|
687
|
+
Arr.map(issue.issues, (issue) =>
|
|
688
|
+
issue._tag === "Pointer" && typeof issue.path[0] === "number"
|
|
689
|
+
? new SchemaIssue.Pointer([record(issue.path[0]), ...issue.path.slice(1)], issue.issue)
|
|
690
|
+
: issue),
|
|
691
|
+
issue.input,
|
|
692
|
+
{ reportInput: "input" in issue }
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Derived codecs are new ASTs, and schema parsers are cached per AST, so they
|
|
698
|
+
// are memoized to avoid recompiling parsers for every channel.
|
|
699
|
+
const stringTreeCodecs = new WeakMap<Schema.Constraint, Schema.Constraint>()
|
|
700
|
+
const arrayCodecs = new WeakMap<Schema.Constraint, Schema.Constraint>()
|
|
701
|
+
|
|
702
|
+
// The schema used for CSV cells. `toCodecStringTree` keeps the decoded type and
|
|
703
|
+
// services and only changes the encoded side, which is treated as unknown here.
|
|
704
|
+
const toCodec = <S extends Schema.Constraint>(schema: S, disableCodecs: boolean | undefined): S => {
|
|
705
|
+
if (disableCodecs === true) return schema
|
|
706
|
+
let codec = stringTreeCodecs.get(schema)
|
|
707
|
+
if (codec === undefined) {
|
|
708
|
+
codec = Schema.toCodecStringTree(schema)
|
|
709
|
+
stringTreeCodecs.set(schema, codec)
|
|
710
|
+
}
|
|
711
|
+
return codec as S
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const toArrayCodec = <S extends Schema.Constraint>(codec: S): Schema.NonEmptyArray<S> => {
|
|
715
|
+
let array = arrayCodecs.get(codec)
|
|
716
|
+
if (array === undefined) {
|
|
717
|
+
array = Schema.NonEmptyArray(codec)
|
|
718
|
+
arrayCodecs.set(codec, array)
|
|
719
|
+
}
|
|
720
|
+
return array as Schema.NonEmptyArray<S>
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const isStringLeaf = (ast: SchemaAST.AST): boolean => {
|
|
724
|
+
switch (ast._tag) {
|
|
725
|
+
case "String":
|
|
726
|
+
case "TemplateLiteral":
|
|
727
|
+
return true
|
|
728
|
+
case "Literal":
|
|
729
|
+
return typeof ast.literal === "string"
|
|
730
|
+
case "Enum":
|
|
731
|
+
return ast.enums.every(([, value]) => typeof value === "string")
|
|
732
|
+
case "Union":
|
|
733
|
+
return ast.types.every((type) => type._tag === "Undefined" || isStringLeaf(type))
|
|
734
|
+
default:
|
|
735
|
+
return false
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const resolveSuspend = (ast: SchemaAST.AST): SchemaAST.AST =>
|
|
740
|
+
SchemaAST.isSuspend(ast) ? resolveSuspend(ast.thunk()) : ast
|
|
741
|
+
|
|
742
|
+
// Throws unless every selected cell of an encoded struct or tuple is a string.
|
|
743
|
+
const checkCells = (ast: SchemaAST.AST, columns: ReadonlyArray<string> | undefined): void => {
|
|
744
|
+
ast = resolveSuspend(ast)
|
|
745
|
+
const check = (name: string, type: SchemaAST.AST) => {
|
|
746
|
+
if (!isStringLeaf(type)) {
|
|
747
|
+
throw new Error(
|
|
748
|
+
`Csv: ${name} encodes to ${type._tag}, not a string. ` +
|
|
749
|
+
"Encode it as a single cell with a transformation, such as SchemaGetter.split for arrays"
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (SchemaAST.isObjects(ast)) {
|
|
754
|
+
for (const ps of ast.propertySignatures) {
|
|
755
|
+
if (typeof ps.name === "string" && (columns === undefined || columns.includes(ps.name))) {
|
|
756
|
+
check(`column ${JSON.stringify(ps.name)}`, ps.type)
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (const is of ast.indexSignatures) check("index signature", is.type)
|
|
760
|
+
} else if (SchemaAST.isArrays(ast)) {
|
|
761
|
+
ast.elements.forEach((type, i) => check(`column ${i + 1}`, type))
|
|
762
|
+
for (const type of ast.rest) check("rest element", type)
|
|
763
|
+
} else if (SchemaAST.isUnion(ast)) {
|
|
764
|
+
for (const type of ast.types) checkCells(type, columns)
|
|
765
|
+
} else {
|
|
766
|
+
throw new Error(
|
|
767
|
+
`Csv: cannot verify string cells for ${ast._tag}. ` +
|
|
768
|
+
"Encode it as a flat struct or tuple with a transformation"
|
|
769
|
+
)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Creates a channel that parses CSV text and decodes each record with a schema.
|
|
775
|
+
*
|
|
776
|
+
* **Details**
|
|
777
|
+
*
|
|
778
|
+
* With `headers: true` the schema receives objects keyed by header name;
|
|
779
|
+
* otherwise it receives arrays of cells. Columns that do not encode to a string,
|
|
780
|
+
* such as arrays or nested structs, throw when the channel is created. Each
|
|
781
|
+
* chunk is decoded in one pass. Schema failures are `SchemaError`s whose issue
|
|
782
|
+
* paths start with the record number.
|
|
783
|
+
*
|
|
784
|
+
* **Example** (Decoding renamed headers)
|
|
785
|
+
*
|
|
786
|
+
* ```ts import.meta.vitest
|
|
787
|
+
* import { Effect, Schema, Stream } from "effect"
|
|
788
|
+
* import { Csv } from "effect/encoding"
|
|
789
|
+
*
|
|
790
|
+
* const Person = Schema.Struct({
|
|
791
|
+
* name: Schema.String,
|
|
792
|
+
* age: Schema.Number
|
|
793
|
+
* }).pipe(Schema.encodeKeys({ name: "Full Name", age: "Age (years)" }))
|
|
794
|
+
*
|
|
795
|
+
* const people = Stream.make("Full Name,Age (years)\nAlice,32").pipe(
|
|
796
|
+
* Stream.pipeThroughChannel(Csv.decodeSchemaString(Person)({ headers: true })),
|
|
797
|
+
* Stream.runCollect
|
|
798
|
+
* )
|
|
799
|
+
* await Effect.runPromise(people) // => [{ name: "Alice", age: 32 }]
|
|
800
|
+
* ```
|
|
801
|
+
*
|
|
802
|
+
* **Example** (Transforming positional records into objects)
|
|
803
|
+
*
|
|
804
|
+
* ```ts import.meta.vitest
|
|
805
|
+
* import { Effect, Schema, SchemaGetter, Stream } from "effect"
|
|
806
|
+
* import { Csv } from "effect/encoding"
|
|
807
|
+
*
|
|
808
|
+
* const Person = Schema.Tuple([Schema.String, Schema.Number]).pipe(
|
|
809
|
+
* Schema.decodeTo(Schema.Struct({ name: Schema.String, age: Schema.Number }), {
|
|
810
|
+
* decode: SchemaGetter.transform(([name, age]) => ({ name, age })),
|
|
811
|
+
* encode: SchemaGetter.transform(({ name, age }) => [name, age] as const)
|
|
812
|
+
* })
|
|
813
|
+
* )
|
|
814
|
+
*
|
|
815
|
+
* const people = Stream.make("Alice,32").pipe(
|
|
816
|
+
* Stream.pipeThroughChannel(Csv.decodeSchemaString(Person)()),
|
|
817
|
+
* Stream.runCollect
|
|
818
|
+
* )
|
|
819
|
+
* await Effect.runPromise(people) // => [{ name: "Alice", age: 32 }]
|
|
820
|
+
* ```
|
|
821
|
+
*
|
|
822
|
+
* @see decodeSchema
|
|
823
|
+
* @see encodeSchemaString
|
|
824
|
+
* @stability unstable
|
|
825
|
+
* @category decoding
|
|
826
|
+
* @since 4.1.0
|
|
827
|
+
*/
|
|
828
|
+
export const decodeSchemaString =
|
|
829
|
+
<S extends Schema.Constraint>(schema: S) =>
|
|
830
|
+
<IE = never, Done = unknown>(options?: DecodeSchemaOptions, parseOptions?: SchemaAST.ParseOptions): Channel.Channel<
|
|
831
|
+
Arr.NonEmptyReadonlyArray<S["Type"]>,
|
|
832
|
+
IE | CsvError | Schema.SchemaError,
|
|
833
|
+
Done,
|
|
834
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
835
|
+
IE,
|
|
836
|
+
Done,
|
|
837
|
+
S["DecodingServices"]
|
|
838
|
+
> => {
|
|
839
|
+
const codec = toCodec(schema, options?.disableCodecs)
|
|
840
|
+
const ast = resolveSuspend(SchemaAST.toEncoded(codec.ast))
|
|
841
|
+
checkCells(ast, undefined)
|
|
842
|
+
// CSV cannot tell an empty cell from a missing one, so empty cells of
|
|
843
|
+
// optional properties are decoded as missing keys.
|
|
844
|
+
const optional = options?.disableCodecs !== true && options?.headers === true && SchemaAST.isObjects(ast)
|
|
845
|
+
? ast.propertySignatures.flatMap((ps) =>
|
|
846
|
+
typeof ps.name === "string" && SchemaAST.isOptional(ps.type) ? [ps.name] : []
|
|
847
|
+
)
|
|
848
|
+
: []
|
|
849
|
+
const input = (record: Decoded) => {
|
|
850
|
+
if (optional.length === 0) return record.value
|
|
851
|
+
// Header records are created per row, so they can be updated in place.
|
|
852
|
+
const value = record.value as globalThis.Record<string, string>
|
|
853
|
+
for (const key of optional) {
|
|
854
|
+
if (value[key] === "") delete value[key]
|
|
855
|
+
}
|
|
856
|
+
return value
|
|
857
|
+
}
|
|
858
|
+
const decode = Schema.decodeUnknownEffect(toArrayCodec(codec), parseOptions)
|
|
859
|
+
return Channel.mapEffect(decodeRecords<IE, Done>(options), (records) =>
|
|
860
|
+
Effect.mapError(
|
|
861
|
+
decode(Arr.map(records, input)),
|
|
862
|
+
(error) => rebaseIssue(error, (index) => records[index].record)
|
|
863
|
+
))
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Creates a channel that parses UTF-8 CSV and decodes each record with a schema.
|
|
868
|
+
*
|
|
869
|
+
* @see decodeSchemaString
|
|
870
|
+
* @stability unstable
|
|
871
|
+
* @category decoding
|
|
872
|
+
* @since 4.1.0
|
|
873
|
+
*/
|
|
874
|
+
export const decodeSchema =
|
|
875
|
+
<S extends Schema.Constraint>(schema: S) =>
|
|
876
|
+
<IE = never, Done = unknown>(options?: DecodeSchemaOptions, parseOptions?: SchemaAST.ParseOptions): Channel.Channel<
|
|
877
|
+
Arr.NonEmptyReadonlyArray<S["Type"]>,
|
|
878
|
+
IE | CsvError | Schema.SchemaError,
|
|
879
|
+
Done,
|
|
880
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
881
|
+
IE,
|
|
882
|
+
Done,
|
|
883
|
+
S["DecodingServices"]
|
|
884
|
+
> =>
|
|
885
|
+
Channel.pipeTo(
|
|
886
|
+
Channel.decodeText<IE, Done>("utf-8", { ignoreBOM: true }),
|
|
887
|
+
decodeSchemaString(schema)<IE, Done>(options, parseOptions)
|
|
888
|
+
)
|
|
889
|
+
|
|
890
|
+
const textEncoder = new TextEncoder()
|
|
891
|
+
|
|
892
|
+
const makeWriter = (options: EncodeOptions | undefined) => {
|
|
893
|
+
const delimiter = checkDelimiter(options?.delimiter ?? ",")
|
|
894
|
+
const newline = options?.newline ?? "\r\n"
|
|
895
|
+
if (newline !== "\n" && newline !== "\r\n") {
|
|
896
|
+
throw new Error(`Csv: newline must be "\\n" or "\\r\\n", got ${JSON.stringify(newline)}`)
|
|
897
|
+
}
|
|
898
|
+
const columns = options?.columns
|
|
899
|
+
if (options?.headers === true && columns === undefined) {
|
|
900
|
+
throw new Error("Csv: headers requires columns")
|
|
901
|
+
}
|
|
902
|
+
// Characters that require a cell to be quoted.
|
|
903
|
+
const special = new RegExp(`[${delimiter.replace(/[\\\]^-]/g, "\\$&")}"\\r\\n]`)
|
|
904
|
+
const cell = (value: unknown): string => {
|
|
905
|
+
if (value === undefined) return ""
|
|
906
|
+
if (typeof value !== "string") throw new TypeError(`Csv: expected a string cell, got ${typeof value}`)
|
|
907
|
+
// A leading BOM is quoted so that decoders do not strip it.
|
|
908
|
+
return special.test(value) || value.charCodeAt(0) === 0xFEFF
|
|
909
|
+
? "\"" + value.replaceAll("\"", "\"\"") + "\""
|
|
910
|
+
: value
|
|
911
|
+
}
|
|
912
|
+
const write = (cells: ReadonlyArray<unknown>): string => {
|
|
913
|
+
// A single empty cell is quoted so that it is not read back as an empty line.
|
|
914
|
+
if (cells.length === 1 && (cells[0] === "" || cells[0] === undefined)) return "\"\"" + newline
|
|
915
|
+
let line = cell(cells[0])
|
|
916
|
+
for (let i = 1; i < cells.length; i++) line += delimiter + cell(cells[i])
|
|
917
|
+
return line + newline
|
|
918
|
+
}
|
|
919
|
+
const header = options?.headers === true ? write(columns!) : undefined
|
|
920
|
+
const writeRecord = (value: unknown): string => {
|
|
921
|
+
if (Array.isArray(value)) return write(value)
|
|
922
|
+
if (columns === undefined) throw new TypeError("Csv: encoding object records requires columns")
|
|
923
|
+
const record = value as Record.ReadonlyRecord<string, unknown>
|
|
924
|
+
return write(columns.map((key) => Object.hasOwn(record, key) ? record[key] : undefined))
|
|
925
|
+
}
|
|
926
|
+
return { header, writeRecord }
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const encodeRecords = <A, IE, Done>(options: EncodeOptions | undefined): Channel.Channel<
|
|
930
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
931
|
+
IE,
|
|
932
|
+
Done,
|
|
933
|
+
Arr.NonEmptyReadonlyArray<A>,
|
|
934
|
+
IE,
|
|
935
|
+
Done
|
|
936
|
+
> => {
|
|
937
|
+
const { header, writeRecord } = makeWriter(options)
|
|
938
|
+
return Channel.fromTransform((upstream, _scope) =>
|
|
939
|
+
Effect.sync(() => {
|
|
940
|
+
let pending = header
|
|
941
|
+
const pull: Pull.Pull<Arr.NonEmptyReadonlyArray<string>, IE, Done> = Effect.suspend(() => {
|
|
942
|
+
if (pending !== undefined) {
|
|
943
|
+
const text = pending
|
|
944
|
+
pending = undefined
|
|
945
|
+
return Effect.succeed(Arr.of(text))
|
|
946
|
+
}
|
|
947
|
+
return Effect.flatMap(upstream, (records) => Effect.sync(() => Arr.map(records, writeRecord)))
|
|
948
|
+
})
|
|
949
|
+
return pull
|
|
950
|
+
})
|
|
951
|
+
)
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Creates a channel that writes CSV records as text, one string per record.
|
|
956
|
+
*
|
|
957
|
+
* **Details**
|
|
958
|
+
*
|
|
959
|
+
* Without `columns`, records are arrays of cells. With `columns`, records are
|
|
960
|
+
* objects encoded by those keys. A non-string cell is a defect.
|
|
961
|
+
*
|
|
962
|
+
* **Example** (Writing header records)
|
|
963
|
+
*
|
|
964
|
+
* ```ts import.meta.vitest
|
|
965
|
+
* import { Effect, Stream } from "effect"
|
|
966
|
+
* import { Csv } from "effect/encoding"
|
|
967
|
+
*
|
|
968
|
+
* const csv = Stream.make({ name: "Smith, Charlie", city: "London" }).pipe(
|
|
969
|
+
* Stream.pipeThroughChannel(Csv.encodeString({ columns: ["name", "city"], headers: true })),
|
|
970
|
+
* Stream.runCollect,
|
|
971
|
+
* Effect.map((parts) => parts.join(""))
|
|
972
|
+
* )
|
|
973
|
+
* await Effect.runPromise(csv) // => 'name,city\r\n"Smith, Charlie",London\r\n'
|
|
974
|
+
* ```
|
|
975
|
+
*
|
|
976
|
+
* @see encode
|
|
977
|
+
* @see encodeSchemaString
|
|
978
|
+
* @stability unstable
|
|
979
|
+
* @category encoding
|
|
980
|
+
* @since 4.1.0
|
|
981
|
+
*/
|
|
982
|
+
export const encodeString: {
|
|
983
|
+
/**
|
|
984
|
+
* Creates a channel that writes CSV records as text, one string per record.
|
|
985
|
+
*
|
|
986
|
+
* **Details**
|
|
987
|
+
*
|
|
988
|
+
* Without `columns`, records are arrays of cells. With `columns`, records are
|
|
989
|
+
* objects encoded by those keys. A non-string cell is a defect.
|
|
990
|
+
*
|
|
991
|
+
* **Example** (Writing header records)
|
|
992
|
+
*
|
|
993
|
+
* ```ts import.meta.vitest
|
|
994
|
+
* import { Effect, Stream } from "effect"
|
|
995
|
+
* import { Csv } from "effect/encoding"
|
|
996
|
+
*
|
|
997
|
+
* const csv = Stream.make({ name: "Smith, Charlie", city: "London" }).pipe(
|
|
998
|
+
* Stream.pipeThroughChannel(Csv.encodeString({ columns: ["name", "city"], headers: true })),
|
|
999
|
+
* Stream.runCollect,
|
|
1000
|
+
* Effect.map((parts) => parts.join(""))
|
|
1001
|
+
* )
|
|
1002
|
+
* await Effect.runPromise(csv) // => 'name,city\r\n"Smith, Charlie",London\r\n'
|
|
1003
|
+
* ```
|
|
1004
|
+
*
|
|
1005
|
+
* @see encode
|
|
1006
|
+
* @see encodeSchemaString
|
|
1007
|
+
* @stability unstable
|
|
1008
|
+
* @category encoding
|
|
1009
|
+
* @since 4.1.0
|
|
1010
|
+
*/
|
|
1011
|
+
<IE = never, Done = unknown>(options: EncodeOptions & { readonly columns: ReadonlyArray<string> }): Channel.Channel<
|
|
1012
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
1013
|
+
IE,
|
|
1014
|
+
Done,
|
|
1015
|
+
Arr.NonEmptyReadonlyArray<Record.ReadonlyRecord<string, string | undefined>>,
|
|
1016
|
+
IE,
|
|
1017
|
+
Done
|
|
1018
|
+
>
|
|
1019
|
+
/**
|
|
1020
|
+
* Creates a channel that writes CSV records as text, one string per record.
|
|
1021
|
+
*
|
|
1022
|
+
* **Details**
|
|
1023
|
+
*
|
|
1024
|
+
* Without `columns`, records are arrays of cells. With `columns`, records are
|
|
1025
|
+
* objects encoded by those keys. A non-string cell is a defect.
|
|
1026
|
+
*
|
|
1027
|
+
* **Example** (Writing header records)
|
|
1028
|
+
*
|
|
1029
|
+
* ```ts import.meta.vitest
|
|
1030
|
+
* import { Effect, Stream } from "effect"
|
|
1031
|
+
* import { Csv } from "effect/encoding"
|
|
1032
|
+
*
|
|
1033
|
+
* const csv = Stream.make({ name: "Smith, Charlie", city: "London" }).pipe(
|
|
1034
|
+
* Stream.pipeThroughChannel(Csv.encodeString({ columns: ["name", "city"], headers: true })),
|
|
1035
|
+
* Stream.runCollect,
|
|
1036
|
+
* Effect.map((parts) => parts.join(""))
|
|
1037
|
+
* )
|
|
1038
|
+
* await Effect.runPromise(csv) // => 'name,city\r\n"Smith, Charlie",London\r\n'
|
|
1039
|
+
* ```
|
|
1040
|
+
*
|
|
1041
|
+
* @see encode
|
|
1042
|
+
* @see encodeSchemaString
|
|
1043
|
+
* @stability unstable
|
|
1044
|
+
* @category encoding
|
|
1045
|
+
* @since 4.1.0
|
|
1046
|
+
*/
|
|
1047
|
+
<IE = never, Done = unknown>(
|
|
1048
|
+
options?: EncodeOptions & { readonly columns?: undefined; readonly headers?: false | undefined }
|
|
1049
|
+
): Channel.Channel<
|
|
1050
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
1051
|
+
IE,
|
|
1052
|
+
Done,
|
|
1053
|
+
Arr.NonEmptyReadonlyArray<Row>,
|
|
1054
|
+
IE,
|
|
1055
|
+
Done
|
|
1056
|
+
>
|
|
1057
|
+
/**
|
|
1058
|
+
* Creates a channel that writes CSV records as text, one string per record.
|
|
1059
|
+
*
|
|
1060
|
+
* **Details**
|
|
1061
|
+
*
|
|
1062
|
+
* Without `columns`, records are arrays of cells. With `columns`, records are
|
|
1063
|
+
* objects encoded by those keys. A non-string cell is a defect.
|
|
1064
|
+
*
|
|
1065
|
+
* **Example** (Writing header records)
|
|
1066
|
+
*
|
|
1067
|
+
* ```ts import.meta.vitest
|
|
1068
|
+
* import { Effect, Stream } from "effect"
|
|
1069
|
+
* import { Csv } from "effect/encoding"
|
|
1070
|
+
*
|
|
1071
|
+
* const csv = Stream.make({ name: "Smith, Charlie", city: "London" }).pipe(
|
|
1072
|
+
* Stream.pipeThroughChannel(Csv.encodeString({ columns: ["name", "city"], headers: true })),
|
|
1073
|
+
* Stream.runCollect,
|
|
1074
|
+
* Effect.map((parts) => parts.join(""))
|
|
1075
|
+
* )
|
|
1076
|
+
* await Effect.runPromise(csv) // => 'name,city\r\n"Smith, Charlie",London\r\n'
|
|
1077
|
+
* ```
|
|
1078
|
+
*
|
|
1079
|
+
* @see encode
|
|
1080
|
+
* @see encodeSchemaString
|
|
1081
|
+
* @stability unstable
|
|
1082
|
+
* @category encoding
|
|
1083
|
+
* @since 4.1.0
|
|
1084
|
+
*/
|
|
1085
|
+
<IE = never, Done = unknown>(options?: EncodeOptions): Channel.Channel<
|
|
1086
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
1087
|
+
IE,
|
|
1088
|
+
Done,
|
|
1089
|
+
Arr.NonEmptyReadonlyArray<Row | Record.ReadonlyRecord<string, string | undefined>>,
|
|
1090
|
+
IE,
|
|
1091
|
+
Done
|
|
1092
|
+
>
|
|
1093
|
+
} = <IE, Done>(options?: EncodeOptions) => encodeRecords<unknown, IE, Done>(options)
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Creates a channel that writes CSV records as UTF-8 bytes, one byte array per
|
|
1097
|
+
* input chunk.
|
|
1098
|
+
*
|
|
1099
|
+
* @see encodeString
|
|
1100
|
+
* @stability unstable
|
|
1101
|
+
* @category encoding
|
|
1102
|
+
* @since 4.1.0
|
|
1103
|
+
*/
|
|
1104
|
+
export const encode: {
|
|
1105
|
+
/**
|
|
1106
|
+
* Creates a channel that writes CSV records as UTF-8 bytes, one byte array per
|
|
1107
|
+
* input chunk.
|
|
1108
|
+
*
|
|
1109
|
+
* @see encodeString
|
|
1110
|
+
* @stability unstable
|
|
1111
|
+
* @category encoding
|
|
1112
|
+
* @since 4.1.0
|
|
1113
|
+
*/
|
|
1114
|
+
<IE = never, Done = unknown>(options: EncodeOptions & { readonly columns: ReadonlyArray<string> }): Channel.Channel<
|
|
1115
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
1116
|
+
IE,
|
|
1117
|
+
Done,
|
|
1118
|
+
Arr.NonEmptyReadonlyArray<Record.ReadonlyRecord<string, string | undefined>>,
|
|
1119
|
+
IE,
|
|
1120
|
+
Done
|
|
1121
|
+
>
|
|
1122
|
+
/**
|
|
1123
|
+
* Creates a channel that writes CSV records as UTF-8 bytes, one byte array per
|
|
1124
|
+
* input chunk.
|
|
1125
|
+
*
|
|
1126
|
+
* @see encodeString
|
|
1127
|
+
* @stability unstable
|
|
1128
|
+
* @category encoding
|
|
1129
|
+
* @since 4.1.0
|
|
1130
|
+
*/
|
|
1131
|
+
<IE = never, Done = unknown>(
|
|
1132
|
+
options?: EncodeOptions & { readonly columns?: undefined; readonly headers?: false | undefined }
|
|
1133
|
+
): Channel.Channel<
|
|
1134
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
1135
|
+
IE,
|
|
1136
|
+
Done,
|
|
1137
|
+
Arr.NonEmptyReadonlyArray<Row>,
|
|
1138
|
+
IE,
|
|
1139
|
+
Done
|
|
1140
|
+
>
|
|
1141
|
+
/**
|
|
1142
|
+
* Creates a channel that writes CSV records as UTF-8 bytes, one byte array per
|
|
1143
|
+
* input chunk.
|
|
1144
|
+
*
|
|
1145
|
+
* @see encodeString
|
|
1146
|
+
* @stability unstable
|
|
1147
|
+
* @category encoding
|
|
1148
|
+
* @since 4.1.0
|
|
1149
|
+
*/
|
|
1150
|
+
<IE = never, Done = unknown>(options?: EncodeOptions): Channel.Channel<
|
|
1151
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
1152
|
+
IE,
|
|
1153
|
+
Done,
|
|
1154
|
+
Arr.NonEmptyReadonlyArray<Row | Record.ReadonlyRecord<string, string | undefined>>,
|
|
1155
|
+
IE,
|
|
1156
|
+
Done
|
|
1157
|
+
>
|
|
1158
|
+
} = <IE, Done>(options?: EncodeOptions) =>
|
|
1159
|
+
Channel.map(encodeRecords<unknown, IE, Done>(options), (texts) => Arr.of(textEncoder.encode(texts.join(""))))
|
|
1160
|
+
|
|
1161
|
+
// Struct schemas encode by their encoded keys in declaration order. Tuple and
|
|
1162
|
+
// array schemas encode by position. Other schemas, including records with
|
|
1163
|
+
// index signatures, need explicit columns.
|
|
1164
|
+
const schemaColumns = (
|
|
1165
|
+
ast: SchemaAST.AST,
|
|
1166
|
+
options: Pick<EncodeOptions, "columns" | "headers"> | undefined
|
|
1167
|
+
): ReadonlyArray<string> | undefined => {
|
|
1168
|
+
if (options?.columns !== undefined) {
|
|
1169
|
+
checkCells(ast, options.columns)
|
|
1170
|
+
return options.columns
|
|
1171
|
+
}
|
|
1172
|
+
if (SchemaAST.isObjects(ast) && ast.indexSignatures.length === 0) {
|
|
1173
|
+
const columns = ast.propertySignatures.flatMap((ps) => typeof ps.name === "string" ? [ps.name] : [])
|
|
1174
|
+
checkCells(ast, columns)
|
|
1175
|
+
return columns
|
|
1176
|
+
}
|
|
1177
|
+
if (SchemaAST.isArrays(ast)) {
|
|
1178
|
+
if (options?.headers === true) throw new Error("Csv: headers for tuple or array schemas require columns")
|
|
1179
|
+
checkCells(ast, undefined)
|
|
1180
|
+
return undefined
|
|
1181
|
+
}
|
|
1182
|
+
throw new Error(`Csv: columns are required to encode ${ast._tag} schemas`)
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const encodeSchemaRecords = <S extends Schema.Constraint, IE, Done>(
|
|
1186
|
+
schema: S,
|
|
1187
|
+
options: EncodeSchemaOptions<S> | undefined
|
|
1188
|
+
): Channel.Channel<
|
|
1189
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
1190
|
+
IE | Schema.SchemaError,
|
|
1191
|
+
Done,
|
|
1192
|
+
Arr.NonEmptyReadonlyArray<S["Type"]>,
|
|
1193
|
+
IE,
|
|
1194
|
+
Done,
|
|
1195
|
+
S["EncodingServices"]
|
|
1196
|
+
> => {
|
|
1197
|
+
const codec = toCodec(schema, options?.disableCodecs)
|
|
1198
|
+
const columns = schemaColumns(SchemaAST.toEncoded(codec.ast), options)
|
|
1199
|
+
const writer = encodeRecords<S["Encoded"], IE | Schema.SchemaError, Done>({ ...options, columns })
|
|
1200
|
+
const encode = Schema.encodeEffect(toArrayCodec(codec))
|
|
1201
|
+
const firstRecord = options?.headers === true ? 2 : 1
|
|
1202
|
+
const values = Channel.fromTransform((
|
|
1203
|
+
upstream: Pull.Pull<Arr.NonEmptyReadonlyArray<S["Type"]>, IE, Done>,
|
|
1204
|
+
_scope
|
|
1205
|
+
) =>
|
|
1206
|
+
Effect.sync(() => {
|
|
1207
|
+
let record = firstRecord
|
|
1208
|
+
return Effect.flatMap(upstream, (chunk) => {
|
|
1209
|
+
const first = record
|
|
1210
|
+
record += chunk.length
|
|
1211
|
+
return Effect.mapError(encode(chunk), (error) => rebaseIssue(error, (index) => first + index))
|
|
1212
|
+
})
|
|
1213
|
+
})
|
|
1214
|
+
)
|
|
1215
|
+
return Channel.pipeTo(values, writer)
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Creates a channel that encodes values with a schema and writes them as CSV
|
|
1220
|
+
* text, one string per record.
|
|
1221
|
+
*
|
|
1222
|
+
* **Details**
|
|
1223
|
+
*
|
|
1224
|
+
* Columns default to the encoded keys of a struct schema, including keys
|
|
1225
|
+
* renamed with `Schema.encodeKeys`. Missing or `undefined` values become empty
|
|
1226
|
+
* cells. Schema failures are `SchemaError`s whose issue paths start with the
|
|
1227
|
+
* output record number, counting the header row.
|
|
1228
|
+
*
|
|
1229
|
+
* **Example** (Encoding renamed headers)
|
|
1230
|
+
*
|
|
1231
|
+
* ```ts import.meta.vitest
|
|
1232
|
+
* import { Effect, Schema, Stream } from "effect"
|
|
1233
|
+
* import { Csv } from "effect/encoding"
|
|
1234
|
+
*
|
|
1235
|
+
* const Person = Schema.Struct({
|
|
1236
|
+
* name: Schema.String,
|
|
1237
|
+
* age: Schema.Number,
|
|
1238
|
+
* joined: Schema.Date
|
|
1239
|
+
* }).pipe(Schema.encodeKeys({ name: "Full Name", age: "Age (years)" }))
|
|
1240
|
+
*
|
|
1241
|
+
* const csv = await Effect.runPromise(
|
|
1242
|
+
* Stream.make({ name: "Alice", age: 32, joined: new Date(0) }).pipe(
|
|
1243
|
+
* Stream.pipeThroughChannel(Csv.encodeSchemaString(Person)({ headers: true, newline: "\n" })),
|
|
1244
|
+
* Stream.runCollect,
|
|
1245
|
+
* Effect.map((parts) => parts.join(""))
|
|
1246
|
+
* )
|
|
1247
|
+
* )
|
|
1248
|
+
* csv.split("\n") // => ["Full Name,Age (years),joined", "Alice,32,1970-01-01T00:00:00.000Z", ""]
|
|
1249
|
+
* ```
|
|
1250
|
+
*
|
|
1251
|
+
* @see decodeSchemaString
|
|
1252
|
+
* @see encodeSchema
|
|
1253
|
+
* @stability unstable
|
|
1254
|
+
* @category encoding
|
|
1255
|
+
* @since 4.1.0
|
|
1256
|
+
*/
|
|
1257
|
+
export const encodeSchemaString =
|
|
1258
|
+
<S extends Schema.Constraint>(schema: S) =>
|
|
1259
|
+
<IE = never, Done = unknown>(options?: EncodeSchemaOptions<S>): Channel.Channel<
|
|
1260
|
+
Arr.NonEmptyReadonlyArray<string>,
|
|
1261
|
+
IE | Schema.SchemaError,
|
|
1262
|
+
Done,
|
|
1263
|
+
Arr.NonEmptyReadonlyArray<S["Type"]>,
|
|
1264
|
+
IE,
|
|
1265
|
+
Done,
|
|
1266
|
+
S["EncodingServices"]
|
|
1267
|
+
> => encodeSchemaRecords(schema, options)
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Creates a channel that encodes values with a schema and writes them as UTF-8
|
|
1271
|
+
* CSV bytes.
|
|
1272
|
+
*
|
|
1273
|
+
* **Gotchas**
|
|
1274
|
+
*
|
|
1275
|
+
* When the bytes stream an HTTP response body, for example through
|
|
1276
|
+
* `HttpApiSchema.StreamUint8Array`, an encoding failure ends the body; it
|
|
1277
|
+
* cannot change a status that was already sent.
|
|
1278
|
+
*
|
|
1279
|
+
* @see encodeSchemaString
|
|
1280
|
+
* @stability unstable
|
|
1281
|
+
* @category encoding
|
|
1282
|
+
* @since 4.1.0
|
|
1283
|
+
*/
|
|
1284
|
+
export const encodeSchema =
|
|
1285
|
+
<S extends Schema.Constraint>(schema: S) =>
|
|
1286
|
+
<IE = never, Done = unknown>(options?: EncodeSchemaOptions<S>): Channel.Channel<
|
|
1287
|
+
Arr.NonEmptyReadonlyArray<Uint8Array>,
|
|
1288
|
+
IE | Schema.SchemaError,
|
|
1289
|
+
Done,
|
|
1290
|
+
Arr.NonEmptyReadonlyArray<S["Type"]>,
|
|
1291
|
+
IE,
|
|
1292
|
+
Done,
|
|
1293
|
+
S["EncodingServices"]
|
|
1294
|
+
> =>
|
|
1295
|
+
Channel.map(
|
|
1296
|
+
encodeSchemaRecords<S, IE, Done>(schema, options),
|
|
1297
|
+
(texts) => Arr.of(textEncoder.encode(texts.join("")))
|
|
1298
|
+
)
|