From 55dd3ac3a882e36b7cbf8ce6f15e85ac7297ed03 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 03:11:07 +0000 Subject: [PATCH] SDK regeneration --- .github/workflows/ci.yml | 27 ++ reference.md | 39 ++- src/api/resources/apiStatus/client/Client.ts | 9 +- src/api/resources/tts/client/Client.ts | 71 +--- .../tts/types/CancelContextRequest.ts | 2 +- .../resources/tts/types/GenerationRequest.ts | 8 +- .../resources/tts/types/Mp3OutputFormat.ts | 4 +- .../resources/tts/types/RawOutputFormat.ts | 2 +- src/api/resources/tts/types/TtsRequest.ts | 4 +- .../tts/types/TtsRequestEmbeddingSpecifier.ts | 3 +- .../tts/types/TtsRequestIdSpecifier.ts | 3 +- .../tts/types/TtsRequestVoiceSpecifier.ts | 14 +- .../tts/types/WebSocketBaseResponse.ts | 4 +- .../tts/types/WebSocketChunkResponse.ts | 2 +- .../tts/types/WebSocketRawOutputFormat.ts | 2 +- .../resources/tts/types/WebSocketResponse.ts | 6 +- .../tts/types/WebSocketStreamOptions.ts | 7 + .../tts/types/WebSocketTimestampResponse.ts | 9 - .../tts/types/WebSocketTimestampsResponse.ts | 9 + .../resources/tts/types/WebSocketTtsOutput.ts | 11 + .../tts/types/WebSocketTtsRequest.ts | 17 + src/api/resources/tts/types/index.ts | 5 +- .../resources/voiceChanger/client/Client.ts | 28 +- .../requests/VoiceChangerBytesRequest.ts | 34 +- .../client/requests/VoiceChangerSseRequest.ts | 34 +- src/api/resources/voices/client/Client.ts | 75 +++- .../voices/types/CreateVoiceRequest.ts | 4 +- .../voices/types/LocalizeVoiceRequest.ts | 4 +- src/api/resources/voices/types/Voice.ts | 8 +- .../resources/voices/types/VoiceMetadata.ts | 6 +- src/core/index.ts | 1 + src/core/schemas/Schema.ts | 99 ++++++ src/core/schemas/builders/bigint/bigint.ts | 50 +++ src/core/schemas/builders/bigint/index.ts | 1 + src/core/schemas/builders/date/date.ts | 65 ++++ src/core/schemas/builders/date/index.ts | 1 + src/core/schemas/builders/enum/enum.ts | 43 +++ src/core/schemas/builders/enum/index.ts | 1 + src/core/schemas/builders/index.ts | 14 + src/core/schemas/builders/lazy/index.ts | 3 + src/core/schemas/builders/lazy/lazy.ts | 32 ++ src/core/schemas/builders/lazy/lazyObject.ts | 20 ++ src/core/schemas/builders/list/index.ts | 1 + src/core/schemas/builders/list/list.ts | 73 ++++ .../builders/literals/booleanLiteral.ts | 29 ++ src/core/schemas/builders/literals/index.ts | 2 + .../builders/literals/stringLiteral.ts | 29 ++ .../object-like/getObjectLikeUtils.ts | 79 +++++ .../schemas/builders/object-like/index.ts | 2 + .../schemas/builders/object-like/types.ts | 11 + src/core/schemas/builders/object/index.ts | 22 ++ src/core/schemas/builders/object/object.ts | 324 ++++++++++++++++++ .../object/objectWithoutOptionalProperties.ts | 18 + src/core/schemas/builders/object/property.ts | 23 ++ src/core/schemas/builders/object/types.ts | 72 ++++ src/core/schemas/builders/primitives/any.ts | 4 + .../schemas/builders/primitives/boolean.ts | 25 ++ src/core/schemas/builders/primitives/index.ts | 5 + .../schemas/builders/primitives/number.ts | 25 ++ .../schemas/builders/primitives/string.ts | 25 ++ .../schemas/builders/primitives/unknown.ts | 4 + src/core/schemas/builders/record/index.ts | 2 + src/core/schemas/builders/record/record.ts | 130 +++++++ src/core/schemas/builders/record/types.ts | 17 + .../builders/schema-utils/JsonError.ts | 9 + .../builders/schema-utils/ParseError.ts | 9 + .../builders/schema-utils/getSchemaUtils.ts | 105 ++++++ .../schemas/builders/schema-utils/index.ts | 4 + .../schema-utils/stringifyValidationErrors.ts | 8 + src/core/schemas/builders/set/index.ts | 1 + src/core/schemas/builders/set/set.ts | 43 +++ .../builders/undiscriminated-union/index.ts | 6 + .../builders/undiscriminated-union/types.ts | 10 + .../undiscriminatedUnion.ts | 60 ++++ .../schemas/builders/union/discriminant.ts | 14 + src/core/schemas/builders/union/index.ts | 10 + src/core/schemas/builders/union/types.ts | 26 ++ src/core/schemas/builders/union/union.ts | 170 +++++++++ src/core/schemas/index.ts | 2 + src/core/schemas/utils/MaybePromise.ts | 1 + .../addQuestionMarksToNullableProperties.ts | 15 + .../utils/createIdentitySchemaCreator.ts | 21 ++ src/core/schemas/utils/entries.ts | 3 + src/core/schemas/utils/filterObject.ts | 10 + .../utils/getErrorMessageForIncorrectType.ts | 25 ++ src/core/schemas/utils/isPlainObject.ts | 17 + src/core/schemas/utils/keys.ts | 3 + src/core/schemas/utils/maybeSkipValidation.ts | 38 ++ src/core/schemas/utils/partition.ts | 12 + src/serialization/index.ts | 1 + .../resources/apiStatus/index.ts | 1 + .../resources/apiStatus/types/ApiInfo.ts | 20 ++ .../resources/apiStatus/types/index.ts | 1 + .../resources/embedding/index.ts | 1 + .../resources/embedding/types/Embedding.ts | 14 + .../resources/embedding/types/index.ts | 1 + src/serialization/resources/index.ts | 10 + src/serialization/resources/tts/index.ts | 1 + .../tts/types/CancelContextRequest.ts | 23 ++ .../resources/tts/types/ContextId.ts | 14 + .../resources/tts/types/Controls.ts | 22 ++ .../resources/tts/types/Emotion.ts | 54 +++ .../resources/tts/types/GenerationRequest.ts | 40 +++ .../resources/tts/types/Mp3OutputFormat.ts | 22 ++ .../resources/tts/types/NaturalSpecifier.ts | 14 + .../resources/tts/types/NumericalSpecifier.ts | 16 + .../resources/tts/types/RawEncoding.ts | 14 + .../resources/tts/types/RawOutputFormat.ts | 23 ++ .../resources/tts/types/Speed.ts | 16 + .../resources/tts/types/SupportedLanguage.ts | 32 ++ .../resources/tts/types/TtsRequest.ts | 31 ++ .../tts/types/TtsRequestEmbeddingSpecifier.ts | 26 ++ .../tts/types/TtsRequestIdSpecifier.ts | 26 ++ .../tts/types/TtsRequestVoiceSpecifier.ts | 18 + .../resources/tts/types/WavOutputFormat.ts | 17 + .../tts/types/WebSocketBaseResponse.ts | 25 ++ .../tts/types/WebSocketChunkResponse.ts | 25 ++ .../tts/types/WebSocketDoneResponse.ts | 17 + .../tts/types/WebSocketErrorResponse.ts | 23 ++ .../tts/types/WebSocketRawOutputFormat.ts | 25 ++ .../resources/tts/types/WebSocketRequest.ts | 16 + .../resources/tts/types/WebSocketResponse.ts | 50 +++ .../tts/types/WebSocketStreamOptions.ts | 20 ++ .../tts/types/WebSocketTimestampsResponse.ts | 24 ++ .../resources/tts/types/WebSocketTtsOutput.ts | 26 ++ .../tts/types/WebSocketTtsRequest.ts | 36 ++ .../resources/tts/types/WordTimestamps.ts | 22 ++ .../resources/tts/types/index.ts | 30 ++ .../resources/voiceChanger/index.ts | 1 + .../types/OutputFormatContainer.ts | 16 + .../voiceChanger/types/StreamingResponse.ts | 40 +++ .../resources/voiceChanger/types/index.ts | 2 + .../resources/voices/client/index.ts | 1 + .../resources/voices/client/list.ts | 15 + src/serialization/resources/voices/index.ts | 2 + .../resources/voices/types/BaseVoiceId.ts | 14 + .../resources/voices/types/CloneMode.ts | 14 + .../voices/types/CreateVoiceRequest.ts | 31 ++ .../voices/types/EmbeddingResponse.ts | 21 ++ .../voices/types/EmbeddingSpecifier.ts | 24 ++ .../resources/voices/types/Gender.ts | 16 + .../resources/voices/types/IdSpecifier.ts | 22 ++ .../resources/voices/types/LocalizeDialect.ts | 14 + .../voices/types/LocalizeTargetLanguage.ts | 32 ++ .../voices/types/LocalizeVoiceRequest.ts | 30 ++ .../voices/types/MixVoiceSpecifier.ts | 18 + .../voices/types/MixVoicesRequest.ts | 21 ++ .../voices/types/UpdateVoiceRequest.ts | 22 ++ .../resources/voices/types/Voice.ts | 37 ++ .../resources/voices/types/VoiceId.ts | 14 + .../resources/voices/types/VoiceMetadata.ts | 32 ++ .../resources/voices/types/Weight.ts | 13 + .../resources/voices/types/index.ts | 17 + tests/unit/zurg/bigint/bigint.test.ts | 24 ++ tests/unit/zurg/date/date.test.ts | 31 ++ tests/unit/zurg/enum/enum.test.ts | 30 ++ tests/unit/zurg/lazy/lazy.test.ts | 57 +++ tests/unit/zurg/lazy/lazyObject.test.ts | 18 + tests/unit/zurg/lazy/recursive/a.ts | 7 + tests/unit/zurg/lazy/recursive/b.ts | 8 + tests/unit/zurg/list/list.test.ts | 41 +++ .../unit/zurg/literals/stringLiteral.test.ts | 21 ++ .../object-like/withParsedProperties.test.ts | 57 +++ tests/unit/zurg/object/extend.test.ts | 89 +++++ tests/unit/zurg/object/object.test.ts | 255 ++++++++++++++ .../objectWithoutOptionalProperties.test.ts | 21 ++ tests/unit/zurg/primitives/any.test.ts | 6 + tests/unit/zurg/primitives/boolean.test.ts | 14 + tests/unit/zurg/primitives/number.test.ts | 14 + tests/unit/zurg/primitives/string.test.ts | 14 + tests/unit/zurg/primitives/unknown.test.ts | 6 + tests/unit/zurg/record/record.test.ts | 34 ++ .../zurg/schema-utils/getSchemaUtils.test.ts | 83 +++++ tests/unit/zurg/schema.test.ts | 78 +++++ tests/unit/zurg/set/set.test.ts | 48 +++ tests/unit/zurg/skipValidation.test.ts | 45 +++ .../undiscriminatedUnion.test.ts | 44 +++ tests/unit/zurg/union/union.test.ts | 113 ++++++ tests/unit/zurg/utils/itSchema.ts | 78 +++++ tests/unit/zurg/utils/itValidate.ts | 56 +++ 180 files changed, 4687 insertions(+), 196 deletions(-) create mode 100644 src/api/resources/tts/types/WebSocketStreamOptions.ts delete mode 100644 src/api/resources/tts/types/WebSocketTimestampResponse.ts create mode 100644 src/api/resources/tts/types/WebSocketTimestampsResponse.ts create mode 100644 src/api/resources/tts/types/WebSocketTtsOutput.ts create mode 100644 src/api/resources/tts/types/WebSocketTtsRequest.ts create mode 100644 src/core/schemas/Schema.ts create mode 100644 src/core/schemas/builders/bigint/bigint.ts create mode 100644 src/core/schemas/builders/bigint/index.ts create mode 100644 src/core/schemas/builders/date/date.ts create mode 100644 src/core/schemas/builders/date/index.ts create mode 100644 src/core/schemas/builders/enum/enum.ts create mode 100644 src/core/schemas/builders/enum/index.ts create mode 100644 src/core/schemas/builders/index.ts create mode 100644 src/core/schemas/builders/lazy/index.ts create mode 100644 src/core/schemas/builders/lazy/lazy.ts create mode 100644 src/core/schemas/builders/lazy/lazyObject.ts create mode 100644 src/core/schemas/builders/list/index.ts create mode 100644 src/core/schemas/builders/list/list.ts create mode 100644 src/core/schemas/builders/literals/booleanLiteral.ts create mode 100644 src/core/schemas/builders/literals/index.ts create mode 100644 src/core/schemas/builders/literals/stringLiteral.ts create mode 100644 src/core/schemas/builders/object-like/getObjectLikeUtils.ts create mode 100644 src/core/schemas/builders/object-like/index.ts create mode 100644 src/core/schemas/builders/object-like/types.ts create mode 100644 src/core/schemas/builders/object/index.ts create mode 100644 src/core/schemas/builders/object/object.ts create mode 100644 src/core/schemas/builders/object/objectWithoutOptionalProperties.ts create mode 100644 src/core/schemas/builders/object/property.ts create mode 100644 src/core/schemas/builders/object/types.ts create mode 100644 src/core/schemas/builders/primitives/any.ts create mode 100644 src/core/schemas/builders/primitives/boolean.ts create mode 100644 src/core/schemas/builders/primitives/index.ts create mode 100644 src/core/schemas/builders/primitives/number.ts create mode 100644 src/core/schemas/builders/primitives/string.ts create mode 100644 src/core/schemas/builders/primitives/unknown.ts create mode 100644 src/core/schemas/builders/record/index.ts create mode 100644 src/core/schemas/builders/record/record.ts create mode 100644 src/core/schemas/builders/record/types.ts create mode 100644 src/core/schemas/builders/schema-utils/JsonError.ts create mode 100644 src/core/schemas/builders/schema-utils/ParseError.ts create mode 100644 src/core/schemas/builders/schema-utils/getSchemaUtils.ts create mode 100644 src/core/schemas/builders/schema-utils/index.ts create mode 100644 src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts create mode 100644 src/core/schemas/builders/set/index.ts create mode 100644 src/core/schemas/builders/set/set.ts create mode 100644 src/core/schemas/builders/undiscriminated-union/index.ts create mode 100644 src/core/schemas/builders/undiscriminated-union/types.ts create mode 100644 src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts create mode 100644 src/core/schemas/builders/union/discriminant.ts create mode 100644 src/core/schemas/builders/union/index.ts create mode 100644 src/core/schemas/builders/union/types.ts create mode 100644 src/core/schemas/builders/union/union.ts create mode 100644 src/core/schemas/index.ts create mode 100644 src/core/schemas/utils/MaybePromise.ts create mode 100644 src/core/schemas/utils/addQuestionMarksToNullableProperties.ts create mode 100644 src/core/schemas/utils/createIdentitySchemaCreator.ts create mode 100644 src/core/schemas/utils/entries.ts create mode 100644 src/core/schemas/utils/filterObject.ts create mode 100644 src/core/schemas/utils/getErrorMessageForIncorrectType.ts create mode 100644 src/core/schemas/utils/isPlainObject.ts create mode 100644 src/core/schemas/utils/keys.ts create mode 100644 src/core/schemas/utils/maybeSkipValidation.ts create mode 100644 src/core/schemas/utils/partition.ts create mode 100644 src/serialization/index.ts create mode 100644 src/serialization/resources/apiStatus/index.ts create mode 100644 src/serialization/resources/apiStatus/types/ApiInfo.ts create mode 100644 src/serialization/resources/apiStatus/types/index.ts create mode 100644 src/serialization/resources/embedding/index.ts create mode 100644 src/serialization/resources/embedding/types/Embedding.ts create mode 100644 src/serialization/resources/embedding/types/index.ts create mode 100644 src/serialization/resources/index.ts create mode 100644 src/serialization/resources/tts/index.ts create mode 100644 src/serialization/resources/tts/types/CancelContextRequest.ts create mode 100644 src/serialization/resources/tts/types/ContextId.ts create mode 100644 src/serialization/resources/tts/types/Controls.ts create mode 100644 src/serialization/resources/tts/types/Emotion.ts create mode 100644 src/serialization/resources/tts/types/GenerationRequest.ts create mode 100644 src/serialization/resources/tts/types/Mp3OutputFormat.ts create mode 100644 src/serialization/resources/tts/types/NaturalSpecifier.ts create mode 100644 src/serialization/resources/tts/types/NumericalSpecifier.ts create mode 100644 src/serialization/resources/tts/types/RawEncoding.ts create mode 100644 src/serialization/resources/tts/types/RawOutputFormat.ts create mode 100644 src/serialization/resources/tts/types/Speed.ts create mode 100644 src/serialization/resources/tts/types/SupportedLanguage.ts create mode 100644 src/serialization/resources/tts/types/TtsRequest.ts create mode 100644 src/serialization/resources/tts/types/TtsRequestEmbeddingSpecifier.ts create mode 100644 src/serialization/resources/tts/types/TtsRequestIdSpecifier.ts create mode 100644 src/serialization/resources/tts/types/TtsRequestVoiceSpecifier.ts create mode 100644 src/serialization/resources/tts/types/WavOutputFormat.ts create mode 100644 src/serialization/resources/tts/types/WebSocketBaseResponse.ts create mode 100644 src/serialization/resources/tts/types/WebSocketChunkResponse.ts create mode 100644 src/serialization/resources/tts/types/WebSocketDoneResponse.ts create mode 100644 src/serialization/resources/tts/types/WebSocketErrorResponse.ts create mode 100644 src/serialization/resources/tts/types/WebSocketRawOutputFormat.ts create mode 100644 src/serialization/resources/tts/types/WebSocketRequest.ts create mode 100644 src/serialization/resources/tts/types/WebSocketResponse.ts create mode 100644 src/serialization/resources/tts/types/WebSocketStreamOptions.ts create mode 100644 src/serialization/resources/tts/types/WebSocketTimestampsResponse.ts create mode 100644 src/serialization/resources/tts/types/WebSocketTtsOutput.ts create mode 100644 src/serialization/resources/tts/types/WebSocketTtsRequest.ts create mode 100644 src/serialization/resources/tts/types/WordTimestamps.ts create mode 100644 src/serialization/resources/tts/types/index.ts create mode 100644 src/serialization/resources/voiceChanger/index.ts create mode 100644 src/serialization/resources/voiceChanger/types/OutputFormatContainer.ts create mode 100644 src/serialization/resources/voiceChanger/types/StreamingResponse.ts create mode 100644 src/serialization/resources/voiceChanger/types/index.ts create mode 100644 src/serialization/resources/voices/client/index.ts create mode 100644 src/serialization/resources/voices/client/list.ts create mode 100644 src/serialization/resources/voices/index.ts create mode 100644 src/serialization/resources/voices/types/BaseVoiceId.ts create mode 100644 src/serialization/resources/voices/types/CloneMode.ts create mode 100644 src/serialization/resources/voices/types/CreateVoiceRequest.ts create mode 100644 src/serialization/resources/voices/types/EmbeddingResponse.ts create mode 100644 src/serialization/resources/voices/types/EmbeddingSpecifier.ts create mode 100644 src/serialization/resources/voices/types/Gender.ts create mode 100644 src/serialization/resources/voices/types/IdSpecifier.ts create mode 100644 src/serialization/resources/voices/types/LocalizeDialect.ts create mode 100644 src/serialization/resources/voices/types/LocalizeTargetLanguage.ts create mode 100644 src/serialization/resources/voices/types/LocalizeVoiceRequest.ts create mode 100644 src/serialization/resources/voices/types/MixVoiceSpecifier.ts create mode 100644 src/serialization/resources/voices/types/MixVoicesRequest.ts create mode 100644 src/serialization/resources/voices/types/UpdateVoiceRequest.ts create mode 100644 src/serialization/resources/voices/types/Voice.ts create mode 100644 src/serialization/resources/voices/types/VoiceId.ts create mode 100644 src/serialization/resources/voices/types/VoiceMetadata.ts create mode 100644 src/serialization/resources/voices/types/Weight.ts create mode 100644 src/serialization/resources/voices/types/index.ts create mode 100644 tests/unit/zurg/bigint/bigint.test.ts create mode 100644 tests/unit/zurg/date/date.test.ts create mode 100644 tests/unit/zurg/enum/enum.test.ts create mode 100644 tests/unit/zurg/lazy/lazy.test.ts create mode 100644 tests/unit/zurg/lazy/lazyObject.test.ts create mode 100644 tests/unit/zurg/lazy/recursive/a.ts create mode 100644 tests/unit/zurg/lazy/recursive/b.ts create mode 100644 tests/unit/zurg/list/list.test.ts create mode 100644 tests/unit/zurg/literals/stringLiteral.test.ts create mode 100644 tests/unit/zurg/object-like/withParsedProperties.test.ts create mode 100644 tests/unit/zurg/object/extend.test.ts create mode 100644 tests/unit/zurg/object/object.test.ts create mode 100644 tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts create mode 100644 tests/unit/zurg/primitives/any.test.ts create mode 100644 tests/unit/zurg/primitives/boolean.test.ts create mode 100644 tests/unit/zurg/primitives/number.test.ts create mode 100644 tests/unit/zurg/primitives/string.test.ts create mode 100644 tests/unit/zurg/primitives/unknown.test.ts create mode 100644 tests/unit/zurg/record/record.test.ts create mode 100644 tests/unit/zurg/schema-utils/getSchemaUtils.test.ts create mode 100644 tests/unit/zurg/schema.test.ts create mode 100644 tests/unit/zurg/set/set.test.ts create mode 100644 tests/unit/zurg/skipValidation.test.ts create mode 100644 tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts create mode 100644 tests/unit/zurg/union/union.test.ts create mode 100644 tests/unit/zurg/utils/itSchema.ts create mode 100644 tests/unit/zurg/utils/itValidate.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf13c1c..1a55fdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,3 +28,30 @@ jobs: - name: Compile run: yarn && yarn test + + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up node + uses: actions/setup-node@v3 + - name: Install dependencies + run: yarn install + - name: Build + run: yarn build + + - name: Publish to npm + run: | + npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} + if [[ ${GITHUB_REF} == *alpha* ]]; then + npm publish --access public --tag alpha + elif [[ ${GITHUB_REF} == *beta* ]]; then + npm publish --access public --tag beta + else + npm publish --access public + fi + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file diff --git a/reference.md b/reference.md index 0d372d0..62278e0 100644 --- a/reference.md +++ b/reference.md @@ -44,7 +44,7 @@ await client.apiStatus.get(); ## Tts -
client.tts.bytes({ ...params }) -> void +
client.tts.bytes({ ...params }) -> stream.Readable
@@ -58,17 +58,17 @@ await client.apiStatus.get(); ```typescript await client.tts.bytes({ - model_id: "sonic-english", + modelId: "sonic-english", transcript: "Hello, world!", voice: { mode: "id", id: "694f9389-aac1-45b6-b726-9d9369183238", }, language: "en", - output_format: { + outputFormat: { container: "mp3", - sample_rate: 44100, - bit_rate: 128000, + sampleRate: 44100, + bitRate: 128000, }, }); ``` @@ -119,13 +119,18 @@ await client.tts.bytes({ ```typescript const response = await client.tts.sse({ - model_id: "string", + modelId: "string", transcript: "string", voice: { mode: "id", + id: "string", + experimentalControls: { + speed: 1.1, + emotion: "anger:lowest", + }, }, language: "en", - output_format: { + outputFormat: { container: "raw", }, duration: 1.1, @@ -200,10 +205,10 @@ This endpoint is priced at 15 characters per second of input audio. ```typescript await client.voiceChanger.bytes(fs.createReadStream("/path/to/your/file"), { - "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - "output_format[container]": "mp3", - "output_format[sample_rate]": 44100, - "output_format[bit_rate]": 128000, + voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + outputFormatContainer: "mp3", + outputFormatSampleRate: 44100, + outputFormatBitRate: 128000, }); ``` @@ -261,10 +266,10 @@ await client.voiceChanger.bytes(fs.createReadStream("/path/to/your/file"), { ```typescript const response = await client.voiceChanger.sse(fs.createReadStream("/path/to/your/file"), { - "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - "output_format[container]": "mp3", - "output_format[sample_rate]": 44100, - "output_format[bit_rate]": 128000, + voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + outputFormatContainer: "mp3", + outputFormatSampleRate: 44100, + outputFormatBitRate: 128000, }); for await (const item of response) { console.log(item); @@ -378,7 +383,7 @@ await client.voices.create({ 1, 1, 1, 1, 1, 1, 1, ], language: "en", - base_voice_id: "string", + baseVoiceId: "string", }); ``` @@ -592,7 +597,7 @@ await client.voices.localize({ 1, 1, 1, 1, 1, 1, 1, ], language: "en", - original_speaker_gender: "male", + originalSpeakerGender: "male", dialect: "au", }); ``` diff --git a/src/api/resources/apiStatus/client/Client.ts b/src/api/resources/apiStatus/client/Client.ts index 1e15a7d..500d7c2 100644 --- a/src/api/resources/apiStatus/client/Client.ts +++ b/src/api/resources/apiStatus/client/Client.ts @@ -5,6 +5,7 @@ import * as environments from "../../../../environments"; import * as core from "../../../../core"; import * as Cartesia from "../../../index"; +import * as serializers from "../../../../serialization/index"; import * as errors from "../../../../errors/index"; export declare namespace ApiStatus { @@ -58,7 +59,13 @@ export class ApiStatus { abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.ApiInfo; + return serializers.ApiInfo.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { diff --git a/src/api/resources/tts/client/Client.ts b/src/api/resources/tts/client/Client.ts index f2a80bd..9d18cbf 100644 --- a/src/api/resources/tts/client/Client.ts +++ b/src/api/resources/tts/client/Client.ts @@ -5,10 +5,10 @@ import * as environments from "../../../../environments"; import * as core from "../../../../core"; import * as Cartesia from "../../../index"; -import urlJoin from "url-join"; -import * as errors from "../../../../errors/index"; import * as stream from "stream"; import * as serializers from "../../../../serialization/index"; +import urlJoin from "url-join"; +import * as errors from "../../../../errors/index"; export declare namespace Tts { interface Options { @@ -34,60 +34,8 @@ export declare namespace Tts { export class Tts { constructor(protected readonly _options: Tts.Options = {}) {} - /** - * @param {Cartesia.TtsRequest} request - * @param {Tts.RequestOptions} requestOptions - Request-specific configuration. - * - * @example - * await client.tts.bytes({ - * model_id: "sonic-english", - * transcript: "Hello, world!", - * voice: { - * mode: "id", - * id: "694f9389-aac1-45b6-b726-9d9369183238" - * }, - * language: "en", - * output_format: { - * container: "mp3", - * sample_rate: 44100, - * bit_rate: 128000 - * } - * }) - * - * @example - * await client.tts.bytes({ - * model_id: "sonic-english", - * transcript: "Hello, world!", - * voice: { - * mode: "id", - * id: "694f9389-aac1-45b6-b726-9d9369183238" - * }, - * language: "en", - * output_format: { - * container: "wav", - * sample_rate: 44100, - * encoding: "pcm_f32le" - * } - * }) - * - * @example - * await client.tts.bytes({ - * model_id: "sonic-english", - * transcript: "Hello, world!", - * voice: { - * mode: "id", - * id: "694f9389-aac1-45b6-b726-9d9369183238" - * }, - * language: "en", - * output_format: { - * container: "raw", - * sample_rate: 44100, - * encoding: "pcm_f32le" - * } - * }) - */ - public async bytes(request: Cartesia.TtsRequest, requestOptions?: Tts.RequestOptions): Promise { - const _response = await (this._options.fetcher ?? core.fetcher)({ + public async bytes(request: Cartesia.TtsRequest, requestOptions?: Tts.RequestOptions): Promise { + const _response = await (this._options.fetcher ?? core.fetcher)({ url: urlJoin( (await core.Supplier.get(this._options.environment)) ?? environments.CartesiaEnvironment.Production, "/tts/bytes" @@ -105,13 +53,14 @@ export class Tts { }, contentType: "application/json", requestType: "json", - body: request, + body: serializers.TtsRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), + responseType: "streaming", timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, maxRetries: requestOptions?.maxRetries, abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return; + return _response.body; } if (_response.error.reason === "status-code") { @@ -158,7 +107,7 @@ export class Tts { }, contentType: "application/json", requestType: "json", - body: request, + body: serializers.TtsRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), responseType: "sse", timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, maxRetries: requestOptions?.maxRetries, @@ -178,8 +127,8 @@ export class Tts { }, signal: requestOptions?.abortSignal, eventShape: { - type: "json", - messageTerminator: "\n", + type: "sse", + streamTerminator: "[DONE]", }, }); } diff --git a/src/api/resources/tts/types/CancelContextRequest.ts b/src/api/resources/tts/types/CancelContextRequest.ts index 31c7bd4..f5622ad 100644 --- a/src/api/resources/tts/types/CancelContextRequest.ts +++ b/src/api/resources/tts/types/CancelContextRequest.ts @@ -6,7 +6,7 @@ import * as Cartesia from "../../../index"; export interface CancelContextRequest { /** The ID of the context to cancel. */ - context_id: Cartesia.ContextId; + contextId: Cartesia.ContextId; /** Whether to cancel the context, so that no more messages are generated for that context. */ cancel: true; } diff --git a/src/api/resources/tts/types/GenerationRequest.ts b/src/api/resources/tts/types/GenerationRequest.ts index 8ac66ce..0dce467 100644 --- a/src/api/resources/tts/types/GenerationRequest.ts +++ b/src/api/resources/tts/types/GenerationRequest.ts @@ -6,22 +6,22 @@ import * as Cartesia from "../../../index"; export interface GenerationRequest { /** The ID of the model to use for the generation. See [Models](/build-with-sonic/models) for available models. */ - model_id: string; + modelId: string; transcript: string; voice: Cartesia.TtsRequestVoiceSpecifier; language?: Cartesia.SupportedLanguage; - output_format: Cartesia.WebSocketRawOutputFormat; + outputFormat: Cartesia.WebSocketRawOutputFormat; /** * The maximum duration of the audio in seconds. You do not usually need to specify this. * If the duration is not appropriate for the length of the transcript, the output audio may be truncated. */ duration?: number; - context_id: Cartesia.ContextId; + contextId: Cartesia.ContextId; /** * Whether this input may be followed by more inputs. * If not specified, this defaults to `false`. */ continue?: boolean; /** Whether to return word-level timestamps. */ - add_timestamps?: boolean; + addTimestamps?: boolean; } diff --git a/src/api/resources/tts/types/Mp3OutputFormat.ts b/src/api/resources/tts/types/Mp3OutputFormat.ts index 7031390..4ca41a6 100644 --- a/src/api/resources/tts/types/Mp3OutputFormat.ts +++ b/src/api/resources/tts/types/Mp3OutputFormat.ts @@ -3,6 +3,6 @@ */ export interface Mp3OutputFormat { - sample_rate: number; - bit_rate: number; + sampleRate: number; + bitRate: number; } diff --git a/src/api/resources/tts/types/RawOutputFormat.ts b/src/api/resources/tts/types/RawOutputFormat.ts index 83118fa..0f32735 100644 --- a/src/api/resources/tts/types/RawOutputFormat.ts +++ b/src/api/resources/tts/types/RawOutputFormat.ts @@ -6,5 +6,5 @@ import * as Cartesia from "../../../index"; export interface RawOutputFormat { encoding: Cartesia.RawEncoding; - sample_rate: number; + sampleRate: number; } diff --git a/src/api/resources/tts/types/TtsRequest.ts b/src/api/resources/tts/types/TtsRequest.ts index 7778620..154f6d1 100644 --- a/src/api/resources/tts/types/TtsRequest.ts +++ b/src/api/resources/tts/types/TtsRequest.ts @@ -6,11 +6,11 @@ import * as Cartesia from "../../../index"; export interface TtsRequest { /** The ID of the model to use for the generation. See [Models](/build-with-sonic/models) for available models. */ - model_id: string; + modelId: string; transcript: string; voice: Cartesia.TtsRequestVoiceSpecifier; language?: Cartesia.SupportedLanguage; - output_format: Cartesia.OutputFormat; + outputFormat: Cartesia.OutputFormat; /** * The maximum duration of the audio in seconds. You do not usually need to specify this. * If the duration is not appropriate for the length of the transcript, the output audio may be truncated. diff --git a/src/api/resources/tts/types/TtsRequestEmbeddingSpecifier.ts b/src/api/resources/tts/types/TtsRequestEmbeddingSpecifier.ts index 0653cb8..b85fc8e 100644 --- a/src/api/resources/tts/types/TtsRequestEmbeddingSpecifier.ts +++ b/src/api/resources/tts/types/TtsRequestEmbeddingSpecifier.ts @@ -5,6 +5,7 @@ import * as Cartesia from "../../../index"; export interface TtsRequestEmbeddingSpecifier { + mode: "embedding"; embedding: Cartesia.Embedding; - __experimental_controls?: Cartesia.Controls; + experimentalControls?: Cartesia.Controls; } diff --git a/src/api/resources/tts/types/TtsRequestIdSpecifier.ts b/src/api/resources/tts/types/TtsRequestIdSpecifier.ts index 447fcc9..ffdce30 100644 --- a/src/api/resources/tts/types/TtsRequestIdSpecifier.ts +++ b/src/api/resources/tts/types/TtsRequestIdSpecifier.ts @@ -5,6 +5,7 @@ import * as Cartesia from "../../../index"; export interface TtsRequestIdSpecifier { + mode: "id"; id: Cartesia.VoiceId; - __experimental_controls?: Cartesia.Controls; + experimentalControls?: Cartesia.Controls; } diff --git a/src/api/resources/tts/types/TtsRequestVoiceSpecifier.ts b/src/api/resources/tts/types/TtsRequestVoiceSpecifier.ts index fbb0619..7b9ccef 100644 --- a/src/api/resources/tts/types/TtsRequestVoiceSpecifier.ts +++ b/src/api/resources/tts/types/TtsRequestVoiceSpecifier.ts @@ -4,16 +4,4 @@ import * as Cartesia from "../../../index"; -export type TtsRequestVoiceSpecifier = - | Cartesia.TtsRequestVoiceSpecifier.Id - | Cartesia.TtsRequestVoiceSpecifier.Embedding; - -export declare namespace TtsRequestVoiceSpecifier { - interface Id extends Cartesia.TtsRequestIdSpecifier { - mode: "id"; - } - - interface Embedding extends Cartesia.TtsRequestEmbeddingSpecifier { - mode: "embedding"; - } -} +export type TtsRequestVoiceSpecifier = Cartesia.TtsRequestIdSpecifier | Cartesia.TtsRequestEmbeddingSpecifier; diff --git a/src/api/resources/tts/types/WebSocketBaseResponse.ts b/src/api/resources/tts/types/WebSocketBaseResponse.ts index d3154e2..62279a6 100644 --- a/src/api/resources/tts/types/WebSocketBaseResponse.ts +++ b/src/api/resources/tts/types/WebSocketBaseResponse.ts @@ -5,7 +5,7 @@ import * as Cartesia from "../../../index"; export interface WebSocketBaseResponse { - context_id: Cartesia.ContextId; - status_code: number; + contextId?: Cartesia.ContextId; + statusCode: number; done: boolean; } diff --git a/src/api/resources/tts/types/WebSocketChunkResponse.ts b/src/api/resources/tts/types/WebSocketChunkResponse.ts index 0be3498..1e1c82e 100644 --- a/src/api/resources/tts/types/WebSocketChunkResponse.ts +++ b/src/api/resources/tts/types/WebSocketChunkResponse.ts @@ -6,5 +6,5 @@ import * as Cartesia from "../../../index"; export interface WebSocketChunkResponse extends Cartesia.WebSocketBaseResponse { data: string; - step_time: number; + stepTime: number; } diff --git a/src/api/resources/tts/types/WebSocketRawOutputFormat.ts b/src/api/resources/tts/types/WebSocketRawOutputFormat.ts index 79bd10d..17a25a7 100644 --- a/src/api/resources/tts/types/WebSocketRawOutputFormat.ts +++ b/src/api/resources/tts/types/WebSocketRawOutputFormat.ts @@ -7,5 +7,5 @@ import * as Cartesia from "../../../index"; export interface WebSocketRawOutputFormat { container: "raw"; encoding: Cartesia.RawEncoding; - sample_rate: number; + sampleRate: number; } diff --git a/src/api/resources/tts/types/WebSocketResponse.ts b/src/api/resources/tts/types/WebSocketResponse.ts index a43b3de..861851b 100644 --- a/src/api/resources/tts/types/WebSocketResponse.ts +++ b/src/api/resources/tts/types/WebSocketResponse.ts @@ -7,7 +7,7 @@ import * as Cartesia from "../../../index"; export type WebSocketResponse = | Cartesia.WebSocketResponse.Chunk | Cartesia.WebSocketResponse.Done - | Cartesia.WebSocketResponse.Timestamp + | Cartesia.WebSocketResponse.Timestamps | Cartesia.WebSocketResponse.Error_; export declare namespace WebSocketResponse { @@ -19,8 +19,8 @@ export declare namespace WebSocketResponse { type: "done"; } - interface Timestamp extends Cartesia.WebSocketTimestampResponse { - type: "timestamp"; + interface Timestamps extends Cartesia.WebSocketTimestampsResponse { + type: "timestamps"; } interface Error_ extends Cartesia.WebSocketErrorResponse { diff --git a/src/api/resources/tts/types/WebSocketStreamOptions.ts b/src/api/resources/tts/types/WebSocketStreamOptions.ts new file mode 100644 index 0000000..ff4ba05 --- /dev/null +++ b/src/api/resources/tts/types/WebSocketStreamOptions.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface WebSocketStreamOptions { + timeout?: number; +} diff --git a/src/api/resources/tts/types/WebSocketTimestampResponse.ts b/src/api/resources/tts/types/WebSocketTimestampResponse.ts deleted file mode 100644 index 991b426..0000000 --- a/src/api/resources/tts/types/WebSocketTimestampResponse.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This file was auto-generated by Fern from our API Definition. - */ - -import * as Cartesia from "../../../index"; - -export interface WebSocketTimestampResponse extends Cartesia.WebSocketBaseResponse { - word_timestamps: Cartesia.WordTimestamps; -} diff --git a/src/api/resources/tts/types/WebSocketTimestampsResponse.ts b/src/api/resources/tts/types/WebSocketTimestampsResponse.ts new file mode 100644 index 0000000..68c368d --- /dev/null +++ b/src/api/resources/tts/types/WebSocketTimestampsResponse.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as Cartesia from "../../../index"; + +export interface WebSocketTimestampsResponse extends Cartesia.WebSocketBaseResponse { + wordTimestamps?: Cartesia.WordTimestamps; +} diff --git a/src/api/resources/tts/types/WebSocketTtsOutput.ts b/src/api/resources/tts/types/WebSocketTtsOutput.ts new file mode 100644 index 0000000..73e2863 --- /dev/null +++ b/src/api/resources/tts/types/WebSocketTtsOutput.ts @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as Cartesia from "../../../index"; + +export interface WebSocketTtsOutput { + wordTimestamps?: Cartesia.WordTimestamps; + audio?: unknown; + contextId?: Cartesia.ContextId; +} diff --git a/src/api/resources/tts/types/WebSocketTtsRequest.ts b/src/api/resources/tts/types/WebSocketTtsRequest.ts new file mode 100644 index 0000000..c0d3d8f --- /dev/null +++ b/src/api/resources/tts/types/WebSocketTtsRequest.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as Cartesia from "../../../index"; + +export interface WebSocketTtsRequest { + /** The ID of the model to use for the generation. See [Models](/build-with-sonic/models) for available models. */ + modelId: string; + outputFormat?: Cartesia.OutputFormat; + transcript?: string; + voice: Cartesia.TtsRequestVoiceSpecifier; + duration?: number; + language?: string; + addTimestamps?: boolean; + contextId?: string; +} diff --git a/src/api/resources/tts/types/index.ts b/src/api/resources/tts/types/index.ts index f1b034c..5212040 100644 --- a/src/api/resources/tts/types/index.ts +++ b/src/api/resources/tts/types/index.ts @@ -3,13 +3,16 @@ export * from "./WebSocketBaseResponse"; export * from "./WebSocketResponse"; export * from "./WebSocketErrorResponse"; export * from "./WebSocketChunkResponse"; -export * from "./WebSocketTimestampResponse"; +export * from "./WebSocketTimestampsResponse"; +export * from "./WebSocketTtsOutput"; +export * from "./WebSocketStreamOptions"; export * from "./WordTimestamps"; export * from "./WebSocketDoneResponse"; export * from "./CancelContextRequest"; export * from "./GenerationRequest"; export * from "./WebSocketRawOutputFormat"; export * from "./WebSocketRequest"; +export * from "./WebSocketTtsRequest"; export * from "./TtsRequest"; export * from "./SupportedLanguage"; export * from "./OutputFormat"; diff --git a/src/api/resources/voiceChanger/client/Client.ts b/src/api/resources/voiceChanger/client/Client.ts index 78dcf26..3cc8b32 100644 --- a/src/api/resources/voiceChanger/client/Client.ts +++ b/src/api/resources/voiceChanger/client/Client.ts @@ -48,15 +48,15 @@ export class VoiceChanger { ): Promise { const _request = await core.newFormData(); await _request.appendFile("clip", clip); - await _request.append("voice[id]", request.voice[id]); - await _request.append("output_format[container]", request.output_format[container]); - await _request.append("output_format[sample_rate]", request.output_format[sample_rate].toString()); - if (request.output_format[encoding] != null) { - await _request.append("output_format[encoding]", request.output_format[encoding]); + await _request.append("voice[id]", request.voiceId); + await _request.append("output_format[container]", request.outputFormatContainer); + await _request.append("output_format[sample_rate]", request.outputFormatSampleRate.toString()); + if (request.outputFormatEncoding != null) { + await _request.append("output_format[encoding]", request.outputFormatEncoding); } - if (request.output_format[bit_rate] != null) { - await _request.append("output_format[bit_rate]", request.output_format[bit_rate].toString()); + if (request.outputFormatBitRate != null) { + await _request.append("output_format[bit_rate]", request.outputFormatBitRate.toString()); } const _maybeEncodedRequest = await _request.getRequest(); @@ -118,15 +118,15 @@ export class VoiceChanger { ): Promise> { const _request = await core.newFormData(); await _request.appendFile("clip", clip); - await _request.append("voice[id]", request.voice[id]); - await _request.append("output_format[container]", request.output_format[container]); - await _request.append("output_format[sample_rate]", request.output_format[sample_rate].toString()); - if (request.output_format[encoding] != null) { - await _request.append("output_format[encoding]", request.output_format[encoding]); + await _request.append("voice[id]", request.voiceId); + await _request.append("output_format[container]", request.outputFormatContainer); + await _request.append("output_format[sample_rate]", request.outputFormatSampleRate.toString()); + if (request.outputFormatEncoding != null) { + await _request.append("output_format[encoding]", request.outputFormatEncoding); } - if (request.output_format[bit_rate] != null) { - await _request.append("output_format[bit_rate]", request.output_format[bit_rate].toString()); + if (request.outputFormatBitRate != null) { + await _request.append("output_format[bit_rate]", request.outputFormatBitRate.toString()); } const _maybeEncodedRequest = await _request.getRequest(); diff --git a/src/api/resources/voiceChanger/client/requests/VoiceChangerBytesRequest.ts b/src/api/resources/voiceChanger/client/requests/VoiceChangerBytesRequest.ts index ecda6ea..d86f3bd 100644 --- a/src/api/resources/voiceChanger/client/requests/VoiceChangerBytesRequest.ts +++ b/src/api/resources/voiceChanger/client/requests/VoiceChangerBytesRequest.ts @@ -7,40 +7,40 @@ import * as Cartesia from "../../../../index"; /** * @example * { - * "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - * "output_format[container]": "mp3", - * "output_format[sample_rate]": 44100, - * "output_format[bit_rate]": 128000 + * voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + * outputFormatContainer: "mp3", + * outputFormatSampleRate: 44100, + * outputFormatBitRate: 128000 * } * * @example * { - * "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - * "output_format[container]": "wav", - * "output_format[sample_rate]": 44100, - * "output_format[encoding]": "pcm_f32le" + * voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + * outputFormatContainer: "wav", + * outputFormatSampleRate: 44100, + * outputFormatEncoding: "pcm_f32le" * } * * @example * { - * "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - * "output_format[container]": "raw", - * "output_format[sample_rate]": 44100, - * "output_format[encoding]": "pcm_f32le" + * voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + * outputFormatContainer: "raw", + * outputFormatSampleRate: 44100, + * outputFormatEncoding: "pcm_f32le" * } */ export interface VoiceChangerBytesRequest { - "voice[id]": string; - "output_format[container]": Cartesia.OutputFormatContainer; - "output_format[sample_rate]": number; + voiceId: string; + outputFormatContainer: Cartesia.OutputFormatContainer; + outputFormatSampleRate: number; /** * Required for `raw` and `wav` containers. * */ - "output_format[encoding]"?: Cartesia.RawEncoding; + outputFormatEncoding?: Cartesia.RawEncoding; /** * Required for `mp3` containers. * */ - "output_format[bit_rate]"?: number; + outputFormatBitRate?: number; } diff --git a/src/api/resources/voiceChanger/client/requests/VoiceChangerSseRequest.ts b/src/api/resources/voiceChanger/client/requests/VoiceChangerSseRequest.ts index 6fa915c..b912610 100644 --- a/src/api/resources/voiceChanger/client/requests/VoiceChangerSseRequest.ts +++ b/src/api/resources/voiceChanger/client/requests/VoiceChangerSseRequest.ts @@ -7,40 +7,40 @@ import * as Cartesia from "../../../../index"; /** * @example * { - * "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - * "output_format[container]": "mp3", - * "output_format[sample_rate]": 44100, - * "output_format[bit_rate]": 128000 + * voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + * outputFormatContainer: "mp3", + * outputFormatSampleRate: 44100, + * outputFormatBitRate: 128000 * } * * @example * { - * "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - * "output_format[container]": "wav", - * "output_format[sample_rate]": 44100, - * "output_format[encoding]": "pcm_f32le" + * voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + * outputFormatContainer: "wav", + * outputFormatSampleRate: 44100, + * outputFormatEncoding: "pcm_f32le" * } * * @example * { - * "voice[id]": "694f9389-aac1-45b6-b726-9d9369183238", - * "output_format[container]": "raw", - * "output_format[sample_rate]": 44100, - * "output_format[encoding]": "pcm_f32le" + * voiceId: "694f9389-aac1-45b6-b726-9d9369183238", + * outputFormatContainer: "raw", + * outputFormatSampleRate: 44100, + * outputFormatEncoding: "pcm_f32le" * } */ export interface VoiceChangerSseRequest { - "voice[id]": string; - "output_format[container]": Cartesia.OutputFormatContainer; - "output_format[sample_rate]": number; + voiceId: string; + outputFormatContainer: Cartesia.OutputFormatContainer; + outputFormatSampleRate: number; /** * Required for `raw` and `wav` containers. * */ - "output_format[encoding]"?: Cartesia.RawEncoding; + outputFormatEncoding?: Cartesia.RawEncoding; /** * Required for `mp3` containers. * */ - "output_format[bit_rate]"?: number; + outputFormatBitRate?: number; } diff --git a/src/api/resources/voices/client/Client.ts b/src/api/resources/voices/client/Client.ts index b52a579..43e709b 100644 --- a/src/api/resources/voices/client/Client.ts +++ b/src/api/resources/voices/client/Client.ts @@ -6,6 +6,7 @@ import * as environments from "../../../../environments"; import * as core from "../../../../core"; import * as Cartesia from "../../../index"; import urlJoin from "url-join"; +import * as serializers from "../../../../serialization/index"; import * as errors from "../../../../errors/index"; import * as fs from "fs"; import { Blob } from "buffer"; @@ -64,7 +65,13 @@ export class Voices { abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.Voice[]; + return serializers.voices.list.Response.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { @@ -99,7 +106,7 @@ export class Voices { * description: "string", * embedding: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], * language: "en", - * base_voice_id: "string" + * baseVoiceId: "string" * }) */ public async create( @@ -124,13 +131,19 @@ export class Voices { }, contentType: "application/json", requestType: "json", - body: request, + body: serializers.CreateVoiceRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, maxRetries: requestOptions?.maxRetries, abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.Voice; + return serializers.Voice.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { @@ -166,7 +179,7 @@ export class Voices { const _response = await (this._options.fetcher ?? core.fetcher)({ url: urlJoin( (await core.Supplier.get(this._options.environment)) ?? environments.CartesiaEnvironment.Production, - `/voices/${encodeURIComponent(id)}` + `/voices/${encodeURIComponent(serializers.VoiceId.jsonOrThrow(id))}` ), method: "DELETE", headers: { @@ -230,7 +243,7 @@ export class Voices { const _response = await (this._options.fetcher ?? core.fetcher)({ url: urlJoin( (await core.Supplier.get(this._options.environment)) ?? environments.CartesiaEnvironment.Production, - `/voices/${encodeURIComponent(id)}` + `/voices/${encodeURIComponent(serializers.VoiceId.jsonOrThrow(id))}` ), method: "PATCH", headers: { @@ -245,13 +258,19 @@ export class Voices { }, contentType: "application/json", requestType: "json", - body: request, + body: serializers.UpdateVoiceRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, maxRetries: requestOptions?.maxRetries, abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.Voice; + return serializers.Voice.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { @@ -287,7 +306,7 @@ export class Voices { const _response = await (this._options.fetcher ?? core.fetcher)({ url: urlJoin( (await core.Supplier.get(this._options.environment)) ?? environments.CartesiaEnvironment.Production, - `/voices/${encodeURIComponent(id)}` + `/voices/${encodeURIComponent(serializers.VoiceId.jsonOrThrow(id))}` ), method: "GET", headers: { @@ -307,7 +326,13 @@ export class Voices { abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.Voice; + return serializers.Voice.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { @@ -340,7 +365,7 @@ export class Voices { * await client.voices.localize({ * embedding: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], * language: "en", - * original_speaker_gender: "male", + * originalSpeakerGender: "male", * dialect: "au" * }) */ @@ -366,13 +391,19 @@ export class Voices { }, contentType: "application/json", requestType: "json", - body: request, + body: serializers.LocalizeVoiceRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, maxRetries: requestOptions?.maxRetries, abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.EmbeddingResponse; + return serializers.EmbeddingResponse.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { @@ -431,13 +462,19 @@ export class Voices { }, contentType: "application/json", requestType: "json", - body: request, + body: serializers.MixVoicesRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, maxRetries: requestOptions?.maxRetries, abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.EmbeddingResponse; + return serializers.EmbeddingResponse.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { @@ -537,7 +574,13 @@ export class Voices { abortSignal: requestOptions?.abortSignal, }); if (_response.ok) { - return _response.body as Cartesia.VoiceMetadata; + return serializers.VoiceMetadata.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); } if (_response.error.reason === "status-code") { diff --git a/src/api/resources/voices/types/CreateVoiceRequest.ts b/src/api/resources/voices/types/CreateVoiceRequest.ts index e51f9b6..71824f9 100644 --- a/src/api/resources/voices/types/CreateVoiceRequest.ts +++ b/src/api/resources/voices/types/CreateVoiceRequest.ts @@ -10,6 +10,6 @@ export interface CreateVoiceRequest { /** The description of the voice. */ description: string; embedding: Cartesia.Embedding; - language: Cartesia.SupportedLanguage; - base_voice_id?: Cartesia.BaseVoiceId; + language?: Cartesia.SupportedLanguage; + baseVoiceId?: Cartesia.BaseVoiceId; } diff --git a/src/api/resources/voices/types/LocalizeVoiceRequest.ts b/src/api/resources/voices/types/LocalizeVoiceRequest.ts index 529aed3..768e437 100644 --- a/src/api/resources/voices/types/LocalizeVoiceRequest.ts +++ b/src/api/resources/voices/types/LocalizeVoiceRequest.ts @@ -7,6 +7,6 @@ import * as Cartesia from "../../../index"; export interface LocalizeVoiceRequest { embedding: Cartesia.Embedding; language: Cartesia.LocalizeTargetLanguage; - original_speaker_gender: Cartesia.Gender; - dialect: Cartesia.LocalizeDialect; + originalSpeakerGender: Cartesia.Gender; + dialect?: Cartesia.LocalizeDialect; } diff --git a/src/api/resources/voices/types/Voice.ts b/src/api/resources/voices/types/Voice.ts index 6c40f16..523cde1 100644 --- a/src/api/resources/voices/types/Voice.ts +++ b/src/api/resources/voices/types/Voice.ts @@ -7,16 +7,16 @@ import * as Cartesia from "../../../index"; export interface Voice { id: Cartesia.VoiceId; /** The ID of the user who owns the voice. */ - user_id: string; + userId?: string; /** Whether the voice is publicly accessible. */ - is_public: boolean; + isPublic: boolean; /** The name of the voice. */ name: string; /** The description of the voice. */ description: string; /** The date and time the voice was created. */ - created_at: string; + createdAt: Date; embedding: Cartesia.Embedding; language: Cartesia.SupportedLanguage; - base_voice_id?: Cartesia.BaseVoiceId; + baseVoiceId?: Cartesia.BaseVoiceId; } diff --git a/src/api/resources/voices/types/VoiceMetadata.ts b/src/api/resources/voices/types/VoiceMetadata.ts index 69e5fdf..d28cf40 100644 --- a/src/api/resources/voices/types/VoiceMetadata.ts +++ b/src/api/resources/voices/types/VoiceMetadata.ts @@ -7,14 +7,14 @@ import * as Cartesia from "../../../index"; export interface VoiceMetadata { id: Cartesia.VoiceId; /** The ID of the user who owns the voice. */ - user_id: string; + userId: string; /** Whether the voice is publicly accessible. */ - is_public: boolean; + isPublic: boolean; /** The name of the voice. */ name: string; /** The description of the voice. */ description: string; /** The date and time the voice was created. */ - created_at: string; + createdAt: Date; language: Cartesia.SupportedLanguage; } diff --git a/src/core/index.ts b/src/core/index.ts index 722f2e3..b6b4fe1 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -2,3 +2,4 @@ export * from "./fetcher"; export * from "./runtime"; export * from "./streaming-fetcher"; export * from "./form-data-utils"; +export * as serialization from "./schemas"; diff --git a/src/core/schemas/Schema.ts b/src/core/schemas/Schema.ts new file mode 100644 index 0000000..2a72eac --- /dev/null +++ b/src/core/schemas/Schema.ts @@ -0,0 +1,99 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + BIGINT: "bigint", + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/src/core/schemas/builders/bigint/bigint.ts b/src/core/schemas/builders/bigint/bigint.ts new file mode 100644 index 0000000..dc9c742 --- /dev/null +++ b/src/core/schemas/builders/bigint/bigint.ts @@ -0,0 +1,50 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function bigint(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + return { + ok: true, + value: BigInt(raw), + }; + }, + json: (bigint, { breadcrumbsPrefix = [] } = {}) => { + if (typeof bigint === "bigint") { + return { + ok: true, + value: bigint.toString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(bigint, "bigint"), + }, + ], + }; + } + }, + getType: () => SchemaType.BIGINT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/src/core/schemas/builders/bigint/index.ts b/src/core/schemas/builders/bigint/index.ts new file mode 100644 index 0000000..e584304 --- /dev/null +++ b/src/core/schemas/builders/bigint/index.ts @@ -0,0 +1 @@ +export { bigint } from "./bigint"; diff --git a/src/core/schemas/builders/date/date.ts b/src/core/schemas/builders/date/date.ts new file mode 100644 index 0000000..b70f24b --- /dev/null +++ b/src/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/src/core/schemas/builders/date/index.ts b/src/core/schemas/builders/date/index.ts new file mode 100644 index 0000000..187b290 --- /dev/null +++ b/src/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/src/core/schemas/builders/enum/enum.ts b/src/core/schemas/builders/enum/enum.ts new file mode 100644 index 0000000..c1e24d6 --- /dev/null +++ b/src/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/src/core/schemas/builders/enum/index.ts b/src/core/schemas/builders/enum/index.ts new file mode 100644 index 0000000..fe6faed --- /dev/null +++ b/src/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/src/core/schemas/builders/index.ts b/src/core/schemas/builders/index.ts new file mode 100644 index 0000000..65211f9 --- /dev/null +++ b/src/core/schemas/builders/index.ts @@ -0,0 +1,14 @@ +export * from "./bigint"; +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/src/core/schemas/builders/lazy/index.ts b/src/core/schemas/builders/lazy/index.ts new file mode 100644 index 0000000..77420fb --- /dev/null +++ b/src/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/src/core/schemas/builders/lazy/lazy.ts b/src/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 0000000..835c61f --- /dev/null +++ b/src/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/src/core/schemas/builders/lazy/lazyObject.ts b/src/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 0000000..38c9e28 --- /dev/null +++ b/src/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/src/core/schemas/builders/list/index.ts b/src/core/schemas/builders/list/index.ts new file mode 100644 index 0000000..25f4bcc --- /dev/null +++ b/src/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/src/core/schemas/builders/list/list.ts b/src/core/schemas/builders/list/list.ts new file mode 100644 index 0000000..e4c5c4a --- /dev/null +++ b/src/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/src/core/schemas/builders/literals/booleanLiteral.ts b/src/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 0000000..a83d22c --- /dev/null +++ b/src/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/src/core/schemas/builders/literals/index.ts b/src/core/schemas/builders/literals/index.ts new file mode 100644 index 0000000..d2bf08f --- /dev/null +++ b/src/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/src/core/schemas/builders/literals/stringLiteral.ts b/src/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 0000000..3939b76 --- /dev/null +++ b/src/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/src/core/schemas/builders/object-like/getObjectLikeUtils.ts b/src/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 0000000..8331d08 --- /dev/null +++ b/src/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/src/core/schemas/builders/object-like/index.ts b/src/core/schemas/builders/object-like/index.ts new file mode 100644 index 0000000..c342e72 --- /dev/null +++ b/src/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/src/core/schemas/builders/object-like/types.ts b/src/core/schemas/builders/object-like/types.ts new file mode 100644 index 0000000..75b3698 --- /dev/null +++ b/src/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/src/core/schemas/builders/object/index.ts b/src/core/schemas/builders/object/index.ts new file mode 100644 index 0000000..e3f4388 --- /dev/null +++ b/src/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/src/core/schemas/builders/object/object.ts b/src/core/schemas/builders/object/object.ts new file mode 100644 index 0000000..e00136d --- /dev/null +++ b/src/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 0000000..a0951f4 --- /dev/null +++ b/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/src/core/schemas/builders/object/property.ts b/src/core/schemas/builders/object/property.ts new file mode 100644 index 0000000..d245c4b --- /dev/null +++ b/src/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/src/core/schemas/builders/object/types.ts b/src/core/schemas/builders/object/types.ts new file mode 100644 index 0000000..de9bb40 --- /dev/null +++ b/src/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/src/core/schemas/builders/primitives/any.ts b/src/core/schemas/builders/primitives/any.ts new file mode 100644 index 0000000..fcaeb04 --- /dev/null +++ b/src/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/src/core/schemas/builders/primitives/boolean.ts b/src/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 0000000..fad6056 --- /dev/null +++ b/src/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/src/core/schemas/builders/primitives/index.ts b/src/core/schemas/builders/primitives/index.ts new file mode 100644 index 0000000..788f941 --- /dev/null +++ b/src/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/src/core/schemas/builders/primitives/number.ts b/src/core/schemas/builders/primitives/number.ts new file mode 100644 index 0000000..c268945 --- /dev/null +++ b/src/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/src/core/schemas/builders/primitives/string.ts b/src/core/schemas/builders/primitives/string.ts new file mode 100644 index 0000000..949f1f2 --- /dev/null +++ b/src/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/src/core/schemas/builders/primitives/unknown.ts b/src/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 0000000..4d52495 --- /dev/null +++ b/src/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/src/core/schemas/builders/record/index.ts b/src/core/schemas/builders/record/index.ts new file mode 100644 index 0000000..82e25c5 --- /dev/null +++ b/src/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/src/core/schemas/builders/record/record.ts b/src/core/schemas/builders/record/record.ts new file mode 100644 index 0000000..6683ac3 --- /dev/null +++ b/src/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/src/core/schemas/builders/record/types.ts b/src/core/schemas/builders/record/types.ts new file mode 100644 index 0000000..eb82cc7 --- /dev/null +++ b/src/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/src/core/schemas/builders/schema-utils/JsonError.ts b/src/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 0000000..2b89ca0 --- /dev/null +++ b/src/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/src/core/schemas/builders/schema-utils/ParseError.ts b/src/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 0000000..d056eb4 --- /dev/null +++ b/src/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/src/core/schemas/builders/schema-utils/getSchemaUtils.ts b/src/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 0000000..79ecad9 --- /dev/null +++ b/src/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/src/core/schemas/builders/schema-utils/index.ts b/src/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 0000000..aa04e05 --- /dev/null +++ b/src/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 0000000..4160f0a --- /dev/null +++ b/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/src/core/schemas/builders/set/index.ts b/src/core/schemas/builders/set/index.ts new file mode 100644 index 0000000..f3310e8 --- /dev/null +++ b/src/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/src/core/schemas/builders/set/set.ts b/src/core/schemas/builders/set/set.ts new file mode 100644 index 0000000..e9e6bb7 --- /dev/null +++ b/src/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/src/core/schemas/builders/undiscriminated-union/index.ts b/src/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 0000000..75b71cb --- /dev/null +++ b/src/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/src/core/schemas/builders/undiscriminated-union/types.ts b/src/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 0000000..43e7108 --- /dev/null +++ b/src/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 0000000..21ed3df --- /dev/null +++ b/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/src/core/schemas/builders/union/discriminant.ts b/src/core/schemas/builders/union/discriminant.ts new file mode 100644 index 0000000..55065bc --- /dev/null +++ b/src/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/src/core/schemas/builders/union/index.ts b/src/core/schemas/builders/union/index.ts new file mode 100644 index 0000000..85fc008 --- /dev/null +++ b/src/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/src/core/schemas/builders/union/types.ts b/src/core/schemas/builders/union/types.ts new file mode 100644 index 0000000..6f82c86 --- /dev/null +++ b/src/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/src/core/schemas/builders/union/union.ts b/src/core/schemas/builders/union/union.ts new file mode 100644 index 0000000..ab61475 --- /dev/null +++ b/src/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/src/core/schemas/index.ts b/src/core/schemas/index.ts new file mode 100644 index 0000000..5429d8b --- /dev/null +++ b/src/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/src/core/schemas/utils/MaybePromise.ts b/src/core/schemas/utils/MaybePromise.ts new file mode 100644 index 0000000..9cd354b --- /dev/null +++ b/src/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 0000000..4111d70 --- /dev/null +++ b/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/src/core/schemas/utils/createIdentitySchemaCreator.ts b/src/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 0000000..de107cf --- /dev/null +++ b/src/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/src/core/schemas/utils/entries.ts b/src/core/schemas/utils/entries.ts new file mode 100644 index 0000000..e122952 --- /dev/null +++ b/src/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/src/core/schemas/utils/filterObject.ts b/src/core/schemas/utils/filterObject.ts new file mode 100644 index 0000000..2c25a34 --- /dev/null +++ b/src/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/src/core/schemas/utils/getErrorMessageForIncorrectType.ts b/src/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 0000000..1a5c310 --- /dev/null +++ b/src/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,25 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + if (value instanceof BigInt) { + return "BigInt"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "bigint": + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/src/core/schemas/utils/isPlainObject.ts b/src/core/schemas/utils/isPlainObject.ts new file mode 100644 index 0000000..db82a72 --- /dev/null +++ b/src/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/src/core/schemas/utils/keys.ts b/src/core/schemas/utils/keys.ts new file mode 100644 index 0000000..0186709 --- /dev/null +++ b/src/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/src/core/schemas/utils/maybeSkipValidation.ts b/src/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 0000000..86c07ab --- /dev/null +++ b/src/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/src/core/schemas/utils/partition.ts b/src/core/schemas/utils/partition.ts new file mode 100644 index 0000000..f58d6f3 --- /dev/null +++ b/src/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/src/serialization/index.ts b/src/serialization/index.ts new file mode 100644 index 0000000..3e5335f --- /dev/null +++ b/src/serialization/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/src/serialization/resources/apiStatus/index.ts b/src/serialization/resources/apiStatus/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/serialization/resources/apiStatus/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/serialization/resources/apiStatus/types/ApiInfo.ts b/src/serialization/resources/apiStatus/types/ApiInfo.ts new file mode 100644 index 0000000..18dd51e --- /dev/null +++ b/src/serialization/resources/apiStatus/types/ApiInfo.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const ApiInfo: core.serialization.ObjectSchema = + core.serialization.object({ + ok: core.serialization.boolean(), + version: core.serialization.string(), + }); + +export declare namespace ApiInfo { + interface Raw { + ok: boolean; + version: string; + } +} diff --git a/src/serialization/resources/apiStatus/types/index.ts b/src/serialization/resources/apiStatus/types/index.ts new file mode 100644 index 0000000..1ce4f3e --- /dev/null +++ b/src/serialization/resources/apiStatus/types/index.ts @@ -0,0 +1 @@ +export * from "./ApiInfo"; diff --git a/src/serialization/resources/embedding/index.ts b/src/serialization/resources/embedding/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/serialization/resources/embedding/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/serialization/resources/embedding/types/Embedding.ts b/src/serialization/resources/embedding/types/Embedding.ts new file mode 100644 index 0000000..8bc8e01 --- /dev/null +++ b/src/serialization/resources/embedding/types/Embedding.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Embedding: core.serialization.Schema = + core.serialization.list(core.serialization.number()); + +export declare namespace Embedding { + type Raw = number[]; +} diff --git a/src/serialization/resources/embedding/types/index.ts b/src/serialization/resources/embedding/types/index.ts new file mode 100644 index 0000000..cd604e1 --- /dev/null +++ b/src/serialization/resources/embedding/types/index.ts @@ -0,0 +1 @@ +export * from "./Embedding"; diff --git a/src/serialization/resources/index.ts b/src/serialization/resources/index.ts new file mode 100644 index 0000000..61ee374 --- /dev/null +++ b/src/serialization/resources/index.ts @@ -0,0 +1,10 @@ +export * as apiStatus from "./apiStatus"; +export * from "./apiStatus/types"; +export * as embedding from "./embedding"; +export * from "./embedding/types"; +export * as tts from "./tts"; +export * from "./tts/types"; +export * as voiceChanger from "./voiceChanger"; +export * from "./voiceChanger/types"; +export * as voices from "./voices"; +export * from "./voices/types"; diff --git a/src/serialization/resources/tts/index.ts b/src/serialization/resources/tts/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/serialization/resources/tts/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/serialization/resources/tts/types/CancelContextRequest.ts b/src/serialization/resources/tts/types/CancelContextRequest.ts new file mode 100644 index 0000000..47e2abc --- /dev/null +++ b/src/serialization/resources/tts/types/CancelContextRequest.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { ContextId } from "./ContextId"; + +export const CancelContextRequest: core.serialization.ObjectSchema< + serializers.CancelContextRequest.Raw, + Cartesia.CancelContextRequest +> = core.serialization.object({ + contextId: core.serialization.property("context_id", ContextId), + cancel: core.serialization.booleanLiteral(true), +}); + +export declare namespace CancelContextRequest { + interface Raw { + context_id: ContextId.Raw; + cancel: true; + } +} diff --git a/src/serialization/resources/tts/types/ContextId.ts b/src/serialization/resources/tts/types/ContextId.ts new file mode 100644 index 0000000..86d5619 --- /dev/null +++ b/src/serialization/resources/tts/types/ContextId.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const ContextId: core.serialization.Schema = + core.serialization.string(); + +export declare namespace ContextId { + type Raw = string; +} diff --git a/src/serialization/resources/tts/types/Controls.ts b/src/serialization/resources/tts/types/Controls.ts new file mode 100644 index 0000000..b89bb6d --- /dev/null +++ b/src/serialization/resources/tts/types/Controls.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Speed } from "./Speed"; +import { Emotion } from "./Emotion"; + +export const Controls: core.serialization.ObjectSchema = + core.serialization.object({ + speed: Speed, + emotion: Emotion, + }); + +export declare namespace Controls { + interface Raw { + speed: Speed.Raw; + emotion: Emotion.Raw; + } +} diff --git a/src/serialization/resources/tts/types/Emotion.ts b/src/serialization/resources/tts/types/Emotion.ts new file mode 100644 index 0000000..910109d --- /dev/null +++ b/src/serialization/resources/tts/types/Emotion.ts @@ -0,0 +1,54 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Emotion: core.serialization.Schema = core.serialization.enum_([ + "anger:lowest", + "anger:low", + "anger", + "anger:high", + "anger:highest", + "positivity:lowest", + "positivity:low", + "positivity", + "positivity:high", + "positivity:highest", + "surprise:lowest", + "surprise:high", + "surprise:highest", + "sadness:lowest", + "sadness:low", + "sadness", + "curiosity:low", + "curiosity", + "curiosity:high", + "curiosity:highest", +]); + +export declare namespace Emotion { + type Raw = + | "anger:lowest" + | "anger:low" + | "anger" + | "anger:high" + | "anger:highest" + | "positivity:lowest" + | "positivity:low" + | "positivity" + | "positivity:high" + | "positivity:highest" + | "surprise:lowest" + | "surprise:high" + | "surprise:highest" + | "sadness:lowest" + | "sadness:low" + | "sadness" + | "curiosity:low" + | "curiosity" + | "curiosity:high" + | "curiosity:highest"; +} diff --git a/src/serialization/resources/tts/types/GenerationRequest.ts b/src/serialization/resources/tts/types/GenerationRequest.ts new file mode 100644 index 0000000..d985791 --- /dev/null +++ b/src/serialization/resources/tts/types/GenerationRequest.ts @@ -0,0 +1,40 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { TtsRequestVoiceSpecifier } from "./TtsRequestVoiceSpecifier"; +import { SupportedLanguage } from "./SupportedLanguage"; +import { WebSocketRawOutputFormat } from "./WebSocketRawOutputFormat"; +import { ContextId } from "./ContextId"; + +export const GenerationRequest: core.serialization.ObjectSchema< + serializers.GenerationRequest.Raw, + Cartesia.GenerationRequest +> = core.serialization.object({ + modelId: core.serialization.property("model_id", core.serialization.string()), + transcript: core.serialization.string(), + voice: TtsRequestVoiceSpecifier, + language: SupportedLanguage.optional(), + outputFormat: core.serialization.property("output_format", WebSocketRawOutputFormat), + duration: core.serialization.number().optional(), + contextId: core.serialization.property("context_id", ContextId), + continue: core.serialization.boolean().optional(), + addTimestamps: core.serialization.property("add_timestamps", core.serialization.boolean().optional()), +}); + +export declare namespace GenerationRequest { + interface Raw { + model_id: string; + transcript: string; + voice: TtsRequestVoiceSpecifier.Raw; + language?: SupportedLanguage.Raw | null; + output_format: WebSocketRawOutputFormat.Raw; + duration?: number | null; + context_id: ContextId.Raw; + continue?: boolean | null; + add_timestamps?: boolean | null; + } +} diff --git a/src/serialization/resources/tts/types/Mp3OutputFormat.ts b/src/serialization/resources/tts/types/Mp3OutputFormat.ts new file mode 100644 index 0000000..d099ddc --- /dev/null +++ b/src/serialization/resources/tts/types/Mp3OutputFormat.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Mp3OutputFormat: core.serialization.ObjectSchema< + serializers.Mp3OutputFormat.Raw, + Cartesia.Mp3OutputFormat +> = core.serialization.object({ + sampleRate: core.serialization.property("sample_rate", core.serialization.number()), + bitRate: core.serialization.property("bit_rate", core.serialization.number()), +}); + +export declare namespace Mp3OutputFormat { + interface Raw { + sample_rate: number; + bit_rate: number; + } +} diff --git a/src/serialization/resources/tts/types/NaturalSpecifier.ts b/src/serialization/resources/tts/types/NaturalSpecifier.ts new file mode 100644 index 0000000..865f64a --- /dev/null +++ b/src/serialization/resources/tts/types/NaturalSpecifier.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const NaturalSpecifier: core.serialization.Schema = + core.serialization.enum_(["slowest", "slow", "normal", "fast", "fastest"]); + +export declare namespace NaturalSpecifier { + type Raw = "slowest" | "slow" | "normal" | "fast" | "fastest"; +} diff --git a/src/serialization/resources/tts/types/NumericalSpecifier.ts b/src/serialization/resources/tts/types/NumericalSpecifier.ts new file mode 100644 index 0000000..02a7731 --- /dev/null +++ b/src/serialization/resources/tts/types/NumericalSpecifier.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const NumericalSpecifier: core.serialization.Schema< + serializers.NumericalSpecifier.Raw, + Cartesia.NumericalSpecifier +> = core.serialization.number(); + +export declare namespace NumericalSpecifier { + type Raw = number; +} diff --git a/src/serialization/resources/tts/types/RawEncoding.ts b/src/serialization/resources/tts/types/RawEncoding.ts new file mode 100644 index 0000000..508fdce --- /dev/null +++ b/src/serialization/resources/tts/types/RawEncoding.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const RawEncoding: core.serialization.Schema = + core.serialization.enum_(["pcm_f32le", "pcm_s16le", "pcm_mulaw", "pcm_alaw"]); + +export declare namespace RawEncoding { + type Raw = "pcm_f32le" | "pcm_s16le" | "pcm_mulaw" | "pcm_alaw"; +} diff --git a/src/serialization/resources/tts/types/RawOutputFormat.ts b/src/serialization/resources/tts/types/RawOutputFormat.ts new file mode 100644 index 0000000..62122f7 --- /dev/null +++ b/src/serialization/resources/tts/types/RawOutputFormat.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { RawEncoding } from "./RawEncoding"; + +export const RawOutputFormat: core.serialization.ObjectSchema< + serializers.RawOutputFormat.Raw, + Cartesia.RawOutputFormat +> = core.serialization.object({ + encoding: RawEncoding, + sampleRate: core.serialization.property("sample_rate", core.serialization.number()), +}); + +export declare namespace RawOutputFormat { + interface Raw { + encoding: RawEncoding.Raw; + sample_rate: number; + } +} diff --git a/src/serialization/resources/tts/types/Speed.ts b/src/serialization/resources/tts/types/Speed.ts new file mode 100644 index 0000000..20343cf --- /dev/null +++ b/src/serialization/resources/tts/types/Speed.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { NumericalSpecifier } from "./NumericalSpecifier"; +import { NaturalSpecifier } from "./NaturalSpecifier"; + +export const Speed: core.serialization.Schema = + core.serialization.undiscriminatedUnion([NumericalSpecifier, NaturalSpecifier]); + +export declare namespace Speed { + type Raw = NumericalSpecifier.Raw | NaturalSpecifier.Raw; +} diff --git a/src/serialization/resources/tts/types/SupportedLanguage.ts b/src/serialization/resources/tts/types/SupportedLanguage.ts new file mode 100644 index 0000000..2065aa7 --- /dev/null +++ b/src/serialization/resources/tts/types/SupportedLanguage.ts @@ -0,0 +1,32 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const SupportedLanguage: core.serialization.Schema< + serializers.SupportedLanguage.Raw, + Cartesia.SupportedLanguage +> = core.serialization.enum_([ + "en", + "fr", + "de", + "es", + "pt", + "zh", + "ja", + "hi", + "it", + "ko", + "nl", + "pl", + "ru", + "sv", + "tr", +]); + +export declare namespace SupportedLanguage { + type Raw = "en" | "fr" | "de" | "es" | "pt" | "zh" | "ja" | "hi" | "it" | "ko" | "nl" | "pl" | "ru" | "sv" | "tr"; +} diff --git a/src/serialization/resources/tts/types/TtsRequest.ts b/src/serialization/resources/tts/types/TtsRequest.ts new file mode 100644 index 0000000..685a0b5 --- /dev/null +++ b/src/serialization/resources/tts/types/TtsRequest.ts @@ -0,0 +1,31 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { TtsRequestVoiceSpecifier } from "./TtsRequestVoiceSpecifier"; +import { SupportedLanguage } from "./SupportedLanguage"; +import { OutputFormat } from "./OutputFormat"; + +export const TtsRequest: core.serialization.ObjectSchema = + core.serialization.object({ + modelId: core.serialization.property("model_id", core.serialization.string()), + transcript: core.serialization.string(), + voice: TtsRequestVoiceSpecifier, + language: SupportedLanguage.optional(), + outputFormat: core.serialization.property("output_format", OutputFormat), + duration: core.serialization.number().optional(), + }); + +export declare namespace TtsRequest { + interface Raw { + model_id: string; + transcript: string; + voice: TtsRequestVoiceSpecifier.Raw; + language?: SupportedLanguage.Raw | null; + output_format: OutputFormat.Raw; + duration?: number | null; + } +} diff --git a/src/serialization/resources/tts/types/TtsRequestEmbeddingSpecifier.ts b/src/serialization/resources/tts/types/TtsRequestEmbeddingSpecifier.ts new file mode 100644 index 0000000..83a0a6c --- /dev/null +++ b/src/serialization/resources/tts/types/TtsRequestEmbeddingSpecifier.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Embedding } from "../../embedding/types/Embedding"; +import { Controls } from "./Controls"; + +export const TtsRequestEmbeddingSpecifier: core.serialization.ObjectSchema< + serializers.TtsRequestEmbeddingSpecifier.Raw, + Cartesia.TtsRequestEmbeddingSpecifier +> = core.serialization.object({ + mode: core.serialization.stringLiteral("embedding"), + embedding: Embedding, + experimentalControls: core.serialization.property("__experimental_controls", Controls.optional()), +}); + +export declare namespace TtsRequestEmbeddingSpecifier { + interface Raw { + mode: "embedding"; + embedding: Embedding.Raw; + __experimental_controls?: Controls.Raw | null; + } +} diff --git a/src/serialization/resources/tts/types/TtsRequestIdSpecifier.ts b/src/serialization/resources/tts/types/TtsRequestIdSpecifier.ts new file mode 100644 index 0000000..26ac33c --- /dev/null +++ b/src/serialization/resources/tts/types/TtsRequestIdSpecifier.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { VoiceId } from "../../voices/types/VoiceId"; +import { Controls } from "./Controls"; + +export const TtsRequestIdSpecifier: core.serialization.ObjectSchema< + serializers.TtsRequestIdSpecifier.Raw, + Cartesia.TtsRequestIdSpecifier +> = core.serialization.object({ + mode: core.serialization.stringLiteral("id"), + id: VoiceId, + experimentalControls: core.serialization.property("__experimental_controls", Controls.optional()), +}); + +export declare namespace TtsRequestIdSpecifier { + interface Raw { + mode: "id"; + id: VoiceId.Raw; + __experimental_controls?: Controls.Raw | null; + } +} diff --git a/src/serialization/resources/tts/types/TtsRequestVoiceSpecifier.ts b/src/serialization/resources/tts/types/TtsRequestVoiceSpecifier.ts new file mode 100644 index 0000000..c2a69c0 --- /dev/null +++ b/src/serialization/resources/tts/types/TtsRequestVoiceSpecifier.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { TtsRequestIdSpecifier } from "./TtsRequestIdSpecifier"; +import { TtsRequestEmbeddingSpecifier } from "./TtsRequestEmbeddingSpecifier"; + +export const TtsRequestVoiceSpecifier: core.serialization.Schema< + serializers.TtsRequestVoiceSpecifier.Raw, + Cartesia.TtsRequestVoiceSpecifier +> = core.serialization.undiscriminatedUnion([TtsRequestIdSpecifier, TtsRequestEmbeddingSpecifier]); + +export declare namespace TtsRequestVoiceSpecifier { + type Raw = TtsRequestIdSpecifier.Raw | TtsRequestEmbeddingSpecifier.Raw; +} diff --git a/src/serialization/resources/tts/types/WavOutputFormat.ts b/src/serialization/resources/tts/types/WavOutputFormat.ts new file mode 100644 index 0000000..2ddd0ad --- /dev/null +++ b/src/serialization/resources/tts/types/WavOutputFormat.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { RawOutputFormat } from "./RawOutputFormat"; + +export const WavOutputFormat: core.serialization.ObjectSchema< + serializers.WavOutputFormat.Raw, + Cartesia.WavOutputFormat +> = core.serialization.object({}).extend(RawOutputFormat); + +export declare namespace WavOutputFormat { + interface Raw extends RawOutputFormat.Raw {} +} diff --git a/src/serialization/resources/tts/types/WebSocketBaseResponse.ts b/src/serialization/resources/tts/types/WebSocketBaseResponse.ts new file mode 100644 index 0000000..2bdb226 --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketBaseResponse.ts @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { ContextId } from "./ContextId"; + +export const WebSocketBaseResponse: core.serialization.ObjectSchema< + serializers.WebSocketBaseResponse.Raw, + Cartesia.WebSocketBaseResponse +> = core.serialization.object({ + contextId: core.serialization.property("context_id", ContextId.optional()), + statusCode: core.serialization.property("status_code", core.serialization.number()), + done: core.serialization.boolean(), +}); + +export declare namespace WebSocketBaseResponse { + interface Raw { + context_id?: ContextId.Raw | null; + status_code: number; + done: boolean; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketChunkResponse.ts b/src/serialization/resources/tts/types/WebSocketChunkResponse.ts new file mode 100644 index 0000000..47b4a17 --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketChunkResponse.ts @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WebSocketBaseResponse } from "./WebSocketBaseResponse"; + +export const WebSocketChunkResponse: core.serialization.ObjectSchema< + serializers.WebSocketChunkResponse.Raw, + Cartesia.WebSocketChunkResponse +> = core.serialization + .object({ + data: core.serialization.string(), + stepTime: core.serialization.property("step_time", core.serialization.number()), + }) + .extend(WebSocketBaseResponse); + +export declare namespace WebSocketChunkResponse { + interface Raw extends WebSocketBaseResponse.Raw { + data: string; + step_time: number; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketDoneResponse.ts b/src/serialization/resources/tts/types/WebSocketDoneResponse.ts new file mode 100644 index 0000000..664b446 --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketDoneResponse.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WebSocketBaseResponse } from "./WebSocketBaseResponse"; + +export const WebSocketDoneResponse: core.serialization.ObjectSchema< + serializers.WebSocketDoneResponse.Raw, + Cartesia.WebSocketDoneResponse +> = core.serialization.object({}).extend(WebSocketBaseResponse); + +export declare namespace WebSocketDoneResponse { + interface Raw extends WebSocketBaseResponse.Raw {} +} diff --git a/src/serialization/resources/tts/types/WebSocketErrorResponse.ts b/src/serialization/resources/tts/types/WebSocketErrorResponse.ts new file mode 100644 index 0000000..9db2694 --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketErrorResponse.ts @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WebSocketBaseResponse } from "./WebSocketBaseResponse"; + +export const WebSocketErrorResponse: core.serialization.ObjectSchema< + serializers.WebSocketErrorResponse.Raw, + Cartesia.WebSocketErrorResponse +> = core.serialization + .object({ + error: core.serialization.string(), + }) + .extend(WebSocketBaseResponse); + +export declare namespace WebSocketErrorResponse { + interface Raw extends WebSocketBaseResponse.Raw { + error: string; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketRawOutputFormat.ts b/src/serialization/resources/tts/types/WebSocketRawOutputFormat.ts new file mode 100644 index 0000000..44639aa --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketRawOutputFormat.ts @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { RawEncoding } from "./RawEncoding"; + +export const WebSocketRawOutputFormat: core.serialization.ObjectSchema< + serializers.WebSocketRawOutputFormat.Raw, + Cartesia.WebSocketRawOutputFormat +> = core.serialization.object({ + container: core.serialization.stringLiteral("raw"), + encoding: RawEncoding, + sampleRate: core.serialization.property("sample_rate", core.serialization.number()), +}); + +export declare namespace WebSocketRawOutputFormat { + interface Raw { + container: "raw"; + encoding: RawEncoding.Raw; + sample_rate: number; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketRequest.ts b/src/serialization/resources/tts/types/WebSocketRequest.ts new file mode 100644 index 0000000..f55376c --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketRequest.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { GenerationRequest } from "./GenerationRequest"; +import { CancelContextRequest } from "./CancelContextRequest"; + +export const WebSocketRequest: core.serialization.Schema = + core.serialization.undiscriminatedUnion([GenerationRequest, CancelContextRequest]); + +export declare namespace WebSocketRequest { + type Raw = GenerationRequest.Raw | CancelContextRequest.Raw; +} diff --git a/src/serialization/resources/tts/types/WebSocketResponse.ts b/src/serialization/resources/tts/types/WebSocketResponse.ts new file mode 100644 index 0000000..315992c --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketResponse.ts @@ -0,0 +1,50 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WebSocketChunkResponse } from "./WebSocketChunkResponse"; +import { WebSocketDoneResponse } from "./WebSocketDoneResponse"; +import { WebSocketTimestampsResponse } from "./WebSocketTimestampsResponse"; +import { WebSocketErrorResponse } from "./WebSocketErrorResponse"; + +export const WebSocketResponse: core.serialization.Schema< + serializers.WebSocketResponse.Raw, + Cartesia.WebSocketResponse +> = core.serialization + .union("type", { + chunk: WebSocketChunkResponse, + done: WebSocketDoneResponse, + timestamps: WebSocketTimestampsResponse, + error: WebSocketErrorResponse, + }) + .transform({ + transform: (value) => value, + untransform: (value) => value, + }); + +export declare namespace WebSocketResponse { + type Raw = + | WebSocketResponse.Chunk + | WebSocketResponse.Done + | WebSocketResponse.Timestamps + | WebSocketResponse.Error; + + interface Chunk extends WebSocketChunkResponse.Raw { + type: "chunk"; + } + + interface Done extends WebSocketDoneResponse.Raw { + type: "done"; + } + + interface Timestamps extends WebSocketTimestampsResponse.Raw { + type: "timestamps"; + } + + interface Error extends WebSocketErrorResponse.Raw { + type: "error"; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketStreamOptions.ts b/src/serialization/resources/tts/types/WebSocketStreamOptions.ts new file mode 100644 index 0000000..9a1566c --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketStreamOptions.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const WebSocketStreamOptions: core.serialization.ObjectSchema< + serializers.WebSocketStreamOptions.Raw, + Cartesia.WebSocketStreamOptions +> = core.serialization.object({ + timeout: core.serialization.number().optional(), +}); + +export declare namespace WebSocketStreamOptions { + interface Raw { + timeout?: number | null; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketTimestampsResponse.ts b/src/serialization/resources/tts/types/WebSocketTimestampsResponse.ts new file mode 100644 index 0000000..12408b0 --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketTimestampsResponse.ts @@ -0,0 +1,24 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WordTimestamps } from "./WordTimestamps"; +import { WebSocketBaseResponse } from "./WebSocketBaseResponse"; + +export const WebSocketTimestampsResponse: core.serialization.ObjectSchema< + serializers.WebSocketTimestampsResponse.Raw, + Cartesia.WebSocketTimestampsResponse +> = core.serialization + .object({ + wordTimestamps: core.serialization.property("word_timestamps", WordTimestamps.optional()), + }) + .extend(WebSocketBaseResponse); + +export declare namespace WebSocketTimestampsResponse { + interface Raw extends WebSocketBaseResponse.Raw { + word_timestamps?: WordTimestamps.Raw | null; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketTtsOutput.ts b/src/serialization/resources/tts/types/WebSocketTtsOutput.ts new file mode 100644 index 0000000..c99916f --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketTtsOutput.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WordTimestamps } from "./WordTimestamps"; +import { ContextId } from "./ContextId"; + +export const WebSocketTtsOutput: core.serialization.ObjectSchema< + serializers.WebSocketTtsOutput.Raw, + Cartesia.WebSocketTtsOutput +> = core.serialization.object({ + wordTimestamps: core.serialization.property("word_timestamps", WordTimestamps.optional()), + audio: core.serialization.unknown().optional(), + contextId: core.serialization.property("context_id", ContextId.optional()), +}); + +export declare namespace WebSocketTtsOutput { + interface Raw { + word_timestamps?: WordTimestamps.Raw | null; + audio?: unknown | null; + context_id?: ContextId.Raw | null; + } +} diff --git a/src/serialization/resources/tts/types/WebSocketTtsRequest.ts b/src/serialization/resources/tts/types/WebSocketTtsRequest.ts new file mode 100644 index 0000000..ce2e328 --- /dev/null +++ b/src/serialization/resources/tts/types/WebSocketTtsRequest.ts @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { OutputFormat } from "./OutputFormat"; +import { TtsRequestVoiceSpecifier } from "./TtsRequestVoiceSpecifier"; + +export const WebSocketTtsRequest: core.serialization.ObjectSchema< + serializers.WebSocketTtsRequest.Raw, + Cartesia.WebSocketTtsRequest +> = core.serialization.object({ + modelId: core.serialization.property("model_id", core.serialization.string()), + outputFormat: core.serialization.property("output_format", OutputFormat.optional()), + transcript: core.serialization.string().optional(), + voice: TtsRequestVoiceSpecifier, + duration: core.serialization.number().optional(), + language: core.serialization.string().optional(), + addTimestamps: core.serialization.property("add_timestamps", core.serialization.boolean().optional()), + contextId: core.serialization.property("context_id", core.serialization.string().optional()), +}); + +export declare namespace WebSocketTtsRequest { + interface Raw { + model_id: string; + output_format?: OutputFormat.Raw | null; + transcript?: string | null; + voice: TtsRequestVoiceSpecifier.Raw; + duration?: number | null; + language?: string | null; + add_timestamps?: boolean | null; + context_id?: string | null; + } +} diff --git a/src/serialization/resources/tts/types/WordTimestamps.ts b/src/serialization/resources/tts/types/WordTimestamps.ts new file mode 100644 index 0000000..8f50ffe --- /dev/null +++ b/src/serialization/resources/tts/types/WordTimestamps.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const WordTimestamps: core.serialization.ObjectSchema = + core.serialization.object({ + words: core.serialization.list(core.serialization.string()), + start: core.serialization.list(core.serialization.number()), + end: core.serialization.list(core.serialization.number()), + }); + +export declare namespace WordTimestamps { + interface Raw { + words: string[]; + start: number[]; + end: number[]; + } +} diff --git a/src/serialization/resources/tts/types/index.ts b/src/serialization/resources/tts/types/index.ts new file mode 100644 index 0000000..5212040 --- /dev/null +++ b/src/serialization/resources/tts/types/index.ts @@ -0,0 +1,30 @@ +export * from "./ContextId"; +export * from "./WebSocketBaseResponse"; +export * from "./WebSocketResponse"; +export * from "./WebSocketErrorResponse"; +export * from "./WebSocketChunkResponse"; +export * from "./WebSocketTimestampsResponse"; +export * from "./WebSocketTtsOutput"; +export * from "./WebSocketStreamOptions"; +export * from "./WordTimestamps"; +export * from "./WebSocketDoneResponse"; +export * from "./CancelContextRequest"; +export * from "./GenerationRequest"; +export * from "./WebSocketRawOutputFormat"; +export * from "./WebSocketRequest"; +export * from "./WebSocketTtsRequest"; +export * from "./TtsRequest"; +export * from "./SupportedLanguage"; +export * from "./OutputFormat"; +export * from "./RawOutputFormat"; +export * from "./RawEncoding"; +export * from "./WavOutputFormat"; +export * from "./Mp3OutputFormat"; +export * from "./TtsRequestVoiceSpecifier"; +export * from "./TtsRequestIdSpecifier"; +export * from "./TtsRequestEmbeddingSpecifier"; +export * from "./Controls"; +export * from "./Speed"; +export * from "./NumericalSpecifier"; +export * from "./NaturalSpecifier"; +export * from "./Emotion"; diff --git a/src/serialization/resources/voiceChanger/index.ts b/src/serialization/resources/voiceChanger/index.ts new file mode 100644 index 0000000..eea524d --- /dev/null +++ b/src/serialization/resources/voiceChanger/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/src/serialization/resources/voiceChanger/types/OutputFormatContainer.ts b/src/serialization/resources/voiceChanger/types/OutputFormatContainer.ts new file mode 100644 index 0000000..ad08730 --- /dev/null +++ b/src/serialization/resources/voiceChanger/types/OutputFormatContainer.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const OutputFormatContainer: core.serialization.Schema< + serializers.OutputFormatContainer.Raw, + Cartesia.OutputFormatContainer +> = core.serialization.enum_(["raw", "wav", "mp3"]); + +export declare namespace OutputFormatContainer { + type Raw = "raw" | "wav" | "mp3"; +} diff --git a/src/serialization/resources/voiceChanger/types/StreamingResponse.ts b/src/serialization/resources/voiceChanger/types/StreamingResponse.ts new file mode 100644 index 0000000..0629aad --- /dev/null +++ b/src/serialization/resources/voiceChanger/types/StreamingResponse.ts @@ -0,0 +1,40 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { WebSocketChunkResponse } from "../../tts/types/WebSocketChunkResponse"; +import { WebSocketDoneResponse } from "../../tts/types/WebSocketDoneResponse"; +import { WebSocketErrorResponse } from "../../tts/types/WebSocketErrorResponse"; + +export const StreamingResponse: core.serialization.Schema< + serializers.StreamingResponse.Raw, + Cartesia.StreamingResponse +> = core.serialization + .union("type", { + chunk: WebSocketChunkResponse, + done: WebSocketDoneResponse, + error: WebSocketErrorResponse, + }) + .transform({ + transform: (value) => value, + untransform: (value) => value, + }); + +export declare namespace StreamingResponse { + type Raw = StreamingResponse.Chunk | StreamingResponse.Done | StreamingResponse.Error; + + interface Chunk extends WebSocketChunkResponse.Raw { + type: "chunk"; + } + + interface Done extends WebSocketDoneResponse.Raw { + type: "done"; + } + + interface Error extends WebSocketErrorResponse.Raw { + type: "error"; + } +} diff --git a/src/serialization/resources/voiceChanger/types/index.ts b/src/serialization/resources/voiceChanger/types/index.ts new file mode 100644 index 0000000..bc81bbd --- /dev/null +++ b/src/serialization/resources/voiceChanger/types/index.ts @@ -0,0 +1,2 @@ +export * from "./OutputFormatContainer"; +export * from "./StreamingResponse"; diff --git a/src/serialization/resources/voices/client/index.ts b/src/serialization/resources/voices/client/index.ts new file mode 100644 index 0000000..abbe30a --- /dev/null +++ b/src/serialization/resources/voices/client/index.ts @@ -0,0 +1 @@ +export * as list from "./list"; diff --git a/src/serialization/resources/voices/client/list.ts b/src/serialization/resources/voices/client/list.ts new file mode 100644 index 0000000..4cd4a34 --- /dev/null +++ b/src/serialization/resources/voices/client/list.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Voice } from "../types/Voice"; + +export const Response: core.serialization.Schema = + core.serialization.list(Voice); + +export declare namespace Response { + type Raw = Voice.Raw[]; +} diff --git a/src/serialization/resources/voices/index.ts b/src/serialization/resources/voices/index.ts new file mode 100644 index 0000000..c9240f8 --- /dev/null +++ b/src/serialization/resources/voices/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./client"; diff --git a/src/serialization/resources/voices/types/BaseVoiceId.ts b/src/serialization/resources/voices/types/BaseVoiceId.ts new file mode 100644 index 0000000..d4c5dd7 --- /dev/null +++ b/src/serialization/resources/voices/types/BaseVoiceId.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { VoiceId } from "./VoiceId"; + +export const BaseVoiceId: core.serialization.Schema = VoiceId; + +export declare namespace BaseVoiceId { + type Raw = VoiceId.Raw; +} diff --git a/src/serialization/resources/voices/types/CloneMode.ts b/src/serialization/resources/voices/types/CloneMode.ts new file mode 100644 index 0000000..98923d5 --- /dev/null +++ b/src/serialization/resources/voices/types/CloneMode.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const CloneMode: core.serialization.Schema = + core.serialization.enum_(["similarity", "stability"]); + +export declare namespace CloneMode { + type Raw = "similarity" | "stability"; +} diff --git a/src/serialization/resources/voices/types/CreateVoiceRequest.ts b/src/serialization/resources/voices/types/CreateVoiceRequest.ts new file mode 100644 index 0000000..3808006 --- /dev/null +++ b/src/serialization/resources/voices/types/CreateVoiceRequest.ts @@ -0,0 +1,31 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Embedding } from "../../embedding/types/Embedding"; +import { SupportedLanguage } from "../../tts/types/SupportedLanguage"; +import { BaseVoiceId } from "./BaseVoiceId"; + +export const CreateVoiceRequest: core.serialization.ObjectSchema< + serializers.CreateVoiceRequest.Raw, + Cartesia.CreateVoiceRequest +> = core.serialization.object({ + name: core.serialization.string(), + description: core.serialization.string(), + embedding: Embedding, + language: SupportedLanguage.optional(), + baseVoiceId: core.serialization.property("base_voice_id", BaseVoiceId.optional()), +}); + +export declare namespace CreateVoiceRequest { + interface Raw { + name: string; + description: string; + embedding: Embedding.Raw; + language?: SupportedLanguage.Raw | null; + base_voice_id?: BaseVoiceId.Raw | null; + } +} diff --git a/src/serialization/resources/voices/types/EmbeddingResponse.ts b/src/serialization/resources/voices/types/EmbeddingResponse.ts new file mode 100644 index 0000000..e4470df --- /dev/null +++ b/src/serialization/resources/voices/types/EmbeddingResponse.ts @@ -0,0 +1,21 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Embedding } from "../../embedding/types/Embedding"; + +export const EmbeddingResponse: core.serialization.ObjectSchema< + serializers.EmbeddingResponse.Raw, + Cartesia.EmbeddingResponse +> = core.serialization.object({ + embedding: Embedding, +}); + +export declare namespace EmbeddingResponse { + interface Raw { + embedding: Embedding.Raw; + } +} diff --git a/src/serialization/resources/voices/types/EmbeddingSpecifier.ts b/src/serialization/resources/voices/types/EmbeddingSpecifier.ts new file mode 100644 index 0000000..443c4f0 --- /dev/null +++ b/src/serialization/resources/voices/types/EmbeddingSpecifier.ts @@ -0,0 +1,24 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Embedding } from "../../embedding/types/Embedding"; +import { Weight } from "./Weight"; + +export const EmbeddingSpecifier: core.serialization.ObjectSchema< + serializers.EmbeddingSpecifier.Raw, + Cartesia.EmbeddingSpecifier +> = core.serialization.object({ + embedding: Embedding, + weight: Weight, +}); + +export declare namespace EmbeddingSpecifier { + interface Raw { + embedding: Embedding.Raw; + weight: Weight.Raw; + } +} diff --git a/src/serialization/resources/voices/types/Gender.ts b/src/serialization/resources/voices/types/Gender.ts new file mode 100644 index 0000000..68d24f1 --- /dev/null +++ b/src/serialization/resources/voices/types/Gender.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Gender: core.serialization.Schema = core.serialization.enum_([ + "male", + "female", +]); + +export declare namespace Gender { + type Raw = "male" | "female"; +} diff --git a/src/serialization/resources/voices/types/IdSpecifier.ts b/src/serialization/resources/voices/types/IdSpecifier.ts new file mode 100644 index 0000000..bd487ed --- /dev/null +++ b/src/serialization/resources/voices/types/IdSpecifier.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { VoiceId } from "./VoiceId"; +import { Weight } from "./Weight"; + +export const IdSpecifier: core.serialization.ObjectSchema = + core.serialization.object({ + id: VoiceId, + weight: Weight, + }); + +export declare namespace IdSpecifier { + interface Raw { + id: VoiceId.Raw; + weight: Weight.Raw; + } +} diff --git a/src/serialization/resources/voices/types/LocalizeDialect.ts b/src/serialization/resources/voices/types/LocalizeDialect.ts new file mode 100644 index 0000000..8391ee4 --- /dev/null +++ b/src/serialization/resources/voices/types/LocalizeDialect.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const LocalizeDialect: core.serialization.Schema = + core.serialization.enum_(["au", "in", "so", "uk", "us"]); + +export declare namespace LocalizeDialect { + type Raw = "au" | "in" | "so" | "uk" | "us"; +} diff --git a/src/serialization/resources/voices/types/LocalizeTargetLanguage.ts b/src/serialization/resources/voices/types/LocalizeTargetLanguage.ts new file mode 100644 index 0000000..4c3c868 --- /dev/null +++ b/src/serialization/resources/voices/types/LocalizeTargetLanguage.ts @@ -0,0 +1,32 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const LocalizeTargetLanguage: core.serialization.Schema< + serializers.LocalizeTargetLanguage.Raw, + Cartesia.LocalizeTargetLanguage +> = core.serialization.enum_([ + "en", + "de", + "es", + "fr", + "ja", + "pt", + "zh", + "hi", + "it", + "ko", + "nl", + "pl", + "ru", + "sv", + "tr", +]); + +export declare namespace LocalizeTargetLanguage { + type Raw = "en" | "de" | "es" | "fr" | "ja" | "pt" | "zh" | "hi" | "it" | "ko" | "nl" | "pl" | "ru" | "sv" | "tr"; +} diff --git a/src/serialization/resources/voices/types/LocalizeVoiceRequest.ts b/src/serialization/resources/voices/types/LocalizeVoiceRequest.ts new file mode 100644 index 0000000..1f08fb9 --- /dev/null +++ b/src/serialization/resources/voices/types/LocalizeVoiceRequest.ts @@ -0,0 +1,30 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { Embedding } from "../../embedding/types/Embedding"; +import { LocalizeTargetLanguage } from "./LocalizeTargetLanguage"; +import { Gender } from "./Gender"; +import { LocalizeDialect } from "./LocalizeDialect"; + +export const LocalizeVoiceRequest: core.serialization.ObjectSchema< + serializers.LocalizeVoiceRequest.Raw, + Cartesia.LocalizeVoiceRequest +> = core.serialization.object({ + embedding: Embedding, + language: LocalizeTargetLanguage, + originalSpeakerGender: core.serialization.property("original_speaker_gender", Gender), + dialect: LocalizeDialect.optional(), +}); + +export declare namespace LocalizeVoiceRequest { + interface Raw { + embedding: Embedding.Raw; + language: LocalizeTargetLanguage.Raw; + original_speaker_gender: Gender.Raw; + dialect?: LocalizeDialect.Raw | null; + } +} diff --git a/src/serialization/resources/voices/types/MixVoiceSpecifier.ts b/src/serialization/resources/voices/types/MixVoiceSpecifier.ts new file mode 100644 index 0000000..594de8a --- /dev/null +++ b/src/serialization/resources/voices/types/MixVoiceSpecifier.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { IdSpecifier } from "./IdSpecifier"; +import { EmbeddingSpecifier } from "./EmbeddingSpecifier"; + +export const MixVoiceSpecifier: core.serialization.Schema< + serializers.MixVoiceSpecifier.Raw, + Cartesia.MixVoiceSpecifier +> = core.serialization.undiscriminatedUnion([IdSpecifier, EmbeddingSpecifier]); + +export declare namespace MixVoiceSpecifier { + type Raw = IdSpecifier.Raw | EmbeddingSpecifier.Raw; +} diff --git a/src/serialization/resources/voices/types/MixVoicesRequest.ts b/src/serialization/resources/voices/types/MixVoicesRequest.ts new file mode 100644 index 0000000..e1c9780 --- /dev/null +++ b/src/serialization/resources/voices/types/MixVoicesRequest.ts @@ -0,0 +1,21 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { MixVoiceSpecifier } from "./MixVoiceSpecifier"; + +export const MixVoicesRequest: core.serialization.ObjectSchema< + serializers.MixVoicesRequest.Raw, + Cartesia.MixVoicesRequest +> = core.serialization.object({ + voices: core.serialization.list(MixVoiceSpecifier), +}); + +export declare namespace MixVoicesRequest { + interface Raw { + voices: MixVoiceSpecifier.Raw[]; + } +} diff --git a/src/serialization/resources/voices/types/UpdateVoiceRequest.ts b/src/serialization/resources/voices/types/UpdateVoiceRequest.ts new file mode 100644 index 0000000..4da8115 --- /dev/null +++ b/src/serialization/resources/voices/types/UpdateVoiceRequest.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const UpdateVoiceRequest: core.serialization.ObjectSchema< + serializers.UpdateVoiceRequest.Raw, + Cartesia.UpdateVoiceRequest +> = core.serialization.object({ + name: core.serialization.string(), + description: core.serialization.string(), +}); + +export declare namespace UpdateVoiceRequest { + interface Raw { + name: string; + description: string; + } +} diff --git a/src/serialization/resources/voices/types/Voice.ts b/src/serialization/resources/voices/types/Voice.ts new file mode 100644 index 0000000..185c729 --- /dev/null +++ b/src/serialization/resources/voices/types/Voice.ts @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { VoiceId } from "./VoiceId"; +import { Embedding } from "../../embedding/types/Embedding"; +import { SupportedLanguage } from "../../tts/types/SupportedLanguage"; +import { BaseVoiceId } from "./BaseVoiceId"; + +export const Voice: core.serialization.ObjectSchema = core.serialization.object({ + id: VoiceId, + userId: core.serialization.property("user_id", core.serialization.string().optional()), + isPublic: core.serialization.property("is_public", core.serialization.boolean()), + name: core.serialization.string(), + description: core.serialization.string(), + createdAt: core.serialization.property("created_at", core.serialization.date()), + embedding: Embedding, + language: SupportedLanguage, + baseVoiceId: core.serialization.property("base_voice_id", BaseVoiceId.optional()), +}); + +export declare namespace Voice { + interface Raw { + id: VoiceId.Raw; + user_id?: string | null; + is_public: boolean; + name: string; + description: string; + created_at: string; + embedding: Embedding.Raw; + language: SupportedLanguage.Raw; + base_voice_id?: BaseVoiceId.Raw | null; + } +} diff --git a/src/serialization/resources/voices/types/VoiceId.ts b/src/serialization/resources/voices/types/VoiceId.ts new file mode 100644 index 0000000..836e6bd --- /dev/null +++ b/src/serialization/resources/voices/types/VoiceId.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const VoiceId: core.serialization.Schema = + core.serialization.string(); + +export declare namespace VoiceId { + type Raw = string; +} diff --git a/src/serialization/resources/voices/types/VoiceMetadata.ts b/src/serialization/resources/voices/types/VoiceMetadata.ts new file mode 100644 index 0000000..c6324ba --- /dev/null +++ b/src/serialization/resources/voices/types/VoiceMetadata.ts @@ -0,0 +1,32 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; +import { VoiceId } from "./VoiceId"; +import { SupportedLanguage } from "../../tts/types/SupportedLanguage"; + +export const VoiceMetadata: core.serialization.ObjectSchema = + core.serialization.object({ + id: VoiceId, + userId: core.serialization.property("user_id", core.serialization.string()), + isPublic: core.serialization.property("is_public", core.serialization.boolean()), + name: core.serialization.string(), + description: core.serialization.string(), + createdAt: core.serialization.property("created_at", core.serialization.date()), + language: SupportedLanguage, + }); + +export declare namespace VoiceMetadata { + interface Raw { + id: VoiceId.Raw; + user_id: string; + is_public: boolean; + name: string; + description: string; + created_at: string; + language: SupportedLanguage.Raw; + } +} diff --git a/src/serialization/resources/voices/types/Weight.ts b/src/serialization/resources/voices/types/Weight.ts new file mode 100644 index 0000000..1a46618 --- /dev/null +++ b/src/serialization/resources/voices/types/Weight.ts @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as Cartesia from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Weight: core.serialization.Schema = core.serialization.number(); + +export declare namespace Weight { + type Raw = number; +} diff --git a/src/serialization/resources/voices/types/index.ts b/src/serialization/resources/voices/types/index.ts new file mode 100644 index 0000000..dd725da --- /dev/null +++ b/src/serialization/resources/voices/types/index.ts @@ -0,0 +1,17 @@ +export * from "./VoiceId"; +export * from "./BaseVoiceId"; +export * from "./Voice"; +export * from "./VoiceMetadata"; +export * from "./CreateVoiceRequest"; +export * from "./UpdateVoiceRequest"; +export * from "./LocalizeTargetLanguage"; +export * from "./LocalizeDialect"; +export * from "./Gender"; +export * from "./LocalizeVoiceRequest"; +export * from "./EmbeddingResponse"; +export * from "./MixVoicesRequest"; +export * from "./Weight"; +export * from "./IdSpecifier"; +export * from "./EmbeddingSpecifier"; +export * from "./MixVoiceSpecifier"; +export * from "./CloneMode"; diff --git a/tests/unit/zurg/bigint/bigint.test.ts b/tests/unit/zurg/bigint/bigint.test.ts new file mode 100644 index 0000000..cf9935a --- /dev/null +++ b/tests/unit/zurg/bigint/bigint.test.ts @@ -0,0 +1,24 @@ +import { bigint } from "../../../../src/core/schemas/builders/bigint"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("bigint", () => { + itSchema("converts between raw string and parsed bigint", bigint(), { + raw: "123456789012345678901234567890123456789012345678901234567890", + parsed: BigInt("123456789012345678901234567890123456789012345678901234567890"), + }); + + itValidateParse("non-string", bigint(), 42, [ + { + message: "Expected string. Received 42.", + path: [], + }, + ]); + + itValidateJson("non-bigint", bigint(), "hello", [ + { + message: 'Expected bigint. Received "hello".', + path: [], + }, + ]); +}); diff --git a/tests/unit/zurg/date/date.test.ts b/tests/unit/zurg/date/date.test.ts new file mode 100644 index 0000000..2790268 --- /dev/null +++ b/tests/unit/zurg/date/date.test.ts @@ -0,0 +1,31 @@ +import { date } from "../../../../src/core/schemas/builders/date"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("date", () => { + itSchema("converts between raw ISO string and parsed Date", date(), { + raw: "2022-09-29T05:41:21.939Z", + parsed: new Date("2022-09-29T05:41:21.939Z"), + }); + + itValidateParse("non-string", date(), 42, [ + { + message: "Expected string. Received 42.", + path: [], + }, + ]); + + itValidateParse("non-ISO", date(), "hello world", [ + { + message: 'Expected ISO 8601 date string. Received "hello world".', + path: [], + }, + ]); + + itValidateJson("non-Date", date(), "hello", [ + { + message: 'Expected Date object. Received "hello".', + path: [], + }, + ]); +}); diff --git a/tests/unit/zurg/enum/enum.test.ts b/tests/unit/zurg/enum/enum.test.ts new file mode 100644 index 0000000..ab0df02 --- /dev/null +++ b/tests/unit/zurg/enum/enum.test.ts @@ -0,0 +1,30 @@ +import { enum_ } from "../../../../src/core/schemas/builders/enum"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("enum", () => { + itSchemaIdentity(enum_(["A", "B", "C"]), "A"); + + itSchemaIdentity(enum_(["A", "B", "C"]), "D" as any, { + opts: { allowUnrecognizedEnumValues: true }, + }); + + itValidate("invalid enum", enum_(["A", "B", "C"]), "D", [ + { + message: 'Expected enum. Received "D".', + path: [], + }, + ]); + + itValidate( + "non-string", + enum_(["A", "B", "C"]), + [], + [ + { + message: "Expected string. Received list.", + path: [], + }, + ] + ); +}); diff --git a/tests/unit/zurg/lazy/lazy.test.ts b/tests/unit/zurg/lazy/lazy.test.ts new file mode 100644 index 0000000..6906bf4 --- /dev/null +++ b/tests/unit/zurg/lazy/lazy.test.ts @@ -0,0 +1,57 @@ +import { Schema } from "../../../../src/core/schemas/Schema"; +import { lazy, list, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + it("doesn't run immediately", () => { + let wasRun = false; + lazy(() => { + wasRun = true; + return string(); + }); + expect(wasRun).toBe(false); + }); + + it("only runs first time", async () => { + let count = 0; + const schema = lazy(() => { + count++; + return string(); + }); + await schema.parse("hello"); + await schema.json("world"); + expect(count).toBe(1); + }); + + itSchemaIdentity( + lazy(() => object({})), + { foo: "hello" }, + { + title: "passes opts through", + opts: { unrecognizedObjectKeys: "passthrough" }, + } + ); + + itSchemaIdentity( + lazy(() => object({ foo: string() })), + { foo: "hello" } + ); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial schema doesn't compile", () => { + () => { + // @ts-expect-error + const a = lazy(() => object({ foo: a })); + }; + }); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial compiles with explicit type", () => { + () => { + interface TreeNode { + children: TreeNode[]; + } + const TreeNode: Schema = lazy(() => object({ children: list(TreeNode) })); + }; + }); +}); diff --git a/tests/unit/zurg/lazy/lazyObject.test.ts b/tests/unit/zurg/lazy/lazyObject.test.ts new file mode 100644 index 0000000..8813cc9 --- /dev/null +++ b/tests/unit/zurg/lazy/lazyObject.test.ts @@ -0,0 +1,18 @@ +import { lazyObject, number, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + itSchemaIdentity( + lazyObject(() => object({ foo: string() })), + { foo: "hello" } + ); + + itSchemaIdentity( + lazyObject(() => object({ foo: string() })).extend(object({ bar: number() })), + { + foo: "hello", + bar: 42, + }, + { title: "returned schema has object utils" } + ); +}); diff --git a/tests/unit/zurg/lazy/recursive/a.ts b/tests/unit/zurg/lazy/recursive/a.ts new file mode 100644 index 0000000..8b7d5e4 --- /dev/null +++ b/tests/unit/zurg/lazy/recursive/a.ts @@ -0,0 +1,7 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { schemaB } from "./b"; + +// @ts-expect-error +export const schemaA = object({ + b: schemaB, +}); diff --git a/tests/unit/zurg/lazy/recursive/b.ts b/tests/unit/zurg/lazy/recursive/b.ts new file mode 100644 index 0000000..fb219d5 --- /dev/null +++ b/tests/unit/zurg/lazy/recursive/b.ts @@ -0,0 +1,8 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { optional } from "../../../../../src/core/schemas/builders/schema-utils"; +import { schemaA } from "./a"; + +// @ts-expect-error +export const schemaB = object({ + a: optional(schemaA), +}); diff --git a/tests/unit/zurg/list/list.test.ts b/tests/unit/zurg/list/list.test.ts new file mode 100644 index 0000000..424ed64 --- /dev/null +++ b/tests/unit/zurg/list/list.test.ts @@ -0,0 +1,41 @@ +import { list, object, property, string } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("list", () => { + itSchemaIdentity(list(string()), ["hello", "world"], { + title: "functions as identity when item type is primitive", + }); + + itSchema( + "converts objects correctly", + list( + object({ + helloWorld: property("hello_world", string()), + }) + ), + { + raw: [{ hello_world: "123" }], + parsed: [{ helloWorld: "123" }], + } + ); + + itValidate("not a list", list(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidate( + "invalid item type", + list(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); +}); diff --git a/tests/unit/zurg/literals/stringLiteral.test.ts b/tests/unit/zurg/literals/stringLiteral.test.ts new file mode 100644 index 0000000..fa6c888 --- /dev/null +++ b/tests/unit/zurg/literals/stringLiteral.test.ts @@ -0,0 +1,21 @@ +import { stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("stringLiteral", () => { + itSchemaIdentity(stringLiteral("A"), "A"); + + itValidate("incorrect string", stringLiteral("A"), "B", [ + { + path: [], + message: 'Expected "A". Received "B".', + }, + ]); + + itValidate("non-string", stringLiteral("A"), 42, [ + { + path: [], + message: 'Expected "A". Received 42.', + }, + ]); +}); diff --git a/tests/unit/zurg/object-like/withParsedProperties.test.ts b/tests/unit/zurg/object-like/withParsedProperties.test.ts new file mode 100644 index 0000000..9f5dd0e --- /dev/null +++ b/tests/unit/zurg/object-like/withParsedProperties.test.ts @@ -0,0 +1,57 @@ +import { object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; + +describe("withParsedProperties", () => { + it("Added properties included on parsed object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + printHelloWorld: () => () => "Hello world", + helloWorld: "Hello world", + }); + + const parsed = await schema.parse({ raw_foo: "value of foo", bar: "bar" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printFoo()).toBe("value of foo"); + expect(parsed.value.printHelloWorld()).toBe("Hello world"); + expect(parsed.value.helloWorld).toBe("Hello world"); + }); + + it("Added property is removed on raw object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + }); + + const original = { raw_foo: "value of foo", bar: "bar" } as const; + const parsed = await schema.parse(original); + if (!parsed.ok) { + throw new Error("Failed to parse()"); + } + + const raw = await schema.json(parsed.value); + + if (!raw.ok) { + throw new Error("Failed to json()"); + } + + expect(raw.value).toEqual(original); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .withParsedProperties(42); + }); + }); +}); diff --git a/tests/unit/zurg/object/extend.test.ts b/tests/unit/zurg/object/extend.test.ts new file mode 100644 index 0000000..54fc8c4 --- /dev/null +++ b/tests/unit/zurg/object/extend.test.ts @@ -0,0 +1,89 @@ +import { boolean, object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("extend", () => { + itSchemaIdentity( + object({ + foo: string(), + }).extend( + object({ + bar: stringLiteral("bar"), + }) + ), + { + foo: "", + bar: "bar", + } as const, + { + title: "extended properties are included in schema", + } + ); + + itSchemaIdentity( + object({ + foo: string(), + }) + .extend( + object({ + bar: stringLiteral("bar"), + }) + ) + .extend( + object({ + baz: boolean(), + }) + ), + { + foo: "", + bar: "bar", + baz: true, + } as const, + { + title: "extensions can be extended", + } + ); + + itSchema( + "converts nested object", + object({ + item: object({ + helloWorld: property("hello_world", string()), + }), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item: { hello_world: "yo" }, goodbye_raw: "peace" }, + parsed: { item: { helloWorld: "yo" }, goodbye: "peace" }, + } + ); + + itSchema( + "extensions work with raw/parsed property name conversions", + object({ + item: property("item_raw", string()), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item_raw: "hi", goodbye_raw: "peace" }, + parsed: { item: "hi", goodbye: "peace" }, + } + ); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .extend([]); + }); + }); +}); diff --git a/tests/unit/zurg/object/object.test.ts b/tests/unit/zurg/object/object.test.ts new file mode 100644 index 0000000..0acf0e2 --- /dev/null +++ b/tests/unit/zurg/object/object.test.ts @@ -0,0 +1,255 @@ +import { any, number, object, property, string, stringLiteral, unknown } from "../../../../src/core/schemas/builders"; +import { itJson, itParse, itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("object", () => { + itSchemaIdentity( + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { + foo: "", + bar: "bar", + }, + { + title: "functions as identity when values are primitives and property() isn't used", + } + ); + + itSchema( + "uses raw key from property()", + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { raw_foo: "foo", bar: "bar" }, + parsed: { foo: "foo", bar: "bar" }, + } + ); + + itSchema( + "keys with unknown type can be omitted", + object({ + foo: unknown(), + }), + { + raw: {}, + parsed: {}, + } + ); + + itSchema( + "keys with any type can be omitted", + object({ + foo: any(), + }), + { + raw: {}, + parsed: {}, + } + ); + + describe("unrecognizedObjectKeys", () => { + describe("parse", () => { + itParse( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itParse( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + + describe("json", () => { + itJson( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itJson( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + }); + + describe("nullish properties", () => { + itSchema("missing properties are not added", object({ foo: property("raw_foo", string().optional()) }), { + raw: {}, + parsed: {}, + }); + + itSchema("undefined properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + itSchema("null properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + describe("extensions", () => { + itSchema( + "undefined properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + + describe("parse()", () => { + itParse( + "null properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + }); + }); + }); + + itValidate( + "missing property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello" }, + [ + { + path: [], + message: 'Missing required key "bar"', + }, + ] + ); + + itValidate( + "extra property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello", bar: "bar", baz: 42 }, + [ + { + path: ["baz"], + message: 'Unexpected key "baz"', + }, + ] + ); + + itValidate( + "not an object", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "nested validation error", + object({ + foo: object({ + bar: number(), + }), + }), + { foo: { bar: "hello" } }, + [ + { + path: ["foo", "bar"], + message: 'Expected number. Received "hello".', + }, + ] + ); +}); diff --git a/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts b/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts new file mode 100644 index 0000000..d87a65f --- /dev/null +++ b/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts @@ -0,0 +1,21 @@ +import { objectWithoutOptionalProperties, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("objectWithoutOptionalProperties", () => { + itSchema( + "all properties are required", + objectWithoutOptionalProperties({ + foo: string(), + bar: stringLiteral("bar").optional(), + }), + { + raw: { + foo: "hello", + }, + // @ts-expect-error + parsed: { + foo: "hello", + }, + } + ); +}); diff --git a/tests/unit/zurg/primitives/any.test.ts b/tests/unit/zurg/primitives/any.test.ts new file mode 100644 index 0000000..1adbbe2 --- /dev/null +++ b/tests/unit/zurg/primitives/any.test.ts @@ -0,0 +1,6 @@ +import { any } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("any", () => { + itSchemaIdentity(any(), true); +}); diff --git a/tests/unit/zurg/primitives/boolean.test.ts b/tests/unit/zurg/primitives/boolean.test.ts new file mode 100644 index 0000000..897a829 --- /dev/null +++ b/tests/unit/zurg/primitives/boolean.test.ts @@ -0,0 +1,14 @@ +import { boolean } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("boolean", () => { + itSchemaIdentity(boolean(), true); + + itValidate("non-boolean", boolean(), {}, [ + { + path: [], + message: "Expected boolean. Received object.", + }, + ]); +}); diff --git a/tests/unit/zurg/primitives/number.test.ts b/tests/unit/zurg/primitives/number.test.ts new file mode 100644 index 0000000..2d01415 --- /dev/null +++ b/tests/unit/zurg/primitives/number.test.ts @@ -0,0 +1,14 @@ +import { number } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("number", () => { + itSchemaIdentity(number(), 42); + + itValidate("non-number", number(), "hello", [ + { + path: [], + message: 'Expected number. Received "hello".', + }, + ]); +}); diff --git a/tests/unit/zurg/primitives/string.test.ts b/tests/unit/zurg/primitives/string.test.ts new file mode 100644 index 0000000..57b2368 --- /dev/null +++ b/tests/unit/zurg/primitives/string.test.ts @@ -0,0 +1,14 @@ +import { string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("string", () => { + itSchemaIdentity(string(), "hello"); + + itValidate("non-string", string(), 42, [ + { + path: [], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/tests/unit/zurg/primitives/unknown.test.ts b/tests/unit/zurg/primitives/unknown.test.ts new file mode 100644 index 0000000..4d17a7d --- /dev/null +++ b/tests/unit/zurg/primitives/unknown.test.ts @@ -0,0 +1,6 @@ +import { unknown } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("unknown", () => { + itSchemaIdentity(unknown(), true); +}); diff --git a/tests/unit/zurg/record/record.test.ts b/tests/unit/zurg/record/record.test.ts new file mode 100644 index 0000000..7e4ba39 --- /dev/null +++ b/tests/unit/zurg/record/record.test.ts @@ -0,0 +1,34 @@ +import { number, record, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("record", () => { + itSchemaIdentity(record(string(), string()), { hello: "world" }); + itSchemaIdentity(record(number(), string()), { 42: "world" }); + + itValidate( + "non-record", + record(number(), string()), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate("invalid key type", record(number(), string()), { hello: "world" }, [ + { + path: ["hello (key)"], + message: 'Expected number. Received "hello".', + }, + ]); + + itValidate("invalid value type", record(string(), number()), { hello: "world" }, [ + { + path: ["hello"], + message: 'Expected number. Received "world".', + }, + ]); +}); diff --git a/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts b/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts new file mode 100644 index 0000000..da10086 --- /dev/null +++ b/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts @@ -0,0 +1,83 @@ +import { object, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("getSchemaUtils", () => { + describe("optional()", () => { + itSchema("optional fields allow original schema", string().optional(), { + raw: "hello", + parsed: "hello", + }); + + itSchema("optional fields are not required", string().optional(), { + raw: null, + parsed: undefined, + }); + }); + + describe("transform()", () => { + itSchema( + "transorm and untransform run correctly", + string().transform({ + transform: (x) => x + "X", + untransform: (x) => (x as string).slice(0, -1), + }), + { + raw: "hello", + parsed: "helloX", + } + ); + }); + + describe("parseOrThrow()", () => { + it("parses valid value", async () => { + const value = string().parseOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("jsonOrThrow()", () => { + it("serializes valid value", async () => { + const value = string().jsonOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("omitUndefined", () => { + it("serializes undefined as null", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow({ + a: "hello", + b: undefined, + }); + expect(value).toEqual({ a: "hello", b: null }); + }); + + it("omits undefined values", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow( + { + a: "hello", + b: undefined, + }, + { + omitUndefined: true, + } + ); + expect(value).toEqual({ a: "hello" }); + }); + }); +}); diff --git a/tests/unit/zurg/schema.test.ts b/tests/unit/zurg/schema.test.ts new file mode 100644 index 0000000..94089a9 --- /dev/null +++ b/tests/unit/zurg/schema.test.ts @@ -0,0 +1,78 @@ +import { + boolean, + discriminant, + list, + number, + object, + string, + stringLiteral, + union, +} from "../../../src/core/schemas/builders"; +import { booleanLiteral } from "../../../src/core/schemas/builders/literals/booleanLiteral"; +import { property } from "../../../src/core/schemas/builders/object/property"; +import { itSchema } from "./utils/itSchema"; + +describe("Schema", () => { + itSchema( + "large nested object", + object({ + a: string(), + b: stringLiteral("b value"), + c: property( + "raw_c", + list( + object({ + animal: union(discriminant("type", "_type"), { + dog: object({ value: boolean() }), + cat: object({ value: property("raw_cat", number()) }), + }), + }) + ) + ), + d: property("raw_d", boolean()), + e: booleanLiteral(true), + }), + { + raw: { + a: "hello", + b: "b value", + raw_c: [ + { + animal: { + _type: "dog", + value: true, + }, + }, + { + animal: { + _type: "cat", + raw_cat: 42, + }, + }, + ], + raw_d: false, + e: true, + }, + parsed: { + a: "hello", + b: "b value", + c: [ + { + animal: { + type: "dog", + value: true, + }, + }, + { + animal: { + type: "cat", + value: 42, + }, + }, + ], + d: false, + e: true, + }, + } + ); +}); diff --git a/tests/unit/zurg/set/set.test.ts b/tests/unit/zurg/set/set.test.ts new file mode 100644 index 0000000..e17f908 --- /dev/null +++ b/tests/unit/zurg/set/set.test.ts @@ -0,0 +1,48 @@ +import { set, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("set", () => { + itSchema("converts between raw list and parsed Set", set(string()), { + raw: ["A", "B"], + parsed: new Set(["A", "B"]), + }); + + itValidateParse("not a list", set(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidateJson( + "not a Set", + set(string()), + [], + [ + { + path: [], + message: "Expected Set. Received list.", + }, + ] + ); + + itValidateParse( + "invalid item type", + set(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); + + itValidateJson("invalid item type", set(string()), new Set([42]), [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/tests/unit/zurg/skipValidation.test.ts b/tests/unit/zurg/skipValidation.test.ts new file mode 100644 index 0000000..5dc8809 --- /dev/null +++ b/tests/unit/zurg/skipValidation.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ + +import { boolean, number, object, property, string, undiscriminatedUnion } from "../../../src/core/schemas/builders"; + +describe("skipValidation", () => { + it("allows data that doesn't conform to the schema", async () => { + const warningLogs: string[] = []; + const originalConsoleWarn = console.warn; + console.warn = (...args) => warningLogs.push(args.join(" ")); + + const schema = object({ + camelCase: property("snake_case", string()), + numberProperty: number(), + requiredProperty: boolean(), + anyPrimitive: undiscriminatedUnion([string(), number(), boolean()]), + }); + + const parsed = await schema.parse( + { + snake_case: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + { + skipValidation: true, + } + ); + + expect(parsed).toEqual({ + ok: true, + value: { + camelCase: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + }); + + expect(warningLogs).toEqual([ + `Failed to validate. + - numberProperty: Expected number. Received "oops".`, + ]); + + console.warn = originalConsoleWarn; + }); +}); diff --git a/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts b/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts new file mode 100644 index 0000000..0e66433 --- /dev/null +++ b/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts @@ -0,0 +1,44 @@ +import { number, object, property, string, undiscriminatedUnion } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("undiscriminatedUnion", () => { + itSchemaIdentity(undiscriminatedUnion([string(), number()]), "hello world"); + + itSchemaIdentity(undiscriminatedUnion([object({ hello: string() }), object({ goodbye: string() })]), { + goodbye: "foo", + }); + + itSchema( + "Correctly transforms", + undiscriminatedUnion([object({ hello: string() }), object({ helloWorld: property("hello_world", string()) })]), + { + raw: { hello_world: "foo " }, + parsed: { helloWorld: "foo " }, + } + ); + + it("Returns errors for all variants", async () => { + const result = await undiscriminatedUnion([string(), number()]).parse(true); + if (result.ok) { + throw new Error("Unexpectedly passed validation"); + } + expect(result.errors).toEqual([ + { + message: "[Variant 0] Expected string. Received true.", + path: [], + }, + { + message: "[Variant 1] Expected number. Received true.", + path: [], + }, + ]); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with zero members", () => { + // @ts-expect-error + () => undiscriminatedUnion([]); + }); + }); +}); diff --git a/tests/unit/zurg/union/union.test.ts b/tests/unit/zurg/union/union.test.ts new file mode 100644 index 0000000..7901846 --- /dev/null +++ b/tests/unit/zurg/union/union.test.ts @@ -0,0 +1,113 @@ +import { boolean, discriminant, number, object, string, union } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("union", () => { + itSchemaIdentity( + union("type", { + lion: object({ + meows: boolean(), + }), + giraffe: object({ + heightInInches: number(), + }), + }), + { type: "lion", meows: true }, + { title: "doesn't transform discriminant when it's a string" } + ); + + itSchema( + "transforms discriminant when it's a discriminant()", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + raw: { _type: "lion", meows: true }, + parsed: { type: "lion", meows: true }, + } + ); + + describe("allowUnrecognizedUnionMembers", () => { + itSchema( + "transforms discriminant & passes through values when discriminant value is unrecognized", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + // @ts-expect-error + raw: { _type: "moose", isAMoose: true }, + // @ts-expect-error + parsed: { type: "moose", isAMoose: true }, + opts: { + allowUnrecognizedUnionMembers: true, + }, + } + ); + }); + + describe("withParsedProperties", () => { + it("Added property is included on parsed object", async () => { + const schema = union("type", { + lion: object({}), + tiger: object({ value: string() }), + }).withParsedProperties({ + printType: (parsed) => () => parsed.type, + }); + + const parsed = await schema.parse({ type: "lion" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printType()).toBe("lion"); + }); + }); + + itValidate( + "non-object", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "missing discriminant", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + {}, + [ + { + path: [], + message: 'Missing discriminant ("type")', + }, + ] + ); + + itValidate( + "unrecognized discriminant value", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + { + type: "bear", + }, + [ + { + path: ["type"], + message: 'Expected enum. Received "bear".', + }, + ] + ); +}); diff --git a/tests/unit/zurg/utils/itSchema.ts b/tests/unit/zurg/utils/itSchema.ts new file mode 100644 index 0000000..67b6c92 --- /dev/null +++ b/tests/unit/zurg/utils/itSchema.ts @@ -0,0 +1,78 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions } from "../../../../src/core/schemas/Schema"; + +export function itSchemaIdentity( + schema: Schema, + value: T, + { title = "functions as identity", opts }: { title?: string; opts?: SchemaOptions } = {} +): void { + itSchema(title, schema, { raw: value, parsed: value, opts }); +} + +export function itSchema( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + only = false, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + only?: boolean; + } +): void { + // eslint-disable-next-line jest/valid-title + (only ? describe.only : describe)(title, () => { + itParse("parse()", schema, { raw, parsed, opts }); + itJson("json()", schema, { raw, parsed, opts }); + }); +} + +export function itParse( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.parse(raw, opts); + if (!maybeValid.ok) { + throw new Error("Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(parsed); + }); +} + +export function itJson( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.json(parsed, opts); + if (!maybeValid.ok) { + throw new Error("Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(raw); + }); +} diff --git a/tests/unit/zurg/utils/itValidate.ts b/tests/unit/zurg/utils/itValidate.ts new file mode 100644 index 0000000..75b2c08 --- /dev/null +++ b/tests/unit/zurg/utils/itValidate.ts @@ -0,0 +1,56 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions, ValidationError } from "../../../../src/core/schemas/Schema"; + +export function itValidate( + title: string, + schema: Schema, + input: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + // eslint-disable-next-line jest/valid-title + describe("parse()", () => { + itValidateParse(title, schema, input, errors, opts); + }); + describe("json()", () => { + itValidateJson(title, schema, input, errors, opts); + }); +} + +export function itValidateParse( + title: string, + schema: Schema, + raw: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("parse", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.parse(raw, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} + +export function itValidateJson( + title: string, + schema: Schema, + parsed: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("json", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.json(parsed, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +}